author | Kris Maglione <maglione.k@gmail.com> |
Tue, 15 Jun 2021 04:40:11 +0000 | |
changeset 583096 | 0fa59c7f0f82c65ab24cf64ebe6aa758b90d50c0 |
parent 583095 | 284dc9ca5f5c47ed566f8909eaa736cf9bf36f42 |
child 583097 | c892c376eb0ba164d7123f1da9d68beb3b325597 |
push id | 38540 |
push user | smolnar@mozilla.com |
push date | Tue, 15 Jun 2021 21:45:02 +0000 |
treeherder | mozilla-central@206721f8064a [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jdescottes, nika, geckoview-reviewers, devtools-backward-compat-reviewers, agi |
bugs | 1646560 |
milestone | 91.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/components/sessionstore/test/browser_capabilities.js +++ b/browser/components/sessionstore/test/browser_capabilities.js @@ -6,33 +6,33 @@ /** * These tests ensures that disabling features by flipping nsIDocShell.allow* * properties are (re)stored as disabled. Disallowed features must be * re-enabled when the tab is re-used by another tab restoration. */ add_task(async function docshell_capabilities() { let tab = await createTab(); let browser = tab.linkedBrowser; - let docShell = browser.docShell; + let { browsingContext, docShell } = browser; // Get the list of capabilities for docShells. let flags = Object.keys(docShell).filter(k => k.startsWith("allow")); // Check that everything is allowed by default for new tabs. let state = JSON.parse(ss.getTabState(tab)); ok(!("disallow" in state), "everything allowed by default"); ok( flags.every(f => docShell[f]), "all flags set to true" ); // Flip a couple of allow* flags. docShell.allowImages = false; docShell.allowMetaRedirects = false; - docShell.allowJavascript = false; + browsingContext.allowJavascript = false; // Now reload the document to ensure that these capabilities // are taken into account. browser.reload(); await promiseBrowserLoaded(browser); // Flush to make sure chrome received all data. await TabStateFlusher.flush(browser); @@ -63,17 +63,17 @@ add_task(async function docshell_capabil // Restore the state with disallowed features. await promiseTabState(tab, disallowedState); // Check that docShell flags are set. ok(!docShell.allowImages, "images not allowed"); ok(!docShell.allowMetaRedirects, "meta redirects not allowed"); // Check that docShell allowJavascript flag is not set. - ok(docShell.allowJavascript, "Javascript still allowed"); + ok(browsingContext.allowJavascript, "Javascript still allowed"); // Check that we correctly restored features as disabled. state = JSON.parse(ss.getTabState(tab)); disallow = new Set(state.disallow.split(",")); ok(disallow.has("Images"), "images not allowed anymore"); ok(disallow.has("MetaRedirects"), "meta redirects not allowed anymore"); ok(!disallow.has("Javascript"), "Javascript still allowed"); is(disallow.size, 2, "two capabilities disallowed");
--- a/caps/tests/mochitest/test_disableScript.xhtml +++ b/caps/tests/mochitest/test_disableScript.xhtml @@ -80,18 +80,18 @@ https://bugzilla.mozilla.org/show_bug.cg } function checkScriptEnabled(win, expectEnabled) { win.wrappedJSObject.gFiredOnclick = false; win.document.body.dispatchEvent(new win.Event('click')); is(win.wrappedJSObject.gFiredOnclick, expectEnabled, "Checking script-enabled for " + win.name + " (" + win.location + ")"); } - function setScriptEnabledForDocShell(win, enabled) { - win.docShell.allowJavascript = enabled; + function setScriptEnabled(win, enabled) { + win.browsingContext.allowJavascript = enabled; } function testList(expectEnabled, win, list, idx) { idx = idx || 0; return new Promise(resolve => { let target = list[idx] + path; info("Testing scriptability for: " + target + ". expecting " + expectEnabled); navigateFrame(win.frameElement, target).then(function() { @@ -133,75 +133,75 @@ https://bugzilla.mozilla.org/show_bug.cg } function go() { var rootWin = rootFrame.contentWindow; var chromeWin = chromeFrame.contentWindow; // Test simple docshell enable/disable. checkScriptEnabled(rootWin, true); - setScriptEnabledForDocShell(rootWin, false); + setScriptEnabled(rootWin, false); checkScriptEnabled(rootWin, false); - setScriptEnabledForDocShell(rootWin, true); + setScriptEnabled(rootWin, true); checkScriptEnabled(rootWin, true); // Privileged frames are immune to docshell flags. ok(chromeWin.document.nodePrincipal.isSystemPrincipal, "Sanity check for System Principal"); - setScriptEnabledForDocShell(chromeWin, false); + setScriptEnabled(chromeWin, false); checkScriptEnabled(chromeWin, true); - setScriptEnabledForDocShell(chromeWin, true); + setScriptEnabled(chromeWin, true); // Play around with the docshell tree and make sure everything works as // we expect. addFrame(rootWin, 'parent', true).then(function() { checkScriptEnabled(rootWin[0], true); return addFrame(rootWin[0], 'childA', true); }).then(function() { checkScriptEnabled(rootWin[0][0], true); - setScriptEnabledForDocShell(rootWin[0], false); + setScriptEnabled(rootWin[0], false); checkScriptEnabled(rootWin, true); checkScriptEnabled(rootWin[0], false); checkScriptEnabled(rootWin[0][0], false); return addFrame(rootWin[0], 'childB', false); }).then(function() { checkScriptEnabled(rootWin[0][1], false); - setScriptEnabledForDocShell(rootWin[0][0], false); - setScriptEnabledForDocShell(rootWin[0], true); + setScriptEnabled(rootWin[0][0], false); + setScriptEnabled(rootWin[0], true); checkScriptEnabled(rootWin[0], true); checkScriptEnabled(rootWin[0][0], false); - setScriptEnabledForDocShell(rootWin[0][0], true); + setScriptEnabled(rootWin[0][0], true); // Flags are inherited from the parent docshell at attach time. Note that // the flag itself is inherited, regardless of whether or not scripts are // currently allowed on the parent (which could depend on the parent's // parent). Check that. checkScriptEnabled(rootWin[0][1], false); - setScriptEnabledForDocShell(rootWin[0], false); - setScriptEnabledForDocShell(rootWin[0][1], true); + setScriptEnabled(rootWin[0], false); + setScriptEnabled(rootWin[0][1], true); return addFrame(rootWin[0][1], 'grandchild', false); }).then(function() { checkScriptEnabled(rootWin[0], false); checkScriptEnabled(rootWin[0][1], false); checkScriptEnabled(rootWin[0][1][0], false); - setScriptEnabledForDocShell(rootWin[0], true); + setScriptEnabled(rootWin[0], true); checkScriptEnabled(rootWin[0], true); checkScriptEnabled(rootWin[0][1], true); checkScriptEnabled(rootWin[0][1][0], true); // Try navigating two frames, then munging docshell scriptability, then // pulling the frames out of the bfcache to make sure that flags are // properly propagated to inactive inner windows. We do this both for an // 'own' docshell, as well as for an ancestor docshell. return navigateFrame(rootWin[0][0].frameElement, rootWin[0][0].location + '-navigated'); }).then(function() { return navigateFrame(rootWin[0][1][0].frameElement, rootWin[0][1][0].location + '-navigated'); }) .then(function() { checkScriptEnabled(rootWin[0][0], true); checkScriptEnabled(rootWin[0][1][0], true); - setScriptEnabledForDocShell(rootWin[0][0], false); - setScriptEnabledForDocShell(rootWin[0][1], false); + setScriptEnabled(rootWin[0][0], false); + setScriptEnabled(rootWin[0][1], false); checkScriptEnabled(rootWin[0][0], false); checkScriptEnabled(rootWin[0][1][0], false); return navigateBack(rootWin[0][0].frameElement); }).then(function() { return navigateBack(rootWin[0][1][0].frameElement); }) .then(function() { checkScriptEnabled(rootWin[0][0], false); checkScriptEnabled(rootWin[0][1][0], false);
--- a/devtools/client/framework/test/browser_toolbox_options_disable_js.js +++ b/devtools/client/framework/test/browser_toolbox_options_disable_js.js @@ -29,18 +29,18 @@ add_task(async function() { await toolbox.destroy(); gBrowser.removeCurrentTab(); }); async function testJSEnabled() { info("Testing that JS is enabled"); - // We use waitForTick here because switching docShell.allowJavascript to true - // takes a while to become live. + // We use waitForTick here because switching browsingContext.allowJavascript + // to true takes a while to become live. await waitForTick(); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() { const doc = content.document; const output = doc.getElementById("output"); doc.querySelector("#logJSEnabled").click(); is( output.textContent,
--- a/devtools/client/framework/toolbox-options.js +++ b/devtools/client/framework/toolbox-options.js @@ -600,20 +600,20 @@ OptionsPanel.prototype = { const prefName = "devtools.source-map.client-service.enabled"; const enabled = GetPref(prefName); const box = this.panelDoc.querySelector(`[data-pref="${prefName}"]`); box.checked = enabled; }, /** * Disables JavaScript for the currently loaded tab. We force a page refresh - * here because setting docShell.allowJavascript to true fails to block JS - * execution from event listeners added using addEventListener(), AJAX calls - * and timers. The page refresh prevents these things from being added in the - * first place. + * here because setting browsingContext.allowJavascript to true fails to block + * JS execution from event listeners added using addEventListener(), AJAX + * calls and timers. The page refresh prevents these things from being added + * in the first place. * * @param {Event} event * The event sent by checking / unchecking the disable JS checkbox. */ _disableJSClicked: function(event) { const checked = event.target.checked; this.commands.targetConfigurationCommand.updateConfiguration({
--- a/devtools/client/framework/toolbox.js +++ b/devtools/client/framework/toolbox.js @@ -2110,24 +2110,28 @@ Toolbox.prototype = { const pref = "devtools.serviceWorkers.testing.enabled"; const serviceWorkersTestingEnabled = Services.prefs.getBoolPref(pref); this.commands.targetConfigurationCommand.updateConfiguration({ serviceWorkersTestingEnabled, }); }, /** - * Read the initial javascriptEnabled configuration from the current target - * and forward it to the configuration actor. + * If we have an older version of the server which handles `javascriptEnabled` + * in the browsing-context target, read the initial javascriptEnabled + * configuration from the current target and forward it to the configuration + * actor. */ _applyJavascriptEnabledSettings: function() { - const javascriptEnabled = this.target._javascriptEnabled; - this.commands.targetConfigurationCommand.updateConfiguration({ - javascriptEnabled, - }); + if (this.target.traits.javascriptEnabled) { + const javascriptEnabled = this.target._javascriptEnabled; + this.commands.targetConfigurationCommand.updateConfiguration({ + javascriptEnabled, + }); + } }, /** * Update the visibility of the buttons. */ updateToolboxButtonsVisibility() { this.toolbarButtons.forEach(button => { button.isVisible = this._commandIsVisible(button);
--- a/devtools/client/fronts/targets/browsing-context.js +++ b/devtools/client/fronts/targets/browsing-context.js @@ -15,19 +15,18 @@ const { TargetMixin } = require("devtool class BrowsingContextTargetFront extends TargetMixin( FrontClassWithSpec(browsingContextTargetSpec) ) { constructor(client, targetFront, parentFront) { super(client, targetFront, parentFront); // For targets which support the Watcher and configuration actor, the status // for the `javascriptEnabled` setting will be available on the configuration - // front, and the target will only be used to read the initial value. - // For other targets, _javascriptEnabled will be updated everytime - // `reconfigure` is called. + // front, and the target will only be used to read the initial value from older + // servers. // Note: this property is marked as private but is accessed by the // TargetCommand to provide the "isJavascriptEnabled" wrapper. It should NOT be // used anywhere else. this._javascriptEnabled = null; this._onTabNavigated = this._onTabNavigated.bind(this); this._onFrameUpdate = this._onFrameUpdate.bind(this); } @@ -116,38 +115,32 @@ class BrowsingContextTargetFront extends // translated on the target class. Listen for them before attaching as they // can start firing on attach call. this.on("tabNavigated", this._onTabNavigated); this.on("frameUpdate", this._onFrameUpdate); const response = await super.attach(); this.targetForm.threadActor = response.threadActor; - this._javascriptEnabled = response.javascriptEnabled; this.traits = response.traits || {}; + if (response.javascriptEnabled != null) { + this.traits.javascriptEnabled = true; + this._javascriptEnabled = response.javascriptEnabled; + } + // xpcshell tests from devtools/server/tests/xpcshell/ are implementing // fake BrowsingContextTargetActor which do not expose any console actor. if (this.targetForm.consoleActor) { await this.attachConsole(); } })(); return this._attach; } - async reconfigure({ options }) { - const response = await super.reconfigure({ options }); - - if (typeof options.javascriptEnabled != "undefined") { - this._javascriptEnabled = options.javascriptEnabled; - } - - return response; - } - async detach() { // When calling this.destroy() at the end of this method, // we will end up calling detach again from TargetMixin.destroy. // Avoid invalid loops and do not try to resolve only once the previous call to detach // is done as it would do async infinite loop that never resolves. if (this._isDetaching) { return; }
--- a/devtools/server/actors/target-configuration.js +++ b/devtools/server/actors/target-configuration.js @@ -188,16 +188,27 @@ const TargetConfigurationActor = ActorCl for (const [key, value] of Object.entries(configuration)) { switch (key) { case "colorSchemeSimulation": this._setColorSchemeSimulation(value); break; case "customUserAgent": this._setCustomUserAgent(value); break; + case "javascriptEnabled": + if (value !== undefined) { + const reload = value != this.isJavascriptEnabled(); + this._setJavascriptEnabled(value); + // This flag requires a reload in order to take full effect, + // so reload if it has changed. + if (reload) { + this._browsingContext.reload(0); + } + } + break; case "overrideDPPX": this._setDPPXOverride(value); break; case "printSimulationEnabled": this._setPrintSimulationEnabled(value); break; case "rdmPaneMaxTouchPoints": this._setRDMPaneMaxTouchPoints(value); @@ -238,16 +249,20 @@ const TargetConfigurationActor = ActorCl } // Restore the origin device pixel ratio only if it was explicitly updated by this // specific actor. if (this._initialDPPXOverride !== undefined) { this._setDPPXOverride(this._initialDPPXOverride); } + if (this._initialJavascriptEnabled !== undefined) { + this._setJavascriptEnabled(this._initialJavascriptEnabled); + } + if (this._initialTouchEventsOverride !== undefined) { this._setTouchEventsOverride(this._initialTouchEventsOverride); } }, /** * Disable or enable the service workers testing features. */ @@ -292,16 +307,28 @@ const TargetConfigurationActor = ActorCl if (this._initialUserAgent === undefined) { this._initialUserAgent = this._browsingContext.customUserAgent; } this._browsingContext.customUserAgent = userAgent; }, + isJavascriptEnabled() { + return this._browsingContext.allowJavascript; + }, + _setJavascriptEnabled(allow) { + if (this._initialJavascriptEnabled === undefined) { + this._initialJavascriptEnabled = this._browsingContext.allowJavascript; + } + if (allow !== undefined) { + this._browsingContext.allowJavascript = allow; + } + }, + /* DPPX override */ _setDPPXOverride(dppx) { if (this._browsingContext.overrideDPPX === dppx) { return; } if (!dppx && this._initialDPPXOverride) { dppx = this._initialDPPXOverride;
--- a/devtools/server/actors/targets/browsing-context.js +++ b/devtools/server/actors/targets/browsing-context.js @@ -1085,17 +1085,16 @@ const browsingContextTargetPrototype = { }; } this._attach(); return { threadActor: this.threadActor.actorID, cacheDisabled: this._getCacheDisabled(), - javascriptEnabled: this._getJavascriptEnabled(), traits: this.traits, }; }, detach(request) { if (!this._detach()) { throw { error: "wrongState", @@ -1273,24 +1272,16 @@ const browsingContextTargetPrototype = { } } if (!this.isTopLevelTarget) { // Following DevTools target options should only apply to the top target and be // propagated through the browsing context tree via the platform. return; } - - if ( - typeof options.javascriptEnabled !== "undefined" && - options.javascriptEnabled !== this._getJavascriptEnabled() - ) { - this._setJavascriptEnabled(options.javascriptEnabled); - reload = true; - } if ( typeof options.cacheDisabled !== "undefined" && options.cacheDisabled !== this._getCacheDisabled() ) { this._setCacheDisabled(options.cacheDisabled); } if ( typeof options.paintFlashing !== "undefined" && @@ -1315,17 +1306,16 @@ const browsingContextTargetPrototype = { return this._touchSimulator; }, /** * Opposite of the updateTargetConfiguration method, that resets document * state when closing the toolbox. */ _restoreTargetConfiguration() { - this._restoreJavascript(); this._setCacheDisabled(false); this._setPaintFlashingEnabled(false); if (this._restoreFocus && this.browsingContext?.isActive) { this.window.focus(); } }, @@ -1335,49 +1325,16 @@ const browsingContextTargetPrototype = { _setCacheDisabled(disabled) { const enable = Ci.nsIRequest.LOAD_NORMAL; const disable = Ci.nsIRequest.LOAD_BYPASS_CACHE; this.docShell.defaultLoadFlags = disabled ? disable : enable; }, /** - * Disable or enable JS via docShell. - */ - _wasJavascriptEnabled: null, - _setJavascriptEnabled(allow) { - if (this._wasJavascriptEnabled === null) { - this._wasJavascriptEnabled = this.docShell.allowJavascript; - } - this.docShell.allowJavascript = allow; - }, - - /** - * Restore JS state, before the actor modified it. - */ - _restoreJavascript() { - if (this._wasJavascriptEnabled !== null) { - this._setJavascriptEnabled(this._wasJavascriptEnabled); - this._wasJavascriptEnabled = null; - } - }, - - /** - * Return JS allowed status. - */ - _getJavascriptEnabled() { - if (!this.docShell) { - // The browsing context is already closed. - return null; - } - - return this.docShell.allowJavascript; - }, - - /** * Disable or enable the paint flashing on the target. */ _setPaintFlashingEnabled(enabled) { const windowUtils = this.window.windowUtils; windowUtils.paintFlashing = enabled; }, /**
--- a/devtools/shared/commands/target-configuration/target-configuration-command.js +++ b/devtools/shared/commands/target-configuration/target-configuration-command.js @@ -56,30 +56,33 @@ class TargetConfigurationCommand { } else { await this._commands.targetCommand.targetFront.reconfigure({ options: configuration, }); } } async isJavascriptEnabled() { - if ( - this._hasTargetWatcherSupport() && - // `javascriptEnabled` is first read by the target and then forwarded by - // the toolbox to the TargetConfigurationCommand, so it might be undefined at this - // point. - typeof this.configuration.javascriptEnabled !== "undefined" - ) { - return this.configuration.javascriptEnabled; + if (this._hasTargetWatcherSupport()) { + const front = await this.getFront(); + return front.isJavascriptEnabled(); } // If the TargetConfigurationActor does not know the value yet, or if the target don't - // support the Watcher + configuration actor, fallback on the initial value cached by - // the target front. - return this._commands.targetCommand.targetFront._javascriptEnabled; + // support the Watcher + configuration actor, and we have an old version of the server + // which handles `javascriptEnabled` in the target, fallback on the initial value + // cached by the target front. + const { targetFront } = this._commands.targetCommand; + if (targetFront.traits.javascriptEnabled) { + return targetFront._javascriptEnabled; + } + + // If we don't have target watcher support, we can't get this value, so just fall back + // to the default. + return true; } /** * Change orientation type and angle (that can be accessed through screen.orientation in * the content page) and simulates the "orientationchange" event when the device screen * was rotated. * Note that this will only be effective if the Responsive Design Mode is enabled. *
--- a/devtools/shared/specs/target-configuration.js +++ b/devtools/shared/specs/target-configuration.js @@ -33,12 +33,18 @@ const targetConfigurationSpec = generate updateConfiguration: { request: { configuration: Arg(0, "target-configuration.configuration"), }, response: { configuration: RetVal("target-configuration.configuration"), }, }, + isJavascriptEnabled: { + request: {}, + response: { + javascriptEnabled: RetVal("boolean"), + }, + }, }, }); exports.targetConfigurationSpec = targetConfigurationSpec;
--- a/devtools/shared/specs/targets/browsing-context.js +++ b/devtools/shared/specs/targets/browsing-context.js @@ -9,17 +9,17 @@ const { RetVal, Option, Arg, } = require("devtools/shared/protocol"); types.addDictType("browsingContextTarget.attach", { threadActor: "number", cacheDisabled: "boolean", - javascriptEnabled: "boolean", + javascriptEnabled: "nullable:boolean", traits: "json", }); types.addDictType("browsingContextTarget.switchtoframe", { message: "string", }); types.addDictType("browsingContextTarget.listframes", { @@ -41,17 +41,16 @@ types.addDictType("browsingContextTarget types.addDictType("browsingContextTarget.reload", { force: "boolean", }); // @backward-compat { version 87 } See backward-compat note for `reconfigure`. types.addDictType("browsingContextTarget.reconfigure", { cacheDisabled: "nullable:boolean", colorSchemeSimulation: "nullable:string", - javascriptEnabled: "nullable:boolean", paintFlashing: "nullable:boolean", printSimulationEnabled: "nullable:boolean", restoreFocus: "nullable:boolean", serviceWorkersTestingEnabled: "nullable:boolean", }); const browsingContextTargetSpecPrototype = { typeName: "browsingContextTarget",
--- a/docshell/base/BrowsingContext.cpp +++ b/docshell/base/BrowsingContext.cpp @@ -401,16 +401,18 @@ already_AddRefed<BrowsingContext> Browsi fields.mOrientationLock = mozilla::hal::eScreenOrientation_None; fields.mUseGlobalHistory = inherit ? inherit->GetUseGlobalHistory() : false; fields.mUseErrorPages = true; fields.mTouchEventsOverrideInternal = TouchEventsOverride::None; + fields.mAllowJavascript = inherit ? inherit->GetAllowJavascript() : true; + RefPtr<BrowsingContext> context; if (XRE_IsParentProcess()) { context = new CanonicalBrowsingContext(parentWC, group, id, /* aOwnerProcessId */ 0, /* aEmbedderProcessId */ 0, aType, std::move(fields)); } else { context = @@ -533,16 +535,17 @@ BrowsingContext::BrowsingContext(WindowC mIsDiscarded(false), mWindowless(false), mDanglingRemoteOuterProxies(false), mEmbeddedByThisProcess(false), mUseRemoteTabs(false), mUseRemoteSubframes(false), mCreatedDynamically(false), mIsInBFCache(false), + mCanExecuteScripts(true), mChildOffset(0) { MOZ_RELEASE_ASSERT(!mParentWindow || mParentWindow->Group() == mGroup); MOZ_RELEASE_ASSERT(mBrowsingContextId != 0); MOZ_RELEASE_ASSERT(mGroup); } void BrowsingContext::SetDocShell(nsIDocShell* aDocShell) { // XXX(nika): We should communicate that we are now an active BrowsingContext @@ -737,16 +740,17 @@ void BrowsingContext::Attach(bool aFromI MOZ_DIAGNOSTIC_ASSERT(mParentWindow->GetWindowGlobalChild(), "local attach call with oop parent window"); MOZ_DIAGNOSTIC_ASSERT(mParentWindow->GetWindowGlobalChild()->CanSend(), "local attach call with dead parent window"); } mChildOffset = mCreatedDynamically ? -1 : mParentWindow->Children().Length(); mParentWindow->AppendChildBrowsingContext(this); + RecomputeCanExecuteScripts(); } else { mGroup->Toplevels().AppendElement(this); } if (GetIsPopupSpam()) { PopupBlocker::RegisterOpenPopupSpam(); } @@ -2623,16 +2627,53 @@ auto BrowsingContext::CanSet(FieldIndex< void BrowsingContext::DidSet(FieldIndex<IDX_HasMainMediaController>, bool aOldValue) { if (!IsTop() || aOldValue == GetHasMainMediaController()) { return; } Group()->UpdateToplevelsSuspendedIfNeeded(); } +auto BrowsingContext::CanSet(FieldIndex<IDX_AllowJavascript>, bool aValue, + ContentParent* aSource) -> CanSetResult { + if (mozilla::SessionHistoryInParent()) { + return XRE_IsParentProcess() && !aSource ? CanSetResult::Allow : CanSetResult::Deny; + } + + // Without Session History in Parent, session restore code still needs to set + // this from content processes. + return LegacyRevertIfNotOwningOrParentProcess(aSource); +} + +void BrowsingContext::DidSet(FieldIndex<IDX_AllowJavascript>, bool aOldValue) { + RecomputeCanExecuteScripts(); +} + + +void BrowsingContext::RecomputeCanExecuteScripts() { + const bool old = mCanExecuteScripts; + if (!AllowJavascript()) { + // Scripting has been explicitly disabled on our BrowsingContext. + mCanExecuteScripts = false; + } else if (GetParentWindowContext()) { + // Otherwise, inherit parent. + mCanExecuteScripts = GetParentWindowContext()->CanExecuteScripts(); + } else { + // Otherwise, we're the root of the tree, and we haven't explicitly disabled + // script. Allow. + mCanExecuteScripts = true; + } + + if (old != mCanExecuteScripts) { + for (WindowContext* wc : GetWindowContexts()) { + wc->RecomputeCanExecuteScripts(); + } + } +} + bool BrowsingContext::InactiveForSuspend() const { if (!StaticPrefs::dom_suspend_inactive_enabled()) { return false; } // We should suspend a page only when it's inactive and doesn't have a main // media controller. Having a main controller in context means it might be // playing media, or waiting media keys to control media (could be not playing // anything currently)
--- a/docshell/base/BrowsingContext.h +++ b/docshell/base/BrowsingContext.h @@ -195,17 +195,20 @@ enum class ExplicitActiveStatus : uint8_ /* True if the top level browsing context owns a main media controller */ \ FIELD(HasMainMediaController, bool) \ /* The number of entries added to the session history because of this \ * browsing context. */ \ FIELD(HistoryEntryCount, uint32_t) \ /* Don't use the getter of the field, but IsInBFCache() method */ \ FIELD(IsInBFCache, bool) \ FIELD(HasRestoreData, bool) \ - FIELD(SessionStoreEpoch, uint32_t) + FIELD(SessionStoreEpoch, uint32_t) \ + /* Whether we can execute scripts in this BrowsingContext. Has no effect \ + * unless scripts are also allowed in the parent WindowContext. */ \ + FIELD(AllowJavascript, bool) // BrowsingContext, in this context, is the cross process replicated // environment in which information about documents is stored. In // particular the tree structure of nested browsing contexts is // represented by the tree of BrowsingContexts. // // The tree of BrowsingContexts is created in step with its // corresponding nsDocShell, and when nsDocShells are connected @@ -846,30 +849,39 @@ class BrowsingContext : public nsILoadCo dom::PrefersColorSchemeOverride PrefersColorSchemeOverride() const { return GetPrefersColorSchemeOverride(); } void FlushSessionStore(); bool IsInBFCache() const { return mIsInBFCache; } + bool AllowJavascript() const { return GetAllowJavascript(); } + bool CanExecuteScripts() const { return mCanExecuteScripts; } + protected: virtual ~BrowsingContext(); BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup, uint64_t aBrowsingContextId, Type aType, FieldValues&& aInit); void SetChildSHistory(ChildSHistory* aChildSHistory); already_AddRefed<ChildSHistory> ForgetChildSHistory() { // FIXME Do we need to unset mHasSessionHistory? return mChildSessionHistory.forget(); } private: void Attach(bool aFromIPC, ContentParent* aOriginProcess); + // Recomputes whether we can execute scripts in this BrowsingContext based on + // the value of AllowJavascript() and whether scripts are allowed in the + // parent WindowContext. Called whenever the AllowJavascript() flag or the + // parent WC changes. + void RecomputeCanExecuteScripts(); + // Find the special browsing context if aName is '_self', '_parent', // '_top', but not '_blank'. The latter is handled in FindWithName BrowsingContext* FindWithSpecialName(const nsAString& aName, BrowsingContext& aRequestingContext); // Is it early enough in the BrowsingContext's lifecycle that it is still // OK to set OriginAttributes? bool CanSetOriginAttributes(); @@ -1064,16 +1076,20 @@ class BrowsingContext : public nsILoadCo bool CanSet(FieldIndex<IDX_PendingInitialization>, bool aNewValue, ContentParent* aSource); CanSetResult CanSet(FieldIndex<IDX_HasMainMediaController>, bool aNewValue, ContentParent* aSource); void DidSet(FieldIndex<IDX_HasMainMediaController>, bool aOldValue); + CanSetResult CanSet(FieldIndex<IDX_AllowJavascript>, bool aValue, + ContentParent* aSource); + void DidSet(FieldIndex<IDX_AllowJavascript>, bool aOldValue); + bool CanSet(FieldIndex<IDX_HasRestoreData>, bool aNewValue, ContentParent* aSource); template <size_t I, typename T> bool CanSet(FieldIndex<I>, const T&, ContentParent*) { return true; } @@ -1180,16 +1196,21 @@ class BrowsingContext : public nsILoadCo // True if this BrowsingContext is for a frame that was added dynamically. bool mCreatedDynamically : 1; // Set to true if the browsing context is in the bfcache and pagehide has been // dispatched. When coming out from the bfcache, the value is set to false // before dispatching pageshow. bool mIsInBFCache : 1; + // Determines if we can execute scripts in this BrowsingContext. True if + // AllowJavascript() is true and script execution is allowed in the parent + // WindowContext. + bool mCanExecuteScripts : 1; + // The original offset of this context in its container. This property is -1 // if this BrowsingContext is for a frame that was added dynamically. int32_t mChildOffset; // The start time of user gesture, this is only available if the browsing // context is in process. TimeStamp mUserGestureStart;
--- a/docshell/base/WindowContext.cpp +++ b/docshell/base/WindowContext.cpp @@ -257,16 +257,54 @@ bool WindowContext::CanSet(FieldIndex<ID return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet(FieldIndex<IDX_HadLazyLoadImage>, const bool& aValue, ContentParent* aSource) { return IsTop(); } +bool WindowContext::CanSet(FieldIndex<IDX_AllowJavascript>, bool aValue, + ContentParent* aSource) { + return (XRE_IsParentProcess() && !aSource) || CheckOnlyOwningProcessCanSet(aSource); +} + +void WindowContext::DidSet(FieldIndex<IDX_AllowJavascript>, bool aOldValue) { + RecomputeCanExecuteScripts(); +} + +void WindowContext::RecomputeCanExecuteScripts(bool aApplyChanges) { + const bool old = mCanExecuteScripts; + if (!AllowJavascript()) { + // Scripting has been explicitly disabled on our WindowContext. + mCanExecuteScripts = false; + } else { + // Otherwise, inherit. + mCanExecuteScripts = mBrowsingContext->CanExecuteScripts(); + } + + if (aApplyChanges && old != mCanExecuteScripts) { + // Inform our active DOM window. + if (nsGlobalWindowInner* window = GetInnerWindow()) { + // Only update scriptability if the window is current. Windows will have + // scriptability disabled when entering the bfcache and updated when + // coming out. + if (window->IsCurrentInnerWindow()) { + auto& scriptability = xpc::Scriptability::Get( + window->GetGlobalJSObject()); + scriptability.SetWindowAllowsScript(mCanExecuteScripts); + } + } + + for (const RefPtr<BrowsingContext>& child : Children()) { + child->RecomputeCanExecuteScripts(); + } + } +} + void WindowContext::DidSet(FieldIndex<IDX_SHEntryHasUserInteraction>, bool aOldValue) { MOZ_ASSERT( TopWindowContext() == this, "SHEntryHasUserInteraction can only be set on the top window context"); // This field is set when the child notifies us of new user interaction, so we // also set the currently active shentry in the parent as having interaction. if (XRE_IsParentProcess() && mBrowsingContext) { @@ -468,16 +506,17 @@ WindowContext::WindowContext(BrowsingCon FieldValues&& aInit) : mFields(std::move(aInit)), mInnerWindowId(aInnerWindowId), mOuterWindowId(aOuterWindowId), mBrowsingContext(aBrowsingContext) { MOZ_ASSERT(mBrowsingContext); MOZ_ASSERT(mInnerWindowId); MOZ_ASSERT(mOuterWindowId); + RecomputeCanExecuteScripts(/* aApplyChanges */ false); } WindowContext::~WindowContext() { if (gWindowContexts) { gWindowContexts->Remove(InnerWindowId()); } }
--- a/docshell/base/WindowContext.h +++ b/docshell/base/WindowContext.h @@ -26,72 +26,75 @@ class LogModule; namespace dom { class WindowGlobalChild; class WindowGlobalParent; class WindowGlobalInit; class BrowsingContext; class BrowsingContextGroup; -#define MOZ_EACH_WC_FIELD(FIELD) \ - /* Whether the SHEntry associated with the current top-level \ - * window has already seen user interaction. \ - * As such, this will be reset to false when a new SHEntry is \ - * created without changing the WC (e.g. when using pushState or \ - * sub-frame navigation) \ - * This flag is set for optimization purposes, to avoid \ - * having to get the top SHEntry and update it on every \ - * user interaction. \ - * This is only meaningful on the top-level WC. */ \ - FIELD(SHEntryHasUserInteraction, bool) \ - FIELD(CookieBehavior, Maybe<uint32_t>) \ - FIELD(IsOnContentBlockingAllowList, bool) \ - /* Whether the given window hierarchy is third party. See \ - * ThirdPartyUtil::IsThirdPartyWindow for details */ \ - FIELD(IsThirdPartyWindow, bool) \ - /* Whether this window's channel has been marked as a third-party \ - * tracking resource */ \ - FIELD(IsThirdPartyTrackingResourceWindow, bool) \ - FIELD(IsSecureContext, bool) \ - FIELD(IsOriginalFrameSource, bool) \ - /* Mixed-Content: If the corresponding documentURI is https, \ - * then this flag is true. */ \ - FIELD(IsSecure, bool) \ - /* Whether the user has overriden the mixed content blocker to allow \ - * mixed content loads to happen */ \ - FIELD(AllowMixedContent, bool) \ - /* Whether this window has registered a "beforeunload" event \ - * handler */ \ - FIELD(HasBeforeUnload, bool) \ - /* Controls whether the WindowContext is currently considered to be \ - * activated by a gesture */ \ - FIELD(UserActivationState, UserActivation::State) \ - FIELD(EmbedderPolicy, nsILoadInfo::CrossOriginEmbedderPolicy) \ - /* True if this document tree contained at least a HTMLMediaElement. \ - * This should only be set on top level context. */ \ - FIELD(DocTreeHadMedia, bool) \ - FIELD(AutoplayPermission, uint32_t) \ - FIELD(ShortcutsPermission, uint32_t) \ - /* Store the Id of the browsing context where active media session \ - * exists on the top level window context */ \ - FIELD(ActiveMediaSessionContextId, Maybe<uint64_t>) \ - /* ALLOW_ACTION if it is allowed to open popups for the sub-tree \ - * starting and including the current WindowContext */ \ - FIELD(PopupPermission, uint32_t) \ - FIELD(DelegatedPermissions, \ - PermissionDelegateHandler::DelegatedPermissionList) \ - FIELD(DelegatedExactHostMatchPermissions, \ - PermissionDelegateHandler::DelegatedPermissionList) \ - FIELD(HasReportedShadowDOMUsage, bool) \ - /* Whether the principal of this window is for a local \ - * IP address */ \ - FIELD(IsLocalIP, bool) \ - /* Whether the corresponding document has `loading='lazy'` \ - * images; It won't become false if the image becomes non-lazy */ \ - FIELD(HadLazyLoadImage, bool) +#define MOZ_EACH_WC_FIELD(FIELD) \ + /* Whether the SHEntry associated with the current top-level \ + * window has already seen user interaction. \ + * As such, this will be reset to false when a new SHEntry is \ + * created without changing the WC (e.g. when using pushState or \ + * sub-frame navigation) \ + * This flag is set for optimization purposes, to avoid \ + * having to get the top SHEntry and update it on every \ + * user interaction. \ + * This is only meaningful on the top-level WC. */ \ + FIELD(SHEntryHasUserInteraction, bool) \ + FIELD(CookieBehavior, Maybe<uint32_t>) \ + FIELD(IsOnContentBlockingAllowList, bool) \ + /* Whether the given window hierarchy is third party. See \ + * ThirdPartyUtil::IsThirdPartyWindow for details */ \ + FIELD(IsThirdPartyWindow, bool) \ + /* Whether this window's channel has been marked as a third-party \ + * tracking resource */ \ + FIELD(IsThirdPartyTrackingResourceWindow, bool) \ + FIELD(IsSecureContext, bool) \ + FIELD(IsOriginalFrameSource, bool) \ + /* Mixed-Content: If the corresponding documentURI is https, \ + * then this flag is true. */ \ + FIELD(IsSecure, bool) \ + /* Whether the user has overriden the mixed content blocker to allow \ + * mixed content loads to happen */ \ + FIELD(AllowMixedContent, bool) \ + /* Whether this window has registered a "beforeunload" event \ + * handler */ \ + FIELD(HasBeforeUnload, bool) \ + /* Controls whether the WindowContext is currently considered to be \ + * activated by a gesture */ \ + FIELD(UserActivationState, UserActivation::State) \ + FIELD(EmbedderPolicy, nsILoadInfo::CrossOriginEmbedderPolicy) \ + /* True if this document tree contained at least a HTMLMediaElement. \ + * This should only be set on top level context. */ \ + FIELD(DocTreeHadMedia, bool) \ + FIELD(AutoplayPermission, uint32_t) \ + FIELD(ShortcutsPermission, uint32_t) \ + /* Store the Id of the browsing context where active media session \ + * exists on the top level window context */ \ + FIELD(ActiveMediaSessionContextId, Maybe<uint64_t>) \ + /* ALLOW_ACTION if it is allowed to open popups for the sub-tree \ + * starting and including the current WindowContext */ \ + FIELD(PopupPermission, uint32_t) \ + FIELD(DelegatedPermissions, \ + PermissionDelegateHandler::DelegatedPermissionList) \ + FIELD(DelegatedExactHostMatchPermissions, \ + PermissionDelegateHandler::DelegatedPermissionList) \ + FIELD(HasReportedShadowDOMUsage, bool) \ + /* Whether the principal of this window is for a local \ + * IP address */ \ + FIELD(IsLocalIP, bool) \ + /* Whether the corresponding document has `loading='lazy'` \ + * images; It won't become false if the image becomes non-lazy */ \ + FIELD(HadLazyLoadImage, bool) \ + /* Whether we can execute scripts in this WindowContext. Has no effect \ + * unless scripts are also allowed in the BrowsingContext. */ \ + FIELD(AllowJavascript, bool) class WindowContext : public nsISupports, public nsWrapperCache { MOZ_DECL_SYNCED_CONTEXT(WindowContext, MOZ_EACH_WC_FIELD) NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WindowContext) public: @@ -177,16 +180,19 @@ class WindowContext : public nsISupports // activation and the transient user gesture activation had been consumed // successfully. bool ConsumeTransientUserGestureActivation(); bool CanShowPopup(); bool HadLazyLoadImage() const { return GetHadLazyLoadImage(); } + bool AllowJavascript() const { return GetAllowJavascript(); } + bool CanExecuteScripts() const { return mCanExecuteScripts; } + protected: WindowContext(BrowsingContext* aBrowsingContext, uint64_t aInnerWindowId, uint64_t aOuterWindowId, FieldValues&& aFields); virtual ~WindowContext(); virtual void Init(); private: @@ -266,43 +272,57 @@ class WindowContext : public nsISupports } bool CanSet(FieldIndex<IDX_IsLocalIP>, const bool& aValue, ContentParent* aSource); bool CanSet(FieldIndex<IDX_HadLazyLoadImage>, const bool& aValue, ContentParent* aSource); + bool CanSet(FieldIndex<IDX_AllowJavascript>, bool aValue, + ContentParent* aSource); + void DidSet(FieldIndex<IDX_AllowJavascript>, bool aOldValue); + void DidSet(FieldIndex<IDX_HasReportedShadowDOMUsage>, bool aOldValue); void DidSet(FieldIndex<IDX_SHEntryHasUserInteraction>, bool aOldValue); // Overload `DidSet` to get notifications for a particular field being set. // // You can also overload the variant that gets the old value if you need it. template <size_t I> void DidSet(FieldIndex<I>) {} template <size_t I, typename T> void DidSet(FieldIndex<I>, T&& aOldValue) {} void DidSet(FieldIndex<IDX_UserActivationState>); + // Recomputes whether we can execute scripts in this WindowContext based on + // the value of AllowJavascript() and whether scripts are allowed in the + // BrowsingContext. + void RecomputeCanExecuteScripts(bool aApplyChanges = true); + const uint64_t mInnerWindowId; const uint64_t mOuterWindowId; RefPtr<BrowsingContext> mBrowsingContext; WeakPtr<WindowGlobalChild> mWindowGlobalChild; // --- NEVER CHANGE `mChildren` DIRECTLY! --- // Changes to this list need to be synchronized to the list within our // `mBrowsingContext`, and should only be performed through the // `AppendChildBrowsingContext` and `RemoveChildBrowsingContext` methods. nsTArray<RefPtr<BrowsingContext>> mChildren; bool mIsDiscarded = false; bool mIsInProcess = false; + // Determines if we can execute scripts in this WindowContext. True if + // AllowJavascript() is true and script execution is allowed in the + // BrowsingContext. + bool mCanExecuteScripts = true; + // The start time of user gesture, this is only available if the window // context is in process. TimeStamp mUserGestureStart; }; using WindowContextTransaction = WindowContext::BaseTransaction; using WindowContextInitializer = WindowContext::IPCInitializer; using MaybeDiscardedWindowContext = MaybeDiscarded<WindowContext>;
--- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -388,31 +388,29 @@ nsDocShell::nsDocShell(BrowsingContext* mMetaViewportOverride(nsIDocShell::META_VIEWPORT_OVERRIDE_NONE), mChannelToDisconnectOnPageHide(0), mCreatingDocument(false), #ifdef DEBUG mInEnsureScriptEnv(false), #endif mInitialized(false), mAllowSubframes(true), - mAllowJavascript(true), mAllowMetaRedirects(true), mAllowImages(true), mAllowMedia(true), mAllowDNSPrefetch(true), mAllowWindowControl(true), mCSSErrorReportingEnabled(false), mAllowAuth(mItemType == typeContent), mAllowKeywordFixup(false), mDisableMetaRefreshWhenInactive(false), mIsAppTab(false), mDeviceSizeIsPageSize(false), mWindowDraggingAllowed(false), mInFrameSwap(false), - mCanExecuteScripts(false), mFiredUnloadEvent(false), mEODForCurrentDocument(false), mURIResultedInDocument(false), mIsBeingDestroyed(false), mIsExecutingOnLoadHandler(false), mSavingOldViewer(false), mAffectPrivateSessionLifetime(true), mInvisible(false), @@ -1745,44 +1743,29 @@ nsDocShell::GetAllowPlugins(bool* aAllow NS_IMETHODIMP nsDocShell::SetAllowPlugins(bool aAllowPlugins) { // XXX should enable or disable a plugin host return mBrowsingContext->SetAllowPlugins(aAllowPlugins); } NS_IMETHODIMP -nsDocShell::GetAllowJavascript(bool* aAllowJavascript) { - NS_ENSURE_ARG_POINTER(aAllowJavascript); - - *aAllowJavascript = mAllowJavascript; - return NS_OK; -} - -NS_IMETHODIMP nsDocShell::GetCssErrorReportingEnabled(bool* aEnabled) { MOZ_ASSERT(aEnabled); *aEnabled = mCSSErrorReportingEnabled; return NS_OK; } NS_IMETHODIMP nsDocShell::SetCssErrorReportingEnabled(bool aEnabled) { mCSSErrorReportingEnabled = aEnabled; return NS_OK; } NS_IMETHODIMP -nsDocShell::SetAllowJavascript(bool aAllowJavascript) { - mAllowJavascript = aAllowJavascript; - RecomputeCanExecuteScripts(); - return NS_OK; -} - -NS_IMETHODIMP nsDocShell::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) { NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing); return mBrowsingContext->GetUsePrivateBrowsing(aUsePrivateBrowsing); } void nsDocShell::NotifyPrivateBrowsingChanged() { MOZ_ASSERT(!mIsBeingDestroyed); @@ -2648,60 +2631,16 @@ Maybe<ClientInfo> nsDocShell::GetInitial if (!doc || !doc->IsInitialDocument()) { return Maybe<ClientInfo>(); } return innerWindow->GetClientInfo(); } -void nsDocShell::RecomputeCanExecuteScripts() { - bool old = mCanExecuteScripts; - RefPtr<nsDocShell> parent = GetInProcessParentDocshell(); - - // If we have no tree owner, that means that we've been detached from the - // docshell tree (this is distinct from having no parent docshell, which - // is the case for root docshells). It would be nice to simply disallow - // script in detached docshells, but bug 986542 demonstrates that this - // behavior breaks at least one website. - // - // So instead, we use our previous value, unless mAllowJavascript has been - // explicitly set to false. - if (!mTreeOwner) { - mCanExecuteScripts = mCanExecuteScripts && mAllowJavascript; - // If scripting has been explicitly disabled on our docshell, we're done. - } else if (!mAllowJavascript) { - mCanExecuteScripts = false; - // If we have a parent, inherit. - } else if (parent) { - mCanExecuteScripts = parent->mCanExecuteScripts; - // Otherwise, we're the root of the tree, and we haven't explicitly disabled - // script. Allow. - } else { - mCanExecuteScripts = true; - } - - // Inform our active DOM window. - // - // This will pass the outer, which will be in the scope of the active inner. - if (mScriptGlobal && mScriptGlobal->GetGlobalJSObject()) { - xpc::Scriptability& scriptability = - xpc::Scriptability::Get(mScriptGlobal->GetGlobalJSObject()); - scriptability.SetDocShellAllowsScript(mCanExecuteScripts); - } - - // If our value has changed, our children might be affected. Recompute their - // value as well. - if (old != mCanExecuteScripts) { - for (auto* child : mChildList.ForwardRange()) { - static_cast<nsDocShell*>(child)->RecomputeCanExecuteScripts(); - } - } -} - nsresult nsDocShell::SetDocLoaderParent(nsDocLoader* aParent) { bool wasFrame = IsFrame(); nsresult rv = nsDocLoader::SetDocLoaderParent(aParent); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsISupportsPriority> priorityGroup = do_QueryInterface(mLoadGroup); if (wasFrame != IsFrame() && priorityGroup) { @@ -2712,20 +2651,16 @@ nsresult nsDocShell::SetDocLoaderParent( nsISupports* parent = GetAsSupports(aParent); // If parent is another docshell, we inherit all their flags for // allowing plugins, scripting etc. bool value; nsCOMPtr<nsIDocShell> parentAsDocShell(do_QueryInterface(parent)); if (parentAsDocShell) { - if (mAllowJavascript && - NS_SUCCEEDED(parentAsDocShell->GetAllowJavascript(&value))) { - SetAllowJavascript(value); - } if (mAllowMetaRedirects && NS_SUCCEEDED(parentAsDocShell->GetAllowMetaRedirects(&value))) { SetAllowMetaRedirects(value); } if (mAllowSubframes && NS_SUCCEEDED(parentAsDocShell->GetAllowSubframes(&value))) { SetAllowSubframes(value); } @@ -2750,19 +2685,16 @@ nsresult nsDocShell::SetDocLoaderParent( // like this that might be embedded within it. } nsCOMPtr<nsIURIContentListener> parentURIListener(do_GetInterface(parent)); if (parentURIListener) { mContentListener->SetParentContentListener(parentURIListener); } - // Our parent has changed. Recompute scriptability. - RecomputeCanExecuteScripts(); - // Inform windows when they're being removed from their parent. if (!aParent) { MaybeClearStorageAccessFlag(); } return NS_OK; } @@ -2977,26 +2909,16 @@ nsDocShell::SetTreeOwner(nsIDocShellTree MOZ_RELEASE_ASSERT( oldBrowserChild == newBrowserChild, "Cannot change BrowserChild during nsDocShell lifetime!"); } else { mBrowserChild = do_GetWeakReference(newBrowserChild); } } - // Our tree owner has changed. Recompute scriptability. - // - // Note that this is near-redundant with the recomputation in - // SetDocLoaderParent(), but not so for the root DocShell, where the call to - // SetTreeOwner() happens after the initial AddDocLoaderAsChildOfRoot(), - // and we never set another parent. Given that this is neither expensive nor - // performance-critical, let's be safe and unconditionally recompute this - // state whenever dependent state changes. - RecomputeCanExecuteScripts(); - return NS_OK; } NS_IMETHODIMP nsDocShell::GetHistoryID(nsID& aID) { aID = mBrowsingContext->GetHistoryID(); return NS_OK; } @@ -7653,19 +7575,16 @@ nsresult nsDocShell::RestoreFromHistory( // Now we simulate appending child docshells for subframes. for (i = 0; i < childShells.Count(); ++i) { nsIDocShellTreeItem* childItem = childShells.ObjectAt(i); nsCOMPtr<nsIDocShell> childShell = do_QueryInterface(childItem); // Make sure to not clobber the state of the child. Since AddChild // always clobbers it, save it off first. - bool allowJavascript; - childShell->GetAllowJavascript(&allowJavascript); - bool allowRedirects; childShell->GetAllowMetaRedirects(&allowRedirects); bool allowSubframes; childShell->GetAllowSubframes(&allowSubframes); bool allowImages; childShell->GetAllowImages(&allowImages); @@ -7679,17 +7598,16 @@ nsresult nsDocShell::RestoreFromHistory( bool allowContentRetargetingOnChildren = childShell->GetAllowContentRetargetingOnChildren(); // this.AddChild(child) calls child.SetDocLoaderParent(this), meaning that // the child inherits our state. Among other things, this means that the // child inherits our mPrivateBrowsingId, which is what we want. AddChild(childItem); - childShell->SetAllowJavascript(allowJavascript); childShell->SetAllowMetaRedirects(allowRedirects); childShell->SetAllowSubframes(allowSubframes); childShell->SetAllowImages(allowImages); childShell->SetAllowMedia(allowMedia); childShell->SetAllowDNSPrefetch(allowDNSPrefetch); childShell->SetAllowContentRetargeting(allowContentRetargeting); childShell->SetAllowContentRetargetingOnChildren( allowContentRetargetingOnChildren); @@ -13086,22 +13004,16 @@ NS_IMETHODIMP nsDocShell::ExitPrintPrevi #if NS_PRINT_PREVIEW nsCOMPtr<nsIWebBrowserPrint> viewer = do_QueryInterface(mContentViewer); return viewer->ExitPrintPreview(); #else return NS_OK; #endif } -NS_IMETHODIMP -nsDocShell::GetCanExecuteScripts(bool* aResult) { - *aResult = mCanExecuteScripts; - return NS_OK; -} - /* [infallible] */ NS_IMETHODIMP nsDocShell::GetIsTopLevelContentDocShell( bool* aIsTopLevelContentDocShell) { *aIsTopLevelContentDocShell = false; if (mItemType == typeContent) { *aIsTopLevelContentDocShell = mBrowsingContext->IsTopContent(); }
--- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -947,17 +947,16 @@ class nsDocShell final : public nsDocLoa void FirePageHideShowNonRecursive(bool aShow); nsresult Dispatch(mozilla::TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable); void SetupReferrerInfoFromChannel(nsIChannel* aChannel); void SetReferrerInfo(nsIReferrerInfo* aReferrerInfo); void ReattachEditorToWindow(nsISHEntry* aSHEntry); - void RecomputeCanExecuteScripts(); void ClearFrameHistory(nsISHEntry* aEntry); // Determine if this type of load should update history. static bool ShouldUpdateGlobalHistory(uint32_t aLoadType); void UpdateGlobalHistoryTitle(nsIURI* aURI); bool IsFrame() { return mBrowsingContext->IsFrame(); } bool CanSetOriginAttributes(); bool ShouldBlockLoadingForBackButton(); static bool ShouldDiscardLayoutState(nsIHttpChannel* aChannel); @@ -1240,36 +1239,30 @@ class nsDocShell final : public nsDocLoa bool mCreatingDocument; // (should be) debugging only #ifdef DEBUG bool mInEnsureScriptEnv; uint64_t mDocShellID = 0; #endif bool mInitialized : 1; bool mAllowSubframes : 1; - bool mAllowJavascript : 1; bool mAllowMetaRedirects : 1; bool mAllowImages : 1; bool mAllowMedia : 1; bool mAllowDNSPrefetch : 1; bool mAllowWindowControl : 1; bool mCSSErrorReportingEnabled : 1; bool mAllowAuth : 1; bool mAllowKeywordFixup : 1; bool mDisableMetaRefreshWhenInactive : 1; bool mIsAppTab : 1; bool mDeviceSizeIsPageSize : 1; bool mWindowDraggingAllowed : 1; bool mInFrameSwap : 1; - // Because scriptability depends on the mAllowJavascript values of our - // ancestors, we cache the effective scriptability and recompute it when - // it might have changed; - bool mCanExecuteScripts : 1; - // This boolean is set to true right before we fire pagehide and generally // unset when we embed a new content viewer. While it's true no navigation // is allowed in this docshell. bool mFiredUnloadEvent : 1; // this flag is for bug #21358. a docshell may load many urls // which don't result in new documents being created (i.e. a new // content viewer) we want to make sure we don't call a on load
--- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -188,21 +188,16 @@ interface nsIDocShell : nsIDocShellTreeI attribute boolean cssErrorReportingEnabled; /** * Whether to allow plugin execution */ attribute boolean allowPlugins; /** - * Whether to allow Javascript execution - */ - attribute boolean allowJavascript; - - /** * Attribute stating if refresh based redirects can be allowed */ attribute boolean allowMetaRedirects; /** * Attribute stating if it should allow subframes (framesets/iframes) or not */ attribute boolean allowSubframes; @@ -464,23 +459,16 @@ interface nsIDocShell : nsIDocShellTreeI /** * Propagated to the print preview document viewer. Must only be called on * a document viewer that has been initialized for print preview. */ void exitPrintPreview(); /** - * Whether this docshell can execute scripts based on its hierarchy. - * The rule of thumb here is that we disable js if this docshell or any - * of its parents disallow scripting. - */ - [infallible] readonly attribute boolean canExecuteScripts; - - /** * The ID of the docshell in the session history. */ readonly attribute nsIDRef historyID; /** * Helper method for accessing this value from C++ */ [noscript, notxpcom] nsIDRef HistoryID();
new file mode 100644 --- /dev/null +++ b/docshell/test/unit/AllowJavascriptChild.jsm @@ -0,0 +1,44 @@ +"use strict"; +var EXPORTED_SYMBOLS = ["AllowJavascriptChild"]; + +class AllowJavascriptChild extends JSWindowActorChild { + async receiveMessage(msg) { + switch (msg.name) { + case "CheckScriptsAllowed": + return this.checkScriptsAllowed(); + case "CheckFiredLoadEvent": + return this.contentWindow.wrappedJSObject.gFiredOnload; + case "CreateIframe": + return this.createIframe(msg.data.url); + } + return null; + } + + handleEvent(event) { + if (event.type === "load") { + this.sendAsyncMessage("LoadFired"); + } + } + + checkScriptsAllowed() { + let win = this.contentWindow; + + win.wrappedJSObject.gFiredOnclick = false; + win.document.body.click(); + return win.wrappedJSObject.gFiredOnclick; + } + + async createIframe(url) { + let doc = this.contentWindow.document; + + let iframe = doc.createElement("iframe"); + iframe.src = url; + doc.body.appendChild(iframe); + + await new Promise(resolve => { + iframe.addEventListener("load", resolve, { once: true }); + }); + + return iframe.browsingContext; + } +}
new file mode 100644 --- /dev/null +++ b/docshell/test/unit/AllowJavascriptParent.jsm @@ -0,0 +1,31 @@ +"use strict"; +var EXPORTED_SYMBOLS = ["AllowJavascriptParent"]; + +let loadPromises = new WeakMap(); + +class AllowJavascriptParent extends JSWindowActorParent { + async receiveMessage(msg) { + switch (msg.name) { + case "LoadFired": + let bc = this.browsingContext; + let deferred = loadPromises.get(bc); + if (deferred) { + loadPromises.delete(bc); + deferred.resolve(this); + } + break; + } + } + + static promiseLoad(bc) { + let deferred = loadPromises.get(bc); + if (!deferred) { + deferred = {}; + deferred.promise = new Promise(resolve => { + deferred.resolve = resolve; + }); + loadPromises.set(bc, deferred); + } + return deferred.promise; + } +}
new file mode 100644 --- /dev/null +++ b/docshell/test/unit/test_allowJavascript.js @@ -0,0 +1,269 @@ +"use strict"; + +const { XPCShellContentUtils } = ChromeUtils.import( + "resource://testing-common/XPCShellContentUtils.jsm" +); + +XPCShellContentUtils.init(this); + +const ACTOR = "AllowJavascript"; + +const HTML = String.raw`<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <script type="application/javascript"> + "use strict"; + var gFiredOnload = false; + var gFiredOnclick = false; + </script> +</head> +<body onload="gFiredOnload = true;" onclick="gFiredOnclick = true;"> +</body> +</html>`; + +const server = XPCShellContentUtils.createHttpServer({ + hosts: ["example.com", "example.org"], +}); + +server.registerPathHandler("/", (request, response) => { + response.setHeader("Content-Type", "text/html"); + response.write(HTML); +}); + +function getResourceURI(file) { + return Services.io.newFileURI(do_get_file(file)).spec; +} + +const { AllowJavascriptParent } = ChromeUtils.import( + getResourceURI("AllowJavascriptParent.jsm") +); + +async function assertScriptsAllowed(bc, expectAllowed, desc) { + let actor = bc.currentWindowGlobal.getActor(ACTOR); + let allowed = await actor.sendQuery("CheckScriptsAllowed"); + equal( + allowed, + expectAllowed, + `Scripts should be ${expectAllowed ? "" : "dis"}allowed for ${desc}` + ); +} + +async function assertLoadFired(bc, expectFired, desc) { + let actor = bc.currentWindowGlobal.getActor(ACTOR); + let fired = await actor.sendQuery("CheckFiredLoadEvent"); + equal( + fired, + expectFired, + `Should ${expectFired ? "" : "not "}have fired load for ${desc}` + ); +} + +function createSubframe(bc, url) { + let actor = bc.currentWindowGlobal.getActor(ACTOR); + return actor.sendQuery("CreateIframe", { url }); +} + +add_task(async function() { + ChromeUtils.registerWindowActor(ACTOR, { + allFrames: true, + child: { + moduleURI: getResourceURI("AllowJavascriptChild.jsm"), + events: { load: { capture: true } }, + }, + parent: { + moduleURI: getResourceURI("AllowJavascriptParent.jsm"), + }, + }); + + let page = await XPCShellContentUtils.loadContentPage("http://example.com/", { + remote: true, + remoteSubframes: true, + }); + + let bc = page.browsingContext; + + { + let oopFrame1 = await createSubframe(bc, "http://example.org/"); + let inprocFrame1 = await createSubframe(bc, "http://example.com/"); + + let oopFrame1OopSub = await createSubframe( + oopFrame1, + "http://example.com/" + ); + let inprocFrame1OopSub = await createSubframe( + inprocFrame1, + "http://example.org/" + ); + + equal( + oopFrame1.allowJavascript, + true, + "OOP BC should inherit allowJavascript from parent" + ); + equal( + inprocFrame1.allowJavascript, + true, + "In-process BC should inherit allowJavascript from parent" + ); + equal( + oopFrame1OopSub.allowJavascript, + true, + "OOP BC child should inherit allowJavascript from parent" + ); + equal( + inprocFrame1OopSub.allowJavascript, + true, + "In-process child BC should inherit allowJavascript from parent" + ); + + await assertLoadFired(bc, true, "top BC"); + await assertScriptsAllowed(bc, true, "top BC"); + + await assertLoadFired(oopFrame1, true, "OOP frame 1"); + await assertScriptsAllowed(oopFrame1, true, "OOP frame 1"); + + await assertLoadFired(inprocFrame1, true, "In-process frame 1"); + await assertScriptsAllowed(inprocFrame1, true, "In-process frame 1"); + + await assertLoadFired(oopFrame1OopSub, true, "OOP frame 1 subframe"); + await assertScriptsAllowed(oopFrame1OopSub, true, "OOP frame 1 subframe"); + + await assertLoadFired( + inprocFrame1OopSub, + true, + "In-process frame 1 subframe" + ); + await assertScriptsAllowed( + inprocFrame1OopSub, + true, + "In-process frame 1 subframe" + ); + + bc.allowJavascript = false; + await assertScriptsAllowed(bc, false, "top BC with scripts disallowed"); + await assertScriptsAllowed( + oopFrame1, + false, + "OOP frame 1 with top BC with scripts disallowed" + ); + await assertScriptsAllowed( + inprocFrame1, + false, + "In-process frame 1 with top BC with scripts disallowed" + ); + await assertScriptsAllowed( + oopFrame1OopSub, + false, + "OOP frame 1 subframe with top BC with scripts disallowed" + ); + await assertScriptsAllowed( + inprocFrame1OopSub, + false, + "In-process frame 1 subframe with top BC with scripts disallowed" + ); + + let oopFrame2 = await createSubframe(bc, "http://example.org/"); + let inprocFrame2 = await createSubframe(bc, "http://example.com/"); + + equal( + oopFrame2.allowJavascript, + false, + "OOP BC 2 should inherit allowJavascript from parent" + ); + equal( + inprocFrame2.allowJavascript, + false, + "In-process BC 2 should inherit allowJavascript from parent" + ); + + await assertLoadFired( + oopFrame2, + undefined, + "OOP frame 2 with top BC with scripts disallowed" + ); + await assertScriptsAllowed( + oopFrame2, + false, + "OOP frame 2 with top BC with scripts disallowed" + ); + await assertLoadFired( + inprocFrame2, + undefined, + "In-process frame 2 with top BC with scripts disallowed" + ); + await assertScriptsAllowed( + inprocFrame2, + false, + "In-process frame 2 with top BC with scripts disallowed" + ); + + bc.allowJavascript = true; + await assertScriptsAllowed(bc, true, "top BC"); + + await assertScriptsAllowed(oopFrame1, true, "OOP frame 1"); + await assertScriptsAllowed(inprocFrame1, true, "In-process frame 1"); + await assertScriptsAllowed(oopFrame1OopSub, true, "OOP frame 1 subframe"); + await assertScriptsAllowed( + inprocFrame1OopSub, + true, + "In-process frame 1 subframe" + ); + + await assertScriptsAllowed(oopFrame2, false, "OOP frame 2"); + await assertScriptsAllowed(inprocFrame2, false, "In-process frame 2"); + + oopFrame1.currentWindowGlobal.allowJavascript = false; + inprocFrame1.currentWindowGlobal.allowJavascript = false; + + await assertScriptsAllowed( + oopFrame1, + false, + "OOP frame 1 with second level WC scripts disallowed" + ); + await assertScriptsAllowed( + inprocFrame1, + false, + "In-process frame 1 with second level WC scripts disallowed" + ); + await assertScriptsAllowed( + oopFrame1OopSub, + false, + "OOP frame 1 subframe second level WC scripts disallowed" + ); + await assertScriptsAllowed( + inprocFrame1OopSub, + false, + "In-process frame 1 subframe with second level WC scripts disallowed" + ); + + oopFrame1.reload(0); + inprocFrame1.reload(0); + await AllowJavascriptParent.promiseLoad(oopFrame1); + await AllowJavascriptParent.promiseLoad(inprocFrame1); + + equal( + oopFrame1.currentWindowGlobal.allowJavascript, + true, + "WindowContext.allowJavascript does not persist after navigation for OOP frame 1" + ); + equal( + inprocFrame1.currentWindowGlobal.allowJavascript, + true, + "WindowContext.allowJavascript does not persist after navigation for in-process frame 1" + ); + + await assertScriptsAllowed(oopFrame1, true, "OOP frame 1"); + await assertScriptsAllowed(inprocFrame1, true, "In-process frame 1"); + } + + bc.allowJavascript = false; + + bc.reload(0); + await AllowJavascriptParent.promiseLoad(bc); + + await assertLoadFired(bc, undefined, "top BC with scripts disabled"); + await assertScriptsAllowed(bc, false, "top BC with scripts disabled"); + + await page.close(); +});
--- a/docshell/test/unit/xpcshell.ini +++ b/docshell/test/unit/xpcshell.ini @@ -1,11 +1,16 @@ [DEFAULT] head = head_docshell.js +[test_allowJavascript.js] +skip-if = os == 'android' +support-files = + AllowJavascriptChild.jsm + AllowJavascriptParent.jsm [test_bug442584.js] [test_browsing_context_structured_clone.js] [test_URIFixup.js] # Disabled for 1563343 -- URI fixup should be done at the app level in GV. skip-if = os == 'android' [test_URIFixup_search.js] skip-if = os == 'android' [test_URIFixup_info.js]
--- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp @@ -1842,17 +1842,17 @@ NS_DEFINE_STATIC_IID_ACCESSOR(WindowStat WindowStateHolder::WindowStateHolder(nsGlobalWindowInner* aWindow) : mInnerWindow(aWindow), mInnerWindowReflector(RootingCx(), aWindow->GetWrapper()) { MOZ_ASSERT(aWindow, "null window"); aWindow->Suspend(); // When a global goes into the bfcache, we disable script. - xpc::Scriptability::Get(mInnerWindowReflector).SetDocShellAllowsScript(false); + xpc::Scriptability::Get(mInnerWindowReflector).SetWindowAllowsScript(false); } WindowStateHolder::~WindowStateHolder() { if (mInnerWindow) { // This window was left in the bfcache and is now going away. We need to // free it up. // Note that FreeInnerObjects may already have been called on the // inner window if its outer has already had SetDocShell(null) @@ -2326,20 +2326,21 @@ nsresult nsGlobalWindowOuter::SetNewDocu JSAutoRealm ar(cx, GetWrapperPreserveColor()); { JS::Rooted<JSObject*> outer(cx, GetWrapperPreserveColor()); js::SetWindowProxy(cx, newInnerGlobal, outer); mBrowsingContext->SetWindowProxy(outer); } - // Set scriptability based on the state of the docshell. - bool allow = GetDocShell()->GetCanExecuteScripts(); + // Set scriptability based on the state of the WindowContext. + WindowContext* wc = mInnerWindow->GetWindowContext(); + bool allow = wc ? wc->CanExecuteScripts() : mBrowsingContext->CanExecuteScripts(); xpc::Scriptability::Get(GetWrapperPreserveColor()) - .SetDocShellAllowsScript(allow); + .SetWindowAllowsScript(allow); if (!aState) { // Get the "window" property once so it will be cached on our inner. We // have to do this here, not in binding code, because this has to happen // after we've created the outer window proxy and stashed it in the outer // nsGlobalWindowOuter, so GetWrapperPreserveColor() on that outer // nsGlobalWindowOuter doesn't return null and // nsGlobalWindowOuter::OuterObject works correctly.
--- a/dom/chrome-webidl/BrowsingContext.webidl +++ b/dom/chrome-webidl/BrowsingContext.webidl @@ -189,16 +189,26 @@ interface BrowsingContext { /** * This allows chrome to override the default choice of whether touch events * are available in a specific BrowsingContext and its descendents. */ readonly attribute TouchEventsOverride touchEventsOverride; /** + * Partially determines whether script execution is allowed in this + * BrowsingContext. Script execution will be permitted only if this + * attribute is true and script execution is allowed in the parent + * WindowContext. + * + * May only be set in the parent process. + */ + [SetterThrows] attribute boolean allowJavascript; + + /** * The nsID of the browsing context in the session history. */ [NewObject, Throws] readonly attribute any historyID; readonly attribute ChildSHistory? childSessionHistory; // Resets the location change rate limit. Used for testing.
--- a/dom/chrome-webidl/WindowGlobalActors.webidl +++ b/dom/chrome-webidl/WindowGlobalActors.webidl @@ -24,16 +24,26 @@ interface WindowContext { readonly attribute boolean hasBeforeUnload; // True if the principal of this window is for a local ip address. readonly attribute boolean isLocalIP; // True if the corresponding document has `loading='lazy'` images; // It won't become false if the image becomes non-lazy. readonly attribute boolean hadLazyLoadImage; + + /** + * Partially determines whether script execution is allowed in this + * BrowsingContext. Script execution will be permitted only if this + * attribute is true and script execution is allowed in the owner + * BrowsingContext. + * + * May only be set in the context's owning process. + */ + [SetterThrows] attribute boolean allowJavascript; }; // Keep this in sync with nsIContentViewer::PermitUnloadAction. enum PermitUnloadAction { "prompt", "dontUnload", "unload", };
--- a/dom/ipc/WindowGlobalActor.cpp +++ b/dom/ipc/WindowGlobalActor.cpp @@ -51,16 +51,17 @@ WindowGlobalInit WindowGlobalActor::Base ctx.mOuterWindowId = aOuterWindowId; ctx.mBrowsingContextId = aBrowsingContext->Id(); // If any synced fields need to be initialized from our BrowsingContext, we // can initialize them here. auto& fields = ctx.mFields; fields.mEmbedderPolicy = InheritedPolicy(aBrowsingContext); fields.mAutoplayPermission = nsIPermissionManager::UNKNOWN_ACTION; + fields.mAllowJavascript = true; return init; } WindowGlobalInit WindowGlobalActor::AboutBlankInitializer( dom::BrowsingContext* aBrowsingContext, nsIPrincipal* aPrincipal) { WindowGlobalInit init = BaseInitializer(aBrowsingContext, nsContentUtils::GenerateWindowId(), nsContentUtils::GenerateWindowId());
--- a/editor/composer/nsEditingSession.cpp +++ b/editor/composer/nsEditingSession.cpp @@ -37,16 +37,17 @@ #include "nsIWebProgress.h" // for nsIWebProgress, etc #include "nsLiteralString.h" // for NS_LITERAL_STRING #include "nsPIDOMWindow.h" // for nsPIDOMWindow #include "nsPresContext.h" // for nsPresContext #include "nsReadableUtils.h" // for AppendUTF16toUTF8 #include "nsStringFwd.h" // for nsString #include "mozilla/dom/BrowsingContext.h" // for BrowsingContext #include "mozilla/dom/Selection.h" // for AutoHideSelectionChanges, etc +#include "mozilla/dom/WindowContext.h" // for WindowContext #include "nsFrameSelection.h" // for nsFrameSelection #include "nsBaseCommandController.h" // for nsBaseCommandController #include "mozilla/dom/LoadURIOptionsBinding.h" class nsISupports; class nsIURI; using namespace mozilla; @@ -115,17 +116,17 @@ nsEditingSession::MakeWindowEditable(moz NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); mDocShell = do_GetWeakReference(docShell); mInteractive = aInteractive; mMakeWholeDocumentEditable = aMakeWholeDocumentEditable; nsresult rv; if (!mInteractive) { - rv = DisableJSAndPlugins(*docShell); + rv = DisableJSAndPlugins(window->GetCurrentInnerWindow()); NS_ENSURE_SUCCESS(rv, rv); } // Always remove existing editor TearDownEditorOnWindow(aWindow); // Tells embedder that startup is in progress mEditorStatus = eEditorCreationInProgress; @@ -167,59 +168,54 @@ nsEditingSession::MakeWindowEditable(moz // it IS ok to destroy current editor if (NS_FAILED(rv)) { TearDownEditorOnWindow(aWindow); } } return rv; } -nsresult nsEditingSession::DisableJSAndPlugins(nsIDocShell& aDocShell) { - bool tmp; - nsresult rv = aDocShell.GetAllowJavascript(&tmp); - NS_ENSURE_SUCCESS(rv, rv); +nsresult nsEditingSession::DisableJSAndPlugins(nsPIDOMWindowInner* aWindow) { + WindowContext* wc = aWindow->GetWindowContext(); + BrowsingContext* bc = wc->GetBrowsingContext(); - mScriptsEnabled = tmp; + mScriptsEnabled = wc->GetAllowJavascript(); - rv = aDocShell.SetAllowJavascript(false); - NS_ENSURE_SUCCESS(rv, rv); + MOZ_TRY(wc->SetAllowJavascript(false)); // Disable plugins in this document: - mPluginsEnabled = aDocShell.PluginsAllowedInCurrentDoc(); + mPluginsEnabled = bc->GetAllowPlugins(); - rv = aDocShell.GetBrowsingContext()->SetAllowPlugins(false); - NS_ENSURE_SUCCESS(rv, rv); + MOZ_TRY(bc->SetAllowPlugins(false)); mDisabledJSAndPlugins = true; return NS_OK; } -nsresult nsEditingSession::RestoreJSAndPlugins(nsPIDOMWindowOuter* aWindow) { +nsresult nsEditingSession::RestoreJSAndPlugins(nsPIDOMWindowInner* aWindow) { if (!mDisabledJSAndPlugins) { return NS_OK; } mDisabledJSAndPlugins = false; if (NS_WARN_IF(!aWindow)) { // DetachFromWindow may call this method with nullptr. return NS_ERROR_FAILURE; } - nsIDocShell* docShell = aWindow->GetDocShell(); - NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); - nsresult rv = docShell->SetAllowJavascript(mScriptsEnabled); - NS_ENSURE_SUCCESS(rv, rv); + WindowContext* wc = aWindow->GetWindowContext(); + BrowsingContext* bc = wc->GetBrowsingContext(); + + MOZ_TRY(wc->SetAllowJavascript(mScriptsEnabled)); // Disable plugins in this document: - auto* browsingContext = aWindow->GetBrowsingContext(); - NS_ENSURE_TRUE(browsingContext, NS_ERROR_FAILURE); - return browsingContext->SetAllowPlugins(mPluginsEnabled); + return bc->SetAllowPlugins(mPluginsEnabled); } /*--------------------------------------------------------------------------- WindowIsEditable boolean windowIsEditable (in nsIDOMWindow aWindow); ----------------------------------------------------------------------------*/ @@ -505,17 +501,17 @@ nsEditingSession::TearDownEditorOnWindow // Null out the editor on the docShell to trigger PreDestroy which // needs to happen before document state listeners are removed below. docShell->SetEditor(nullptr); RemoveListenersAndControllers(window, htmlEditor); if (stopEditing) { // Make things the way they were before we started editing. - RestoreJSAndPlugins(window); + RestoreJSAndPlugins(window->GetCurrentInnerWindow()); RestoreAnimationMode(window); if (mMakeWholeDocumentEditable) { doc->SetEditableFlag(false); doc->SetEditingState(Document::EditingState::eOff); } } @@ -1187,17 +1183,17 @@ nsresult nsEditingSession::DetachFromWin mLoadBlankDocTimer->Cancel(); mLoadBlankDocTimer = nullptr; } // Remove controllers, webprogress listener, and otherwise // make things the way they were before we started editing. RemoveEditorControllers(aWindow); RemoveWebProgressListener(aWindow); - RestoreJSAndPlugins(aWindow); + RestoreJSAndPlugins(aWindow->GetCurrentInnerWindow()); RestoreAnimationMode(aWindow); // Kill our weak reference to our original window, in case // it changes on restore, or otherwise dies. mDocShell = nullptr; return NS_OK; } @@ -1214,17 +1210,17 @@ nsresult nsEditingSession::ReattachToWin nsresult rv; nsIDocShell* docShell = aWindow->GetDocShell(); NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); mDocShell = do_GetWeakReference(docShell); // Disable plugins. if (!mInteractive) { - rv = DisableJSAndPlugins(*docShell); + rv = DisableJSAndPlugins(aWindow->GetCurrentInnerWindow()); NS_ENSURE_SUCCESS(rv, rv); } // Tells embedder that startup is in progress. mEditorStatus = eEditorCreationInProgress; // Adds back web progress listener. rv = PrepareForEditing(aWindow);
--- a/editor/composer/nsEditingSession.h +++ b/editor/composer/nsEditingSession.h @@ -26,16 +26,18 @@ class mozIDOMWindowProxy; class nsBaseCommandController; class nsIDOMWindow; class nsISupports; class nsITimer; class nsIChannel; class nsIControllers; class nsIDocShell; class nsIWebProgress; +class nsIPIDOMWindowOuter; +class nsIPIDOMWindowInner; namespace mozilla { class ComposerCommandsUpdater; class HTMLEditor; } // namespace mozilla class nsEditingSession final : public nsIEditingSession, public nsIWebProgressListener, @@ -108,23 +110,23 @@ class nsEditingSession final : public ns void RemoveWebProgressListener(nsPIDOMWindowOuter* aWindow); void RestoreAnimationMode(nsPIDOMWindowOuter* aWindow); void RemoveListenersAndControllers(nsPIDOMWindowOuter* aWindow, mozilla::HTMLEditor* aHTMLEditor); /** * Disable scripts and plugins in aDocShell. */ - nsresult DisableJSAndPlugins(nsIDocShell& aDocShell); + nsresult DisableJSAndPlugins(nsPIDOMWindowInner* aWindow); /** * Restore JS and plugins (enable/disable them) according to the state they * were before the last call to disableJSAndPlugins. */ - nsresult RestoreJSAndPlugins(nsPIDOMWindowOuter* aWindow); + nsresult RestoreJSAndPlugins(nsPIDOMWindowInner* aWindow); protected: bool mDoneSetup; // have we prepared for editing yet? // Used to prevent double creation of editor because nsIWebProgressListener // receives a STATE_STOP notification before the STATE_START // for our document, so we wait for the STATE_START, then STATE_STOP // before creating an editor
--- a/editor/composer/test/test_bug519928.html +++ b/editor/composer/test/test_bug519928.html @@ -17,17 +17,17 @@ https://bugzilla.mozilla.org/show_bug.cg <pre id="test"> <script class="testbody" type="text/javascript"> var iframe = document.getElementById("load-frame"); function enableJS() { allowJS(true, iframe); } function disableJS() { allowJS(false, iframe); } function allowJS(allow, frame) { - SpecialPowers.wrap(frame.contentWindow).docShell.allowJavascript = allow; + SpecialPowers.wrap(frame.contentWindow).windowGlobalChild.windowContext.allowJavascript = allow; } function expectJSAllowed(allowed, testCondition, callback) { window.ICanRunMyJS = false; var self_ = window; testCondition(); var doc = iframe.contentDocument;
--- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -451,17 +451,17 @@ void NukeJSStackFrames(JS::Realm* aRealm return; } realmPrivate->NukeJSStackFrames(); } Scriptability::Scriptability(JS::Realm* realm) : mScriptBlocks(0), - mDocShellAllowsScript(true), + mWindowAllowsScript(true), mScriptBlockedByPolicy(false) { nsIPrincipal* prin = nsJSPrincipals::get(JS::GetRealmPrincipals(realm)); mImmuneToScriptPolicy = PrincipalImmuneToScriptPolicy(prin); if (mImmuneToScriptPolicy) { return; } // If we're not immune, we should have a real principal with a URI. @@ -472,30 +472,30 @@ Scriptability::Scriptability(JS::Realm* mScriptBlockedByPolicy = !policyAllows; return; } // Something went wrong - be safe and block script. mScriptBlockedByPolicy = true; } bool Scriptability::Allowed() { - return mDocShellAllowsScript && !mScriptBlockedByPolicy && mScriptBlocks == 0; + return mWindowAllowsScript && !mScriptBlockedByPolicy && mScriptBlocks == 0; } bool Scriptability::IsImmuneToScriptPolicy() { return mImmuneToScriptPolicy; } void Scriptability::Block() { ++mScriptBlocks; } void Scriptability::Unblock() { MOZ_ASSERT(mScriptBlocks > 0); --mScriptBlocks; } -void Scriptability::SetDocShellAllowsScript(bool aAllowed) { - mDocShellAllowsScript = aAllowed || mImmuneToScriptPolicy; +void Scriptability::SetWindowAllowsScript(bool aAllowed) { + mWindowAllowsScript = aAllowed || mImmuneToScriptPolicy; } /* static */ Scriptability& Scriptability::Get(JSObject* aScope) { return RealmPrivate::Get(aScope)->scriptability; } bool IsUAWidgetCompartment(JS::Compartment* compartment) {
--- a/js/xpconnect/src/xpcpublic.h +++ b/js/xpconnect/src/xpcpublic.h @@ -76,30 +76,30 @@ namespace xpc { class Scriptability { public: explicit Scriptability(JS::Realm* realm); bool Allowed(); bool IsImmuneToScriptPolicy(); void Block(); void Unblock(); - void SetDocShellAllowsScript(bool aAllowed); + void SetWindowAllowsScript(bool aAllowed); static Scriptability& Get(JSObject* aScope); private: // Whenever a consumer wishes to prevent script from running on a global, // it increments this value with a call to Block(). When it wishes to // re-enable it (if ever), it decrements this value with a call to Unblock(). // Script may not run if this value is non-zero. uint32_t mScriptBlocks; - // Whether the docshell allows javascript in this scope. If this scope - // doesn't have a docshell, this value is always true. - bool mDocShellAllowsScript; + // Whether the DOM window allows javascript in this scope. If this scope + // doesn't have a window, this value is always true. + bool mWindowAllowsScript; // Whether this scope is immune to user-defined or addon-defined script // policy. bool mImmuneToScriptPolicy; // Whether the new-style domain policy when this compartment was created // forbids script execution. bool mScriptBlockedByPolicy;
--- a/mobile/android/actors/GeckoViewSettingsChild.jsm +++ b/mobile/android/actors/GeckoViewSettingsChild.jsm @@ -23,34 +23,25 @@ class GeckoViewSettingsChild extends Gec receiveMessage(message) { const { name } = message; debug`receiveMessage: ${name}`; switch (name) { case "SettingsUpdate": { const settings = message.data; - this.allowJavascript = settings.allowJavascript; this.viewportMode = settings.viewportMode; if (settings.isPopup) { // Allow web extensions to close their own action popups (bz1612363) this.contentWindow.windowUtils.allowScriptsToClose(); } } } } - get allowJavascript() { - return this.docShell.allowJavascript; - } - - set allowJavascript(aAllowJavascript) { - this.docShell.allowJavascript = aAllowJavascript; - } - set viewportMode(aMode) { const { windowUtils } = this.contentWindow; if (aMode === windowUtils.desktopModeViewport) { return; } windowUtils.desktopModeViewport = aMode === VIEWPORT_MODE_DESKTOP; } }
--- a/mobile/android/modules/geckoview/GeckoViewSettings.jsm +++ b/mobile/android/modules/geckoview/GeckoViewSettings.jsm @@ -83,16 +83,24 @@ class GeckoViewSettings extends GeckoVie // When the page is loading from the main process (e.g. from an extension // page) we won't be able to query the actor here. this.getActor("GeckoViewSettings")?.sendAsyncMessage( "SettingsUpdate", settings ); } + get allowJavascript() { + return this.browsingContext.allowJavascript; + } + + set allowJavascript(aAllowJavascript) { + this.browsingContext.allowJavascript = aAllowJavascript; + } + get customUserAgent() { if (this.userAgentOverride !== null) { return this.userAgentOverride; } if (this.userAgentMode === USER_AGENT_MODE_DESKTOP) { return DESKTOP_USER_AGENT; } if (this.userAgentMode === USER_AGENT_MODE_VR) {
--- a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension.html +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension.html @@ -106,17 +106,17 @@ async function starttest(){ .createInstance(SpecialPowers.Ci.nsIURIMutator) .setSpec("http://www.foobar.org/") .finalize(); is(testURI.spec, "http://www.foobar.org/", "Getters/Setters should work correctly"); is(SpecialPowers.wrap(document).getElementsByTagName('details').length, 0, "Should work with proxy-based DOM bindings."); // Play with the window object. var docShell = SpecialPowers.wrap(window).docShell; - ok(docShell.allowJavascript, "Able to pull properties off of docshell!"); + ok(docShell.browsingContext, "Able to pull properties off of docshell!"); // Make sure Xray-wrapped functions work. try { SpecialPowers.wrap(SpecialPowers.Components).ID('{00000000-0000-0000-0000-000000000000}'); ok(true, "Didn't throw"); } catch (e) { ok(false, "Threw while trying to call Xray-wrapped function.");
--- a/testing/modules/XPCShellContentUtils.jsm +++ b/testing/modules/XPCShellContentUtils.jsm @@ -225,16 +225,20 @@ class ContentPage { this.browser = browser; this.loadFrameScript(frameScript); return browser; } + get browsingContext() { + return this.browser.browsingContext; + } + sendMessage(msg, data) { return MessageChannel.sendMessage(this.browser.messageManager, msg, data); } loadFrameScript(func) { let frameScript = `data:text/javascript,(${encodeURI(func)}).call(this)`; this.browser.messageManager.loadFrameScript(frameScript, true, true); }
--- a/toolkit/components/sessionstore/SessionStoreUtils.cpp +++ b/toolkit/components/sessionstore/SessionStoreUtils.cpp @@ -33,16 +33,17 @@ #include "nsContentList.h" #include "nsContentUtils.h" #include "nsFocusManager.h" #include "nsGlobalWindowOuter.h" #include "nsIDocShell.h" #include "nsIFormControl.h" #include "nsIScrollableFrame.h" #include "nsISHistory.h" +#include "nsIXULRuntime.h" #include "nsPresContext.h" #include "nsPrintfCString.h" using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::dom::sessionstore; namespace { @@ -217,32 +218,32 @@ void SessionStoreUtils::CollectDocShellC TRY_ALLOWPROP(ContentRetargetingOnChildren); #undef TRY_ALLOWPROP } /* static */ void SessionStoreUtils::RestoreDocShellCapabilities( nsIDocShell* aDocShell, const nsCString& aDisallowCapabilities) { aDocShell->SetAllowPlugins(true); - aDocShell->SetAllowJavascript(true); aDocShell->SetAllowMetaRedirects(true); aDocShell->SetAllowSubframes(true); aDocShell->SetAllowImages(true); aDocShell->SetAllowMedia(true); aDocShell->SetAllowDNSPrefetch(true); aDocShell->SetAllowWindowControl(true); aDocShell->SetAllowContentRetargeting(true); aDocShell->SetAllowContentRetargetingOnChildren(true); + bool allowJavascript = true; for (const nsACString& token : nsCCharSeparatedTokenizer(aDisallowCapabilities, ',').ToRange()) { if (token.EqualsLiteral("Plugins")) { aDocShell->SetAllowPlugins(false); } else if (token.EqualsLiteral("Javascript")) { - aDocShell->SetAllowJavascript(false); + allowJavascript = false; } else if (token.EqualsLiteral("MetaRedirects")) { aDocShell->SetAllowMetaRedirects(false); } else if (token.EqualsLiteral("Subframes")) { aDocShell->SetAllowSubframes(false); } else if (token.EqualsLiteral("Images")) { aDocShell->SetAllowImages(false); } else if (token.EqualsLiteral("Media")) { aDocShell->SetAllowMedia(false); @@ -256,16 +257,22 @@ void SessionStoreUtils::RestoreDocShellC aDocShell->SetAllowContentRetargeting( false); // will also set AllowContentRetargetingOnChildren aDocShell->SetAllowContentRetargetingOnChildren( allow); // restore the allowProp to original } else if (token.EqualsLiteral("ContentRetargetingOnChildren")) { aDocShell->SetAllowContentRetargetingOnChildren(false); } } + + if (!mozilla::SessionHistoryInParent()) { + // With SessionHistoryInParent, this is set from the parent process. + BrowsingContext* bc = aDocShell->GetBrowsingContext(); + Unused << bc->SetAllowJavascript(allowJavascript); + } } static void CollectCurrentScrollPosition(JSContext* aCx, Document& aDocument, Nullable<CollectedData>& aRetVal) { PresShell* presShell = aDocument.GetPresShell(); if (!presShell) { return; } @@ -1458,16 +1465,26 @@ already_AddRefed<Promise> SessionStoreUt nsCOMPtr<nsIURI> uri; if (!aURL.IsEmpty()) { if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aURL))) { aError.Throw(NS_ERROR_FAILURE); return nullptr; } } + bool allowJavascript = true; + for (const nsACString& token : + nsCCharSeparatedTokenizer(aDocShellCaps, ',').ToRange()) { + if (token.EqualsLiteral("Javascript")) { + allowJavascript = false; + } + } + + Unused << aContext.SetAllowJavascript(allowJavascript); + DocShellRestoreState state = {uri, aDocShellCaps}; // TODO (anny): Investigate removing this roundtrip. wgp->SendRestoreDocShellState(state)->Then( GetMainThreadSerialEventTarget(), __func__, [promise](void) { promise->MaybeResolveWithUndefined(); }, [promise](void) { promise->MaybeRejectWithUndefined(); });