1 /* 2 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.webkit; 27 28 import java.net.MalformedURLException; 29 import java.net.URL; 30 import java.util.Date; 31 import java.util.LinkedList; 32 import java.util.List; 33 34 import com.sun.webkit.event.WCChangeEvent; 35 import com.sun.webkit.event.WCChangeListener; 36 import com.sun.webkit.graphics.WCImage; 37 38 import static com.sun.webkit.network.URLs.newURL; 39 40 public final class BackForwardList { 41 42 public static final class Entry { 43 /** 44 * Native pointer to the HistoryItem object. 45 * If 0, the corresponding object has already been destroyed. 46 */ 47 private long pitem = 0; 48 49 // Native pointer to the Page object. 50 private long ppage = 0; 51 52 private Entry[] children; 53 private URL url; 54 private String title; 55 private Date lastVisitedDate; 56 private WCImage icon; 57 private String target; 58 private boolean isTargetItem; 59 60 // Only called from the native code. 61 private Entry(long pitem, long ppage) { 62 this.pitem = pitem; 63 this.ppage = ppage; 64 65 // When the Entry is disposed its fields may be 66 // left uninitialized. As the Entry may still 67 // be referenced from a global history, the fields 68 // should get their initial values here. 69 getURL(); 70 getTitle(); 71 getLastVisitedDate(); 72 getIcon(); 73 getTarget(); 74 isTargetItem(); 75 getChildren(); 76 } 77 78 // Only called from the native code. 79 private void notifyItemDestroyed() { 80 pitem = 0; 81 } 82 83 // Called from the native code as well. 84 private void notifyItemChanged() { 85 for (WCChangeListener l : listenerList) { 86 l.stateChanged(new WCChangeEvent(this)); 87 } 88 } 89 90 public URL getURL() { 91 try { 92 return (pitem == 0 ? url : (url = newURL(bflItemGetURL(pitem)))); 93 } catch (MalformedURLException ex) { 94 return url = null; 95 } 96 } 97 98 public String getTitle() { 99 return (pitem == 0 ? title : (title = bflItemGetTitle(pitem))); 100 } 101 102 public WCImage getIcon() { 103 return (pitem == 0 ? icon : (icon = bflItemGetIcon(pitem))); 104 } 105 106 public String getTarget() { 107 return (pitem == 0 ? target : (target = bflItemGetTarget(pitem))); 108 } 109 110 public Date getLastVisitedDate() { 111 return lastVisitedDate == null ? null : (Date)lastVisitedDate.clone(); 112 } 113 114 private void updateLastVisitedDate() { 115 lastVisitedDate = new Date(System.currentTimeMillis()); 116 notifyItemChanged(); 117 } 118 119 public boolean isTargetItem() { 120 return (pitem == 0 ? isTargetItem : (isTargetItem = bflItemIsTargetItem(pitem))); 121 } 122 123 public Entry[] getChildren() { 124 return (pitem == 0 ? children : (children = bflItemGetChildren(pitem, ppage))); 125 } 126 127 @Override 128 public String toString() { 129 return "url=" + getURL() + 130 ",title=" + getTitle() + 131 ",date=" + getLastVisitedDate(); 132 } 133 134 135 private final List<WCChangeListener> listenerList = 136 new LinkedList<WCChangeListener>(); 137 138 public void addChangeListener(WCChangeListener l) { 139 if (l == null) 140 return; 141 listenerList.add(l); 142 } 143 144 public void removeChangeListener(WCChangeListener l) { 145 if (l == null) 146 return; 147 listenerList.remove(l); 148 } 149 } 150 151 private final WebPage page; 152 private final List<WCChangeListener> listenerList = 153 new LinkedList<WCChangeListener>(); 154 155 BackForwardList(WebPage page) { 156 this.page = page; 157 158 // WebKit doesn't set a page's visiting date. We do it here as workaround. 159 // This way it works for page reload as well. 160 page.addLoadListenerClient(new LoadListenerClient() { 161 @Override public void dispatchLoadEvent(long frame, 162 int state, 163 String url, 164 String contentType, 165 double progress, 166 int errorCode) 167 { 168 if (state == LoadListenerClient.DOCUMENT_AVAILABLE) { 169 Entry entry = getCurrentEntry(); 170 if (entry != null) { 171 entry.updateLastVisitedDate(); 172 } 173 } 174 } 175 176 @Override public void dispatchResourceLoadEvent(long frame, 177 int state, 178 String url, 179 String contentType, 180 double progress, 181 int errorCode) 182 {} 183 }); 184 } 185 186 public int size() { 187 return bflSize(page.getPage()); 188 } 189 190 public int getMaximumSize() { 191 return bflGetMaximumSize(page.getPage()); 192 } 193 194 public void setMaximumSize(int size) { 195 bflSetMaximumSize(page.getPage(), size); 196 } 197 198 public int getCurrentIndex() { 199 return bflGetCurrentIndex(page.getPage()); 200 } 201 202 public boolean isEmpty() { 203 return size() == 0; 204 } 205 206 public void setEnabled(boolean flag) { 207 bflSetEnabled(page.getPage(), flag); 208 } 209 210 public boolean isEnabled() { 211 return bflIsEnabled(page.getPage()); 212 } 213 214 public Entry get(int index) { 215 Entry host = (Entry)bflGet(page.getPage(), index); 216 return host; 217 } 218 219 public Entry getCurrentEntry() { 220 return get(getCurrentIndex()); 221 } 222 223 public void clearBackForwardListForDRT() { 224 bflClearBackForwardListForDRT(page.getPage()); 225 } 226 227 public int indexOf(Entry e) { 228 return bflIndexOf(page.getPage(), e.pitem, false); 229 } 230 231 public boolean contains(Entry e) { 232 return indexOf(e) >= 0; 233 } 234 235 public Entry[] toArray() { 236 int size = size(); 237 Entry[] entries = new Entry[size]; 238 for (int i = 0; i < size; i++) { 239 entries[i] = get(i); 240 } 241 return entries; 242 } 243 244 public void setCurrentIndex(int index) { 245 if (bflSetCurrentIndex(page.getPage(), index) < 0) { 246 throw new IllegalArgumentException("invalid index: " + index); 247 } 248 } 249 250 private boolean canGoBack(int index) { 251 return index > 0; 252 } 253 254 public boolean canGoBack() { 255 return canGoBack(getCurrentIndex()); 256 } 257 258 public boolean goBack() { 259 int index = getCurrentIndex(); 260 if (canGoBack(index)) { 261 setCurrentIndex(index - 1); 262 return true; 263 } 264 return false; 265 } 266 267 private boolean canGoForward(int index) { 268 return index < (size() - 1); 269 } 270 271 public boolean canGoForward() { 272 return canGoForward(getCurrentIndex()); 273 } 274 275 public boolean goForward() { 276 int index = getCurrentIndex(); 277 if (canGoForward(index)) { 278 setCurrentIndex(index + 1); 279 return true; 280 } 281 return false; 282 } 283 284 public void addChangeListener(WCChangeListener l) { 285 if (l == null) { 286 return; 287 } 288 if (listenerList.isEmpty()) { 289 bflSetHostObject(page.getPage(), this); 290 } 291 listenerList.add(l); 292 } 293 294 public void removeChangeListener(WCChangeListener l) { 295 if (l == null) { 296 return; 297 } 298 listenerList.remove(l); 299 if (listenerList.isEmpty()) { 300 bflSetHostObject(page.getPage(), null); 301 } 302 } 303 304 public WCChangeListener[] getChangeListeners() { 305 return listenerList.toArray(new WCChangeListener[0]); 306 } 307 308 // Only called from the native code. 309 private void notifyChanged() { 310 for (WCChangeListener l : listenerList) { 311 l.stateChanged(new WCChangeEvent(this)); 312 } 313 } 314 315 native private static String bflItemGetURL(long item); 316 native private static String bflItemGetTitle(long item); 317 native private static WCImage bflItemGetIcon(long item); 318 native private static long bflItemGetLastVisitedDate(long item); 319 native private static boolean bflItemIsTargetItem(long item); 320 native private static Entry[] bflItemGetChildren(long item, long page); 321 native private static String bflItemGetTarget(long item); 322 native private static void bflClearBackForwardListForDRT(long page); 323 324 native private static int bflSize(long page); 325 native private static int bflGetMaximumSize(long page); 326 native private static void bflSetMaximumSize(long page, int size); 327 native private static int bflGetCurrentIndex(long page); 328 native private static int bflIndexOf(long page, long item, boolean reverse); 329 native private static void bflSetEnabled(long page, boolean flag); 330 native private static boolean bflIsEnabled(long page); 331 native private static Object bflGet(long page, int index); 332 native private static int bflSetCurrentIndex(long page, int index); 333 native private static void bflSetHostObject(long page, Object host); 334 }