зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to autoland. a=merge CLOSED TREE
This commit is contained in:
Коммит
156ea01527
|
@ -858,6 +858,8 @@ pref("browser.sessionstore.debug", false);
|
|||
pref("browser.sessionstore.debug.no_auto_updates", false);
|
||||
// Forget closed windows/tabs after two weeks
|
||||
pref("browser.sessionstore.cleanup.forget_closed_after", 1209600000);
|
||||
// Maximum number of bytes of DOMSessionStorage data we collect per origin.
|
||||
pref("browser.sessionstore.dom_storage_limit", 2048);
|
||||
// Amount of failed SessionFile writes until we restart the worker.
|
||||
pref("browser.sessionstore.max_write_failures", 5);
|
||||
|
||||
|
|
|
@ -7,14 +7,12 @@ const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/
|
|||
const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
|
||||
const TP_PREF = "privacy.trackingprotection.enabled";
|
||||
const ANIMATIONS_PREF = "toolkit.cosmeticAnimations.enabled";
|
||||
const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
|
||||
|
||||
// Test that the shield icon animation can be controlled by the cosmetic
|
||||
// animations pref and that one of the icons is visible in each case.
|
||||
add_task(async function testShieldAnimation() {
|
||||
await UrlClassifierTestUtils.addTestTrackers();
|
||||
Services.prefs.setBoolPref(TP_PREF, true);
|
||||
Services.prefs.setBoolPref(DTSCBN_PREF, true);
|
||||
|
||||
let tab = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
|
||||
|
||||
|
@ -22,8 +20,7 @@ add_task(async function testShieldAnimation() {
|
|||
let noAnimationIcon = document.getElementById("tracking-protection-icon");
|
||||
|
||||
Services.prefs.setBoolPref(ANIMATIONS_PREF, true);
|
||||
await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
|
||||
waitForContentBlockingEvent(2, tab.linkedBrowser.ownerGlobal)]);
|
||||
await promiseTabLoadEvent(tab, TRACKING_PAGE);
|
||||
ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden when animations are enabled");
|
||||
ok(BrowserTestUtils.is_visible(animationIcon), "the animated icon is shown when animations are enabled");
|
||||
|
||||
|
@ -32,14 +29,12 @@ add_task(async function testShieldAnimation() {
|
|||
ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden");
|
||||
|
||||
Services.prefs.setBoolPref(ANIMATIONS_PREF, false);
|
||||
await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
|
||||
waitForContentBlockingEvent(2, tab.linkedBrowser.ownerGlobal)]);
|
||||
await promiseTabLoadEvent(tab, TRACKING_PAGE);
|
||||
ok(BrowserTestUtils.is_visible(noAnimationIcon), "the default icon is shown when animations are disabled");
|
||||
ok(BrowserTestUtils.is_hidden(animationIcon), "the animated icon is hidden when animations are disabled");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
Services.prefs.clearUserPref(ANIMATIONS_PREF);
|
||||
Services.prefs.clearUserPref(TP_PREF);
|
||||
Services.prefs.clearUserPref(DTSCBN_PREF);
|
||||
UrlClassifierTestUtils.cleanupTestTrackers();
|
||||
});
|
||||
|
|
|
@ -11,7 +11,6 @@ const NCB_PREF = "network.cookie.cookieBehavior";
|
|||
const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
|
||||
const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
|
||||
const COOKIE_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/cookiePage.html";
|
||||
const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
|
@ -20,13 +19,10 @@ registerCleanupFunction(function() {
|
|||
Services.prefs.clearUserPref(TP_PREF);
|
||||
Services.prefs.clearUserPref(TP_PB_PREF);
|
||||
Services.prefs.clearUserPref(NCB_PREF);
|
||||
Services.prefs.clearUserPref(DTSCBN_PREF);
|
||||
Services.prefs.clearUserPref(ContentBlocking.prefIntroCount);
|
||||
});
|
||||
|
||||
async function testTrackingProtectionAnimation(tabbrowser) {
|
||||
Services.prefs.setBoolPref(DTSCBN_PREF, true);
|
||||
|
||||
info("Load a test page not containing tracking elements");
|
||||
let benignTab = await BrowserTestUtils.openNewForegroundTab(tabbrowser, BENIGN_PAGE);
|
||||
let ContentBlocking = tabbrowser.ownerGlobal.ContentBlocking;
|
||||
|
|
|
@ -6,14 +6,12 @@
|
|||
|
||||
const TP_PB_PREF = "privacy.trackingprotection.enabled";
|
||||
const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
|
||||
const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
|
||||
var TrackingProtection = null;
|
||||
var ContentBlocking = null;
|
||||
var browser = null;
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref(TP_PB_PREF);
|
||||
Services.prefs.clearUserPref(DTSCBN_PREF);
|
||||
ContentBlocking = TrackingProtection = browser = null;
|
||||
UrlClassifierTestUtils.cleanupTestTrackers();
|
||||
});
|
||||
|
@ -81,7 +79,6 @@ function testTrackingPageUnblocked() {
|
|||
|
||||
add_task(async function testExceptionAddition() {
|
||||
await UrlClassifierTestUtils.addTestTrackers();
|
||||
Services.prefs.setBoolPref(DTSCBN_PREF, true);
|
||||
let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
|
||||
browser = privateWin.gBrowser;
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab({ gBrowser: browser, waitForLoad: true, waitForStateStop: true });
|
||||
|
@ -95,8 +92,7 @@ add_task(async function testExceptionAddition() {
|
|||
ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
|
||||
|
||||
info("Load a test page containing tracking elements");
|
||||
await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
|
||||
waitForContentBlockingEvent(2, tab.ownerGlobal)]);
|
||||
await promiseTabLoadEvent(tab, TRACKING_PAGE);
|
||||
|
||||
testTrackingPage(tab.ownerGlobal);
|
||||
|
||||
|
@ -132,8 +128,7 @@ add_task(async function testExceptionPersistence() {
|
|||
ok(TrackingProtection.enabled, "TP is still enabled");
|
||||
|
||||
info("Load a test page containing tracking elements");
|
||||
await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
|
||||
waitForContentBlockingEvent(2, tab.ownerGlobal)]);
|
||||
await promiseTabLoadEvent(tab, TRACKING_PAGE);
|
||||
|
||||
testTrackingPage(tab.ownerGlobal);
|
||||
|
||||
|
@ -142,8 +137,7 @@ add_task(async function testExceptionPersistence() {
|
|||
clickButton("#tracking-action-unblock");
|
||||
is(identityPopupState(), "closed", "Identity popup is closed");
|
||||
|
||||
await Promise.all([tabReloadPromise,
|
||||
waitForContentBlockingEvent(2, tab.ownerGlobal)]);
|
||||
await tabReloadPromise;
|
||||
testTrackingPageUnblocked();
|
||||
|
||||
privateWin.close();
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
const TP_PREF = "privacy.trackingprotection.enabled";
|
||||
const TP_PB_PREF = "privacy.trackingprotection.pbmode.enabled";
|
||||
const TPC_PREF = "network.cookie.cookieBehavior";
|
||||
const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
|
||||
const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
|
||||
const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
|
||||
const COOKIE_PAGE = "http://not-tracking.example.com/browser/browser/base/content/test/trackingUI/cookiePage.html";
|
||||
|
@ -34,7 +33,6 @@ registerCleanupFunction(function() {
|
|||
Services.prefs.clearUserPref(TP_PREF);
|
||||
Services.prefs.clearUserPref(TP_PB_PREF);
|
||||
Services.prefs.clearUserPref(TPC_PREF);
|
||||
Services.prefs.clearUserPref(DTSCBN_PREF);
|
||||
});
|
||||
|
||||
// This is a special version of "hidden" that doesn't check for item
|
||||
|
@ -108,8 +106,8 @@ function testTrackingPage(window) {
|
|||
ok(ContentBlocking.content.hasAttribute("detected"), "trackers are detected");
|
||||
ok(!ContentBlocking.content.hasAttribute("hasException"), "content shows no exception");
|
||||
|
||||
let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
|
||||
let blockedByTP = areTrackersBlocked(isWindowPrivate);
|
||||
let isPrivateBrowsing = PrivateBrowsingUtils.isWindowPrivate(window);
|
||||
let blockedByTP = areTrackersBlocked(isPrivateBrowsing);
|
||||
is(BrowserTestUtils.is_visible(ContentBlocking.iconBox), blockedByTP,
|
||||
"icon box is" + (blockedByTP ? "" : " not") + " visible");
|
||||
is(ContentBlocking.iconBox.hasAttribute("active"), blockedByTP,
|
||||
|
@ -121,6 +119,7 @@ function testTrackingPage(window) {
|
|||
|
||||
ok(hidden("#tracking-action-block"), "blockButton is hidden");
|
||||
|
||||
let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
|
||||
if (isWindowPrivate) {
|
||||
ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
|
||||
is(!hidden("#tracking-action-unblock-private"), blockedByTP,
|
||||
|
@ -219,8 +218,6 @@ async function testContentBlocking(tab) {
|
|||
add_task(async function testNormalBrowsing() {
|
||||
await UrlClassifierTestUtils.addTestTrackers();
|
||||
|
||||
Services.prefs.setBoolPref(DTSCBN_PREF, true);
|
||||
|
||||
tabbrowser = gBrowser;
|
||||
let tab = tabbrowser.selectedTab = BrowserTestUtils.addTab(tabbrowser);
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/te
|
|||
const TP_PREF = "privacy.trackingprotection.enabled";
|
||||
const COOKIE_PREF = "network.cookie.cookieBehavior";
|
||||
const ANIMATIONS_PREF = "toolkit.cosmeticAnimations.enabled";
|
||||
const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
|
||||
|
||||
// Check that the shield icon is always hidden when all content blocking
|
||||
// categories are turned off, even when content blocking is on.
|
||||
|
@ -16,7 +15,6 @@ add_task(async function testContentBlockingAllDisabled() {
|
|||
await SpecialPowers.pushPrefEnv({set: [
|
||||
[TP_PREF, false],
|
||||
[COOKIE_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT],
|
||||
[DTSCBN_PREF, true],
|
||||
]});
|
||||
await UrlClassifierTestUtils.addTestTrackers();
|
||||
|
||||
|
@ -32,8 +30,7 @@ add_task(async function testContentBlockingAllDisabled() {
|
|||
let animationIcon = document.getElementById("tracking-protection-icon-animatable-image");
|
||||
let noAnimationIcon = document.getElementById("tracking-protection-icon");
|
||||
|
||||
await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
|
||||
waitForContentBlockingEvent(2, tab.ownerGlobal)]);
|
||||
await promiseTabLoadEvent(tab, TRACKING_PAGE);
|
||||
ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden");
|
||||
ok(BrowserTestUtils.is_hidden(animationIcon), "the animated icon is hidden");
|
||||
|
||||
|
@ -50,7 +47,6 @@ add_task(async function testContentBlockingAllDisabled() {
|
|||
await SpecialPowers.pushPrefEnv({set: [
|
||||
[TP_PREF, true],
|
||||
]});
|
||||
await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
|
||||
waitForContentBlockingEvent(2, tab.ownerGlobal)]);
|
||||
await promiseTabLoadEvent(tab, TRACKING_PAGE);
|
||||
ok(BrowserTestUtils.is_visible(noAnimationIcon), "the default icon is shown");
|
||||
});
|
||||
|
|
|
@ -8,8 +8,12 @@ var EXPORTED_SYMBOLS = ["ContentRestore"];
|
|||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm", this);
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "FormData",
|
||||
"resource://gre/modules/FormData.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "SessionHistory",
|
||||
"resource://gre/modules/sessionstore/SessionHistory.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "SessionStorage",
|
||||
"resource:///modules/sessionstore/SessionStorage.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "Utils",
|
||||
"resource://gre/modules/sessionstore/Utils.jsm");
|
||||
|
||||
|
@ -137,7 +141,7 @@ ContentRestoreInternal.prototype = {
|
|||
|
||||
|
||||
if (tabData.storage && this.docShell instanceof Ci.nsIDocShell) {
|
||||
SessionStoreUtils.restoreSessionStorage(this.docShell, tabData.storage);
|
||||
SessionStorage.restore(this.docShell, tabData.storage);
|
||||
delete tabData.storage;
|
||||
}
|
||||
|
||||
|
@ -299,7 +303,7 @@ ContentRestoreInternal.prototype = {
|
|||
// restore() will return false, and thus abort restoration for the
|
||||
// current |frame| and its descendants, if |data.url| is given but
|
||||
// doesn't match the loaded document's URL.
|
||||
return SessionStoreUtils.restoreFormData(frame.document, data);
|
||||
return FormData.restore(frame, data);
|
||||
});
|
||||
|
||||
// Restore scroll data.
|
||||
|
|
|
@ -18,6 +18,8 @@ ChromeUtils.defineModuleGetter(this, "ContentRestore",
|
|||
"resource:///modules/sessionstore/ContentRestore.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "SessionHistory",
|
||||
"resource://gre/modules/sessionstore/SessionHistory.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "SessionStorage",
|
||||
"resource:///modules/sessionstore/SessionStorage.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "Utils",
|
||||
"resource://gre/modules/sessionstore/Utils.jsm");
|
||||
|
||||
|
@ -531,10 +533,7 @@ class SessionStorageListener extends Handler {
|
|||
// messages.
|
||||
this.resetChanges();
|
||||
|
||||
this.messageQueue.push("storage", () => {
|
||||
let data = SessionStoreUtils.collectSessionStorage(content);
|
||||
return Object.keys(data).length ? data : null;
|
||||
});
|
||||
this.messageQueue.push("storage", () => SessionStorage.collect(content));
|
||||
}
|
||||
|
||||
onPageLoadCompleted() {
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
/* 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";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["SessionStorage"];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// A bound to the size of data to store for DOM Storage.
|
||||
const DOM_STORAGE_LIMIT_PREF = "browser.sessionstore.dom_storage_limit";
|
||||
|
||||
// Returns the principal for a given |frame| contained in a given |docShell|.
|
||||
function getPrincipalForFrame(docShell, frame) {
|
||||
let ssm = Services.scriptSecurityManager;
|
||||
let uri = frame.document.documentURIObject;
|
||||
return ssm.getDocShellCodebasePrincipal(uri, docShell);
|
||||
}
|
||||
|
||||
var SessionStorage = Object.freeze({
|
||||
/**
|
||||
* Updates all sessionStorage "super cookies"
|
||||
* @param content
|
||||
* A tab's global, i.e. the root frame we want to collect for.
|
||||
* @return Returns a nested object that will have hosts as keys and per-origin
|
||||
* session storage data as strings. For example:
|
||||
* {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
|
||||
*/
|
||||
collect(content) {
|
||||
return SessionStorageInternal.collect(content);
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores all sessionStorage "super cookies".
|
||||
* @param aDocShell
|
||||
* A tab's docshell (containing the sessionStorage)
|
||||
* @param aStorageData
|
||||
* A nested object with storage data to be restored that has hosts as
|
||||
* keys and per-origin session storage data as strings. For example:
|
||||
* {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
|
||||
*/
|
||||
restore(aDocShell, aStorageData) {
|
||||
SessionStorageInternal.restore(aDocShell, aStorageData);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Calls the given callback |cb|, passing |frame| and each of its descendants.
|
||||
*/
|
||||
function forEachNonDynamicChildFrame(frame, cb) {
|
||||
// Call for current frame.
|
||||
cb(frame);
|
||||
|
||||
// Call the callback recursively for each descendant.
|
||||
SessionStoreUtils.forEachNonDynamicChildFrame(frame, subframe => {
|
||||
return forEachNonDynamicChildFrame(subframe, cb);
|
||||
});
|
||||
}
|
||||
|
||||
var SessionStorageInternal = {
|
||||
/**
|
||||
* Reads all session storage data from the given docShell.
|
||||
* @param content
|
||||
* A tab's global, i.e. the root frame we want to collect for.
|
||||
* @return Returns a nested object that will have hosts as keys and per-origin
|
||||
* session storage data as strings. For example:
|
||||
* {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
|
||||
*/
|
||||
collect(content) {
|
||||
let data = {};
|
||||
let visitedOrigins = new Set();
|
||||
let docShell = content.docShell;
|
||||
|
||||
forEachNonDynamicChildFrame(content, frame => {
|
||||
let principal = getPrincipalForFrame(docShell, frame);
|
||||
if (!principal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the origin of the current history entry
|
||||
// and use that as a key for the per-principal storage data.
|
||||
let origin;
|
||||
try {
|
||||
// The origin getter may throw for about:blank iframes as of bug 1340710,
|
||||
// but we should ignore them anyway.
|
||||
origin = principal.origin;
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
if (visitedOrigins.has(origin)) {
|
||||
// Don't read a host twice.
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark the current origin as visited.
|
||||
visitedOrigins.add(origin);
|
||||
|
||||
let originData = this._readEntry(principal, docShell);
|
||||
if (Object.keys(originData).length) {
|
||||
data[origin] = originData;
|
||||
}
|
||||
});
|
||||
|
||||
return Object.keys(data).length ? data : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes session storage data to the given tab.
|
||||
* @param aDocShell
|
||||
* A tab's docshell (containing the sessionStorage)
|
||||
* @param aStorageData
|
||||
* A nested object with storage data to be restored that has hosts as
|
||||
* keys and per-origin session storage data as strings. For example:
|
||||
* {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
|
||||
*/
|
||||
restore(aDocShell, aStorageData) {
|
||||
for (let origin of Object.keys(aStorageData)) {
|
||||
let data = aStorageData[origin];
|
||||
|
||||
let principal;
|
||||
|
||||
try {
|
||||
// NOTE: In capture() we record the full origin for the URI which the
|
||||
// sessionStorage is being captured for. As of bug 1235657 this code
|
||||
// stopped parsing any origins which have originattributes correctly, as
|
||||
// it decided to use the origin attributes from the docshell, and try to
|
||||
// interpret the origin as a URI. Since bug 1353844 this code now correctly
|
||||
// parses the full origin, and then discards the origin attributes, to
|
||||
// make the behavior line up with the original intentions in bug 1235657
|
||||
// while preserving the ability to read all session storage from
|
||||
// previous versions. In the future, if this behavior is desired, we may
|
||||
// want to use the spec instead of the origin as the key, and avoid
|
||||
// transmitting origin attribute information which we then discard when
|
||||
// restoring.
|
||||
//
|
||||
// If changing this logic, make sure to also change the principal
|
||||
// computation logic in SessionStore::_sendRestoreHistory.
|
||||
let attrs = aDocShell.getOriginAttributes();
|
||||
let dataPrincipal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
|
||||
principal = Services.scriptSecurityManager.createCodebasePrincipal(dataPrincipal.URI, attrs);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
continue;
|
||||
}
|
||||
|
||||
let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
|
||||
|
||||
// There is no need to pass documentURI, it's only used to fill
|
||||
// documentURI property of domstorage event, which in this case has no
|
||||
// consumer. Prevention of events in case of missing documentURI will be
|
||||
// solved in a followup bug to bug 600307.
|
||||
// Null window because the current window doesn't match the principal yet
|
||||
// and loads about:blank.
|
||||
let storage = storageManager.createStorage(null, principal, "", aDocShell.usePrivateBrowsing);
|
||||
|
||||
for (let key of Object.keys(data)) {
|
||||
try {
|
||||
storage.setItem(key, data[key]);
|
||||
} catch (e) {
|
||||
// throws e.g. for URIs that can't have sessionStorage
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reads an entry in the session storage data contained in a tab's history.
|
||||
* @param aURI
|
||||
* That history entry uri
|
||||
* @param aDocShell
|
||||
* A tab's docshell (containing the sessionStorage)
|
||||
*/
|
||||
_readEntry(aPrincipal, aDocShell) {
|
||||
let hostData = {};
|
||||
let storage;
|
||||
|
||||
let window = aDocShell.domWindow;
|
||||
|
||||
try {
|
||||
let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
|
||||
storage = storageManager.getStorage(window, aPrincipal);
|
||||
storage.length; // XXX: Bug 1232955 - storage.length can throw, catch that failure
|
||||
} catch (e) {
|
||||
// sessionStorage might throw if it's turned off, see bug 458954
|
||||
storage = null;
|
||||
}
|
||||
|
||||
if (!storage || !storage.length) {
|
||||
return hostData;
|
||||
}
|
||||
|
||||
// If the DOMSessionStorage contains too much data, ignore it.
|
||||
let usage = window.windowUtils.getStorageUsage(storage);
|
||||
if (usage > Services.prefs.getIntPref(DOM_STORAGE_LIMIT_PREF)) {
|
||||
return hostData;
|
||||
}
|
||||
|
||||
for (let i = 0; i < storage.length; i++) {
|
||||
try {
|
||||
let key = storage.key(i);
|
||||
hostData[key] = storage.getItem(key);
|
||||
} catch (e) {
|
||||
// This currently throws for secured items (cf. bug 442048).
|
||||
}
|
||||
}
|
||||
|
||||
return hostData;
|
||||
},
|
||||
};
|
|
@ -20,6 +20,7 @@ EXTRA_JS_MODULES.sessionstore = [
|
|||
'SessionMigration.jsm',
|
||||
'SessionSaver.jsm',
|
||||
'SessionStartup.jsm',
|
||||
'SessionStorage.jsm',
|
||||
'SessionStore.jsm',
|
||||
'SessionWorker.js',
|
||||
'SessionWorker.jsm',
|
||||
|
|
|
@ -3575,8 +3575,6 @@ class Document : public nsINode,
|
|||
void SetDocTreeHadAudibleMedia();
|
||||
void SetDocTreeHadPlayRevoked();
|
||||
|
||||
mozilla::dom::XPathEvaluator* XPathEvaluator();
|
||||
|
||||
protected:
|
||||
void DoUpdateSVGUseElementShadowTrees();
|
||||
|
||||
|
@ -3706,6 +3704,8 @@ class Document : public nsINode,
|
|||
|
||||
nsCString GetContentTypeInternal() const { return mContentType; }
|
||||
|
||||
mozilla::dom::XPathEvaluator* XPathEvaluator();
|
||||
|
||||
// Update our frame request callback scheduling state, if needed. This will
|
||||
// schedule or unschedule them, if necessary, and update
|
||||
// mFrameRequestCallbacksScheduled. aOldShell should only be passed when
|
||||
|
|
|
@ -1274,7 +1274,7 @@ void nsGlobalWindowInner::FreeInnerObjects(bool aForDocumentOpen) {
|
|||
}
|
||||
|
||||
if (mWindowGlobalChild && !mWindowGlobalChild->IsClosed()) {
|
||||
mWindowGlobalChild->Destroy();
|
||||
mWindowGlobalChild->Send__delete__(mWindowGlobalChild);
|
||||
}
|
||||
mWindowGlobalChild = nullptr;
|
||||
|
||||
|
|
|
@ -319,8 +319,6 @@ static LazyLogModule gDOMLeakPRLogOuter("DOMLeakOuter");
|
|||
|
||||
static int32_t gOpenPopupSpamCount = 0;
|
||||
|
||||
static bool gSyncContentBlockingNotifications = false;
|
||||
|
||||
nsGlobalWindowOuter::OuterWindowByIdTable*
|
||||
nsGlobalWindowOuter::sOuterWindowsById = nullptr;
|
||||
|
||||
|
@ -5407,130 +5405,100 @@ void nsGlobalWindowOuter::NotifyContentBlockingEvent(unsigned aEvent,
|
|||
nsCOMPtr<Document> doc = docShell->GetDocument();
|
||||
NS_ENSURE_TRUE_VOID(doc);
|
||||
|
||||
nsCOMPtr<nsIURI> uri(aURIHint);
|
||||
nsCOMPtr<nsIChannel> channel(aChannel);
|
||||
|
||||
static bool prefInitialized = false;
|
||||
if (!prefInitialized) {
|
||||
Preferences::AddBoolVarCache(
|
||||
&gSyncContentBlockingNotifications,
|
||||
"dom.testing.sync-content-blocking-notifications", false);
|
||||
prefInitialized = true;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> func = NS_NewRunnableFunction(
|
||||
"NotifyContentBlockingEventDelayed",
|
||||
[doc, docShell, uri, channel, aEvent, aBlocked]() {
|
||||
// This event might come after the user has navigated to another
|
||||
// page. To prevent showing the TrackingProtection UI on the wrong
|
||||
// page, we need to check that the loading URI for the channel is
|
||||
// the same as the URI currently loaded in the document.
|
||||
if (!SameLoadingURI(doc, channel) &&
|
||||
aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify nsIWebProgressListeners of this content blocking event.
|
||||
// Can be used to change the UI state.
|
||||
nsresult rv = NS_OK;
|
||||
nsCOMPtr<nsISecurityEventSink> eventSink =
|
||||
do_QueryInterface(docShell, &rv);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
uint32_t event = 0;
|
||||
nsCOMPtr<nsISecureBrowserUI> securityUI;
|
||||
docShell->GetSecurityUI(getter_AddRefs(securityUI));
|
||||
if (!securityUI) {
|
||||
return;
|
||||
}
|
||||
securityUI->GetContentBlockingEvent(&event);
|
||||
nsAutoCString origin;
|
||||
nsContentUtils::GetASCIIOrigin(uri, origin);
|
||||
|
||||
bool blockedValue = aBlocked;
|
||||
bool unblocked = false;
|
||||
if (aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
|
||||
doc->SetHasTrackingContentBlocked(aBlocked, origin);
|
||||
if (!aBlocked) {
|
||||
unblocked = !doc->GetHasTrackingContentBlocked();
|
||||
}
|
||||
} else if (aEvent ==
|
||||
nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
|
||||
doc->SetHasTrackingContentLoaded(aBlocked, origin);
|
||||
if (!aBlocked) {
|
||||
unblocked = !doc->GetHasTrackingContentLoaded();
|
||||
}
|
||||
} else if (aEvent == nsIWebProgressListener::
|
||||
STATE_COOKIES_BLOCKED_BY_PERMISSION) {
|
||||
doc->SetHasCookiesBlockedByPermission(aBlocked, origin);
|
||||
if (!aBlocked) {
|
||||
unblocked = !doc->GetHasCookiesBlockedByPermission();
|
||||
}
|
||||
} else if (aEvent ==
|
||||
nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) {
|
||||
doc->SetHasTrackingCookiesBlocked(aBlocked, origin);
|
||||
if (!aBlocked) {
|
||||
unblocked = !doc->GetHasTrackingCookiesBlocked();
|
||||
}
|
||||
} else if (aEvent ==
|
||||
nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) {
|
||||
doc->SetHasAllCookiesBlocked(aBlocked, origin);
|
||||
if (!aBlocked) {
|
||||
unblocked = !doc->GetHasAllCookiesBlocked();
|
||||
}
|
||||
} else if (aEvent ==
|
||||
nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN) {
|
||||
doc->SetHasForeignCookiesBlocked(aBlocked, origin);
|
||||
if (!aBlocked) {
|
||||
unblocked = !doc->GetHasForeignCookiesBlocked();
|
||||
}
|
||||
} else if (aEvent == nsIWebProgressListener::STATE_COOKIES_LOADED) {
|
||||
MOZ_ASSERT(!aBlocked,
|
||||
"We don't expected to see blocked STATE_COOKIES_LOADED");
|
||||
// Note that the logic in this branch is the logical negation of
|
||||
// the logic in other branches, since the Document API we have is
|
||||
// phrased in "loaded" terms as opposed to "blocked" terms.
|
||||
blockedValue = !aBlocked;
|
||||
doc->SetHasCookiesLoaded(blockedValue, origin);
|
||||
if (!aBlocked) {
|
||||
unblocked = !doc->GetHasCookiesLoaded();
|
||||
}
|
||||
} else {
|
||||
// Ignore nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT;
|
||||
}
|
||||
const uint32_t oldEvent = event;
|
||||
if (blockedValue) {
|
||||
event |= aEvent;
|
||||
} else if (unblocked) {
|
||||
event &= ~aEvent;
|
||||
}
|
||||
|
||||
if (event == oldEvent
|
||||
#ifdef ANDROID
|
||||
// GeckoView always needs to notify about blocked trackers,
|
||||
// since the GeckoView API always needs to report the URI and
|
||||
// type of any blocked tracker. We use a platform-dependent code
|
||||
// path here because reporting this notification on desktop
|
||||
// platforms isn't necessary and doing so can have a big
|
||||
// performance cost.
|
||||
&& aEvent != nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT
|
||||
#endif
|
||||
) {
|
||||
// Avoid dispatching repeated notifications when nothing has
|
||||
// changed
|
||||
return;
|
||||
}
|
||||
|
||||
eventSink->OnContentBlockingEvent(channel, event);
|
||||
});
|
||||
nsresult rv;
|
||||
if (gSyncContentBlockingNotifications) {
|
||||
rv = func->Run();
|
||||
} else {
|
||||
rv = NS_IdleDispatchToCurrentThread(func.forget(), 100);
|
||||
}
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
// This event might come after the user has navigated to another page.
|
||||
// To prevent showing the TrackingProtection UI on the wrong page, we need to
|
||||
// check that the loading URI for the channel is the same as the URI currently
|
||||
// loaded in the document.
|
||||
if (!SameLoadingURI(doc, aChannel) &&
|
||||
aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify nsIWebProgressListeners of this content blocking event.
|
||||
// Can be used to change the UI state.
|
||||
nsresult rv = NS_OK;
|
||||
nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell, &rv);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
uint32_t event = 0;
|
||||
nsCOMPtr<nsISecureBrowserUI> securityUI;
|
||||
docShell->GetSecurityUI(getter_AddRefs(securityUI));
|
||||
if (!securityUI) {
|
||||
return;
|
||||
}
|
||||
securityUI->GetContentBlockingEvent(&event);
|
||||
nsAutoCString origin;
|
||||
nsContentUtils::GetASCIIOrigin(aURIHint, origin);
|
||||
|
||||
bool blockedValue = aBlocked;
|
||||
bool unblocked = false;
|
||||
if (aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
|
||||
doc->SetHasTrackingContentBlocked(aBlocked, origin);
|
||||
if (!aBlocked) {
|
||||
unblocked = !doc->GetHasTrackingContentBlocked();
|
||||
}
|
||||
} else if (aEvent == nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
|
||||
doc->SetHasTrackingContentLoaded(aBlocked, origin);
|
||||
if (!aBlocked) {
|
||||
unblocked = !doc->GetHasTrackingContentLoaded();
|
||||
}
|
||||
} else if (aEvent ==
|
||||
nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION) {
|
||||
doc->SetHasCookiesBlockedByPermission(aBlocked, origin);
|
||||
if (!aBlocked) {
|
||||
unblocked = !doc->GetHasCookiesBlockedByPermission();
|
||||
}
|
||||
} else if (aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) {
|
||||
doc->SetHasTrackingCookiesBlocked(aBlocked, origin);
|
||||
if (!aBlocked) {
|
||||
unblocked = !doc->GetHasTrackingCookiesBlocked();
|
||||
}
|
||||
} else if (aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) {
|
||||
doc->SetHasAllCookiesBlocked(aBlocked, origin);
|
||||
if (!aBlocked) {
|
||||
unblocked = !doc->GetHasAllCookiesBlocked();
|
||||
}
|
||||
} else if (aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN) {
|
||||
doc->SetHasForeignCookiesBlocked(aBlocked, origin);
|
||||
if (!aBlocked) {
|
||||
unblocked = !doc->GetHasForeignCookiesBlocked();
|
||||
}
|
||||
} else if (aEvent == nsIWebProgressListener::STATE_COOKIES_LOADED) {
|
||||
MOZ_ASSERT(!aBlocked,
|
||||
"We don't expected to see blocked STATE_COOKIES_LOADED");
|
||||
// Note that the logic in this branch is the logical negation of the logic
|
||||
// in other branches, since the Document API we have is phrased in
|
||||
// "loaded" terms as opposed to "blocked" terms.
|
||||
blockedValue = !aBlocked;
|
||||
doc->SetHasCookiesLoaded(blockedValue, origin);
|
||||
if (!aBlocked) {
|
||||
unblocked = !doc->GetHasCookiesLoaded();
|
||||
}
|
||||
} else {
|
||||
// Ignore nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT;
|
||||
}
|
||||
const uint32_t oldEvent = event;
|
||||
if (blockedValue) {
|
||||
event |= aEvent;
|
||||
} else if (unblocked) {
|
||||
event &= ~aEvent;
|
||||
}
|
||||
|
||||
if (event == oldEvent
|
||||
#ifdef ANDROID
|
||||
// GeckoView always needs to notify about blocked trackers, since the
|
||||
// GeckoView API always needs to report the URI and type of any blocked
|
||||
// tracker.
|
||||
// We use a platform-dependent code path here because reporting this
|
||||
// notification on desktop platforms isn't necessary and doing so can have
|
||||
// a big performance cost.
|
||||
&& aEvent != nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT
|
||||
#endif
|
||||
) {
|
||||
// Avoid dispatching repeated notifications when nothing has changed
|
||||
return;
|
||||
}
|
||||
|
||||
eventSink->OnContentBlockingEvent(aChannel, event);
|
||||
}
|
||||
|
||||
// static
|
||||
|
|
|
@ -111,28 +111,6 @@ namespace SessionStoreUtils {
|
|||
* Form data encoded in an object.
|
||||
*/
|
||||
CollectedFormData collectFormData(Document document);
|
||||
boolean restoreFormData(Document document, optional CollectedFormData data);
|
||||
|
||||
/**
|
||||
* Updates all sessionStorage "super cookies"
|
||||
* @param content
|
||||
* A tab's global, i.e. the root frame we want to collect for.
|
||||
* @return Returns a nested object that will have hosts as keys and per-origin
|
||||
* session storage data as strings. For example:
|
||||
* {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
|
||||
*/
|
||||
record<DOMString, record<DOMString, DOMString>> collectSessionStorage(WindowProxy window);
|
||||
|
||||
/**
|
||||
* Restores all sessionStorage "super cookies".
|
||||
* @param aDocShell
|
||||
* A tab's docshell (containing the sessionStorage)
|
||||
* @param aStorageData
|
||||
* A nested object with storage data to be restored that has hosts as
|
||||
* keys and per-origin session storage data as strings. For example:
|
||||
* {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
|
||||
*/
|
||||
void restoreSessionStorage(nsIDocShell docShell, record<DOMString, record<DOMString, DOMString>> data);
|
||||
};
|
||||
|
||||
dictionary SSScrollPositionDict {
|
||||
|
@ -152,7 +130,7 @@ dictionary CollectedNonMultipleSelectValue
|
|||
};
|
||||
|
||||
// object contains either a CollectedFileListValue or a CollectedNonMultipleSelectValue or Sequence<DOMString>
|
||||
typedef (DOMString or boolean or object) CollectedFormDataValue;
|
||||
typedef (DOMString or boolean or long or object) CollectedFormDataValue;
|
||||
|
||||
dictionary CollectedFormData
|
||||
{
|
||||
|
|
|
@ -23,9 +23,6 @@ async protocol PWindowGlobal
|
|||
{
|
||||
manager PBrowser or PInProcess;
|
||||
|
||||
child:
|
||||
async __delete__();
|
||||
|
||||
parent:
|
||||
/// Update the URI of the document in this WindowGlobal.
|
||||
async UpdateDocumentURI(nsIURI aUri);
|
||||
|
@ -33,7 +30,7 @@ parent:
|
|||
/// Notify the parent that this PWindowGlobal is now the current global.
|
||||
async BecomeCurrentWindowGlobal();
|
||||
|
||||
async Destroy();
|
||||
async __delete__();
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -90,24 +90,6 @@ already_AddRefed<WindowGlobalParent> WindowGlobalChild::GetParentActor() {
|
|||
return do_AddRef(static_cast<WindowGlobalParent*>(otherSide));
|
||||
}
|
||||
|
||||
already_AddRefed<TabChild> WindowGlobalChild::GetTabChild() {
|
||||
if (IsInProcess() || mIPCClosed) {
|
||||
return nullptr;
|
||||
}
|
||||
return do_AddRef(static_cast<TabChild*>(Manager()));
|
||||
}
|
||||
|
||||
void WindowGlobalChild::Destroy() {
|
||||
// Perform async IPC shutdown unless we're not in-process, and our TabChild is
|
||||
// in the process of being destroyed, which will destroy us as well.
|
||||
RefPtr<TabChild> tabChild = GetTabChild();
|
||||
if (!tabChild || !tabChild->IsDestroyed()) {
|
||||
SendDestroy();
|
||||
}
|
||||
|
||||
mIPCClosed = true;
|
||||
}
|
||||
|
||||
void WindowGlobalChild::ActorDestroy(ActorDestroyReason aWhy) {
|
||||
mIPCClosed = true;
|
||||
gWindowGlobalChildById->Remove(mInnerWindowId);
|
||||
|
|
|
@ -42,7 +42,6 @@ class WindowGlobalChild : public nsWrapperCache, public PWindowGlobalChild {
|
|||
|
||||
// Has this actor been shut down
|
||||
bool IsClosed() { return mIPCClosed; }
|
||||
void Destroy();
|
||||
|
||||
// Check if this actor is managed by PInProcess, as-in the document is loaded
|
||||
// in the chrome process.
|
||||
|
@ -58,10 +57,6 @@ class WindowGlobalChild : public nsWrapperCache, public PWindowGlobalChild {
|
|||
// |nullptr| if the actor has been torn down, or is not in-process.
|
||||
already_AddRefed<WindowGlobalParent> GetParentActor();
|
||||
|
||||
// Get this actor's manager if it is not an in-process actor. Returns
|
||||
// |nullptr| if the actor has been torn down, or is in-process.
|
||||
already_AddRefed<TabChild> GetTabChild();
|
||||
|
||||
// Create and initialize the WindowGlobalChild object.
|
||||
static already_AddRefed<WindowGlobalChild> Create(
|
||||
nsGlobalWindowInner* aWindow);
|
||||
|
|
|
@ -116,13 +116,6 @@ already_AddRefed<WindowGlobalChild> WindowGlobalParent::GetChildActor() {
|
|||
return do_AddRef(static_cast<WindowGlobalChild*>(otherSide));
|
||||
}
|
||||
|
||||
already_AddRefed<TabParent> WindowGlobalParent::GetTabParent() {
|
||||
if (IsInProcess() || mIPCClosed) {
|
||||
return nullptr;
|
||||
}
|
||||
return do_AddRef(static_cast<TabParent*>(Manager()));
|
||||
}
|
||||
|
||||
IPCResult WindowGlobalParent::RecvUpdateDocumentURI(nsIURI* aURI) {
|
||||
// XXX(nika): Assert that the URI change was one which makes sense (either
|
||||
// about:blank -> a real URI, or a legal push/popstate URI change?)
|
||||
|
@ -135,16 +128,6 @@ IPCResult WindowGlobalParent::RecvBecomeCurrentWindowGlobal() {
|
|||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult WindowGlobalParent::RecvDestroy() {
|
||||
if (!mIPCClosed) {
|
||||
RefPtr<TabParent> tabParent = GetTabParent();
|
||||
if (!tabParent || !tabParent->IsDestroyed()) {
|
||||
Unused << Send__delete__(this);
|
||||
}
|
||||
}
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
bool WindowGlobalParent::IsCurrentGlobal() {
|
||||
return !mIPCClosed && mBrowsingContext->GetCurrentWindowGlobal() == this;
|
||||
}
|
||||
|
|
|
@ -51,10 +51,6 @@ class WindowGlobalParent final : public nsISupports,
|
|||
// |nullptr| if the actor has been torn down, or is not in-process.
|
||||
already_AddRefed<WindowGlobalChild> GetChildActor();
|
||||
|
||||
// Get this actor's manager if it is not an in-process actor. Returns
|
||||
// |nullptr| if the actor has been torn down, or is in-process.
|
||||
already_AddRefed<TabParent> GetTabParent();
|
||||
|
||||
// The principal of this WindowGlobal. This value will not change over the
|
||||
// lifetime of the WindowGlobal object, even to reflect changes in
|
||||
// |document.domain|.
|
||||
|
@ -94,7 +90,6 @@ class WindowGlobalParent final : public nsISupports,
|
|||
// IPC messages
|
||||
mozilla::ipc::IPCResult RecvUpdateDocumentURI(nsIURI* aURI) override;
|
||||
mozilla::ipc::IPCResult RecvBecomeCurrentWindowGlobal() override;
|
||||
mozilla::ipc::IPCResult RecvDestroy() override;
|
||||
|
||||
void ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
||||
|
|
|
@ -48,9 +48,6 @@ class XPathEvaluator final : public NonRefcountedDOMObject {
|
|||
ErrorResult& rv);
|
||||
XPathExpression* CreateExpression(const nsAString& aExpression,
|
||||
nsINode* aResolver, ErrorResult& aRv);
|
||||
XPathExpression* CreateExpression(const nsAString& aExpression,
|
||||
txIParseContext* aContext,
|
||||
Document* aDocument, ErrorResult& aRv);
|
||||
nsINode* CreateNSResolver(nsINode& aNodeResolver) { return &aNodeResolver; }
|
||||
already_AddRefed<XPathResult> Evaluate(
|
||||
JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
|
||||
|
@ -58,6 +55,10 @@ class XPathEvaluator final : public NonRefcountedDOMObject {
|
|||
ErrorResult& rv);
|
||||
|
||||
private:
|
||||
XPathExpression* CreateExpression(const nsAString& aExpression,
|
||||
txIParseContext* aContext,
|
||||
Document* aDocument, ErrorResult& aRv);
|
||||
|
||||
nsWeakPtr mDocument;
|
||||
RefPtr<txResultRecycler> mRecycler;
|
||||
};
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
'txIXPathContext.h',
|
||||
'XPathEvaluator.h',
|
||||
'XPathExpression.h',
|
||||
'XPathResult.h',
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
#ifndef __TX_I_XPATH_CONTEXT
|
||||
#define __TX_I_XPATH_CONTEXT
|
||||
|
||||
#include "nscore.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsStringFwd.h"
|
||||
#include "txCore.h"
|
||||
|
||||
class FunctionCall;
|
||||
class nsAtom;
|
||||
|
|
|
@ -7,6 +7,7 @@ ChromeUtils.import("resource://gre/modules/GeckoViewChildModule.jsm");
|
|||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
FormData: "resource://gre/modules/FormData.jsm",
|
||||
FormLikeFactory: "resource://gre/modules/FormLikeFactory.jsm",
|
||||
GeckoViewAutoFill: "resource://gre/modules/GeckoViewAutoFill.jsm",
|
||||
PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
|
||||
|
@ -202,7 +203,7 @@ class GeckoViewContentChild extends GeckoViewChildModule {
|
|||
// restore() will return false, and thus abort restoration for the
|
||||
// current |frame| and its descendants, if |data.url| is given but
|
||||
// doesn't match the loaded document's URL.
|
||||
return SessionStoreUtils.restoreFormData(frame.document, data);
|
||||
return FormData.restore(frame, data);
|
||||
});
|
||||
}
|
||||
}, {capture: true, mozSystemGroup: true, once: true});
|
||||
|
|
|
@ -9,6 +9,7 @@ ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
EventDispatcher: "resource://gre/modules/Messaging.jsm",
|
||||
FormData: "resource://gre/modules/FormData.jsm",
|
||||
OS: "resource://gre/modules/osfile.jsm",
|
||||
PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
|
||||
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
|
||||
|
@ -1394,7 +1395,7 @@ SessionStore.prototype = {
|
|||
// restore() will return false, and thus abort restoration for the
|
||||
// current |frame| and its descendants, if |data.url| is given but
|
||||
// doesn't match the loaded document's URL.
|
||||
return SessionStoreUtils.restoreFormData(frame.document, data);
|
||||
return FormData.restore(frame, data);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1801,16 +1801,6 @@ VARCACHE_PREF(
|
|||
bool, false
|
||||
)
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// ContentSessionStore prefs
|
||||
//---------------------------------------------------------------------------
|
||||
// Maximum number of bytes of DOMSessionStorage data we collect per origin.
|
||||
VARCACHE_PREF(
|
||||
"browser.sessionstore.dom_storage_limit",
|
||||
browser_sessionstore_dom_storage_limit,
|
||||
uint32_t, 2048
|
||||
)
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Preferences prefs
|
||||
//---------------------------------------------------------------------------
|
||||
|
|
|
@ -3,16 +3,11 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "js/JSON.h"
|
||||
#include "jsapi.h"
|
||||
#include "mozilla/dom/HTMLInputElement.h"
|
||||
#include "mozilla/dom/HTMLSelectElement.h"
|
||||
#include "mozilla/dom/HTMLTextAreaElement.h"
|
||||
#include "mozilla/dom/SessionStoreUtils.h"
|
||||
#include "mozilla/dom/txIXPathContext.h"
|
||||
#include "mozilla/dom/WindowProxyHolder.h"
|
||||
#include "mozilla/dom/XPathResult.h"
|
||||
#include "mozilla/dom/XPathEvaluator.h"
|
||||
#include "mozilla/dom/XPathExpression.h"
|
||||
#include "nsCharSeparatedTokenizer.h"
|
||||
#include "nsContentList.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
@ -327,11 +322,11 @@ static bool IsValidCCNumber(nsAString& aValue) {
|
|||
static const uint16_t kMaxTraversedXPaths = 100;
|
||||
|
||||
// A helper function to append a element into mId or mXpath of CollectedFormData
|
||||
static Record<nsString, OwningStringOrBooleanOrObject>::EntryType*
|
||||
static Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType*
|
||||
AppendEntryToCollectedData(nsINode* aNode, const nsAString& aId,
|
||||
uint16_t& aGeneratedCount,
|
||||
CollectedFormData& aRetVal) {
|
||||
Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry;
|
||||
Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry;
|
||||
if (!aId.IsEmpty()) {
|
||||
if (!aRetVal.mId.WasPassed()) {
|
||||
aRetVal.mId.Construct();
|
||||
|
@ -391,7 +386,7 @@ static void CollectFromTextAreaElement(Document& aDocument,
|
|||
eCaseMatters)) {
|
||||
continue;
|
||||
}
|
||||
Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
|
||||
Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
|
||||
AppendEntryToCollectedData(textArea, id, aGeneratedCount, aRetVal);
|
||||
entry->mValue.SetAsString() = value;
|
||||
}
|
||||
|
@ -446,7 +441,7 @@ static void CollectFromInputElement(JSContext* aCx, Document& aDocument,
|
|||
if (checked == input->DefaultChecked()) {
|
||||
continue;
|
||||
}
|
||||
Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
|
||||
Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
|
||||
AppendEntryToCollectedData(input, id, aGeneratedCount, aRetVal);
|
||||
entry->mValue.SetAsBoolean() = checked;
|
||||
} else if (input->ControlType() == NS_FORM_INPUT_FILE) {
|
||||
|
@ -465,7 +460,7 @@ static void CollectFromInputElement(JSContext* aCx, Document& aDocument,
|
|||
JS_ClearPendingException(aCx);
|
||||
continue;
|
||||
}
|
||||
Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
|
||||
Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
|
||||
AppendEntryToCollectedData(input, id, aGeneratedCount, aRetVal);
|
||||
entry->mValue.SetAsObject() = &jsval.toObject();
|
||||
} else {
|
||||
|
@ -491,7 +486,7 @@ static void CollectFromInputElement(JSContext* aCx, Document& aDocument,
|
|||
JS::Rooted<JS::Value> jsval(aCx);
|
||||
if (JS_ParseJSON(aCx, value.get(), value.Length(), &jsval) &&
|
||||
jsval.isObject()) {
|
||||
Record<nsString, OwningStringOrBooleanOrObject>::EntryType*
|
||||
Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType*
|
||||
entry = AppendEntryToCollectedData(input, id, aGeneratedCount,
|
||||
aRetVal);
|
||||
entry->mValue.SetAsObject() = &jsval.toObject();
|
||||
|
@ -502,7 +497,7 @@ static void CollectFromInputElement(JSContext* aCx, Document& aDocument,
|
|||
}
|
||||
}
|
||||
}
|
||||
Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
|
||||
Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
|
||||
AppendEntryToCollectedData(input, id, aGeneratedCount, aRetVal);
|
||||
entry->mValue.SetAsString() = value;
|
||||
}
|
||||
|
@ -553,7 +548,7 @@ static void CollectFromSelectElement(JSContext* aCx, Document& aDocument,
|
|||
JS_ClearPendingException(aCx);
|
||||
continue;
|
||||
}
|
||||
Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
|
||||
Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
|
||||
AppendEntryToCollectedData(select, id, aGeneratedCount, aRetVal);
|
||||
entry->mValue.SetAsObject() = &jsval.toObject();
|
||||
} else {
|
||||
|
@ -565,8 +560,8 @@ static void CollectFromSelectElement(JSContext* aCx, Document& aDocument,
|
|||
}
|
||||
bool hasDefaultValue = true;
|
||||
nsTArray<nsString> selectslist;
|
||||
uint32_t numOptions = options->Length();
|
||||
for (uint32_t idx = 0; idx < numOptions; idx++) {
|
||||
int numOptions = options->Length();
|
||||
for (int idx = 0; idx < numOptions; idx++) {
|
||||
HTMLOptionElement* option = options->ItemAsOption(idx);
|
||||
bool selected = option->Selected();
|
||||
if (!selected) {
|
||||
|
@ -586,7 +581,7 @@ static void CollectFromSelectElement(JSContext* aCx, Document& aDocument,
|
|||
JS_ClearPendingException(aCx);
|
||||
continue;
|
||||
}
|
||||
Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
|
||||
Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
|
||||
AppendEntryToCollectedData(select, id, aGeneratedCount, aRetVal);
|
||||
entry->mValue.SetAsObject() = &jsval.toObject();
|
||||
}
|
||||
|
@ -635,7 +630,7 @@ static void CollectFromXULTextbox(Document& aDocument,
|
|||
continue;
|
||||
}
|
||||
uint16_t generatedCount = 0;
|
||||
Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
|
||||
Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
|
||||
AppendEntryToCollectedData(input, id, generatedCount, aRetVal);
|
||||
entry->mValue.SetAsString() = value;
|
||||
return;
|
||||
|
@ -673,474 +668,3 @@ static void CollectFromXULTextbox(Document& aDocument,
|
|||
uri->GetSpecIgnoringRef(aRetVal.mUrl.Construct());
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
static void SetElementAsString(Element* aElement, const nsAString& aValue) {
|
||||
IgnoredErrorResult rv;
|
||||
HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNodeOrNull(aElement);
|
||||
if (textArea) {
|
||||
textArea->SetValue(aValue, rv);
|
||||
if (!rv.Failed()) {
|
||||
nsContentUtils::DispatchInputEvent(aElement);
|
||||
}
|
||||
return;
|
||||
}
|
||||
HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(aElement);
|
||||
if (input) {
|
||||
input->SetValue(aValue, CallerType::NonSystem, rv);
|
||||
if (!rv.Failed()) {
|
||||
nsContentUtils::DispatchInputEvent(aElement);
|
||||
return;
|
||||
}
|
||||
}
|
||||
input = HTMLInputElement::FromNodeOrNull(nsFocusManager::GetRedirectedFocus(aElement));
|
||||
if (input) {
|
||||
input->SetValue(aValue, CallerType::NonSystem, rv);
|
||||
if (!rv.Failed()) {
|
||||
nsContentUtils::DispatchInputEvent(aElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
static void SetElementAsBool(Element* aElement, bool aValue) {
|
||||
HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(aElement);
|
||||
if (input) {
|
||||
bool checked = input->Checked();
|
||||
if (aValue != checked) {
|
||||
input->SetChecked(aValue);
|
||||
nsContentUtils::DispatchInputEvent(aElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
static void SetElementAsFiles(HTMLInputElement* aElement,
|
||||
const CollectedFileListValue& aValue) {
|
||||
nsTArray<nsString> fileList;
|
||||
IgnoredErrorResult rv;
|
||||
aElement->MozSetFileNameArray(aValue.mFileList, rv);
|
||||
if (rv.Failed()) {
|
||||
return;
|
||||
}
|
||||
nsContentUtils::DispatchInputEvent(aElement);
|
||||
}
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
static void SetElementAsSelect(HTMLSelectElement* aElement,
|
||||
const CollectedNonMultipleSelectValue& aValue) {
|
||||
HTMLOptionsCollection* options = aElement->GetOptions();
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
int32_t selectIdx = options->SelectedIndex();
|
||||
if (selectIdx >= 0) {
|
||||
nsAutoString selectOptionVal;
|
||||
options->ItemAsOption(selectIdx)->GetValue(selectOptionVal);
|
||||
if (aValue.mValue.Equals(selectOptionVal)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
uint32_t numOptions = options->Length();
|
||||
for (uint32_t idx = 0; idx < numOptions; idx++) {
|
||||
HTMLOptionElement* option = options->ItemAsOption(idx);
|
||||
nsAutoString optionValue;
|
||||
option->GetValue(optionValue);
|
||||
if (aValue.mValue.Equals(optionValue)) {
|
||||
aElement->SetSelectedIndex(idx);
|
||||
nsContentUtils::DispatchInputEvent(aElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
static void SetElementAsMultiSelect(HTMLSelectElement* aElement,
|
||||
const nsTArray<nsString>& aValueArray) {
|
||||
bool fireEvent = false;
|
||||
HTMLOptionsCollection* options = aElement->GetOptions();
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
uint32_t numOptions = options->Length();
|
||||
for (uint32_t idx = 0; idx < numOptions; idx++) {
|
||||
HTMLOptionElement* option = options->ItemAsOption(idx);
|
||||
nsAutoString optionValue;
|
||||
option->GetValue(optionValue);
|
||||
for (uint32_t i = 0, l = aValueArray.Length(); i < l; ++i) {
|
||||
if (optionValue.Equals(aValueArray[i])) {
|
||||
option->SetSelected(true);
|
||||
if (!option->DefaultSelected()) {
|
||||
fireEvent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fireEvent) {
|
||||
nsContentUtils::DispatchInputEvent(aElement);
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
static void SetElementAsObject(JSContext* aCx, Element* aElement,
|
||||
JS::Handle<JS::Value> aObject) {
|
||||
RefPtr<HTMLInputElement> input = HTMLInputElement::FromNodeOrNull(aElement);
|
||||
if (input) {
|
||||
if (input->ControlType() == NS_FORM_INPUT_FILE) {
|
||||
CollectedFileListValue value;
|
||||
if (value.Init(aCx, aObject)) {
|
||||
SetElementAsFiles(input, value);
|
||||
} else {
|
||||
JS_ClearPendingException(aCx);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNodeOrNull(aElement);
|
||||
if (select) {
|
||||
// For Single Select Element
|
||||
if (!select->Multiple()) {
|
||||
CollectedNonMultipleSelectValue value;
|
||||
if (value.Init(aCx, aObject)) {
|
||||
SetElementAsSelect(select, value);
|
||||
} else {
|
||||
JS_ClearPendingException(aCx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// For Multiple Selects Element
|
||||
bool isArray = false;
|
||||
JS_IsArrayObject(aCx, aObject, &isArray);
|
||||
if (!isArray) {
|
||||
return;
|
||||
}
|
||||
JS::Rooted<JSObject*> arrayObj(aCx, &aObject.toObject());
|
||||
uint32_t arrayLength = 0;
|
||||
if (!JS_GetArrayLength(aCx, arrayObj, &arrayLength)) {
|
||||
JS_ClearPendingException(aCx);
|
||||
return;
|
||||
}
|
||||
nsTArray<nsString> array(arrayLength);
|
||||
for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; arrayIdx++) {
|
||||
JS::Rooted<JS::Value> element(aCx);
|
||||
if (!JS_GetElement(aCx, arrayObj, arrayIdx, &element)) {
|
||||
JS_ClearPendingException(aCx);
|
||||
return;
|
||||
}
|
||||
if (!element.isString()) {
|
||||
return;
|
||||
}
|
||||
nsAutoJSString value;
|
||||
if (!value.init(aCx, element)) {
|
||||
JS_ClearPendingException(aCx);
|
||||
return;
|
||||
}
|
||||
array.AppendElement(value);
|
||||
}
|
||||
SetElementAsMultiSelect(select, array);
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
static void SetRestoreData(JSContext* aCx, Element* aElement,
|
||||
JS::MutableHandle<JS::Value> aObject) {
|
||||
nsAutoString data;
|
||||
if (nsContentUtils::StringifyJSON(aCx, aObject, data)) {
|
||||
SetElementAsString(aElement, data);
|
||||
} else {
|
||||
JS_ClearPendingException(aCx);
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
static void SetInnerHTML(Document& aDocument, const CollectedFormData& aData) {
|
||||
RefPtr<Element> bodyElement = aDocument.GetBody();
|
||||
if (aDocument.HasFlag(NODE_IS_EDITABLE) && bodyElement) {
|
||||
IgnoredErrorResult rv;
|
||||
bodyElement->SetInnerHTML(aData.mInnerHTML.Value(),
|
||||
aDocument.NodePrincipal(), rv);
|
||||
if (!rv.Failed()) {
|
||||
nsContentUtils::DispatchInputEvent(bodyElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FormDataParseContext : public txIParseContext {
|
||||
public:
|
||||
explicit FormDataParseContext(bool aCaseInsensitive)
|
||||
: mIsCaseInsensitive(aCaseInsensitive) {}
|
||||
|
||||
nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override {
|
||||
if (aPrefix == nsGkAtoms::xul) {
|
||||
aID = kNameSpaceID_XUL;
|
||||
} else {
|
||||
MOZ_ASSERT(nsDependentAtomString(aPrefix).EqualsLiteral("xhtml"));
|
||||
aID = kNameSpaceID_XHTML;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult resolveFunctionCall(nsAtom* aName, int32_t aID,
|
||||
FunctionCall** aFunction) override {
|
||||
return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
|
||||
}
|
||||
|
||||
bool caseInsensitiveNameTests() override { return mIsCaseInsensitive; }
|
||||
|
||||
void SetErrorOffset(uint32_t aOffset) override {}
|
||||
|
||||
private:
|
||||
bool mIsCaseInsensitive;
|
||||
};
|
||||
|
||||
static Element* FindNodeByXPath(JSContext* aCx, Document& aDocument,
|
||||
const nsAString& aExpression) {
|
||||
FormDataParseContext parsingContext(aDocument.IsHTMLDocument());
|
||||
IgnoredErrorResult rv;
|
||||
nsAutoPtr<XPathExpression> expression(
|
||||
aDocument.XPathEvaluator()->CreateExpression(aExpression, &parsingContext,
|
||||
&aDocument, rv));
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
RefPtr<XPathResult> result = expression->Evaluate(
|
||||
aCx, aDocument, XPathResult::FIRST_ORDERED_NODE_TYPE, nullptr, rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
return Element::FromNodeOrNull(result->GetSingleNodeValue(rv));
|
||||
}
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||
/* static */ bool SessionStoreUtils::RestoreFormData(
|
||||
const GlobalObject& aGlobal, Document& aDocument,
|
||||
const CollectedFormData& aData) {
|
||||
if (!aData.mUrl.WasPassed()) {
|
||||
return true;
|
||||
}
|
||||
// Don't restore any data for the given frame if the URL
|
||||
// stored in the form data doesn't match its current URL.
|
||||
nsAutoCString url;
|
||||
Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
|
||||
if (!aData.mUrl.Value().Equals(url)) {
|
||||
return false;
|
||||
}
|
||||
if (aData.mInnerHTML.WasPassed()) {
|
||||
SetInnerHTML(aDocument, aData);
|
||||
}
|
||||
if (aData.mId.WasPassed()) {
|
||||
for (auto& entry : aData.mId.Value().Entries()) {
|
||||
RefPtr<Element> node = aDocument.GetElementById(entry.mKey);
|
||||
if (entry.mValue.IsString()) {
|
||||
SetElementAsString(node, entry.mValue.GetAsString());
|
||||
} else if (entry.mValue.IsBoolean()) {
|
||||
SetElementAsBool(node, entry.mValue.GetAsBoolean());
|
||||
} else {
|
||||
// For about:{sessionrestore,welcomeback} we saved the field as JSON to
|
||||
// avoid nested instances causing humongous sessionstore.js files.
|
||||
// cf. bug 467409
|
||||
JSContext* cx = aGlobal.Context();
|
||||
if (entry.mKey.EqualsLiteral("sessionData")) {
|
||||
nsAutoCString url;
|
||||
Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
|
||||
if (url.EqualsLiteral("about:sessionrestore") ||
|
||||
url.EqualsLiteral("about:welcomeback")) {
|
||||
JS::Rooted<JS::Value> object(
|
||||
cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
|
||||
SetRestoreData(cx, node, &object);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
JS::Rooted<JS::Value> object(
|
||||
cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
|
||||
SetElementAsObject(cx, node, object);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aData.mXpath.WasPassed()) {
|
||||
for (auto& entry : aData.mXpath.Value().Entries()) {
|
||||
RefPtr<Element> node = FindNodeByXPath(aGlobal.Context(), aDocument, entry.mKey);
|
||||
if (entry.mValue.IsString()) {
|
||||
SetElementAsString(node, entry.mValue.GetAsString());
|
||||
} else if (entry.mValue.IsBoolean()) {
|
||||
SetElementAsBool(node, entry.mValue.GetAsBoolean());
|
||||
} else {
|
||||
JS::Rooted<JS::Value> object(
|
||||
aGlobal.Context(), JS::ObjectValue(*entry.mValue.GetAsObject()));
|
||||
SetElementAsObject(aGlobal.Context(), node, object);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Read entries in the session storage data contained in a tab's history. */
|
||||
static void ReadAllEntriesFromStorage(
|
||||
nsPIDOMWindowOuter* aWindow,
|
||||
nsTHashtable<nsCStringHashKey>& aVisitedOrigins,
|
||||
Record<nsString, Record<nsString, nsString>>& aRetVal) {
|
||||
nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
|
||||
if (!docShell) {
|
||||
return;
|
||||
}
|
||||
|
||||
Document* doc = aWindow->GetDoc();
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
|
||||
if (!principal) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoCString origin;
|
||||
nsresult rv = principal->GetOrigin(origin);
|
||||
if (NS_FAILED(rv) || aVisitedOrigins.Contains(origin)) {
|
||||
// Don't read a host twice.
|
||||
return;
|
||||
}
|
||||
|
||||
/* Completed checking for recursion and is about to read storage*/
|
||||
nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(docShell);
|
||||
if (!storageManager) {
|
||||
return;
|
||||
}
|
||||
RefPtr<Storage> storage;
|
||||
storageManager->GetStorage(aWindow->GetCurrentInnerWindow(), principal, false,
|
||||
getter_AddRefs(storage));
|
||||
if (!storage) {
|
||||
return;
|
||||
}
|
||||
mozilla::IgnoredErrorResult result;
|
||||
uint32_t len = storage->GetLength(*principal, result);
|
||||
if (result.Failed() || len == 0) {
|
||||
return;
|
||||
}
|
||||
int64_t storageUsage = storage->GetOriginQuotaUsage();
|
||||
if (storageUsage > StaticPrefs::browser_sessionstore_dom_storage_limit()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Record<nsString, Record<nsString, nsString>>::EntryType* recordEntry = nullptr;
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
Record<nsString, nsString>::EntryType entry;
|
||||
mozilla::IgnoredErrorResult res;
|
||||
storage->Key(i, entry.mKey, *principal, res);
|
||||
if (res.Failed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
storage->GetItem(entry.mKey, entry.mValue, *principal, res);
|
||||
if (res.Failed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!recordEntry) {
|
||||
recordEntry = aRetVal.Entries().AppendElement();
|
||||
recordEntry->mKey = NS_ConvertUTF8toUTF16(origin);
|
||||
aVisitedOrigins.PutEntry(origin);
|
||||
}
|
||||
recordEntry->mValue.Entries().AppendElement(std::move(entry));
|
||||
}
|
||||
}
|
||||
|
||||
/* Collect Collect session storage from current frame and all child frame */
|
||||
static void CollectedSessionStorageInternal(
|
||||
JSContext* aCx, BrowsingContext* aBrowsingContext,
|
||||
nsTHashtable<nsCStringHashKey>& aVisitedOrigins,
|
||||
Record<nsString, Record<nsString, nsString>>& aRetVal) {
|
||||
/* Collect session store from current frame */
|
||||
nsPIDOMWindowOuter* window = aBrowsingContext->GetDOMWindow();
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
ReadAllEntriesFromStorage(window, aVisitedOrigins, aRetVal);
|
||||
|
||||
/* Collect session storage from all child frame */
|
||||
nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
|
||||
if (!docShell) {
|
||||
return;
|
||||
}
|
||||
int32_t length;
|
||||
nsresult rv = docShell->GetChildCount(&length);
|
||||
if (NS_FAILED(rv)) {
|
||||
return;
|
||||
}
|
||||
for (int32_t i = 0; i < length; ++i) {
|
||||
nsCOMPtr<nsIDocShellTreeItem> item;
|
||||
docShell->GetChildAt(i, getter_AddRefs(item));
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
nsCOMPtr<nsIDocShell> childDocShell(do_QueryInterface(item));
|
||||
if (!childDocShell) {
|
||||
return;
|
||||
}
|
||||
bool isDynamic = false;
|
||||
rv = childDocShell->GetCreatedDynamically(&isDynamic);
|
||||
if (NS_SUCCEEDED(rv) && isDynamic) {
|
||||
continue;
|
||||
}
|
||||
CollectedSessionStorageInternal(
|
||||
aCx, nsDocShell::Cast(childDocShell)->GetBrowsingContext(),
|
||||
aVisitedOrigins, aRetVal);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void SessionStoreUtils::CollectSessionStorage(
|
||||
const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
|
||||
Record<nsString, Record<nsString, nsString>>& aRetVal) {
|
||||
nsTHashtable<nsCStringHashKey> visitedOrigins;
|
||||
CollectedSessionStorageInternal(aGlobal.Context(), aWindow.get(),
|
||||
visitedOrigins, aRetVal);
|
||||
}
|
||||
|
||||
/* static */ void SessionStoreUtils::RestoreSessionStorage(
|
||||
const GlobalObject& aGlobal, nsIDocShell* aDocShell,
|
||||
const Record<nsString, Record<nsString, nsString>>& aData) {
|
||||
for (auto& entry : aData.Entries()) {
|
||||
// NOTE: In capture() we record the full origin for the URI which the
|
||||
// sessionStorage is being captured for. As of bug 1235657 this code
|
||||
// stopped parsing any origins which have originattributes correctly, as
|
||||
// it decided to use the origin attributes from the docshell, and try to
|
||||
// interpret the origin as a URI. Since bug 1353844 this code now correctly
|
||||
// parses the full origin, and then discards the origin attributes, to
|
||||
// make the behavior line up with the original intentions in bug 1235657
|
||||
// while preserving the ability to read all session storage from
|
||||
// previous versions. In the future, if this behavior is desired, we may
|
||||
// want to use the spec instead of the origin as the key, and avoid
|
||||
// transmitting origin attribute information which we then discard when
|
||||
// restoring.
|
||||
//
|
||||
// If changing this logic, make sure to also change the principal
|
||||
// computation logic in SessionStore::_sendRestoreHistory.
|
||||
|
||||
// OriginAttributes are always after a '^' character
|
||||
int32_t pos = entry.mKey.RFindChar('^');
|
||||
nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateCodebasePrincipal(
|
||||
NS_ConvertUTF16toUTF8(Substring(entry.mKey, 0, pos)));
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(aDocShell, &rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
return;
|
||||
}
|
||||
RefPtr<Storage> storage;
|
||||
// There is no need to pass documentURI, it's only used to fill documentURI
|
||||
// property of domstorage event, which in this case has no consumer.
|
||||
// Prevention of events in case of missing documentURI will be solved in a
|
||||
// followup bug to bug 600307.
|
||||
// Null window because the current window doesn't match the principal yet
|
||||
// and loads about:blank.
|
||||
storageManager->CreateStorage(nullptr, principal, EmptyString(), false, getter_AddRefs(storage));
|
||||
if (!storage) {
|
||||
continue;
|
||||
}
|
||||
for (auto& InnerEntry : entry.mValue.Entries()) {
|
||||
IgnoredErrorResult result;
|
||||
storage->SetItem(InnerEntry.mKey, InnerEntry.mValue, *principal, result);
|
||||
if (result.Failed()) {
|
||||
NS_WARNING("storage set item failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,18 +54,6 @@ class SessionStoreUtils {
|
|||
|
||||
static void CollectFormData(const GlobalObject& aGlobal, Document& aDocument,
|
||||
CollectedFormData& aRetVal);
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||
static bool RestoreFormData(const GlobalObject& aGlobal, Document& aDocument,
|
||||
const CollectedFormData& aData);
|
||||
|
||||
static void CollectSessionStorage(
|
||||
const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
|
||||
Record<nsString, Record<nsString, nsString>>& aRetVal);
|
||||
|
||||
static void RestoreSessionStorage(
|
||||
const GlobalObject& aGlobal, nsIDocShell* aDocShell,
|
||||
const Record<nsString, Record<nsString, nsString>>& aData);
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -210,10 +210,7 @@ function testOnWindow(aTestData) {
|
|||
});
|
||||
}
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{"set": [
|
||||
["browser.safebrowsing.phishing.enabled", true],
|
||||
["dom.testing.sync-content-blocking-notifications", true],
|
||||
]},
|
||||
{"set": [["browser.safebrowsing.phishing.enabled", true]]},
|
||||
test);
|
||||
|
||||
function test() {
|
||||
|
|
|
@ -241,6 +241,7 @@ EXTRA_JS_MODULES += [
|
|||
'SelectParentHelper.jsm',
|
||||
'ServiceRequest.jsm',
|
||||
'Services.jsm',
|
||||
'sessionstore/FormData.jsm',
|
||||
'ShortcutUtils.jsm',
|
||||
'Sqlite.jsm',
|
||||
'Timer.jsm',
|
||||
|
|
|
@ -0,0 +1,280 @@
|
|||
/* 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";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["FormData"];
|
||||
|
||||
/**
|
||||
* Returns whether the given URL very likely has input
|
||||
* fields that contain serialized session store data.
|
||||
*/
|
||||
function isRestorationPage(url) {
|
||||
return url == "about:sessionrestore" || url == "about:welcomeback";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given form |data| object contains nested restoration
|
||||
* data for a page like about:sessionrestore or about:welcomeback.
|
||||
*/
|
||||
function hasRestorationData(data) {
|
||||
if (isRestorationPage(data.url) && data.id) {
|
||||
return typeof(data.id.sessionData) == "object";
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given document's current URI and strips
|
||||
* off the URI's anchor part, if any.
|
||||
*/
|
||||
function getDocumentURI(doc) {
|
||||
return doc.documentURI.replace(/#.*$/, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* The public API exported by this module that allows to collect
|
||||
* and restore form data for a document and its subframes.
|
||||
*/
|
||||
var FormData = Object.freeze({
|
||||
restore(frame, data) {
|
||||
return FormDataInternal.restore(frame, data);
|
||||
},
|
||||
|
||||
restoreTree(root, data) {
|
||||
FormDataInternal.restoreTree(root, data);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* This module's internal API.
|
||||
*/
|
||||
var FormDataInternal = {
|
||||
namespaceURIs: {
|
||||
"xhtml": "http://www.w3.org/1999/xhtml",
|
||||
"xul": "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolves an XPath query generated by node.generateXPath.
|
||||
*/
|
||||
resolve(aDocument, aQuery) {
|
||||
let xptype = aDocument.defaultView.XPathResult.FIRST_ORDERED_NODE_TYPE;
|
||||
return aDocument.evaluate(aQuery, aDocument, this.resolveNS.bind(this), xptype, null).singleNodeValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Namespace resolver for the above XPath resolver.
|
||||
*/
|
||||
resolveNS(aPrefix) {
|
||||
return this.namespaceURIs[aPrefix] || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns an XPath query to all savable form field nodes
|
||||
*/
|
||||
get restorableFormNodesXPath() {
|
||||
let formNodesXPath = "//textarea|//xhtml:textarea|" +
|
||||
"//select|//xhtml:select|" +
|
||||
"//input|//xhtml:input" +
|
||||
// Special case for about:config's search field.
|
||||
"|/xul:window[@id='config']//xul:textbox[@id='textbox']";
|
||||
|
||||
delete this.restorableFormNodesXPath;
|
||||
return (this.restorableFormNodesXPath = formNodesXPath);
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores form |data| for the given frame. The data is expected to be in
|
||||
* the same format that FormData.collect() returns.
|
||||
*
|
||||
* @param frame (DOMWindow)
|
||||
* The frame to restore form data to.
|
||||
* @param data (object)
|
||||
* An object holding form data.
|
||||
*/
|
||||
restore({document: doc}, data) {
|
||||
if (!data.url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't restore any data for the given frame if the URL
|
||||
// stored in the form data doesn't match its current URL.
|
||||
if (data.url != getDocumentURI(doc)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For about:{sessionrestore,welcomeback} we saved the field as JSON to
|
||||
// avoid nested instances causing humongous sessionstore.js files.
|
||||
// cf. bug 467409
|
||||
if (hasRestorationData(data)) {
|
||||
data.id.sessionData = JSON.stringify(data.id.sessionData);
|
||||
}
|
||||
|
||||
if ("id" in data) {
|
||||
let retrieveNode = id => doc.getElementById(id);
|
||||
this.restoreManyInputValues(data.id, retrieveNode);
|
||||
}
|
||||
|
||||
if ("xpath" in data) {
|
||||
let retrieveNode = xpath => this.resolve(doc, xpath);
|
||||
this.restoreManyInputValues(data.xpath, retrieveNode);
|
||||
}
|
||||
|
||||
if ("innerHTML" in data) {
|
||||
if (doc.body && doc.designMode == "on") {
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
doc.body.innerHTML = data.innerHTML;
|
||||
this.fireInputEvent(doc.body);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterates the given form data, retrieving nodes for all the keys and
|
||||
* restores their appropriate values.
|
||||
*
|
||||
* @param data (object)
|
||||
* A subset of the form data as collected by FormData.collect(). This
|
||||
* is either data stored under "id" or under "xpath".
|
||||
* @param retrieve (function)
|
||||
* The function used to retrieve the input field belonging to a key
|
||||
* in the given |data| object.
|
||||
*/
|
||||
restoreManyInputValues(data, retrieve) {
|
||||
for (let key of Object.keys(data)) {
|
||||
let input = retrieve(key);
|
||||
if (input) {
|
||||
this.restoreSingleInputValue(input, data[key]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores a given form value to a given DOMNode and takes care of firing
|
||||
* the appropriate DOM event should the input's value change.
|
||||
*
|
||||
* @param aNode
|
||||
* DOMNode to set form value on.
|
||||
* @param aValue
|
||||
* Value to set form element to.
|
||||
*/
|
||||
restoreSingleInputValue(aNode, aValue) {
|
||||
let fireEvent = false;
|
||||
|
||||
if (typeof aValue == "string" && aNode.type != "file") {
|
||||
// Don't dispatch an input event if there is no change.
|
||||
if (aNode.value == aValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
aNode.value = aValue;
|
||||
fireEvent = true;
|
||||
} else if (typeof aValue == "boolean") {
|
||||
// Don't dispatch a change event for no change.
|
||||
if (aNode.checked == aValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
aNode.checked = aValue;
|
||||
fireEvent = true;
|
||||
} else if (aValue && aValue.selectedIndex >= 0 && aValue.value) {
|
||||
// Don't dispatch a change event for no change
|
||||
if (aNode.options[aNode.selectedIndex].value == aValue.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find first option with matching aValue if possible
|
||||
for (let i = 0; i < aNode.options.length; i++) {
|
||||
if (aNode.options[i].value == aValue.value) {
|
||||
aNode.selectedIndex = i;
|
||||
fireEvent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (aValue && aValue.fileList && aValue.type == "file" &&
|
||||
aNode.type == "file") {
|
||||
try {
|
||||
// FIXME (bug 1122855): This won't work in content processes.
|
||||
aNode.mozSetFileNameArray(aValue.fileList, aValue.fileList.length);
|
||||
} catch (e) {
|
||||
Cu.reportError("mozSetFileNameArray: " + e);
|
||||
}
|
||||
fireEvent = true;
|
||||
} else if (Array.isArray(aValue) && aNode.options) {
|
||||
Array.forEach(aNode.options, function(opt, index) {
|
||||
// don't worry about malformed options with same values
|
||||
opt.selected = aValue.indexOf(opt.value) > -1;
|
||||
|
||||
// Only fire the event here if this wasn't selected by default
|
||||
if (!opt.defaultSelected) {
|
||||
fireEvent = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fire events for this node if applicable
|
||||
if (fireEvent) {
|
||||
this.fireInputEvent(aNode);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches an event of type "input" to the given |node|.
|
||||
*
|
||||
* @param node (DOMNode)
|
||||
*/
|
||||
fireInputEvent(node) {
|
||||
// "inputType" value hasn't been decided for session restor:
|
||||
// https://github.com/w3c/input-events/issues/30#issuecomment-438693664
|
||||
let event = node.isInputEventTarget ?
|
||||
new node.ownerGlobal.InputEvent("input", {bubbles: true, inputType: ""}) :
|
||||
new node.ownerGlobal.Event("input", {bubbles: true});
|
||||
node.dispatchEvent(event);
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores form data for the current frame hierarchy starting at |root|
|
||||
* using the given form |data|.
|
||||
*
|
||||
* If the given |root| frame's hierarchy doesn't match that of the given
|
||||
* |data| object we will silently discard data for unreachable frames. For
|
||||
* security reasons we will never restore form data to the wrong frames as
|
||||
* we bail out silently if the stored URL doesn't match the frame's current
|
||||
* URL.
|
||||
*
|
||||
* @param root (DOMWindow)
|
||||
* @param data (object)
|
||||
* {
|
||||
* formdata: {id: {input1: "value1"}},
|
||||
* children: [
|
||||
* {formdata: {id: {input2: "value2"}}},
|
||||
* null,
|
||||
* {formdata: {xpath: { ... }}, children: [ ... ]}
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
restoreTree(root, data) {
|
||||
// Restore data for the given |root| frame and its descendants. If restore()
|
||||
// returns false this indicates the |data.url| doesn't match the loaded
|
||||
// document URI. We then must ignore this branch for security reasons.
|
||||
if (this.restore(root, data) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.hasOwnProperty("children")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let frames = root.frames;
|
||||
for (let index of Object.keys(data.children)) {
|
||||
if (index < frames.length) {
|
||||
this.restoreTree(frames[index], data.children[index]);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
Загрузка…
Ссылка в новой задаче