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 }