Wed, 31 Dec 2014 06:09:35 +0100
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 };