1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/components/sessionstore/src/TabState.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,252 @@ 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 = ["TabState"]; 1.11 + 1.12 +const Cu = Components.utils; 1.13 + 1.14 +Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); 1.15 +Cu.import("resource://gre/modules/Promise.jsm", this); 1.16 +Cu.import("resource://gre/modules/Task.jsm", this); 1.17 + 1.18 +XPCOMUtils.defineLazyModuleGetter(this, "console", 1.19 + "resource://gre/modules/devtools/Console.jsm"); 1.20 +XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter", 1.21 + "resource:///modules/sessionstore/PrivacyFilter.jsm"); 1.22 +XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache", 1.23 + "resource:///modules/sessionstore/TabStateCache.jsm"); 1.24 +XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes", 1.25 + "resource:///modules/sessionstore/TabAttributes.jsm"); 1.26 +XPCOMUtils.defineLazyModuleGetter(this, "Utils", 1.27 + "resource:///modules/sessionstore/Utils.jsm"); 1.28 + 1.29 +/** 1.30 + * Module that contains tab state collection methods. 1.31 + */ 1.32 +this.TabState = Object.freeze({ 1.33 + setSyncHandler: function (browser, handler) { 1.34 + TabStateInternal.setSyncHandler(browser, handler); 1.35 + }, 1.36 + 1.37 + update: function (browser, data) { 1.38 + TabStateInternal.update(browser, data); 1.39 + }, 1.40 + 1.41 + flush: function (browser) { 1.42 + TabStateInternal.flush(browser); 1.43 + }, 1.44 + 1.45 + flushWindow: function (window) { 1.46 + TabStateInternal.flushWindow(window); 1.47 + }, 1.48 + 1.49 + collect: function (tab) { 1.50 + return TabStateInternal.collect(tab); 1.51 + }, 1.52 + 1.53 + clone: function (tab) { 1.54 + return TabStateInternal.clone(tab); 1.55 + } 1.56 +}); 1.57 + 1.58 +let TabStateInternal = { 1.59 + // A map (xul:browser -> handler) that maps a tab to the 1.60 + // synchronous collection handler object for that tab. 1.61 + // See SyncHandler in content-sessionStore.js. 1.62 + _syncHandlers: new WeakMap(), 1.63 + 1.64 + // A map (xul:browser -> int) that maps a browser to the 1.65 + // last "SessionStore:update" message ID we received for it. 1.66 + _latestMessageID: new WeakMap(), 1.67 + 1.68 + /** 1.69 + * Install the sync handler object from a given tab. 1.70 + */ 1.71 + setSyncHandler: function (browser, handler) { 1.72 + this._syncHandlers.set(browser.permanentKey, handler); 1.73 + this._latestMessageID.set(browser.permanentKey, 0); 1.74 + }, 1.75 + 1.76 + /** 1.77 + * Processes a data update sent by the content script. 1.78 + */ 1.79 + update: function (browser, {id, data}) { 1.80 + // Only ever process messages that have an ID higher than the last one we 1.81 + // saw. This ensures we don't use stale data that has already been received 1.82 + // synchronously. 1.83 + if (id > this._latestMessageID.get(browser.permanentKey)) { 1.84 + this._latestMessageID.set(browser.permanentKey, id); 1.85 + TabStateCache.update(browser, data); 1.86 + } 1.87 + }, 1.88 + 1.89 + /** 1.90 + * Flushes all data currently queued in the given browser's content script. 1.91 + */ 1.92 + flush: function (browser) { 1.93 + if (this._syncHandlers.has(browser.permanentKey)) { 1.94 + let lastID = this._latestMessageID.get(browser.permanentKey); 1.95 + this._syncHandlers.get(browser.permanentKey).flush(lastID); 1.96 + } 1.97 + }, 1.98 + 1.99 + /** 1.100 + * Flushes queued content script data for all browsers of a given window. 1.101 + */ 1.102 + flushWindow: function (window) { 1.103 + for (let browser of window.gBrowser.browsers) { 1.104 + this.flush(browser); 1.105 + } 1.106 + }, 1.107 + 1.108 + /** 1.109 + * Collect data related to a single tab, synchronously. 1.110 + * 1.111 + * @param tab 1.112 + * tabbrowser tab 1.113 + * 1.114 + * @returns {TabData} An object with the data for this tab. If the 1.115 + * tab has not been invalidated since the last call to 1.116 + * collect(aTab), the same object is returned. 1.117 + */ 1.118 + collect: function (tab) { 1.119 + return this._collectBaseTabData(tab); 1.120 + }, 1.121 + 1.122 + /** 1.123 + * Collect data related to a single tab, including private data. 1.124 + * Use with caution. 1.125 + * 1.126 + * @param tab 1.127 + * tabbrowser tab 1.128 + * 1.129 + * @returns {object} An object with the data for this tab. This data is never 1.130 + * cached, it will always be read from the tab and thus be 1.131 + * up-to-date. 1.132 + */ 1.133 + clone: function (tab) { 1.134 + return this._collectBaseTabData(tab, {includePrivateData: true}); 1.135 + }, 1.136 + 1.137 + /** 1.138 + * Collects basic tab data for a given tab. 1.139 + * 1.140 + * @param tab 1.141 + * tabbrowser tab 1.142 + * @param options (object) 1.143 + * {includePrivateData: true} to always include private data 1.144 + * 1.145 + * @returns {object} An object with the basic data for this tab. 1.146 + */ 1.147 + _collectBaseTabData: function (tab, options) { 1.148 + let tabData = {entries: [], lastAccessed: tab.lastAccessed }; 1.149 + let browser = tab.linkedBrowser; 1.150 + 1.151 + if (!browser || !browser.currentURI) { 1.152 + // can happen when calling this function right after .addTab() 1.153 + return tabData; 1.154 + } 1.155 + if (browser.__SS_data) { 1.156 + // Use the data to be restored when the tab hasn't been 1.157 + // completely loaded. We clone the data, since we're updating it 1.158 + // here and the caller may update it further. 1.159 + tabData = Utils.shallowCopy(browser.__SS_data); 1.160 + if (tab.pinned) 1.161 + tabData.pinned = true; 1.162 + else 1.163 + delete tabData.pinned; 1.164 + tabData.hidden = tab.hidden; 1.165 + 1.166 + // If __SS_extdata is set then we'll use that since it might be newer. 1.167 + if (tab.__SS_extdata) 1.168 + tabData.extData = tab.__SS_extdata; 1.169 + // If it exists but is empty then a key was likely deleted. In that case just 1.170 + // delete extData. 1.171 + if (tabData.extData && !Object.keys(tabData.extData).length) 1.172 + delete tabData.extData; 1.173 + return tabData; 1.174 + } 1.175 + 1.176 + // If there is a userTypedValue set, then either the user has typed something 1.177 + // in the URL bar, or a new tab was opened with a URI to load. userTypedClear 1.178 + // is used to indicate whether the tab was in some sort of loading state with 1.179 + // userTypedValue. 1.180 + if (browser.userTypedValue) { 1.181 + tabData.userTypedValue = browser.userTypedValue; 1.182 + tabData.userTypedClear = browser.userTypedClear; 1.183 + } else { 1.184 + delete tabData.userTypedValue; 1.185 + delete tabData.userTypedClear; 1.186 + } 1.187 + 1.188 + if (tab.pinned) 1.189 + tabData.pinned = true; 1.190 + else 1.191 + delete tabData.pinned; 1.192 + tabData.hidden = tab.hidden; 1.193 + 1.194 + // Save tab attributes. 1.195 + tabData.attributes = TabAttributes.get(tab); 1.196 + 1.197 + // Store the tab icon. 1.198 + let tabbrowser = tab.ownerDocument.defaultView.gBrowser; 1.199 + tabData.image = tabbrowser.getIcon(tab); 1.200 + 1.201 + if (tab.__SS_extdata) 1.202 + tabData.extData = tab.__SS_extdata; 1.203 + else if (tabData.extData) 1.204 + delete tabData.extData; 1.205 + 1.206 + // Copy data from the tab state cache only if the tab has fully finished 1.207 + // restoring. We don't want to overwrite data contained in __SS_data. 1.208 + this._copyFromCache(tab, tabData, options); 1.209 + 1.210 + return tabData; 1.211 + }, 1.212 + 1.213 + /** 1.214 + * Copy tab data for the given |tab| from the cache to |tabData|. 1.215 + * 1.216 + * @param tab (xul:tab) 1.217 + * The tab belonging to the given |tabData| object. 1.218 + * @param tabData (object) 1.219 + * The tab data belonging to the given |tab|. 1.220 + * @param options (object) 1.221 + * {includePrivateData: true} to always include private data 1.222 + */ 1.223 + _copyFromCache: function (tab, tabData, options = {}) { 1.224 + let data = TabStateCache.get(tab.linkedBrowser); 1.225 + if (!data) { 1.226 + return; 1.227 + } 1.228 + 1.229 + // The caller may explicitly request to omit privacy checks. 1.230 + let includePrivateData = options && options.includePrivateData; 1.231 + 1.232 + for (let key of Object.keys(data)) { 1.233 + let value = data[key]; 1.234 + 1.235 + // Filter sensitive data according to the current privacy level. 1.236 + if (!includePrivateData) { 1.237 + if (key === "storage") { 1.238 + value = PrivacyFilter.filterSessionStorageData(value, tab.pinned); 1.239 + } else if (key === "formdata") { 1.240 + value = PrivacyFilter.filterFormData(value, tab.pinned); 1.241 + } 1.242 + } 1.243 + 1.244 + if (key === "history") { 1.245 + tabData.entries = value.entries; 1.246 + 1.247 + if (value.hasOwnProperty("index")) { 1.248 + tabData.index = value.index; 1.249 + } 1.250 + } else if (value) { 1.251 + tabData[key] = value; 1.252 + } 1.253 + } 1.254 + } 1.255 +};