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