forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			254 lines
		
	
	
	
		
			7.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			254 lines
		
	
	
	
		
			7.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | |
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| this.EXPORTED_SYMBOLS = ["FrameTree"];
 | |
| 
 | |
| const Cu = Components.utils;
 | |
| const Ci = Components.interfaces;
 | |
| 
 | |
| Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 | |
| 
 | |
| const EXPORTED_METHODS = ["addObserver", "contains", "map", "forEach"];
 | |
| 
 | |
| /**
 | |
|  * A FrameTree represents all frames that were reachable when the document
 | |
|  * was loaded. We use this information to ignore frames when collecting
 | |
|  * sessionstore data as we can't currently restore anything for frames that
 | |
|  * have been created dynamically after or at the load event.
 | |
|  *
 | |
|  * @constructor
 | |
|  */
 | |
| function FrameTree(chromeGlobal) {
 | |
|   let internal = new FrameTreeInternal(chromeGlobal);
 | |
|   let external = {};
 | |
| 
 | |
|   for (let method of EXPORTED_METHODS) {
 | |
|     external[method] = internal[method].bind(internal);
 | |
|   }
 | |
| 
 | |
|   return Object.freeze(external);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * The internal frame tree API that the public one points to.
 | |
|  *
 | |
|  * @constructor
 | |
|  */
 | |
| function FrameTreeInternal(chromeGlobal) {
 | |
|   // A WeakMap that uses frames (DOMWindows) as keys and their initial indices
 | |
|   // in their parents' child lists as values. Suppose we have a root frame with
 | |
|   // three subframes i.e. a page with three iframes. The WeakMap would have
 | |
|   // four entries and look as follows:
 | |
|   //
 | |
|   // root -> 0
 | |
|   // subframe1 -> 0
 | |
|   // subframe2 -> 1
 | |
|   // subframe3 -> 2
 | |
|   //
 | |
|   // Should one of the subframes disappear we will stop collecting data for it
 | |
|   // as |this._frames.has(frame) == false|. All other subframes will maintain
 | |
|   // their initial indices to ensure we can restore frame data appropriately.
 | |
|   this._frames = new WeakMap();
 | |
| 
 | |
|   // The Set of observers that will be notified when the frame changes.
 | |
|   this._observers = new Set();
 | |
| 
 | |
|   // The chrome global we use to retrieve the current DOMWindow.
 | |
|   this._chromeGlobal = chromeGlobal;
 | |
| 
 | |
|   // Register a web progress listener to be notified about new page loads.
 | |
|   let docShell = chromeGlobal.docShell;
 | |
|   let ifreq = docShell.QueryInterface(Ci.nsIInterfaceRequestor);
 | |
|   let webProgress = ifreq.getInterface(Ci.nsIWebProgress);
 | |
|   webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
 | |
| }
 | |
| 
 | |
| FrameTreeInternal.prototype = {
 | |
| 
 | |
|   // Returns the docShell's current global.
 | |
|   get content() {
 | |
|     return this._chromeGlobal.content;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Adds a given observer |obs| to the set of observers that will be notified
 | |
|    * when the frame tree is reset (when a new document starts loading) or
 | |
|    * recollected (when a document finishes loading).
 | |
|    *
 | |
|    * @param obs (object)
 | |
|    */
 | |
|   addObserver: function (obs) {
 | |
|     this._observers.add(obs);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Notifies all observers that implement the given |method|.
 | |
|    *
 | |
|    * @param method (string)
 | |
|    */
 | |
|   notifyObservers: function (method) {
 | |
|     for (let obs of this._observers) {
 | |
|       if (obs.hasOwnProperty(method)) {
 | |
|         obs[method]();
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Checks whether a given |frame| is contained in the collected frame tree.
 | |
|    * If it is not, this indicates that we should not collect data for it.
 | |
|    *
 | |
|    * @param frame (nsIDOMWindow)
 | |
|    * @return bool
 | |
|    */
 | |
|   contains: function (frame) {
 | |
|     return this._frames.has(frame);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Recursively applies the given function |cb| to the stored frame tree. Use
 | |
|    * this method to collect sessionstore data for all reachable frames stored
 | |
|    * in the frame tree.
 | |
|    *
 | |
|    * If a given function |cb| returns a value, it must be an object. It may
 | |
|    * however return "null" to indicate that there is no data to be stored for
 | |
|    * the given frame.
 | |
|    *
 | |
|    * The object returned by |cb| cannot have any property named "children" as
 | |
|    * that is used to store information about subframes in the tree returned
 | |
|    * by |map()| and might be overridden.
 | |
|    *
 | |
|    * @param cb (function)
 | |
|    * @return object
 | |
|    */
 | |
|   map: function (cb) {
 | |
|     let frames = this._frames;
 | |
| 
 | |
|     function walk(frame) {
 | |
|       let obj = cb(frame) || {};
 | |
| 
 | |
|       if (frames.has(frame)) {
 | |
|         let children = [];
 | |
| 
 | |
|         Array.forEach(frame.frames, subframe => {
 | |
|           // Don't collect any data if the frame is not contained in the
 | |
|           // initial frame tree. It's a dynamic frame added later.
 | |
|           if (!frames.has(subframe)) {
 | |
|             return;
 | |
|           }
 | |
| 
 | |
|           // Retrieve the frame's original position in its parent's child list.
 | |
|           let index = frames.get(subframe);
 | |
| 
 | |
|           // Recursively collect data for the current subframe.
 | |
|           let result = walk(subframe, cb);
 | |
|           if (result && Object.keys(result).length) {
 | |
|             children[index] = result;
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         if (children.length) {
 | |
|           obj.children = children;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       return Object.keys(obj).length ? obj : null;
 | |
|     }
 | |
| 
 | |
|     return walk(this.content);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Applies the given function |cb| to all frames stored in the tree. Use this
 | |
|    * method if |map()| doesn't suit your needs and you want more control over
 | |
|    * how data is collected.
 | |
|    *
 | |
|    * @param cb (function)
 | |
|    *        This callback receives the current frame as the only argument.
 | |
|    */
 | |
|   forEach: function (cb) {
 | |
|     let frames = this._frames;
 | |
| 
 | |
|     function walk(frame) {
 | |
|       cb(frame);
 | |
| 
 | |
|       if (!frames.has(frame)) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       Array.forEach(frame.frames, subframe => {
 | |
|         if (frames.has(subframe)) {
 | |
|           cb(subframe);
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     walk(this.content);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Stores a given |frame| and its children in the frame tree.
 | |
|    *
 | |
|    * @param frame (nsIDOMWindow)
 | |
|    * @param index (int)
 | |
|    *        The index in the given frame's parent's child list.
 | |
|    */
 | |
|   collect: function (frame, index = 0) {
 | |
|     // Mark the given frame as contained in the frame tree.
 | |
|     this._frames.set(frame, index);
 | |
| 
 | |
|     // Mark the given frame's subframes as contained in the tree.
 | |
|     Array.forEach(frame.frames, this.collect, this);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * @see nsIWebProgressListener.onStateChange
 | |
|    *
 | |
|    * We want to be notified about:
 | |
|    *  - new documents that start loading to clear the current frame tree;
 | |
|    *  - completed document loads to recollect reachable frames.
 | |
|    */
 | |
|   onStateChange: function (webProgress, request, stateFlags, status) {
 | |
|     // Ignore state changes for subframes because we're only interested in the
 | |
|     // top-document starting or stopping its load. We thus only care about any
 | |
|     // changes to the root of the frame tree, not to any of its nodes/leafs.
 | |
|     if (!webProgress.isTopLevel || webProgress.DOMWindow != this.content) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // onStateChange will be fired when loading the initial about:blank URI for
 | |
|     // a browser, which we don't actually care about. This is particularly for
 | |
|     // the case of unrestored background tabs, where the content has not yet
 | |
|     // been restored: we don't want to accidentally send any updates to the
 | |
|     // parent when the about:blank placeholder page has loaded.
 | |
|     if (!this._chromeGlobal.docShell.hasLoadedNonBlankURI) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
 | |
|       // Clear the list of frames until we can recollect it.
 | |
|       this._frames = new WeakMap();
 | |
| 
 | |
|       // Notify observers that the frame tree has been reset.
 | |
|       this.notifyObservers("onFrameTreeReset");
 | |
|     } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
 | |
|       // The document and its resources have finished loading.
 | |
|       this.collect(webProgress.DOMWindow);
 | |
| 
 | |
|       // Notify observers that the frame tree has been reset.
 | |
|       this.notifyObservers("onFrameTreeCollected");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // Unused nsIWebProgressListener methods.
 | |
|   onLocationChange: function () {},
 | |
|   onProgressChange: function () {},
 | |
|   onSecurityChange: function () {},
 | |
|   onStatusChange: function () {},
 | |
| 
 | |
|   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
 | |
|                                          Ci.nsISupportsWeakReference])
 | |
| };
 | 
