browser/components/sessionstore/src/FrameTree.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 = ["FrameTree"];
     9 const Cu = Components.utils;
    10 const Ci = Components.interfaces;
    12 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
    14 const EXPORTED_METHODS = ["addObserver", "contains", "map", "forEach"];
    16 /**
    17  * A FrameTree represents all frames that were reachable when the document
    18  * was loaded. We use this information to ignore frames when collecting
    19  * sessionstore data as we can't currently restore anything for frames that
    20  * have been created dynamically after or at the load event.
    21  *
    22  * @constructor
    23  */
    24 function FrameTree(chromeGlobal) {
    25   let internal = new FrameTreeInternal(chromeGlobal);
    26   let external = {};
    28   for (let method of EXPORTED_METHODS) {
    29     external[method] = internal[method].bind(internal);
    30   }
    32   return Object.freeze(external);
    33 }
    35 /**
    36  * The internal frame tree API that the public one points to.
    37  *
    38  * @constructor
    39  */
    40 function FrameTreeInternal(chromeGlobal) {
    41   // A WeakMap that uses frames (DOMWindows) as keys and their initial indices
    42   // in their parents' child lists as values. Suppose we have a root frame with
    43   // three subframes i.e. a page with three iframes. The WeakMap would have
    44   // four entries and look as follows:
    45   //
    46   // root -> 0
    47   // subframe1 -> 0
    48   // subframe2 -> 1
    49   // subframe3 -> 2
    50   //
    51   // Should one of the subframes disappear we will stop collecting data for it
    52   // as |this._frames.has(frame) == false|. All other subframes will maintain
    53   // their initial indices to ensure we can restore frame data appropriately.
    54   this._frames = new WeakMap();
    56   // The Set of observers that will be notified when the frame changes.
    57   this._observers = new Set();
    59   // The chrome global we use to retrieve the current DOMWindow.
    60   this._chromeGlobal = chromeGlobal;
    62   // Register a web progress listener to be notified about new page loads.
    63   let docShell = chromeGlobal.docShell;
    64   let ifreq = docShell.QueryInterface(Ci.nsIInterfaceRequestor);
    65   let webProgress = ifreq.getInterface(Ci.nsIWebProgress);
    66   webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
    67 }
    69 FrameTreeInternal.prototype = {
    71   // Returns the docShell's current global.
    72   get content() {
    73     return this._chromeGlobal.content;
    74   },
    76   /**
    77    * Adds a given observer |obs| to the set of observers that will be notified
    78    * when the frame tree is reset (when a new document starts loading) or
    79    * recollected (when a document finishes loading).
    80    *
    81    * @param obs (object)
    82    */
    83   addObserver: function (obs) {
    84     this._observers.add(obs);
    85   },
    87   /**
    88    * Notifies all observers that implement the given |method|.
    89    *
    90    * @param method (string)
    91    */
    92   notifyObservers: function (method) {
    93     for (let obs of this._observers) {
    94       if (obs.hasOwnProperty(method)) {
    95         obs[method]();
    96       }
    97     }
    98   },
   100   /**
   101    * Checks whether a given |frame| is contained in the collected frame tree.
   102    * If it is not, this indicates that we should not collect data for it.
   103    *
   104    * @param frame (nsIDOMWindow)
   105    * @return bool
   106    */
   107   contains: function (frame) {
   108     return this._frames.has(frame);
   109   },
   111   /**
   112    * Recursively applies the given function |cb| to the stored frame tree. Use
   113    * this method to collect sessionstore data for all reachable frames stored
   114    * in the frame tree.
   115    *
   116    * If a given function |cb| returns a value, it must be an object. It may
   117    * however return "null" to indicate that there is no data to be stored for
   118    * the given frame.
   119    *
   120    * The object returned by |cb| cannot have any property named "children" as
   121    * that is used to store information about subframes in the tree returned
   122    * by |map()| and might be overridden.
   123    *
   124    * @param cb (function)
   125    * @return object
   126    */
   127   map: function (cb) {
   128     let frames = this._frames;
   130     function walk(frame) {
   131       let obj = cb(frame) || {};
   133       if (frames.has(frame)) {
   134         let children = [];
   136         Array.forEach(frame.frames, subframe => {
   137           // Don't collect any data if the frame is not contained in the
   138           // initial frame tree. It's a dynamic frame added later.
   139           if (!frames.has(subframe)) {
   140             return;
   141           }
   143           // Retrieve the frame's original position in its parent's child list.
   144           let index = frames.get(subframe);
   146           // Recursively collect data for the current subframe.
   147           let result = walk(subframe, cb);
   148           if (result && Object.keys(result).length) {
   149             children[index] = result;
   150           }
   151         });
   153         if (children.length) {
   154           obj.children = children;
   155         }
   156       }
   158       return Object.keys(obj).length ? obj : null;
   159     }
   161     return walk(this.content);
   162   },
   164   /**
   165    * Applies the given function |cb| to all frames stored in the tree. Use this
   166    * method if |map()| doesn't suit your needs and you want more control over
   167    * how data is collected.
   168    *
   169    * @param cb (function)
   170    *        This callback receives the current frame as the only argument.
   171    */
   172   forEach: function (cb) {
   173     let frames = this._frames;
   175     function walk(frame) {
   176       cb(frame);
   178       if (!frames.has(frame)) {
   179         return;
   180       }
   182       Array.forEach(frame.frames, subframe => {
   183         if (frames.has(subframe)) {
   184           cb(subframe);
   185         }
   186       });
   187     }
   189     walk(this.content);
   190   },
   192   /**
   193    * Stores a given |frame| and its children in the frame tree.
   194    *
   195    * @param frame (nsIDOMWindow)
   196    * @param index (int)
   197    *        The index in the given frame's parent's child list.
   198    */
   199   collect: function (frame, index = 0) {
   200     // Mark the given frame as contained in the frame tree.
   201     this._frames.set(frame, index);
   203     // Mark the given frame's subframes as contained in the tree.
   204     Array.forEach(frame.frames, this.collect, this);
   205   },
   207   /**
   208    * @see nsIWebProgressListener.onStateChange
   209    *
   210    * We want to be notified about:
   211    *  - new documents that start loading to clear the current frame tree;
   212    *  - completed document loads to recollect reachable frames.
   213    */
   214   onStateChange: function (webProgress, request, stateFlags, status) {
   215     // Ignore state changes for subframes because we're only interested in the
   216     // top-document starting or stopping its load. We thus only care about any
   217     // changes to the root of the frame tree, not to any of its nodes/leafs.
   218     if (!webProgress.isTopLevel || webProgress.DOMWindow != this.content) {
   219       return;
   220     }
   222     if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
   223       // Clear the list of frames until we can recollect it.
   224       this._frames.clear();
   226       // Notify observers that the frame tree has been reset.
   227       this.notifyObservers("onFrameTreeReset");
   228     } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
   229       // The document and its resources have finished loading.
   230       this.collect(webProgress.DOMWindow);
   232       // Notify observers that the frame tree has been reset.
   233       this.notifyObservers("onFrameTreeCollected");
   234     }
   235   },
   237   // Unused nsIWebProgressListener methods.
   238   onLocationChange: function () {},
   239   onProgressChange: function () {},
   240   onSecurityChange: function () {},
   241   onStatusChange: function () {},
   243   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
   244                                          Ci.nsISupportsWeakReference])
   245 };

mercurial