browser/components/sessionstore/src/TabState.jsm

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:7d49117a4ad4
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/. */
4
5 "use strict";
6
7 this.EXPORTED_SYMBOLS = ["TabState"];
8
9 const Cu = Components.utils;
10
11 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
12 Cu.import("resource://gre/modules/Promise.jsm", this);
13 Cu.import("resource://gre/modules/Task.jsm", this);
14
15 XPCOMUtils.defineLazyModuleGetter(this, "console",
16 "resource://gre/modules/devtools/Console.jsm");
17 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
18 "resource:///modules/sessionstore/PrivacyFilter.jsm");
19 XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
20 "resource:///modules/sessionstore/TabStateCache.jsm");
21 XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
22 "resource:///modules/sessionstore/TabAttributes.jsm");
23 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
24 "resource:///modules/sessionstore/Utils.jsm");
25
26 /**
27 * Module that contains tab state collection methods.
28 */
29 this.TabState = Object.freeze({
30 setSyncHandler: function (browser, handler) {
31 TabStateInternal.setSyncHandler(browser, handler);
32 },
33
34 update: function (browser, data) {
35 TabStateInternal.update(browser, data);
36 },
37
38 flush: function (browser) {
39 TabStateInternal.flush(browser);
40 },
41
42 flushWindow: function (window) {
43 TabStateInternal.flushWindow(window);
44 },
45
46 collect: function (tab) {
47 return TabStateInternal.collect(tab);
48 },
49
50 clone: function (tab) {
51 return TabStateInternal.clone(tab);
52 }
53 });
54
55 let TabStateInternal = {
56 // A map (xul:browser -> handler) that maps a tab to the
57 // synchronous collection handler object for that tab.
58 // See SyncHandler in content-sessionStore.js.
59 _syncHandlers: new WeakMap(),
60
61 // A map (xul:browser -> int) that maps a browser to the
62 // last "SessionStore:update" message ID we received for it.
63 _latestMessageID: new WeakMap(),
64
65 /**
66 * Install the sync handler object from a given tab.
67 */
68 setSyncHandler: function (browser, handler) {
69 this._syncHandlers.set(browser.permanentKey, handler);
70 this._latestMessageID.set(browser.permanentKey, 0);
71 },
72
73 /**
74 * Processes a data update sent by the content script.
75 */
76 update: function (browser, {id, data}) {
77 // Only ever process messages that have an ID higher than the last one we
78 // saw. This ensures we don't use stale data that has already been received
79 // synchronously.
80 if (id > this._latestMessageID.get(browser.permanentKey)) {
81 this._latestMessageID.set(browser.permanentKey, id);
82 TabStateCache.update(browser, data);
83 }
84 },
85
86 /**
87 * Flushes all data currently queued in the given browser's content script.
88 */
89 flush: function (browser) {
90 if (this._syncHandlers.has(browser.permanentKey)) {
91 let lastID = this._latestMessageID.get(browser.permanentKey);
92 this._syncHandlers.get(browser.permanentKey).flush(lastID);
93 }
94 },
95
96 /**
97 * Flushes queued content script data for all browsers of a given window.
98 */
99 flushWindow: function (window) {
100 for (let browser of window.gBrowser.browsers) {
101 this.flush(browser);
102 }
103 },
104
105 /**
106 * Collect data related to a single tab, synchronously.
107 *
108 * @param tab
109 * tabbrowser tab
110 *
111 * @returns {TabData} An object with the data for this tab. If the
112 * tab has not been invalidated since the last call to
113 * collect(aTab), the same object is returned.
114 */
115 collect: function (tab) {
116 return this._collectBaseTabData(tab);
117 },
118
119 /**
120 * Collect data related to a single tab, including private data.
121 * Use with caution.
122 *
123 * @param tab
124 * tabbrowser tab
125 *
126 * @returns {object} An object with the data for this tab. This data is never
127 * cached, it will always be read from the tab and thus be
128 * up-to-date.
129 */
130 clone: function (tab) {
131 return this._collectBaseTabData(tab, {includePrivateData: true});
132 },
133
134 /**
135 * Collects basic tab data for a given tab.
136 *
137 * @param tab
138 * tabbrowser tab
139 * @param options (object)
140 * {includePrivateData: true} to always include private data
141 *
142 * @returns {object} An object with the basic data for this tab.
143 */
144 _collectBaseTabData: function (tab, options) {
145 let tabData = {entries: [], lastAccessed: tab.lastAccessed };
146 let browser = tab.linkedBrowser;
147
148 if (!browser || !browser.currentURI) {
149 // can happen when calling this function right after .addTab()
150 return tabData;
151 }
152 if (browser.__SS_data) {
153 // Use the data to be restored when the tab hasn't been
154 // completely loaded. We clone the data, since we're updating it
155 // here and the caller may update it further.
156 tabData = Utils.shallowCopy(browser.__SS_data);
157 if (tab.pinned)
158 tabData.pinned = true;
159 else
160 delete tabData.pinned;
161 tabData.hidden = tab.hidden;
162
163 // If __SS_extdata is set then we'll use that since it might be newer.
164 if (tab.__SS_extdata)
165 tabData.extData = tab.__SS_extdata;
166 // If it exists but is empty then a key was likely deleted. In that case just
167 // delete extData.
168 if (tabData.extData && !Object.keys(tabData.extData).length)
169 delete tabData.extData;
170 return tabData;
171 }
172
173 // If there is a userTypedValue set, then either the user has typed something
174 // in the URL bar, or a new tab was opened with a URI to load. userTypedClear
175 // is used to indicate whether the tab was in some sort of loading state with
176 // userTypedValue.
177 if (browser.userTypedValue) {
178 tabData.userTypedValue = browser.userTypedValue;
179 tabData.userTypedClear = browser.userTypedClear;
180 } else {
181 delete tabData.userTypedValue;
182 delete tabData.userTypedClear;
183 }
184
185 if (tab.pinned)
186 tabData.pinned = true;
187 else
188 delete tabData.pinned;
189 tabData.hidden = tab.hidden;
190
191 // Save tab attributes.
192 tabData.attributes = TabAttributes.get(tab);
193
194 // Store the tab icon.
195 let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
196 tabData.image = tabbrowser.getIcon(tab);
197
198 if (tab.__SS_extdata)
199 tabData.extData = tab.__SS_extdata;
200 else if (tabData.extData)
201 delete tabData.extData;
202
203 // Copy data from the tab state cache only if the tab has fully finished
204 // restoring. We don't want to overwrite data contained in __SS_data.
205 this._copyFromCache(tab, tabData, options);
206
207 return tabData;
208 },
209
210 /**
211 * Copy tab data for the given |tab| from the cache to |tabData|.
212 *
213 * @param tab (xul:tab)
214 * The tab belonging to the given |tabData| object.
215 * @param tabData (object)
216 * The tab data belonging to the given |tab|.
217 * @param options (object)
218 * {includePrivateData: true} to always include private data
219 */
220 _copyFromCache: function (tab, tabData, options = {}) {
221 let data = TabStateCache.get(tab.linkedBrowser);
222 if (!data) {
223 return;
224 }
225
226 // The caller may explicitly request to omit privacy checks.
227 let includePrivateData = options && options.includePrivateData;
228
229 for (let key of Object.keys(data)) {
230 let value = data[key];
231
232 // Filter sensitive data according to the current privacy level.
233 if (!includePrivateData) {
234 if (key === "storage") {
235 value = PrivacyFilter.filterSessionStorageData(value, tab.pinned);
236 } else if (key === "formdata") {
237 value = PrivacyFilter.filterFormData(value, tab.pinned);
238 }
239 }
240
241 if (key === "history") {
242 tabData.entries = value.entries;
243
244 if (value.hasOwnProperty("index")) {
245 tabData.index = value.index;
246 }
247 } else if (value) {
248 tabData[key] = value;
249 }
250 }
251 }
252 };

mercurial