browser/components/sessionstore/src/FrameTree.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/sessionstore/src/FrameTree.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,245 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +this.EXPORTED_SYMBOLS = ["FrameTree"];
    1.11 +
    1.12 +const Cu = Components.utils;
    1.13 +const Ci = Components.interfaces;
    1.14 +
    1.15 +Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
    1.16 +
    1.17 +const EXPORTED_METHODS = ["addObserver", "contains", "map", "forEach"];
    1.18 +
    1.19 +/**
    1.20 + * A FrameTree represents all frames that were reachable when the document
    1.21 + * was loaded. We use this information to ignore frames when collecting
    1.22 + * sessionstore data as we can't currently restore anything for frames that
    1.23 + * have been created dynamically after or at the load event.
    1.24 + *
    1.25 + * @constructor
    1.26 + */
    1.27 +function FrameTree(chromeGlobal) {
    1.28 +  let internal = new FrameTreeInternal(chromeGlobal);
    1.29 +  let external = {};
    1.30 +
    1.31 +  for (let method of EXPORTED_METHODS) {
    1.32 +    external[method] = internal[method].bind(internal);
    1.33 +  }
    1.34 +
    1.35 +  return Object.freeze(external);
    1.36 +}
    1.37 +
    1.38 +/**
    1.39 + * The internal frame tree API that the public one points to.
    1.40 + *
    1.41 + * @constructor
    1.42 + */
    1.43 +function FrameTreeInternal(chromeGlobal) {
    1.44 +  // A WeakMap that uses frames (DOMWindows) as keys and their initial indices
    1.45 +  // in their parents' child lists as values. Suppose we have a root frame with
    1.46 +  // three subframes i.e. a page with three iframes. The WeakMap would have
    1.47 +  // four entries and look as follows:
    1.48 +  //
    1.49 +  // root -> 0
    1.50 +  // subframe1 -> 0
    1.51 +  // subframe2 -> 1
    1.52 +  // subframe3 -> 2
    1.53 +  //
    1.54 +  // Should one of the subframes disappear we will stop collecting data for it
    1.55 +  // as |this._frames.has(frame) == false|. All other subframes will maintain
    1.56 +  // their initial indices to ensure we can restore frame data appropriately.
    1.57 +  this._frames = new WeakMap();
    1.58 +
    1.59 +  // The Set of observers that will be notified when the frame changes.
    1.60 +  this._observers = new Set();
    1.61 +
    1.62 +  // The chrome global we use to retrieve the current DOMWindow.
    1.63 +  this._chromeGlobal = chromeGlobal;
    1.64 +
    1.65 +  // Register a web progress listener to be notified about new page loads.
    1.66 +  let docShell = chromeGlobal.docShell;
    1.67 +  let ifreq = docShell.QueryInterface(Ci.nsIInterfaceRequestor);
    1.68 +  let webProgress = ifreq.getInterface(Ci.nsIWebProgress);
    1.69 +  webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
    1.70 +}
    1.71 +
    1.72 +FrameTreeInternal.prototype = {
    1.73 +
    1.74 +  // Returns the docShell's current global.
    1.75 +  get content() {
    1.76 +    return this._chromeGlobal.content;
    1.77 +  },
    1.78 +
    1.79 +  /**
    1.80 +   * Adds a given observer |obs| to the set of observers that will be notified
    1.81 +   * when the frame tree is reset (when a new document starts loading) or
    1.82 +   * recollected (when a document finishes loading).
    1.83 +   *
    1.84 +   * @param obs (object)
    1.85 +   */
    1.86 +  addObserver: function (obs) {
    1.87 +    this._observers.add(obs);
    1.88 +  },
    1.89 +
    1.90 +  /**
    1.91 +   * Notifies all observers that implement the given |method|.
    1.92 +   *
    1.93 +   * @param method (string)
    1.94 +   */
    1.95 +  notifyObservers: function (method) {
    1.96 +    for (let obs of this._observers) {
    1.97 +      if (obs.hasOwnProperty(method)) {
    1.98 +        obs[method]();
    1.99 +      }
   1.100 +    }
   1.101 +  },
   1.102 +
   1.103 +  /**
   1.104 +   * Checks whether a given |frame| is contained in the collected frame tree.
   1.105 +   * If it is not, this indicates that we should not collect data for it.
   1.106 +   *
   1.107 +   * @param frame (nsIDOMWindow)
   1.108 +   * @return bool
   1.109 +   */
   1.110 +  contains: function (frame) {
   1.111 +    return this._frames.has(frame);
   1.112 +  },
   1.113 +
   1.114 +  /**
   1.115 +   * Recursively applies the given function |cb| to the stored frame tree. Use
   1.116 +   * this method to collect sessionstore data for all reachable frames stored
   1.117 +   * in the frame tree.
   1.118 +   *
   1.119 +   * If a given function |cb| returns a value, it must be an object. It may
   1.120 +   * however return "null" to indicate that there is no data to be stored for
   1.121 +   * the given frame.
   1.122 +   *
   1.123 +   * The object returned by |cb| cannot have any property named "children" as
   1.124 +   * that is used to store information about subframes in the tree returned
   1.125 +   * by |map()| and might be overridden.
   1.126 +   *
   1.127 +   * @param cb (function)
   1.128 +   * @return object
   1.129 +   */
   1.130 +  map: function (cb) {
   1.131 +    let frames = this._frames;
   1.132 +
   1.133 +    function walk(frame) {
   1.134 +      let obj = cb(frame) || {};
   1.135 +
   1.136 +      if (frames.has(frame)) {
   1.137 +        let children = [];
   1.138 +
   1.139 +        Array.forEach(frame.frames, subframe => {
   1.140 +          // Don't collect any data if the frame is not contained in the
   1.141 +          // initial frame tree. It's a dynamic frame added later.
   1.142 +          if (!frames.has(subframe)) {
   1.143 +            return;
   1.144 +          }
   1.145 +
   1.146 +          // Retrieve the frame's original position in its parent's child list.
   1.147 +          let index = frames.get(subframe);
   1.148 +
   1.149 +          // Recursively collect data for the current subframe.
   1.150 +          let result = walk(subframe, cb);
   1.151 +          if (result && Object.keys(result).length) {
   1.152 +            children[index] = result;
   1.153 +          }
   1.154 +        });
   1.155 +
   1.156 +        if (children.length) {
   1.157 +          obj.children = children;
   1.158 +        }
   1.159 +      }
   1.160 +
   1.161 +      return Object.keys(obj).length ? obj : null;
   1.162 +    }
   1.163 +
   1.164 +    return walk(this.content);
   1.165 +  },
   1.166 +
   1.167 +  /**
   1.168 +   * Applies the given function |cb| to all frames stored in the tree. Use this
   1.169 +   * method if |map()| doesn't suit your needs and you want more control over
   1.170 +   * how data is collected.
   1.171 +   *
   1.172 +   * @param cb (function)
   1.173 +   *        This callback receives the current frame as the only argument.
   1.174 +   */
   1.175 +  forEach: function (cb) {
   1.176 +    let frames = this._frames;
   1.177 +
   1.178 +    function walk(frame) {
   1.179 +      cb(frame);
   1.180 +
   1.181 +      if (!frames.has(frame)) {
   1.182 +        return;
   1.183 +      }
   1.184 +
   1.185 +      Array.forEach(frame.frames, subframe => {
   1.186 +        if (frames.has(subframe)) {
   1.187 +          cb(subframe);
   1.188 +        }
   1.189 +      });
   1.190 +    }
   1.191 +
   1.192 +    walk(this.content);
   1.193 +  },
   1.194 +
   1.195 +  /**
   1.196 +   * Stores a given |frame| and its children in the frame tree.
   1.197 +   *
   1.198 +   * @param frame (nsIDOMWindow)
   1.199 +   * @param index (int)
   1.200 +   *        The index in the given frame's parent's child list.
   1.201 +   */
   1.202 +  collect: function (frame, index = 0) {
   1.203 +    // Mark the given frame as contained in the frame tree.
   1.204 +    this._frames.set(frame, index);
   1.205 +
   1.206 +    // Mark the given frame's subframes as contained in the tree.
   1.207 +    Array.forEach(frame.frames, this.collect, this);
   1.208 +  },
   1.209 +
   1.210 +  /**
   1.211 +   * @see nsIWebProgressListener.onStateChange
   1.212 +   *
   1.213 +   * We want to be notified about:
   1.214 +   *  - new documents that start loading to clear the current frame tree;
   1.215 +   *  - completed document loads to recollect reachable frames.
   1.216 +   */
   1.217 +  onStateChange: function (webProgress, request, stateFlags, status) {
   1.218 +    // Ignore state changes for subframes because we're only interested in the
   1.219 +    // top-document starting or stopping its load. We thus only care about any
   1.220 +    // changes to the root of the frame tree, not to any of its nodes/leafs.
   1.221 +    if (!webProgress.isTopLevel || webProgress.DOMWindow != this.content) {
   1.222 +      return;
   1.223 +    }
   1.224 +
   1.225 +    if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
   1.226 +      // Clear the list of frames until we can recollect it.
   1.227 +      this._frames.clear();
   1.228 +
   1.229 +      // Notify observers that the frame tree has been reset.
   1.230 +      this.notifyObservers("onFrameTreeReset");
   1.231 +    } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
   1.232 +      // The document and its resources have finished loading.
   1.233 +      this.collect(webProgress.DOMWindow);
   1.234 +
   1.235 +      // Notify observers that the frame tree has been reset.
   1.236 +      this.notifyObservers("onFrameTreeCollected");
   1.237 +    }
   1.238 +  },
   1.239 +
   1.240 +  // Unused nsIWebProgressListener methods.
   1.241 +  onLocationChange: function () {},
   1.242 +  onProgressChange: function () {},
   1.243 +  onSecurityChange: function () {},
   1.244 +  onStatusChange: function () {},
   1.245 +
   1.246 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
   1.247 +                                         Ci.nsISupportsWeakReference])
   1.248 +};

mercurial