browser/components/sessionstore/src/ContentRestore.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 this.EXPORTED_SYMBOLS = ["ContentRestore"];
     9 const Cu = Components.utils;
    10 const Ci = Components.interfaces;
    12 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
    14 XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
    15   "resource:///modules/sessionstore/DocShellCapabilities.jsm");
    16 XPCOMUtils.defineLazyModuleGetter(this, "FormData",
    17   "resource://gre/modules/FormData.jsm");
    18 XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
    19   "resource:///modules/sessionstore/PageStyle.jsm");
    20 XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
    21   "resource://gre/modules/ScrollPosition.jsm");
    22 XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
    23   "resource:///modules/sessionstore/SessionHistory.jsm");
    24 XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
    25   "resource:///modules/sessionstore/SessionStorage.jsm");
    26 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
    27   "resource:///modules/sessionstore/Utils.jsm");
    29 /**
    30  * This module implements the content side of session restoration. The chrome
    31  * side is handled by SessionStore.jsm. The functions in this module are called
    32  * by content-sessionStore.js based on messages received from SessionStore.jsm
    33  * (or, in one case, based on a "load" event). Each tab has its own
    34  * ContentRestore instance, constructed by content-sessionStore.js.
    35  *
    36  * In a typical restore, content-sessionStore.js will call the following based
    37  * on messages and events it receives:
    38  *
    39  *   restoreHistory(epoch, tabData, reloadCallback)
    40  *     Restores the tab's history and session cookies.
    41  *   restoreTabContent(finishCallback)
    42  *     Starts loading the data for the current page to restore.
    43  *   restoreDocument()
    44  *     Restore form and scroll data.
    45  *
    46  * When the page has been loaded from the network, we call finishCallback. It
    47  * should send a message to SessionStore.jsm, which may cause other tabs to be
    48  * restored.
    49  *
    50  * When the page has finished loading, a "load" event will trigger in
    51  * content-sessionStore.js, which will call restoreDocument. At that point,
    52  * form data is restored and the restore is complete.
    53  *
    54  * At any time, SessionStore.jsm can cancel the ongoing restore by sending a
    55  * reset message, which causes resetRestore to be called. At that point it's
    56  * legal to begin another restore.
    57  *
    58  * The epoch that is passed into restoreHistory is merely a token. All messages
    59  * sent back to SessionStore.jsm include the epoch. This way, SessionStore.jsm
    60  * can discard messages that relate to restores that it has canceled (by
    61  * starting a new restore, say).
    62  */
    63 function ContentRestore(chromeGlobal) {
    64   let internal = new ContentRestoreInternal(chromeGlobal);
    65   let external = {};
    67   let EXPORTED_METHODS = ["restoreHistory",
    68                           "restoreTabContent",
    69                           "restoreDocument",
    70                           "resetRestore",
    71                           "getRestoreEpoch",
    72                          ];
    74   for (let method of EXPORTED_METHODS) {
    75     external[method] = internal[method].bind(internal);
    76   }
    78   return Object.freeze(external);
    79 }
    81 function ContentRestoreInternal(chromeGlobal) {
    82   this.chromeGlobal = chromeGlobal;
    84   // The following fields are only valid during certain phases of the restore
    85   // process.
    87   // The epoch that was passed into restoreHistory. Removed in restoreDocument.
    88   this._epoch = 0;
    90   // The tabData for the restore. Set in restoreHistory and removed in
    91   // restoreTabContent.
    92   this._tabData = null;
    94   // Contains {entry, pageStyle, scrollPositions, formdata}, where entry is a
    95   // single entry from the tabData.entries array. Set in
    96   // restoreTabContent and removed in restoreDocument.
    97   this._restoringDocument = null;
    99   // This listener is used to detect reloads on restoring tabs. Set in
   100   // restoreHistory and removed in restoreTabContent.
   101   this._historyListener = null;
   103   // This listener detects when a restoring tab has finished loading data from
   104   // the network. Set in restoreTabContent and removed in resetRestore.
   105   this._progressListener = null;
   106 }
   108 /**
   109  * The API for the ContentRestore module. Methods listed in EXPORTED_METHODS are
   110  * public.
   111  */
   112 ContentRestoreInternal.prototype = {
   114   get docShell() {
   115     return this.chromeGlobal.docShell;
   116   },
   118   /**
   119    * Starts the process of restoring a tab. The tabData to be restored is passed
   120    * in here and used throughout the restoration. The epoch (which must be
   121    * non-zero) is passed through to all the callbacks. If the tab is ever
   122    * reloaded during the restore process, reloadCallback is called.
   123    */
   124   restoreHistory: function (epoch, tabData, reloadCallback) {
   125     this._tabData = tabData;
   126     this._epoch = epoch;
   128     // In case about:blank isn't done yet.
   129     let webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
   130     webNavigation.stop(Ci.nsIWebNavigation.STOP_ALL);
   132     // Make sure currentURI is set so that switch-to-tab works before the tab is
   133     // restored. We'll reset this to about:blank when we try to restore the tab
   134     // to ensure that docshell doeesn't get confused.
   135     let activeIndex = tabData.index - 1;
   136     let activePageData = tabData.entries[activeIndex] || {};
   137     let uri = activePageData.url || null;
   138     if (uri) {
   139       webNavigation.setCurrentURI(Utils.makeURI(uri));
   140     }
   142     SessionHistory.restore(this.docShell, tabData);
   144     // Add a listener to watch for reloads.
   145     let listener = new HistoryListener(this.docShell, reloadCallback);
   146     webNavigation.sessionHistory.addSHistoryListener(listener);
   147     this._historyListener = listener;
   149     // Make sure to reset the capabilities and attributes in case this tab gets
   150     // reused.
   151     let disallow = new Set(tabData.disallow && tabData.disallow.split(","));
   152     DocShellCapabilities.restore(this.docShell, disallow);
   154     if (tabData.storage && this.docShell instanceof Ci.nsIDocShell)
   155       SessionStorage.restore(this.docShell, tabData.storage);
   156   },
   158   /**
   159    * Start loading the current page. When the data has finished loading from the
   160    * network, finishCallback is called. Returns true if the load was successful.
   161    */
   162   restoreTabContent: function (finishCallback) {
   163     let tabData = this._tabData;
   164     this._tabData = null;
   166     let webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
   167     let history = webNavigation.sessionHistory;
   169     // The reload listener is no longer needed.
   170     this._historyListener.uninstall();
   171     this._historyListener = null;
   173     // We're about to start a load. This listener will be called when the load
   174     // has finished getting everything from the network.
   175     let progressListener = new ProgressListener(this.docShell, () => {
   176       // Call resetRestore to reset the state back to normal. The data needed
   177       // for restoreDocument (which hasn't happened yet) will remain in
   178       // _restoringDocument.
   179       this.resetRestore(this.docShell);
   181       finishCallback();
   182     });
   183     this._progressListener = progressListener;
   185     // Reset the current URI to about:blank. We changed it above for
   186     // switch-to-tab, but now it must go back to the correct value before the
   187     // load happens.
   188     webNavigation.setCurrentURI(Utils.makeURI("about:blank"));
   190     try {
   191       if (tabData.userTypedValue && tabData.userTypedClear) {
   192         // If the user typed a URL into the URL bar and hit enter right before
   193         // we crashed, we want to start loading that page again. A non-zero
   194         // userTypedClear value means that the load had started.
   195         let activeIndex = tabData.index - 1;
   196         if (activeIndex > 0) {
   197           // Go to the right history entry, but don't load anything yet.
   198           history.getEntryAtIndex(activeIndex, true);
   199         }
   201         // Load userTypedValue and fix up the URL if it's partial/broken.
   202         webNavigation.loadURI(tabData.userTypedValue,
   203                               Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP,
   204                               null, null, null);
   205       } else if (tabData.entries.length) {
   206         // Stash away the data we need for restoreDocument.
   207         let activeIndex = tabData.index - 1;
   208         this._restoringDocument = {entry: tabData.entries[activeIndex] || {},
   209                                    formdata: tabData.formdata || {},
   210                                    pageStyle: tabData.pageStyle || {},
   211                                    scrollPositions: tabData.scroll || {}};
   213         // In order to work around certain issues in session history, we need to
   214         // force session history to update its internal index and call reload
   215         // instead of gotoIndex. See bug 597315.
   216         history.getEntryAtIndex(activeIndex, true);
   217         history.reloadCurrentEntry();
   218       } else {
   219         // If there's nothing to restore, we should still blank the page.
   220         webNavigation.loadURI("about:blank",
   221                               Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY,
   222                               null, null, null);
   223       }
   225       return true;
   226     } catch (ex if ex instanceof Ci.nsIException) {
   227       // Ignore page load errors, but return false to signal that the load never
   228       // happened.
   229       return false;
   230     }
   231   },
   233   /**
   234    * Accumulates a list of frames that need to be restored for the given browser
   235    * element. A frame is only restored if its current URL matches the one saved
   236    * in the session data. Each frame to be restored is returned along with its
   237    * associated session data.
   238    *
   239    * @param browser the browser being restored
   240    * @return an array of [frame, data] pairs
   241    */
   242   getFramesToRestore: function (content, data) {
   243     function hasExpectedURL(aDocument, aURL) {
   244       return !aURL || aURL.replace(/#.*/, "") == aDocument.location.href.replace(/#.*/, "");
   245     }
   247     let frameList = [];
   249     function enumerateFrame(content, data) {
   250       // Skip the frame if the user has navigated away before loading finished.
   251       if (!hasExpectedURL(content.document, data.url)) {
   252         return;
   253       }
   255       frameList.push([content, data]);
   257       for (let i = 0; i < content.frames.length; i++) {
   258         if (data.children && data.children[i]) {
   259           enumerateFrame(content.frames[i], data.children[i]);
   260         }
   261       }
   262     }
   264     enumerateFrame(content, data);
   266     return frameList;
   267   },
   269   /**
   270    * Finish restoring the tab by filling in form data and setting the scroll
   271    * position. The restore is complete when this function exits. It should be
   272    * called when the "load" event fires for the restoring tab.
   273    */
   274   restoreDocument: function () {
   275     this._epoch = 0;
   277     if (!this._restoringDocument) {
   278       return;
   279     }
   280     let {entry, pageStyle, formdata, scrollPositions} = this._restoringDocument;
   281     this._restoringDocument = null;
   283     let window = this.docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
   284     let frameList = this.getFramesToRestore(window, entry);
   286     // Support the old pageStyle format.
   287     if (typeof(pageStyle) === "string") {
   288       PageStyle.restore(this.docShell, frameList, pageStyle);
   289     } else {
   290       PageStyle.restoreTree(this.docShell, pageStyle);
   291     }
   293     FormData.restoreTree(window, formdata);
   294     ScrollPosition.restoreTree(window, scrollPositions);
   296     // We need to support the old form and scroll data for a while at least.
   297     for (let [frame, data] of frameList) {
   298       if (data.hasOwnProperty("formdata") || data.hasOwnProperty("innerHTML")) {
   299         let formdata = data.formdata || {};
   300         formdata.url = data.url;
   302         if (data.hasOwnProperty("innerHTML")) {
   303           formdata.innerHTML = data.innerHTML;
   304         }
   306         FormData.restore(frame, formdata);
   307       }
   309       ScrollPosition.restore(frame, data.scroll || "");
   310     }
   311   },
   313   /**
   314    * Cancel an ongoing restore. This function can be called any time between
   315    * restoreHistory and restoreDocument.
   316    *
   317    * This function is called externally (if a restore is canceled) and
   318    * internally (when the loads for a restore have finished). In the latter
   319    * case, it's called before restoreDocument, so it cannot clear
   320    * _restoringDocument.
   321    */
   322   resetRestore: function () {
   323     this._tabData = null;
   325     if (this._historyListener) {
   326       this._historyListener.uninstall();
   327     }
   328     this._historyListener = null;
   330     if (this._progressListener) {
   331       this._progressListener.uninstall();
   332     }
   333     this._progressListener = null;
   334   },
   336   /**
   337    * If a restore is ongoing, this function returns the value of |epoch| that
   338    * was passed to restoreHistory. If no restore is ongoing, it returns 0.
   339    */
   340   getRestoreEpoch: function () {
   341     return this._epoch;
   342   },
   343 };
   345 /*
   346  * This listener detects when a page being restored is reloaded. It triggers a
   347  * callback and cancels the reload. The callback will send a message to
   348  * SessionStore.jsm so that it can restore the content immediately.
   349  */
   350 function HistoryListener(docShell, callback) {
   351   let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
   352   webNavigation.sessionHistory.addSHistoryListener(this);
   354   this.webNavigation = webNavigation;
   355   this.callback = callback;
   356 }
   357 HistoryListener.prototype = {
   358   QueryInterface: XPCOMUtils.generateQI([
   359     Ci.nsISHistoryListener,
   360     Ci.nsISupportsWeakReference
   361   ]),
   363   uninstall: function () {
   364     this.webNavigation.sessionHistory.removeSHistoryListener(this);
   365   },
   367   OnHistoryNewEntry: function(newURI) {},
   368   OnHistoryGoBack: function(backURI) { return true; },
   369   OnHistoryGoForward: function(forwardURI) { return true; },
   370   OnHistoryGotoIndex: function(index, gotoURI) { return true; },
   371   OnHistoryPurge: function(numEntries) { return true; },
   372   OnHistoryReplaceEntry: function(index) {},
   374   OnHistoryReload: function(reloadURI, reloadFlags) {
   375     this.callback();
   377     // Cancel the load.
   378     return false;
   379   },
   380 }
   382 /**
   383  * This class informs SessionStore.jsm whenever the network requests for a
   384  * restoring page have completely finished. We only restore three tabs
   385  * simultaneously, so this is the signal for SessionStore.jsm to kick off
   386  * another restore (if there are more to do).
   387  */
   388 function ProgressListener(docShell, callback)
   389 {
   390   let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
   391                             .getInterface(Ci.nsIWebProgress);
   392   webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
   394   this.webProgress = webProgress;
   395   this.callback = callback;
   396 }
   397 ProgressListener.prototype = {
   398   QueryInterface: XPCOMUtils.generateQI([
   399     Ci.nsIWebProgressListener,
   400     Ci.nsISupportsWeakReference
   401   ]),
   403   uninstall: function() {
   404     this.webProgress.removeProgressListener(this);
   405   },
   407   onStateChange: function(webProgress, request, stateFlags, status) {
   408     if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
   409         stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
   410         stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
   411       this.callback();
   412     }
   413   },
   415   onLocationChange: function() {},
   416   onProgressChange: function() {},
   417   onStatusChange: function() {},
   418   onSecurityChange: function() {},
   419 };

mercurial