diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index fa96c6b8ac72..381f0ef787c3 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1532,6 +1532,9 @@ pref("browser.contentblocking.allowlist.storage.enabled", true); pref("dom.storage_access.enabled", true); #endif +pref("dom.storage_access.auto_grants", true); +pref("dom.storage_access.max_concurrent_auto_grants", 5); + // Define a set of default features for the Content Blocking UI. pref("browser.contentblocking.trackingprotection.control-center.ui.enabled", true); pref("browser.contentblocking.rejecttrackers.control-center.ui.enabled", true); @@ -1795,3 +1798,5 @@ pref("prio.enabled", true); pref("browser.discovery.enabled", false); pref("browser.discovery.containers.enabled", true); pref("browser.discovery.sites", "addons.mozilla.org"); + +pref("browser.engagement.recent_visited_origins.expiry", 86400); // 24 * 60 * 60 (24 hours in seconds) diff --git a/browser/modules/BrowserUsageTelemetry.jsm b/browser/modules/BrowserUsageTelemetry.jsm index 9044ac5629de..6e8ef1edf47c 100644 --- a/browser/modules/BrowserUsageTelemetry.jsm +++ b/browser/modules/BrowserUsageTelemetry.jsm @@ -7,6 +7,7 @@ var EXPORTED_SYMBOLS = [ "BrowserUsageTelemetry", + "URICountListener", "URLBAR_SELECTED_RESULT_TYPES", "URLBAR_SELECTED_RESULT_METHODS", "MINIMUM_TAB_COUNT_INTERVAL_MS", @@ -18,8 +19,14 @@ XPCOMUtils.defineLazyModuleGetters(this, { PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", SearchTelemetry: "resource:///modules/SearchTelemetry.jsm", Services: "resource://gre/modules/Services.jsm", + setTimeout: "resource://gre/modules/Timer.jsm", }); +// This pref is in seconds! +XPCOMUtils.defineLazyPreferenceGetter(this, + "gRecentVisitedOriginsExpiry", + "browser.engagement.recent_visited_origins.expiry"); + // The upper bound for the count of the visited unique domain names. const MAX_UNIQUE_VISITED_DOMAINS = 100; @@ -128,6 +135,8 @@ function shouldRecordSearchCount(tabbrowser) { let URICountListener = { // A set containing the visited domains, see bug 1271310. _domainSet: new Set(), + // A set containing the visited origins during the last 24 hours (similar to domains, but not quite the same) + _origin24hrSet: new Set(), // A map to keep track of the URIs loaded from the restored tabs. _restoredURIsMap: new WeakMap(), @@ -230,13 +239,26 @@ let URICountListener = { // Unique domains should be aggregated by (eTLD + 1): x.test.com and y.test.com // are counted once as test.com. + let baseDomain; try { // Even if only considering http(s) URIs, |getBaseDomain| could still throw // due to the URI containing invalid characters or the domain actually being // an ipv4 or ipv6 address. - this._domainSet.add(Services.eTLD.getBaseDomain(uri)); + baseDomain = Services.eTLD.getBaseDomain(uri); + this._domainSet.add(baseDomain); } catch (e) { - return; + baseDomain = uri.host; + } + + // Record the origin, but with the base domain (eTLD + 1). + let baseDomainURI = uri.mutate() + .setHost(baseDomain) + .finalize(); + this._origin24hrSet.add(baseDomainURI.prePath); + if (gRecentVisitedOriginsExpiry) { + setTimeout(() => { + this._origin24hrSet.delete(baseDomainURI.prePath); + }, gRecentVisitedOriginsExpiry * 1000); } Services.telemetry.scalarSet(UNIQUE_DOMAINS_COUNT_SCALAR_NAME, this._domainSet.size); @@ -249,6 +271,21 @@ let URICountListener = { this._domainSet.clear(); }, + /** + * Returns the number of unique origins visited in this session during the + * last 24 hours. + */ + get uniqueOriginsVisitedInPast24Hours() { + return this._origin24hrSet.size; + }, + + /** + * Resets the number of unique origins visited in this session. + */ + resetUniqueOriginsVisitedInPast24Hours() { + this._origin24hrSet.clear(); + }, + QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]), }; diff --git a/browser/modules/PermissionUI.jsm b/browser/modules/PermissionUI.jsm index b84d02fb2970..e657541bbea8 100644 --- a/browser/modules/PermissionUI.jsm +++ b/browser/modules/PermissionUI.jsm @@ -68,6 +68,8 @@ ChromeUtils.defineModuleGetter(this, "SitePermissions", "resource:///modules/SitePermissions.jsm"); ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"); +ChromeUtils.defineModuleGetter(this, "URICountListener", + "resource:///modules/BrowserUsageTelemetry.jsm"); XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() { return Services.strings @@ -253,8 +255,12 @@ var PermissionPromptPrototype = { * be called just before. Subclasses may want to override this * in order to, for example, bump a counter Telemetry probe for * how often a particular permission request is seen. + * + * If this returns false, it cancels the process of showing the prompt. In + * that case, it is the responsibility of the onBeforeShow() implementation + * to ensure that allow() or cancel() are called on the object appropriately. */ - onBeforeShow() {}, + onBeforeShow() { return true; }, /** * If the prompt was shown to the user, this callback will be called just @@ -440,14 +446,15 @@ var PermissionPromptPrototype = { return false; }; - this.onBeforeShow(); - chromeWin.PopupNotifications.show(this.browser, - this.notificationID, - this.message, - this.anchorID, - mainAction, - secondaryActions, - options); + if (this.onBeforeShow() !== false) { + chromeWin.PopupNotifications.show(this.browser, + this.notificationID, + this.message, + this.anchorID, + mainAction, + secondaryActions, + options); + } }, }; @@ -590,6 +597,7 @@ GeolocationPermissionPrompt.prototype = { let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI"); const SHOW_REQUEST = Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST; secHistogram.add(SHOW_REQUEST); + return true; }, }; @@ -826,9 +834,6 @@ MIDIPermissionPrompt.prototype = { action: Ci.nsIPermissionManager.DENY_ACTION, }]; }, - - onBeforeShow() { - }, }; PermissionUI.MIDIPermissionPrompt = MIDIPermissionPrompt; @@ -911,6 +916,7 @@ AutoplayPermissionPrompt.prototype = { }; this.browser.addEventListener( "DOMAudioPlaybackStarted", this.handlePlaybackStart); + return true; }, }; @@ -918,6 +924,11 @@ PermissionUI.AutoplayPermissionPrompt = AutoplayPermissionPrompt; function StorageAccessPermissionPrompt(request) { this.request = request; + + XPCOMUtils.defineLazyPreferenceGetter(this, "_autoGrants", + "dom.storage_access.auto_grants"); + XPCOMUtils.defineLazyPreferenceGetter(this, "_maxConcurrentAutoGrants", + "dom.storage_access.max_concurrent_auto_grants"); } StorageAccessPermissionPrompt.prototype = { @@ -1008,6 +1019,38 @@ StorageAccessPermissionPrompt.prototype = { get topLevelPrincipal() { return this.request.topLevelPrincipal; }, + + get maxConcurrentAutomaticGrants() { + // one percent of the number of top-levels origins visited in the current + // session (but not to exceed 24 hours), or the value of the + // dom.storage_access.max_concurrent_auto_grants preference, whichever is + // higher. + return Math.max(Math.max(Math.floor(URICountListener.uniqueOriginsVisitedInPast24Hours / 100), + this._maxConcurrentAutoGrants), 0); + }, + + getOriginsThirdPartyHasAccessTo(thirdPartyOrigin) { + let prefix = `3rdPartyStorage^${thirdPartyOrigin}`; + let perms = Services.perms.getAllWithTypePrefix(prefix); + let origins = new Set(); + while (perms.length) { + let perm = perms.shift(); + origins.add(perm.principal.origin); + } + return origins.size; + }, + + onBeforeShow() { + let thirdPartyOrigin = this.request.principal.origin; + if (this._autoGrants && + this.getOriginsThirdPartyHasAccessTo(thirdPartyOrigin) < + this.maxConcurrentAutomaticGrants) { + // Automatically accept the prompt + this.allow({"storage-access": "allow-auto-grant"}); + return false; + } + return true; + }, }; PermissionUI.StorageAccessPermissionPrompt = StorageAccessPermissionPrompt; diff --git a/browser/modules/test/browser/browser.ini b/browser/modules/test/browser/browser.ini index 96f9bdbd496e..0ce6bac65c8a 100644 --- a/browser/modules/test/browser/browser.ini +++ b/browser/modules/test/browser/browser.ini @@ -39,6 +39,7 @@ run-if = crashreporter [browser_UsageTelemetry_domains.js] [browser_UsageTelemetry_private_and_restore.js] skip-if = verify && debug +[browser_UsageTelemetry_uniqueOriginsVisitedInPast24Hours.js] [browser_UsageTelemetry_urlbar.js] support-files = usageTelemetrySearchSuggestions.sjs diff --git a/browser/modules/test/browser/browser_PermissionUI.js b/browser/modules/test/browser/browser_PermissionUI.js index 2bf9872ffe3b..32fc682086be 100644 --- a/browser/modules/test/browser/browser_PermissionUI.js +++ b/browser/modules/test/browser/browser_PermissionUI.js @@ -294,6 +294,7 @@ add_task(async function test_on_before_show() { promptActions: [mainAction], onBeforeShow() { beforeShown = true; + return true; }, }; @@ -353,6 +354,7 @@ add_task(async function test_no_request() { promptActions: [mainAction, secondaryAction], onBeforeShow() { beforeShown = true; + return true; }, }; diff --git a/browser/modules/test/browser/browser_PermissionUI_prompts.js b/browser/modules/test/browser/browser_PermissionUI_prompts.js index 7354b8f7f102..9d92f2d9ec1b 100644 --- a/browser/modules/test/browser/browser_PermissionUI_prompts.js +++ b/browser/modules/test/browser/browser_PermissionUI_prompts.js @@ -39,7 +39,9 @@ add_task(async function test_autoplay_permission_prompt() { // Tests that AutoplayPermissionPrompt works as expected add_task(async function test_storage_access_permission_prompt() { + Services.prefs.setBoolPref("dom.storage_access.auto_grants", false); await testPrompt(PermissionUI.StorageAccessPermissionPrompt); + Services.prefs.clearUserPref("dom.storage_access.auto_grants"); }); async function testPrompt(Prompt) { diff --git a/browser/modules/test/browser/browser_UsageTelemetry_uniqueOriginsVisitedInPast24Hours.js b/browser/modules/test/browser/browser_UsageTelemetry_uniqueOriginsVisitedInPast24Hours.js new file mode 100644 index 000000000000..f79e8032f382 --- /dev/null +++ b/browser/modules/test/browser/browser_UsageTelemetry_uniqueOriginsVisitedInPast24Hours.js @@ -0,0 +1,56 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +/* 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"; + +ChromeUtils.defineModuleGetter(this, "URICountListener", + "resource:///modules/BrowserUsageTelemetry.jsm"); + +add_task(async function test_uniqueOriginsVisitedInPast24Hours() { + registerCleanupFunction(async () => { + info("Cleaning up"); + URICountListener.resetUniqueOriginsVisitedInPast24Hours(); + }); + + URICountListener.resetUniqueOriginsVisitedInPast24Hours(); + let startingCount = URICountListener.uniqueOriginsVisitedInPast24Hours; + is(startingCount, 0, "We should have no origins recorded in the history right after resetting"); + + // Add a new window and then some tabs in it. + let win = await BrowserTestUtils.openNewBrowserWindow(); + await BrowserTestUtils.openNewForegroundTab(win.gBrowser, "http://example.com"); + + await BrowserTestUtils.openNewForegroundTab(win.gBrowser, "http://test1.example.com"); + is(URICountListener.uniqueOriginsVisitedInPast24Hours, startingCount + 1, + "test1.example.com should only count as a unique visit if example.com wasn't visited before"); + + // http://www.exämple.test + await BrowserTestUtils.openNewForegroundTab(win.gBrowser, "http://xn--exmple-cua.test"); + is(URICountListener.uniqueOriginsVisitedInPast24Hours, startingCount + 2, + "www.exämple.test should count as a unique visit"); + + // Set the expiry time to 1 second + await SpecialPowers.pushPrefEnv({set: [["browser.engagement.recent_visited_origins.expiry", 1]]}); + + await BrowserTestUtils.openNewForegroundTab(win.gBrowser, "http://127.0.0.1"); + is(URICountListener.uniqueOriginsVisitedInPast24Hours, startingCount + 3, + "127.0.0.1 should count as a unique visit"); + + let countBefore = URICountListener.uniqueOriginsVisitedInPast24Hours; + + await new Promise(resolve => { + setTimeout(_ => { + let countAfter = URICountListener.uniqueOriginsVisitedInPast24Hours; + is(countAfter, countBefore - 1, + "The expiry should work correctly"); + resolve(); + }, 1100); + }); + + BrowserTestUtils.removeTab(win.gBrowser.selectedTab); + BrowserTestUtils.removeTab(win.gBrowser.selectedTab); + await BrowserTestUtils.closeWindow(win); +}); + diff --git a/devtools/client/webconsole/components/message-types/PageError.js b/devtools/client/webconsole/components/message-types/PageError.js index 6fb0e59f9bde..3c5fcf281424 100644 --- a/devtools/client/webconsole/components/message-types/PageError.js +++ b/devtools/client/webconsole/components/message-types/PageError.js @@ -36,6 +36,7 @@ function PageError(props) { } = props; const { id: messageId, + executionPoint, indent, source, type, @@ -58,6 +59,7 @@ function PageError(props) { return Message({ dispatch, messageId, + executionPoint, isPaused, open, collapsible: Array.isArray(stacktrace), diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index 60275261df2f..5af5408782b4 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -22,7 +22,6 @@ #include "mozilla/dom/Text.h" #include "mozilla/gfx/Matrix.h" #include "nsAtom.h" -#include "nsCSSFrameConstructor.h" #include "nsDOMAttributeMap.h" #include "nsIContentInlines.h" #include "mozilla/dom/NodeInfo.h" diff --git a/dom/base/StorageAccessPermissionRequest.cpp b/dom/base/StorageAccessPermissionRequest.cpp index 40f48cd84b0e..52f7a08d8353 100644 --- a/dom/base/StorageAccessPermissionRequest.cpp +++ b/dom/base/StorageAccessPermissionRequest.cpp @@ -19,12 +19,14 @@ StorageAccessPermissionRequest::StorageAccessPermissionRequest( nsPIDOMWindowInner* aWindow, nsIPrincipal* aNodePrincipal, AllowCallback&& aAllowCallback, + AllowAutoGrantCallback&& aAllowAutoGrantCallback, AllowAnySiteCallback&& aAllowAnySiteCallback, CancelCallback&& aCancelCallback) : ContentPermissionRequestBase(aNodePrincipal, false, aWindow, NS_LITERAL_CSTRING("dom.storage_access"), NS_LITERAL_CSTRING("storage-access")), mAllowCallback(std::move(aAllowCallback)), + mAllowAutoGrantCallback(std::move(aAllowAutoGrantCallback)), mAllowAnySiteCallback(std::move(aAllowAnySiteCallback)), mCancelCallback(std::move(aCancelCallback)), mCallbackCalled(false) @@ -61,6 +63,9 @@ StorageAccessPermissionRequest::Allow(JS::HandleValue aChoices) if (choices.Length() == 1 && choices[0].choice().EqualsLiteral("allow-on-any-site")) { mAllowAnySiteCallback(); + } else if (choices.Length() == 1 && + choices[0].choice().EqualsLiteral("allow-auto-grant")) { + mAllowAutoGrantCallback(); } else { mAllowCallback(); } @@ -71,6 +76,7 @@ StorageAccessPermissionRequest::Allow(JS::HandleValue aChoices) already_AddRefed StorageAccessPermissionRequest::Create(nsPIDOMWindowInner* aWindow, AllowCallback&& aAllowCallback, + AllowAutoGrantCallback&& aAllowAutoGrantCallback, AllowAnySiteCallback&& aAllowAnySiteCallback, CancelCallback&& aCancelCallback) { @@ -85,6 +91,7 @@ StorageAccessPermissionRequest::Create(nsPIDOMWindowInner* aWindow, new StorageAccessPermissionRequest(aWindow, win->GetPrincipal(), std::move(aAllowCallback), + std::move(aAllowAutoGrantCallback), std::move(aAllowAnySiteCallback), std::move(aCancelCallback)); return request.forget(); diff --git a/dom/base/StorageAccessPermissionRequest.h b/dom/base/StorageAccessPermissionRequest.h index 5af43b95b705..bdc112718c7f 100644 --- a/dom/base/StorageAccessPermissionRequest.h +++ b/dom/base/StorageAccessPermissionRequest.h @@ -28,12 +28,14 @@ public: NS_IMETHOD Allow(JS::HandleValue choices) override; typedef std::function AllowCallback; + typedef std::function AllowAutoGrantCallback; typedef std::function AllowAnySiteCallback; typedef std::function CancelCallback; static already_AddRefed Create( nsPIDOMWindowInner* aWindow, AllowCallback&& aAllowCallback, + AllowAutoGrantCallback&& aAllowAutoGrantCallback, AllowAnySiteCallback&& aAllowAnySiteCallback, CancelCallback&& aCancelCallback); @@ -41,11 +43,13 @@ private: StorageAccessPermissionRequest(nsPIDOMWindowInner* aWindow, nsIPrincipal* aNodePrincipal, AllowCallback&& aAllowCallback, + AllowAutoGrantCallback&& aAllowAutoGrantCallback, AllowAnySiteCallback&& aAllowAnySiteCallback, CancelCallback&& aCancelCallback); ~StorageAccessPermissionRequest(); AllowCallback mAllowCallback; + AllowAutoGrantCallback mAllowAutoGrantCallback; AllowAnySiteCallback mAllowAnySiteCallback; CancelCallback mCancelCallback; nsTArray mPermissionRequests; diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 7ad28bee6b7e..f85d4b94fc9a 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -13988,9 +13988,11 @@ nsIDocument::RequestStorageAccess(mozilla::ErrorResult& aRv) RefPtr sapr = StorageAccessPermissionRequest::Create(inner, // Allow - [p] { p->Resolve(false, __func__); }, + [p] { p->Resolve(AntiTrackingCommon::eAllow, __func__); }, + // Allow auto grant + [p] { p->Resolve(AntiTrackingCommon::eAllowAutoGrant, __func__); }, // Allow on any site - [p] { p->Resolve(true, __func__); }, + [p] { p->Resolve(AntiTrackingCommon::eAllowOnAnySite, __func__); }, // Block [p] { p->Reject(false, __func__); }); @@ -14011,7 +14013,8 @@ nsIDocument::RequestStorageAccess(mozilla::ErrorResult& aRv) pr == PromptResult::Denied); if (pr == PromptResult::Granted) { return AntiTrackingCommon::StorageAccessFinalCheckPromise:: - CreateAndResolve(onAnySite, __func__); + CreateAndResolve(onAnySite ? AntiTrackingCommon::eAllowOnAnySite : + AntiTrackingCommon::eAllow, __func__); } return AntiTrackingCommon::StorageAccessFinalCheckPromise:: CreateAndReject(false, __func__); diff --git a/dom/base/nsGlobalWindowInner.cpp b/dom/base/nsGlobalWindowInner.cpp index 230e9a5c2c04..112502b61ffc 100644 --- a/dom/base/nsGlobalWindowInner.cpp +++ b/dom/base/nsGlobalWindowInner.cpp @@ -7632,10 +7632,6 @@ nsGlobalWindowInner::GetSidebar(OwningExternalOrWindowProxy& aResult, void nsGlobalWindowInner::ClearDocumentDependentSlots(JSContext* aCx) { - if (js::GetContextCompartment(aCx) != js::GetObjectCompartment(GetWrapperPreserveColor())) { - MOZ_CRASH("Looks like bug 1488480/1405521, with ClearDocumentDependentSlots in a bogus compartment"); - } - // If JSAPI OOMs here, there is basically nothing we can do to recover safely. if (!Window_Binding::ClearCachedDocumentValue(aCx, this) || !Window_Binding::ClearCachedPerformanceValue(aCx, this)) { diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp index b764c7841407..fe6bc24a69b9 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -2886,9 +2886,6 @@ nsINode::WrapObject(JSContext *aCx, JS::Handle aGivenProto) if (!OwnerDoc()->GetScriptHandlingObject(hasHadScriptHandlingObject) && !hasHadScriptHandlingObject && !nsContentUtils::IsSystemCaller(aCx)) { - if (IsDocument()) { - MOZ_CRASH("Looks like bug 1488480/1405521, with a document that lost its script handling object"); - } Throw(aCx, NS_ERROR_UNEXPECTED); return nullptr; } @@ -2897,9 +2894,6 @@ nsINode::WrapObject(JSContext *aCx, JS::Handle aGivenProto) MOZ_ASSERT_IF(obj && ChromeOnlyAccess(), xpc::IsInContentXBLScope(obj) || !xpc::UseContentXBLScope(JS::GetObjectRealmOrNull(obj))); - if (!obj && IsDocument()) { - MOZ_CRASH("Looks like bug 1488480/1405521, with WrapNode on a document returning null"); - } return obj; } diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index e07e5d51cf78..42de02459871 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -689,48 +689,16 @@ DefineConstants(JSContext* cx, JS::Handle obj, } static inline bool -Define(JSContext* cx, JS::Handle obj, const JSFunctionSpec* spec) -{ - bool ok = JS_DefineFunctions(cx, obj, spec); - if (ok) { - return true; - } - - if (!strcmp(js::GetObjectClass(obj)->name, "DocumentPrototype")) { - MOZ_CRASH("Bug 1405521/1488480: JS_DefineFunctions failed for Document.prototype"); - } - - return false; +Define(JSContext* cx, JS::Handle obj, const JSFunctionSpec* spec) { + return JS_DefineFunctions(cx, obj, spec); } static inline bool -Define(JSContext* cx, JS::Handle obj, const JSPropertySpec* spec) -{ - bool ok = JS_DefineProperties(cx, obj, spec); - if (ok) { - return true; - } - - if (!strcmp(js::GetObjectClass(obj)->name, "DocumentPrototype")) { - MOZ_CRASH("Bug 1405521/1488480: JS_DefineProperties failed for Document.prototype"); - } - - return false; +Define(JSContext* cx, JS::Handle obj, const JSPropertySpec* spec) { + return JS_DefineProperties(cx, obj, spec); } - static inline bool -Define(JSContext* cx, JS::Handle obj, const ConstantSpec* spec) -{ - bool ok = DefineConstants(cx, obj, spec); - if (ok) { - return true; - } - - - if (!strcmp(js::GetObjectClass(obj)->name, "DocumentPrototype")) { - MOZ_CRASH("Bug 1405521/1488480: DefineConstants failed for Document.prototype"); - } - - return false; +Define(JSContext* cx, JS::Handle obj, const ConstantSpec* spec) { + return DefineConstants(cx, obj, spec); } template @@ -966,13 +934,6 @@ CreateInterfacePrototypeObject(JSContext* cx, JS::Handle global, // properties go on the global itself. (!isGlobal && !DefineProperties(cx, ourProto, properties, chromeOnlyProperties))) { - if (!strcmp(protoClass->name, "DocumentPrototype")) { - if (!ourProto) { - MOZ_CRASH("Bug 1405521/1488480: JS_NewObjectWithUniqueType failed for Document.prototype"); - } else { - MOZ_CRASH("Bug 1405521/1488480: DefineProperties failed for Document.prototype"); - } - } return nullptr; } @@ -980,18 +941,12 @@ CreateInterfacePrototypeObject(JSContext* cx, JS::Handle global, JS::Rooted unscopableObj(cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr)); if (!unscopableObj) { - if (!strcmp(protoClass->name, "DocumentPrototype")) { - MOZ_CRASH("Bug 1405521/1488480: Unscopable object creation failed for Document.prototype"); - } return nullptr; } for (; *unscopableNames; ++unscopableNames) { if (!JS_DefineProperty(cx, unscopableObj, *unscopableNames, JS::TrueHandleValue, JSPROP_ENUMERATE)) { - if (!strcmp(protoClass->name, "DocumentPrototype")) { - MOZ_CRASH("Bug 1405521/1488480: Defining property on unscopable object failed for Document.prototype"); - } return nullptr; } } @@ -1001,9 +956,6 @@ CreateInterfacePrototypeObject(JSContext* cx, JS::Handle global, // Readonly and non-enumerable to match Array.prototype. if (!JS_DefinePropertyById(cx, ourProto, unscopableId, unscopableObj, JSPROP_READONLY)) { - if (!strcmp(protoClass->name, "DocumentPrototype")) { - MOZ_CRASH("Bug 1405521/1488480: Defining @@unscopables failed for Document.prototype"); - } return nullptr; } } @@ -1012,9 +964,6 @@ CreateInterfacePrototypeObject(JSContext* cx, JS::Handle global, JS::Rooted toStringTagStr(cx, JS_NewStringCopyZ(cx, toStringTag)); if (!toStringTagStr) { - if (!strcmp(protoClass->name, "DocumentPrototype")) { - MOZ_CRASH("Bug 1405521/1488480: Copying string tag failed for Document.prototype"); - } return nullptr; } @@ -1022,9 +971,6 @@ CreateInterfacePrototypeObject(JSContext* cx, JS::Handle global, SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::toStringTag))); if (!JS_DefinePropertyById(cx, ourProto, toStringTagId, toStringTagStr, JSPROP_READONLY)) { - if (!strcmp(protoClass->name, "DocumentPrototype")) { - MOZ_CRASH("Bug 1405521/1488480: Defining @@toStringTag failed for Document.prototype"); - } return nullptr; } } @@ -1129,9 +1075,6 @@ CreateInterfaceObjects(JSContext* cx, JS::Handle global, isChrome ? chromeOnlyProperties : nullptr, unscopableNames, toStringTag, isGlobal); if (!proto) { - if (name && !strcmp(name, "Document")) { - MOZ_CRASH("Bug 1405521/1488480: CreateInterfacePrototypeObject failed for Document.prototype"); - } return; } @@ -1150,9 +1093,6 @@ CreateInterfaceObjects(JSContext* cx, JS::Handle global, isChrome, defineOnGlobal); if (!interface) { - if (name && !strcmp(name, "Document")) { - MOZ_CRASH("Bug 1405521/1488480: CreateInterfaceObject failed for Document"); - } if (protoCache) { // If we fail we need to make sure to clear the value of protoCache we // set above. @@ -4299,10 +4239,6 @@ GetPerInterfaceObjectHandle(JSContext* aCx, /* Make sure our global is sane. Hopefully we can remove this sometime */ JSObject* global = JS::CurrentGlobalOrNull(aCx); if (!(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL)) { - if (aSlotId == prototypes::id::HTMLDocument || - aSlotId == prototypes::id::Document) { - MOZ_CRASH("Looks like bug 1488480/1405521, with a non-DOM global in GetPerInterfaceObjectHandle"); - } return nullptr; } @@ -4327,30 +4263,6 @@ GetPerInterfaceObjectHandle(JSContext* aCx, const JS::Heap& entrySlot = protoAndIfaceCache.EntrySlotMustExist(aSlotId); MOZ_ASSERT(JS::ObjectIsNotGray(entrySlot)); - - if (!entrySlot) { - switch (aSlotId) { - case prototypes::id::HTMLDocument: { - MOZ_CRASH("Looks like bug 1488480/1405521, with aCreator failing to create HTMLDocument.prototype"); - break; - } - case prototypes::id::Document: { - MOZ_CRASH("Looks like bug 1488480/1405521, with aCreator failing to create Document.prototype"); - break; - } - case prototypes::id::Node: { - MOZ_CRASH("Looks like bug 1488480/1405521, with aCreator failing to create Node.prototype"); - break; - } - case prototypes::id::EventTarget: { - MOZ_CRASH("Looks like bug 1488480/1405521, with aCreator failing to create EventTarget.prototype"); - break; - } - default: - break; - } - } - return JS::Handle::fromMarkedLocation(entrySlot.address()); } diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 2957f4cdc565..d5ddd3134bf8 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -2979,20 +2979,13 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): # if we don't need to create anything, why are we generating this? assert needInterfaceObject or needInterfacePrototypeObject - def maybecrash(reason): - if self.descriptor.name == "Document": - return 'MOZ_CRASH("Bug 1405521/1488480: %s");\n' % reason - return "" - getParentProto = fill( """ JS::${type} parentProto(${getParentProto}); if (!parentProto) { - $*{maybeCrash} return; } """, - maybeCrash=maybecrash("Can't get Node.prototype"), type=parentProtoType, getParentProto=getParentProto) @@ -3000,11 +2993,9 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): """ JS::${type} constructorProto(${getConstructorProto}); if (!constructorProto) { - $*{maybeCrash} return; } """, - maybeCrash=maybecrash("Can't get Node"), type=constructorProtoType, getConstructorProto=getConstructorProto) @@ -3020,12 +3011,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): for properties in idsToInit] idsInitedFlag = CGGeneric("static bool sIdsInited = false;\n") setFlag = CGGeneric("sIdsInited = true;\n") - initIdConditionals = [CGIfWrapper(CGGeneric(fill( - """ - $*{maybeCrash} - return; - """, - maybeCrash=maybecrash("Can't init IDs"))), call) + initIdConditionals = [CGIfWrapper(CGGeneric("return;\n"), call) for call in initIdCalls] initIds = CGList([idsInitedFlag, CGIfWrapper(CGList(initIdConditionals + [setFlag]), @@ -3112,9 +3098,6 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): ${name}, aDefineOnGlobal, ${unscopableNames}, ${isGlobal}); - if (protoCache && !*protoCache) { - $*{maybeCrash} - } """, protoClass=protoClass, parentProto=parentProto, @@ -3129,8 +3112,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): chromeProperties=chromeProperties, name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr", unscopableNames="unscopableNames" if self.haveUnscopables else "nullptr", - isGlobal=toStringBool(isGlobal), - maybeCrash=maybecrash("dom::CreateInterfaceObjects failed for Document")) + isGlobal=toStringBool(isGlobal)) # If we fail after here, we must clear interface and prototype caches # using this code: intermediate failure must not expose the interface in @@ -3232,12 +3214,10 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): JS::Rooted holderProto(aCx, ${holderProto}); unforgeableHolder = JS_NewObjectWithoutMetadata(aCx, ${holderClass}, holderProto); if (!unforgeableHolder) { - $*{maybeCrash} $*{failureCode} } } """, - maybeCrash=maybecrash("Can't create unforgeable holder"), holderProto=holderProto, holderClass=holderClass, failureCode=failureCode)) @@ -3522,7 +3502,7 @@ class CGConstructorEnabled(CGAbstractMethod): return body.define() -def CreateBindingJSObject(descriptor, properties, failureCode = ""): +def CreateBindingJSObject(descriptor, properties): objDecl = "BindingJSObjectCreator<%s> creator(aCx);\n" % descriptor.nativeType # We don't always need to root obj, but there are a variety @@ -3547,14 +3527,12 @@ def CreateBindingJSObject(descriptor, properties, failureCode = ""): """ creator.CreateObject(aCx, sClass.ToJSClass(), proto, aObject, aReflector); """) - return objDecl + create + fill( + return objDecl + create + dedent( """ if (!aReflector) { - $*{failureCode} return false; } - """, - failureCode=failureCode) + """) def InitUnforgeablePropertiesOnHolder(descriptor, properties, failureCode, @@ -3573,22 +3551,12 @@ def InitUnforgeablePropertiesOnHolder(descriptor, properties, failureCode, unforgeables = [] - if descriptor.name == "Document": - maybeCrash = dedent( - """ - MOZ_CRASH("Bug 1405521/1488480: Can't define unforgeable attributes"); - """); - else: - maybeCrash = ""; - defineUnforgeableAttrs = fill( """ if (!DefineUnforgeableAttributes(aCx, ${holderName}, %s)) { - $*{maybeCrash} $*{failureCode} } """, - maybeCrash=maybeCrash, failureCode=failureCode, holderName=holderName) defineUnforgeableMethods = fill( @@ -3734,15 +3702,14 @@ def SetImmutablePrototype(descriptor, failureCode): failureCode=failureCode) -def DeclareProto(noProto = "", wrapFail = ""): +def DeclareProto(): """ Declare the canonicalProto and proto we have for our wrapping operation. """ - return fill( + return dedent( """ JS::Handle canonicalProto = GetProtoObjectHandle(aCx); if (!canonicalProto) { - $*{noProto} return false; } JS::Rooted proto(aCx); @@ -3753,16 +3720,13 @@ def DeclareProto(noProto = "", wrapFail = ""): // to wrap the proto here. if (js::GetContextCompartment(aCx) != js::GetObjectCompartment(proto)) { if (!JS_WrapObject(aCx, &proto)) { - $*{wrapFail} return false; } } } else { proto = canonicalProto; } - """, - noProto=noProto, - wrapFail=wrapFail) + """) class CGWrapWithCacheMethod(CGAbstractMethod): @@ -3789,47 +3753,6 @@ class CGWrapWithCacheMethod(CGAbstractMethod): return false; """) - isDocument = False - iface = self.descriptor.interface - while iface: - if iface.identifier.name == "Document": - isDocument = True - break - iface = iface.parent - - if isDocument: - noGlobal = fill( - """ - MOZ_CRASH("Looks like bug 1488480/1405521, with ${name} not having a global"); - """, - name = self.descriptor.name) - noProto = fill( - """ - MOZ_CRASH("Looks like bug 1488480/1405521, with ${name} not having a proto"); - """, - name = self.descriptor.name) - protoWrapFail = fill( - """ - MOZ_CRASH("Looks like bug 1488480/1405521, with ${name} failing to wrap a custom proto"); - """, - name = self.descriptor.name) - createObjectFailed = fill( - """ - MOZ_CRASH("Looks like bug 1488480/1405521, with ${name} failing to CreateObject/CreateProxyObject"); - """, - name = self.descriptor.name) - expandoAllocFail = fill( - """ - MOZ_CRASH("Looks like bug 1488480/1405521, with ${name} failing to EnsureExpandoObject or JS_InitializePropertiesFromCompatibleNativeObject"); - """, - name = self.descriptor.name) - else: - noGlobal = "" - noProto = "" - protoWrapFail = "" - createObjectFailed = "" - expandoAllocFail = "" - return fill( """ static_assert(!IsBaseOf::value, @@ -3845,7 +3768,6 @@ class CGWrapWithCacheMethod(CGAbstractMethod): JS::Rooted global(aCx, FindAssociatedGlobal(aCx, aObject->GetParentObject())); if (!global) { - $*{noGlobal} return false; } MOZ_ASSERT(JS_IsGlobalObject(global)); @@ -3886,14 +3808,11 @@ class CGWrapWithCacheMethod(CGAbstractMethod): return true; """, - noGlobal=noGlobal, nativeType=self.descriptor.nativeType, assertInheritance=AssertInheritanceChain(self.descriptor), - declareProto=DeclareProto(noProto, protoWrapFail), - createObject=CreateBindingJSObject(self.descriptor, self.properties, - createObjectFailed), + declareProto=DeclareProto(), + createObject=CreateBindingJSObject(self.descriptor, self.properties), unforgeable=CopyUnforgeablePropertiesToInstance(self.descriptor, - expandoAllocFail + failureCode), slots=InitMemberSlots(self.descriptor, failureCode), setImmutablePrototype=SetImmutablePrototype(self.descriptor, diff --git a/dom/html/ImageDocument.cpp b/dom/html/ImageDocument.cpp index 5cd20b735e88..b2b0724dd64a 100644 --- a/dom/html/ImageDocument.cpp +++ b/dom/html/ImageDocument.cpp @@ -203,11 +203,7 @@ ImageDocument::Init() JSObject* ImageDocument::WrapNode(JSContext* aCx, JS::Handle aGivenProto) { - JSObject* obj = ImageDocument_Binding::Wrap(aCx, this, aGivenProto); - if (!obj) { - MOZ_CRASH("Looks like bug 1488480/1405521, with ImageDocument::WrapNode failing"); - } - return obj; + return ImageDocument_Binding::Wrap(aCx, this, aGivenProto); } nsresult diff --git a/dom/html/nsHTMLDocument.cpp b/dom/html/nsHTMLDocument.cpp index 3ecfa4a8a6eb..d8b3202d6e2c 100644 --- a/dom/html/nsHTMLDocument.cpp +++ b/dom/html/nsHTMLDocument.cpp @@ -203,11 +203,7 @@ NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(nsHTMLDocument, JSObject* nsHTMLDocument::WrapNode(JSContext* aCx, JS::Handle aGivenProto) { - JSObject* obj = HTMLDocument_Binding::Wrap(aCx, this, aGivenProto); - if (!obj) { - MOZ_CRASH("Looks like bug 1488480/1405521, with nsHTMLDocument::WrapNode failing"); - } - return obj; + return HTMLDocument_Binding::Wrap(aCx, this, aGivenProto); } nsresult diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 8103a04ab866..34bb8d865226 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -56,7 +56,6 @@ #include "mozilla/TouchEvents.h" #include "mozilla/Unused.h" #include "nsContentUtils.h" -#include "nsCSSFrameConstructor.h" #include "nsDocShell.h" #include "nsEmbedCID.h" #include "nsGlobalWindow.h" diff --git a/dom/xbl/nsXBLResourceLoader.cpp b/dom/xbl/nsXBLResourceLoader.cpp index 75715fe9bfd0..f4d8932a7c68 100644 --- a/dom/xbl/nsXBLResourceLoader.cpp +++ b/dom/xbl/nsXBLResourceLoader.cpp @@ -4,7 +4,6 @@ * 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/. */ -#include "nsCSSFrameConstructor.h" #include "nsTArray.h" #include "nsString.h" #include "nsIDocument.h" diff --git a/dom/xml/XMLDocument.cpp b/dom/xml/XMLDocument.cpp index c154d809ff44..02e1888ff823 100644 --- a/dom/xml/XMLDocument.cpp +++ b/dom/xml/XMLDocument.cpp @@ -628,16 +628,11 @@ XMLDocument::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const JSObject* XMLDocument::WrapNode(JSContext *aCx, JS::Handle aGivenProto) { - JSObject* obj; if (mIsPlainDocument) { - obj = Document_Binding::Wrap(aCx, this, aGivenProto); - } else { - obj = XMLDocument_Binding::Wrap(aCx, this, aGivenProto); + return Document_Binding::Wrap(aCx, this, aGivenProto); } - if (!obj) { - MOZ_CRASH("Looks like bug 1488480/1405521, with XMLDocument::WrapNode failing"); - } - return obj; + + return XMLDocument_Binding::Wrap(aCx, this, aGivenProto); } } // namespace dom diff --git a/dom/xul/XULDocument.cpp b/dom/xul/XULDocument.cpp index a74511024104..58d481b06484 100644 --- a/dom/xul/XULDocument.cpp +++ b/dom/xul/XULDocument.cpp @@ -1648,11 +1648,7 @@ XULDocument::DirectionChanged(const char* aPrefName, XULDocument* aDoc) JSObject* XULDocument::WrapNode(JSContext *aCx, JS::Handle aGivenProto) { - JSObject* obj = XULDocument_Binding::Wrap(aCx, this, aGivenProto); - if (!obj) { - MOZ_CRASH("Looks like bug 1488480/1405521, with XULDocument_Binding::Wrap failing"); - } - return obj; + return XULDocument_Binding::Wrap(aCx, this, aGivenProto); } } // namespace dom diff --git a/js/src/jit-test/tests/ion/bug1510684.js b/js/src/jit-test/tests/ion/bug1510684.js new file mode 100644 index 000000000000..514b934f32da --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1510684.js @@ -0,0 +1,38 @@ +var verified = false; +function f(a) { + if (a < 10000) + return 5; + assertEq(g_fwd.caller.arguments.length, 0); + assertEq(h_fwd.caller.arguments.length, 0); + verified = true; + return 6; +} + +function g_fwd(x) { + with({}) {}; + return f(x); +} +function g(a) { + var x = a; + function inline() { + return g_fwd(x); + } + return inline(); +} + +function h_fwd(x) { + with({}) {}; + return g(x); +} +function h(a) { + var x = a; + function inline() { + return h_fwd(x); + } + return inline(); +} + +var i = 0; +while (!verified) { + h(i++); +} diff --git a/js/src/jit/BaselineDebugModeOSR.cpp b/js/src/jit/BaselineDebugModeOSR.cpp index 02353635c94a..6914f30bf1f7 100644 --- a/js/src/jit/BaselineDebugModeOSR.cpp +++ b/js/src/jit/BaselineDebugModeOSR.cpp @@ -1103,17 +1103,42 @@ JitRuntime::getBaselineDebugModeOSRHandlerAddress(JSContext* cx, bool popFrameRe : baselineDebugModeOSRHandlerNoFrameRegPopAddr_.ref(); } +static void +PushCallVMOutputRegisters(MacroAssembler& masm) +{ + // callVMs can use several different output registers, depending on the + // type of their outparam. + masm.push(ReturnReg); + masm.push(ReturnDoubleReg); + masm.Push(JSReturnOperand); +} + +static void +PopCallVMOutputRegisters(MacroAssembler& masm) +{ + masm.Pop(JSReturnOperand); + masm.pop(ReturnDoubleReg); + masm.pop(ReturnReg); +} + +static void +TakeCallVMOutputRegisters(AllocatableGeneralRegisterSet& regs) +{ + regs.take(ReturnReg); + regs.take(JSReturnOperand); +} + static void EmitBaselineDebugModeOSRHandlerTail(MacroAssembler& masm, Register temp, bool returnFromCallVM) { // Save real return address on the stack temporarily. // // If we're returning from a callVM, we don't need to worry about R0 and - // R1 but do need to propagate the original ReturnReg value. Otherwise we - // need to worry about R0 and R1 but can clobber ReturnReg. Indeed, on - // x86, R1 contains ReturnReg. + // R1 but do need to propagate the registers the call might write to. + // Otherwise we need to worry about R0 and R1 but can clobber ReturnReg. + // Indeed, on x86, R1 contains ReturnReg. if (returnFromCallVM) { - masm.push(ReturnReg); + PushCallVMOutputRegisters(masm); } else { masm.pushValue(Address(temp, offsetof(BaselineDebugModeOSRInfo, valueR0))); masm.pushValue(Address(temp, offsetof(BaselineDebugModeOSRInfo, valueR1))); @@ -1130,7 +1155,7 @@ EmitBaselineDebugModeOSRHandlerTail(MacroAssembler& masm, Register temp, bool re // Restore saved values. AllocatableGeneralRegisterSet jumpRegs(GeneralRegisterSet::All()); if (returnFromCallVM) { - jumpRegs.take(ReturnReg); + TakeCallVMOutputRegisters(jumpRegs); } else { jumpRegs.take(R0); jumpRegs.take(R1); @@ -1141,7 +1166,7 @@ EmitBaselineDebugModeOSRHandlerTail(MacroAssembler& masm, Register temp, bool re masm.pop(target); masm.pop(BaselineFrameReg); if (returnFromCallVM) { - masm.pop(ReturnReg); + PopCallVMOutputRegisters(masm); } else { masm.popValue(R1); masm.popValue(R0); @@ -1157,7 +1182,7 @@ JitRuntime::generateBaselineDebugModeOSRHandler(JSContext* cx, uint32_t* noFrame AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); regs.take(BaselineFrameReg); - regs.take(ReturnReg); + TakeCallVMOutputRegisters(regs); Register temp = regs.takeAny(); Register syncedStackStart = regs.takeAny(); @@ -1170,7 +1195,7 @@ JitRuntime::generateBaselineDebugModeOSRHandler(JSContext* cx, uint32_t* noFrame // Record the stack pointer for syncing. masm.moveStackPtrTo(syncedStackStart); - masm.push(ReturnReg); + PushCallVMOutputRegisters(masm); masm.push(BaselineFrameReg); // Call a stub to fully initialize the info. @@ -1186,7 +1211,7 @@ JitRuntime::generateBaselineDebugModeOSRHandler(JSContext* cx, uint32_t* noFrame // a callVM, and prepareCallVM in BaselineCompiler always fully syncs the // stack. masm.pop(BaselineFrameReg); - masm.pop(ReturnReg); + PopCallVMOutputRegisters(masm); masm.loadPtr(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfScratchValue()), temp); masm.addToStackPtr(Address(temp, offsetof(BaselineDebugModeOSRInfo, stackAdjust))); diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 202b4dd3ee93..7dfc587bbf29 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -1835,7 +1835,7 @@ jit::JitActivation::registerIonFrameRecovery(RInstructionResults&& results) jit::RInstructionResults* jit::JitActivation::maybeIonFrameRecovery(JitFrameLayout* fp) { - for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end(); ) { + for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end(); it++) { if (it->frame() == fp) { return it; } diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index fbc143fe4050..add3e263ce00 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -11030,13 +11030,6 @@ nsIFrame::CreateOwnLayerIfNeeded(nsDisplayListBuilder* aBuilder, } } -bool -nsIFrame::IsSelected() const -{ - return (GetContent() && GetContent()->IsSelectionDescendant()) ? - IsFrameSelected() : false; -} - bool nsIFrame::IsStackingContext(EffectSet* aEffectSet, const nsStyleDisplay* aStyleDisplay, diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index edc0822a5249..c11ae9741762 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -3232,7 +3232,10 @@ public: /** * @returns true if this frame is selected. */ - bool IsSelected() const; + bool IsSelected() const { + return (GetContent() && GetContent()->IsSelectionDescendant()) ? + IsFrameSelected() : false; + } /** * called to discover where this frame, or a parent frame has user-select style diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h index 4f61cfac067e..4fc76f1a5a34 100644 --- a/layout/generic/nsTextFrame.h +++ b/layout/generic/nsTextFrame.h @@ -72,7 +72,7 @@ public: // nsIFrame void BuildDisplayList(nsDisplayListBuilder* aBuilder, - const nsDisplayListSet& aLists) override; + const nsDisplayListSet& aLists) final; void Init(nsIContent* aContent, nsContainerFrame* aParent, @@ -80,7 +80,7 @@ public: void DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) override; - nsresult GetCursor(const nsPoint& aPoint, nsIFrame::Cursor& aCursor) override; + nsresult GetCursor(const nsPoint& aPoint, nsIFrame::Cursor& aCursor) final; nsresult CharacterDataChanged(const CharacterDataChangeInfo&) final; @@ -103,7 +103,7 @@ public: GetContent()->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY); } } - nsIFrame* GetNextInFlowVirtual() const override { return GetNextInFlow(); } + nsIFrame* GetNextInFlowVirtual() const final { return GetNextInFlow(); } nsTextFrame* GetNextInFlow() const { return mNextContinuation && @@ -156,20 +156,20 @@ public: return Style()->ShouldSuppressLineBreak(); } - void InvalidateFrame(uint32_t aDisplayItemKey = 0, bool aRebuildDisplayItems = true) override; + void InvalidateFrame(uint32_t aDisplayItemKey = 0, bool aRebuildDisplayItems = true) final; void InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey = 0, - bool aRebuildDisplayItems = true) override; + bool aRebuildDisplayItems = true) final; #ifdef DEBUG_FRAME_DUMP void List(FILE* out = stderr, const char* aPrefix = "", - uint32_t aFlags = 0) const override; - nsresult GetFrameName(nsAString& aResult) const override; + uint32_t aFlags = 0) const final; + nsresult GetFrameName(nsAString& aResult) const final; void ToCString(nsCString& aBuf, int32_t* aTotalContentLength) const; #endif - ContentOffsets CalcContentOffsetsFromFramePoint(const nsPoint& aPoint) override; + ContentOffsets CalcContentOffsetsFromFramePoint(const nsPoint& aPoint) final; ContentOffsets GetCharacterOffsetAtFramePoint(const nsPoint& aPoint); /** @@ -187,24 +187,24 @@ public: SelectionType aSelectionType); FrameSearchResult PeekOffsetNoAmount(bool aForward, - int32_t* aOffset) override; + int32_t* aOffset) final; FrameSearchResult PeekOffsetCharacter(bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions = - PeekOffsetCharacterOptions()) override; + PeekOffsetCharacterOptions()) final; FrameSearchResult PeekOffsetWord(bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect, int32_t* aOffset, - PeekWordState* aState) override; + PeekWordState* aState) final; nsresult CheckVisibility(nsPresContext* aContext, int32_t aStartIndex, int32_t aEndIndex, bool aRecurse, bool* aFinished, - bool* _retval) override; + bool* _retval) final; // Flags for aSetLengthFlags enum @@ -217,27 +217,27 @@ public: nsLineLayout* aLineLayout, uint32_t aSetLengthFlags = 0); - nsresult GetOffsets(int32_t& start, int32_t& end) const override; + nsresult GetOffsets(int32_t& start, int32_t& end) const final; - void AdjustOffsetsForBidi(int32_t start, int32_t end) override; + void AdjustOffsetsForBidi(int32_t start, int32_t end) final; - nsresult GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) override; + nsresult GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) final; nsresult GetCharacterRectsInRange(int32_t aInOffset, int32_t aLength, - nsTArray& aRects) override; + nsTArray& aRects) final; nsresult GetChildFrameContainingOffset(int32_t inContentOffset, bool inHint, int32_t* outFrameContentOffset, - nsIFrame** outChildFrame) override; + nsIFrame** outChildFrame) final; - bool IsVisibleInSelection(mozilla::dom::Selection* aSelection) override; + bool IsVisibleInSelection(mozilla::dom::Selection* aSelection) final; - bool IsEmpty() override; - bool IsSelfEmpty() override { return IsEmpty(); } + bool IsEmpty() final; + bool IsSelfEmpty() final { return IsEmpty(); } nscoord GetLogicalBaseline(mozilla::WritingMode aWritingMode) const final; - bool HasSignificantTerminalNewline() const override; + bool HasSignificantTerminalNewline() const final; /** * Returns true if this text frame is logically adjacent to the end of the @@ -255,7 +255,7 @@ public: } #ifdef ACCESSIBILITY - mozilla::a11y::AccType AccessibleType() override; + mozilla::a11y::AccType AccessibleType() final; #endif float GetFontSizeInflation() const; @@ -266,9 +266,9 @@ public: } void SetFontSizeInflation(float aInflation); - void MarkIntrinsicISizesDirty() override; - nscoord GetMinISize(gfxContext* aRenderingContext) override; - nscoord GetPrefISize(gfxContext* aRenderingContext) override; + void MarkIntrinsicISizesDirty() final; + nscoord GetMinISize(gfxContext* aRenderingContext) final; + nscoord GetPrefISize(gfxContext* aRenderingContext) final; void AddInlineMinISize(gfxContext* aRenderingContext, InlineMinISizeData* aData) override; void AddInlinePrefISize(gfxContext* aRenderingContext, @@ -280,16 +280,16 @@ public: const mozilla::LogicalSize& aMargin, const mozilla::LogicalSize& aBorder, const mozilla::LogicalSize& aPadding, - ComputeSizeFlags aFlags) override; - nsRect ComputeTightBounds(DrawTarget* aDrawTarget) const override; + ComputeSizeFlags aFlags) final; + nsRect ComputeTightBounds(DrawTarget* aDrawTarget) const final; nsresult GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX, - nscoord* aXMost) override; + nscoord* aXMost) final; void Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics, const ReflowInput& aReflowInput, - nsReflowStatus& aStatus) override; - bool CanContinueTextRun() const override; + nsReflowStatus& aStatus) final; + bool CanContinueTextRun() const final; // Method that is called for a text frame that is logically // adjacent to the end of the line (i.e. followed only by empty text frames, // placeholders or inlines containing such). @@ -307,7 +307,7 @@ public: uint32_t aEndOffset = UINT32_MAX, TextOffsetType aOffsetType = TextOffsetType::OFFSETS_IN_CONTENT_TEXT, TrailingWhitespace aTrimTrailingWhitespace = - TrailingWhitespace::TRIM_TRAILING_WHITESPACE) override; + TrailingWhitespace::TRIM_TRAILING_WHITESPACE) final; nsOverflowAreas RecomputeOverflow(nsIFrame* aBlockFrame); @@ -544,7 +544,7 @@ public: const nscolor* aDecorationOverrideColor, PropertyProvider* aProvider); - nscolor GetCaretColorAt(int32_t aOffset) override; + nscolor GetCaretColorAt(int32_t aOffset) final; int16_t GetSelectionStatus(int16_t* aSelectionFlags); @@ -655,14 +655,14 @@ public: bool IsInitialLetterChild() const; - bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override; + bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) final; void AssignJustificationGaps(const mozilla::JustificationAssignment& aAssign); mozilla::JustificationAssignment GetJustificationAssignment() const; uint32_t CountGraphemeClusters() const; - bool HasAnyNoncollapsedCharacters() override; + bool HasAnyNoncollapsedCharacters() final; /** * Call this after you have manually changed the text node contents without @@ -704,7 +704,7 @@ protected: * Return true if the frame is part of a Selection. * Helper method to implement the public IsSelected() API. */ - bool IsFrameSelected() const override; + bool IsFrameSelected() const final; mozilla::UniquePtr GetSelectionDetails(); diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp index cb4021582764..39b50a6c192c 100644 --- a/layout/painting/nsDisplayList.cpp +++ b/layout/painting/nsDisplayList.cpp @@ -90,6 +90,7 @@ #include "nsSVGMaskFrame.h" #include "nsTableCellFrame.h" #include "nsTableColFrame.h" +#include "nsTextFrame.h" #include "nsSliderFrame.h" #include "ClientLayerManager.h" #include "mozilla/layers/StackingContextHelper.h" @@ -9614,6 +9615,18 @@ nsCharClipDisplayItem::ComputeInvalidationRegion( } } +bool +nsCharClipDisplayItem::IsSelected() const +{ + if (mIsFrameSelected.isNothing()) { + MOZ_ASSERT((nsTextFrame*)do_QueryFrame(mFrame)); + auto* f = static_cast(mFrame); + mIsFrameSelected.emplace(f->IsSelected()); + } + + return mIsFrameSelected.value(); +} + nsDisplayEffectsBase::nsDisplayEffectsBase( nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, diff --git a/layout/painting/nsDisplayList.h b/layout/painting/nsDisplayList.h index d47c52c518bf..0399a43f90a3 100644 --- a/layout/painting/nsDisplayList.h +++ b/layout/painting/nsDisplayList.h @@ -7798,14 +7798,7 @@ public: : nullptr; } - bool IsSelected() const - { - if (mIsFrameSelected.isNothing()) { - mIsFrameSelected.emplace(mFrame->IsSelected()); - } - - return mIsFrameSelected.value(); - } + bool IsSelected() const; // Lengths measured from the visual inline start and end sides // (i.e. left and right respectively in horizontal writing modes, diff --git a/toolkit/components/antitracking/AntiTrackingCommon.cpp b/toolkit/components/antitracking/AntiTrackingCommon.cpp index 4b87fefa29db..484092b2f115 100644 --- a/toolkit/components/antitracking/AntiTrackingCommon.cpp +++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp @@ -408,6 +408,89 @@ CompareBaseDomains(nsIURI* aTrackingURI, nsCaseInsensitiveCStringComparator()); } +class TemporaryAccessGrantObserver final : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + static void + Create(nsIPermissionManager* aPM, + nsIPrincipal* aPrincipal, + const nsACString& aType) + { + nsCOMPtr timer; + RefPtr observer = + new TemporaryAccessGrantObserver(aPM, aPrincipal, aType); + nsresult rv = + NS_NewTimerWithObserver(getter_AddRefs(timer), + observer, + 24 * 60 * 60 * 1000, // 24 hours + nsITimer::TYPE_ONE_SHOT); + + if (NS_SUCCEEDED(rv)) { + observer->SetTimer(timer); + } else { + timer->Cancel(); + } + } + + void SetTimer(nsITimer* aTimer) + { + mTimer = aTimer; + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + } + } + +private: + TemporaryAccessGrantObserver(nsIPermissionManager* aPM, + nsIPrincipal* aPrincipal, + const nsACString& aType) + : mPM(aPM) + , mPrincipal(aPrincipal) + , mType(aType) + { + MOZ_ASSERT(XRE_IsParentProcess(), + "Enforcing temporary access grant lifetimes can only be done in " + "the parent process"); + } + + ~TemporaryAccessGrantObserver() = default; + +private: + nsCOMPtr mTimer; + nsCOMPtr mPM; + nsCOMPtr mPrincipal; + nsCString mType; +}; + +NS_IMPL_ISUPPORTS(TemporaryAccessGrantObserver, nsIObserver) + +NS_IMETHODIMP +TemporaryAccessGrantObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + if (strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0) { + Unused << mPM->RemoveFromPrincipal(mPrincipal, mType.get()); + } else if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + } + + return NS_OK; +} + } // anonymous /* static */ RefPtr @@ -439,11 +522,11 @@ AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipa nsICookieService::BEHAVIOR_REJECT_TRACKER) { LOG(("Disabled by network.cookie.cookieBehavior pref (%d), bailing out early", StaticPrefs::network_cookie_cookieBehavior())); - return StorageAccessGrantPromise::CreateAndResolve(true, __func__); + return StorageAccessGrantPromise::CreateAndResolve(eAllowOnAnySite, __func__); } if (CheckContentBlockingAllowList(aParentWindow)) { - return StorageAccessGrantPromise::CreateAndResolve(true, __func__); + return StorageAccessGrantPromise::CreateAndResolve(eAllowOnAnySite, __func__); } nsCOMPtr topLevelStoragePrincipal; @@ -534,7 +617,7 @@ AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipa auto storePermission = [pwin, parentWindow, origin, trackingOrigin, trackingPrincipal, trackingURI, topInnerWindow, topLevelStoragePrincipal, aReason] - (bool aAnySite) -> RefPtr { + (int aAllowMode) -> RefPtr { NS_ConvertUTF16toUTF8 grantedOrigin(origin); nsAutoCString permissionKey; @@ -562,11 +645,12 @@ AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipa trackingPrincipal, trackingOrigin, grantedOrigin, - aAnySite) + aAllowMode) ->Then(GetCurrentThreadSerialEventTarget(), __func__, [] (FirstPartyStorageAccessGrantPromise::ResolveOrRejectValue&& aValue) { if (aValue.IsResolve()) { - return StorageAccessGrantPromise::CreateAndResolve(NS_SUCCEEDED(aValue.ResolveValue()), __func__); + return StorageAccessGrantPromise::CreateAndResolve(NS_SUCCEEDED(aValue.ResolveValue()) ? + eAllowOnAnySite : eAllow, __func__); } return StorageAccessGrantPromise::CreateAndReject(false, __func__); }); @@ -584,7 +668,7 @@ AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipa IPC::Principal(trackingPrincipal), trackingOrigin, grantedOrigin, - aAnySite) + aAllowMode) ->Then(GetCurrentThreadSerialEventTarget(), __func__, [] (const ContentChild::FirstPartyStorageAccessGrantedForOriginPromise::ResolveOrRejectValue& aValue) { if (aValue.IsResolve()) { @@ -612,9 +696,12 @@ AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(n nsIPrincipal* aTrackingPrincipal, const nsCString& aTrackingOrigin, const nsCString& aGrantedOrigin, - bool aAnySite) + int aAllowMode) { MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(aAllowMode == eAllow || + aAllowMode == eAllowAutoGrant || + aAllowMode == eAllowOnAnySite); nsCOMPtr parentPrincipalURI; Unused << aParentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI)); @@ -640,7 +727,7 @@ AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(n int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + expirationTime; nsresult rv; - if (aAnySite) { + if (aAllowMode == eAllowOnAnySite) { uint32_t privateBrowsingId = 0; rv = aTrackingPrincipal->GetPrivateBrowsingId(&privateBrowsingId); if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) { @@ -659,9 +746,11 @@ AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(n } else { uint32_t privateBrowsingId = 0; rv = aParentPrincipal->GetPrivateBrowsingId(&privateBrowsingId); - if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) { - // If we are coming from a private window, make sure to store a session-only - // permission which won't get persisted to disk. + if ((!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) || + (aAllowMode == eAllowAutoGrant)) { + // If we are coming from a private window or are automatically granting a + // permission, make sure to store a session-only permission which won't + // get persisted to disk. expirationType = nsIPermissionManager::EXPIRE_SESSION; when = 0; } @@ -675,6 +764,11 @@ AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(n rv = pm->AddFromPrincipal(aParentPrincipal, type.get(), nsIPermissionManager::ALLOW_ACTION, expirationType, when); + + if (NS_SUCCEEDED(rv) && (aAllowMode == eAllowAutoGrant)) { + // Make sure temporary access grants do not survive more than 24 hours. + TemporaryAccessGrantObserver::Create(pm, aParentPrincipal, type); + } } Unused << NS_WARN_IF(NS_FAILED(rv)); diff --git a/toolkit/components/antitracking/AntiTrackingCommon.h b/toolkit/components/antitracking/AntiTrackingCommon.h index 3000c6fd38be..6c48dc4aa81b 100644 --- a/toolkit/components/antitracking/AntiTrackingCommon.h +++ b/toolkit/components/antitracking/AntiTrackingCommon.h @@ -77,6 +77,12 @@ public: eOpenerAfterUserInteraction, eOpener }; + enum StorageAccessPromptChoices + { + eAllow, + eAllowAutoGrant, + eAllowOnAnySite + }; // Grant the permission for aOrigin to have access to the first party storage. // This method can handle 2 different scenarios: @@ -93,9 +99,9 @@ public: // Ex: example.net import tracker.com/script.js which does opens a popup and // the user interacts with it. tracker.com is allowed when loaded by // example.net. - typedef MozPromise StorageAccessFinalCheckPromise; + typedef MozPromise StorageAccessFinalCheckPromise; typedef std::function()> PerformFinalChecks; - typedef MozPromise StorageAccessGrantPromise; + typedef MozPromise StorageAccessGrantPromise; static MOZ_MUST_USE RefPtr AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipal, nsPIDOMWindowInner* aParentWindow, @@ -120,7 +126,7 @@ public: nsIPrincipal* aTrackingPrinciapl, const nsCString& aParentOrigin, const nsCString& aGrantedOrigin, - bool aAnySite); + int aAllowMode); enum ContentBlockingAllowListPurpose { eStorageChecks, diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js b/toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js index 67cf2dae47de..47154fc244e1 100644 --- a/toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js +++ b/toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js @@ -1,18 +1,30 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ ChromeUtils.import("resource://gre/modules/Services.jsm"); const CHROME_BASE = "chrome://mochitests/content/browser/browser/modules/test/browser/"; Services.scriptloader.loadSubScript(CHROME_BASE + "head.js", this); /* import-globals-from ../../../../../browser/modules/test/browser/head.js */ -async function testDoorHanger(choice) { - info(`Running doorhanger test with choice #${choice}`); +const BLOCK = 0; +const ALLOW = 1; +const ALLOW_ON_ANY_SITE = 2; + +async function testDoorHanger(choice, showPrompt, topPage, maxConcurrent) { + info(`Running doorhanger test with choice #${choice}, showPrompt: ${showPrompt} and ` + + `topPage: ${topPage}, maxConcurrent: ${maxConcurrent}`); + + if (!showPrompt) { + is(choice, ALLOW, "When not showing a prompt, we can only auto-grant"); + } await SpecialPowers.flushPrefEnv(); await SpecialPowers.pushPrefEnv({"set": [ ["browser.contentblocking.allowlist.annotations.enabled", true], ["browser.contentblocking.allowlist.storage.enabled", true], [ContentBlocking.prefIntroCount, ContentBlocking.MAX_INTROS], + ["dom.storage_access.auto_grants", true], ["dom.storage_access.enabled", true], + ["dom.storage_access.max_concurrent_auto_grants", maxConcurrent], ["dom.storage_access.prompt.testing", false], ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER], ["privacy.trackingprotection.enabled", false], @@ -23,13 +35,17 @@ async function testDoorHanger(choice) { await UrlClassifierTestUtils.addTestTrackers(); - let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE); + let tab = BrowserTestUtils.addTab(gBrowser, topPage); gBrowser.selectedTab = tab; let browser = gBrowser.getBrowserForTab(tab); await BrowserTestUtils.browserLoaded(browser); async function runChecks() { + // We need to repeat this constant here since runChecks is stringified + // and sent to the content process. + const BLOCK = 0; + await new Promise(resolve => { addEventListener("message", function onMessage(e) { if (e.data.startsWith("choice:")) { @@ -61,7 +77,7 @@ async function testDoorHanger(choice) { /* import-globals-from storageAccessAPIHelpers.js */ await callRequestStorageAccess(); - if (choice == 0) { + if (choice == BLOCK) { // We've said no, so cookies are still blocked is(document.cookie, "", "Still no cookies for me"); document.cookie = "name=value"; @@ -74,49 +90,55 @@ async function testDoorHanger(choice) { } } + let permChanged = TestUtils.topicObserved("perm-changed", + (subject, data) => { + let result; + if (choice == ALLOW) { + result = subject && + subject.QueryInterface(Ci.nsIPermission) + .type.startsWith("3rdPartyStorage^") && + subject.principal.origin == (new URL(topPage)).origin && + data == "added"; + } else if (choice == ALLOW_ON_ANY_SITE) { + result = subject && + subject.QueryInterface(Ci.nsIPermission) + .type == "cookie" && + subject.principal.origin == "https://tracking.example.org" && + data == "added"; + } + return result; + }); let shownPromise = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown"); shownPromise.then(async _ => { - let notification = - PopupNotifications.getNotification("storage-access", browser); + if (topPage != gBrowser.currentURI.spec) { + return; + } + ok(showPrompt, "We shouldn't show the prompt when we don't intend to"); + let notification = await new Promise(function poll(resolve) { + let notification = + PopupNotifications.getNotification("storage-access", browser); + if (notification) { + resolve(notification); + return; + } + setTimeout(poll, 10); + }); Assert.ok(notification, "Should have gotten the notification"); - let permChanged = TestUtils.topicObserved("perm-changed", - (subject, data) => { - let result; - if (choice == 1) { - result = subject && - subject.QueryInterface(Ci.nsIPermission) - .type.startsWith("3rdPartyStorage^") && - subject.principal.origin == "http://example.net" && - data == "added"; - } else if (choice == 2) { - result = subject && - subject.QueryInterface(Ci.nsIPermission) - .type == "cookie" && - subject.principal.origin == "https://tracking.example.org" && - data == "added"; - } - return result; - }); - if (choice == 0) { + if (choice == BLOCK) { await clickMainAction(); - } else if (choice == 1) { + } else if (choice == ALLOW) { await clickSecondaryAction(choice - 1); - } else if (choice == 2) { + } else if (choice == ALLOW_ON_ANY_SITE) { await clickSecondaryAction(choice - 1); } - if (choice != 0) { + if (choice != BLOCK) { await permChanged; } }); - let url; - if (choice == 2) { - url = TEST_3RD_PARTY_PAGE + "?disableWaitUntilPermission"; - } else { - url = TEST_3RD_PARTY_PAGE; - } + let url = TEST_3RD_PARTY_PAGE + "?disableWaitUntilPermission"; let ct = ContentTask.spawn(browser, { page: url, callback: runChecks.toString(), @@ -159,13 +181,114 @@ async function testDoorHanger(choice) { ifr.src = obj.page; }); }); - await Promise.all([ct, shownPromise]); + if (showPrompt) { + await Promise.all([ct, shownPromise]); + } else { + await Promise.all([ct, permChanged]); + } BrowserTestUtils.removeTab(tab); UrlClassifierTestUtils.cleanupTestTrackers(); } +async function preparePermissionsFromOtherSites(topPage) { + info("Faking permissions from other sites"); + let type = "3rdPartyStorage^https://tracking.example.org"; + let permission = Services.perms.ALLOW_ACTION; + let expireType = Services.perms.EXPIRE_SESSION; + if (topPage == TEST_TOP_PAGE) { + // For the first page, don't do anything + } else if (topPage == TEST_TOP_PAGE_2) { + // For the second page, only add the permission from the first page + Services.perms.add(Services.io.newURI(TEST_DOMAIN), + type, + permission, + expireType, + 0); + } else if (topPage == TEST_TOP_PAGE_3) { + // For the third page, add the permissions from the first two pages + Services.perms.add(Services.io.newURI(TEST_DOMAIN), + type, + permission, + expireType, + 0); + Services.perms.add(Services.io.newURI(TEST_DOMAIN_2), + type, + permission, + expireType, + 0); + } else if (topPage == TEST_TOP_PAGE_4) { + // For the fourth page, add the permissions from the first three pages + Services.perms.add(Services.io.newURI(TEST_DOMAIN), + type, + permission, + expireType, + 0); + Services.perms.add(Services.io.newURI(TEST_DOMAIN_2), + type, + permission, + expireType, + 0); + Services.perms.add(Services.io.newURI(TEST_DOMAIN_3), + type, + permission, + expireType, + 0); + } else if (topPage == TEST_TOP_PAGE_5) { + // For the fifth page, add the permissions from the first four pages + Services.perms.add(Services.io.newURI(TEST_DOMAIN), + type, + permission, + expireType, + 0); + Services.perms.add(Services.io.newURI(TEST_DOMAIN_2), + type, + permission, + expireType, + 0); + Services.perms.add(Services.io.newURI(TEST_DOMAIN_3), + type, + permission, + expireType, + 0); + Services.perms.add(Services.io.newURI(TEST_DOMAIN_4), + type, + permission, + expireType, + 0); + } else if (topPage == TEST_TOP_PAGE_6) { + // For the sixth page, add the permissions from the first five pages + Services.perms.add(Services.io.newURI(TEST_DOMAIN), + type, + permission, + expireType, + 0); + Services.perms.add(Services.io.newURI(TEST_DOMAIN_2), + type, + permission, + expireType, + 0); + Services.perms.add(Services.io.newURI(TEST_DOMAIN_3), + type, + permission, + expireType, + 0); + Services.perms.add(Services.io.newURI(TEST_DOMAIN_4), + type, + permission, + expireType, + 0); + Services.perms.add(Services.io.newURI(TEST_DOMAIN_5), + type, + permission, + expireType, + 0); + } else { + ok(false, "Unexpected top page: " + topPage); + } +} + async function cleanUp() { info("Cleaning up."); await new Promise(resolve => { @@ -173,13 +296,30 @@ async function cleanUp() { }); } -async function runRound(n) { - await testDoorHanger(n); +async function runRound(topPage, showPrompt, maxConcurrent) { + if (showPrompt) { + await preparePermissionsFromOtherSites(topPage); + await testDoorHanger(BLOCK, showPrompt, topPage, maxConcurrent); + await cleanUp(); + await preparePermissionsFromOtherSites(topPage); + await testDoorHanger(ALLOW, showPrompt, topPage, maxConcurrent); + await cleanUp(); + await preparePermissionsFromOtherSites(topPage); + await testDoorHanger(ALLOW_ON_ANY_SITE, showPrompt, topPage, maxConcurrent); + } else { + await preparePermissionsFromOtherSites(topPage); + await testDoorHanger(ALLOW, showPrompt, topPage, maxConcurrent); + } await cleanUp(); } add_task(async function() { - await runRound(0); - await runRound(1); - await runRound(2); + await runRound(TEST_TOP_PAGE, false, 1); + await runRound(TEST_TOP_PAGE_2, true, 1); + await runRound(TEST_TOP_PAGE, false, 5); + await runRound(TEST_TOP_PAGE_2, false, 5); + await runRound(TEST_TOP_PAGE_3, false, 5); + await runRound(TEST_TOP_PAGE_4, false, 5); + await runRound(TEST_TOP_PAGE_5, false, 5); + await runRound(TEST_TOP_PAGE_6, true, 5); }); diff --git a/toolkit/components/antitracking/test/browser/head.js b/toolkit/components/antitracking/test/browser/head.js index 15e5916f53e2..7cd73c09f87a 100644 --- a/toolkit/components/antitracking/test/browser/head.js +++ b/toolkit/components/antitracking/test/browser/head.js @@ -1,4 +1,9 @@ const TEST_DOMAIN = "http://example.net"; +const TEST_DOMAIN_2 = "http://xn--exmple-cua.test"; +const TEST_DOMAIN_3 = "https://xn--hxajbheg2az3al.xn--jxalpdlp"; +const TEST_DOMAIN_4 = "http://prefixexample.com"; +const TEST_DOMAIN_5 = "http://test"; +const TEST_DOMAIN_6 = "http://mochi.test:8888"; const TEST_3RD_PARTY_DOMAIN = "https://tracking.example.org"; const TEST_3RD_PARTY_DOMAIN_TP = "https://tracking.example.com"; const TEST_4TH_PARTY_DOMAIN = "http://not-tracking.example.com"; @@ -7,6 +12,11 @@ const TEST_ANOTHER_3RD_PARTY_DOMAIN = "https://another-tracking.example.net"; const TEST_PATH = "/browser/toolkit/components/antitracking/test/browser/"; const TEST_TOP_PAGE = TEST_DOMAIN + TEST_PATH + "page.html"; +const TEST_TOP_PAGE_2 = TEST_DOMAIN_2 + TEST_PATH + "page.html"; +const TEST_TOP_PAGE_3 = TEST_DOMAIN_3 + TEST_PATH + "page.html"; +const TEST_TOP_PAGE_4 = TEST_DOMAIN_4 + TEST_PATH + "page.html"; +const TEST_TOP_PAGE_5 = TEST_DOMAIN_5 + TEST_PATH + "page.html"; +const TEST_TOP_PAGE_6 = TEST_DOMAIN_6 + TEST_PATH + "page.html"; const TEST_EMBEDDER_PAGE = TEST_DOMAIN + TEST_PATH + "embedder.html"; const TEST_POPUP_PAGE = TEST_DOMAIN + TEST_PATH + "popup.html"; const TEST_3RD_PARTY_PAGE = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdParty.html"; diff --git a/toolkit/recordreplay/MiddlemanCall.cpp b/toolkit/recordreplay/MiddlemanCall.cpp index b5a07e5ed467..79e56c3aa7b9 100644 --- a/toolkit/recordreplay/MiddlemanCall.cpp +++ b/toolkit/recordreplay/MiddlemanCall.cpp @@ -172,12 +172,16 @@ ProcessMiddlemanCall(const char* aInputData, size_t aInputSize, CallArguments arguments; call->mArguments.CopyTo(&arguments); + bool skipCall; { MiddlemanCallContext cx(call, &arguments, MiddlemanCallPhase::MiddlemanInput); redirection.mMiddlemanCall(cx); + skipCall = cx.mSkipCallInMiddleman; } - RecordReplayInvokeCall(redirection.mBaseFunction, &arguments); + if (!skipCall) { + RecordReplayInvokeCall(redirection.mBaseFunction, &arguments); + } { MiddlemanCallContext cx(call, &arguments, MiddlemanCallPhase::MiddlemanOutput); diff --git a/toolkit/recordreplay/MiddlemanCall.h b/toolkit/recordreplay/MiddlemanCall.h index 9c4a4e214a8c..868de436a456 100644 --- a/toolkit/recordreplay/MiddlemanCall.h +++ b/toolkit/recordreplay/MiddlemanCall.h @@ -172,6 +172,10 @@ struct MiddlemanCallContext // divergence and associated rewind will occur. bool mFailed; + // This can be set in the MiddlemanInput phase to avoid performing the call + // in the middleman process. + bool mSkipCallInMiddleman; + // During the ReplayInput phase, this can be used to fill in any middleman // calls whose output the current one depends on. InfallibleVector* mDependentCalls; @@ -198,7 +202,8 @@ struct MiddlemanCallContext MiddlemanCallContext(MiddlemanCall* aCall, CallArguments* aArguments, MiddlemanCallPhase aPhase) : mCall(aCall), mArguments(aArguments), mPhase(aPhase), - mFailed(false), mDependentCalls(nullptr), mReplayOutputIsOld(false) + mFailed(false), mSkipCallInMiddleman(false), + mDependentCalls(nullptr), mReplayOutputIsOld(false) { switch (mPhase) { case MiddlemanCallPhase::ReplayPreface: @@ -429,6 +434,15 @@ MM_StackArgumentData(MiddlemanCallContext& aCx) } } +// Avoid calling a function in the middleman process. +static inline void +MM_SkipInMiddleman(MiddlemanCallContext& aCx) +{ + if (aCx.mPhase == MiddlemanCallPhase::MiddlemanInput) { + aCx.mSkipCallInMiddleman = true; + } +} + static inline void MM_NoOp(MiddlemanCallContext& aCx) { diff --git a/toolkit/recordreplay/ProcessRedirectDarwin.cpp b/toolkit/recordreplay/ProcessRedirectDarwin.cpp index b6f7b672af5e..280942a476eb 100644 --- a/toolkit/recordreplay/ProcessRedirectDarwin.cpp +++ b/toolkit/recordreplay/ProcessRedirectDarwin.cpp @@ -199,6 +199,7 @@ MM_ObjCInput(MiddlemanCallContext& aCx, id* aThingPtr) // List of the Objective C classes which messages might be sent to directly. static const char* gStaticClasses[] = { // Standard classes. + "NSAutoreleasePool", "NSBezierPath", "NSButtonCell", "NSColor", @@ -1131,31 +1132,23 @@ RR_objc_msgSend(Stream& aEvents, CallArguments* aArguments, ErrorType* aError) } } -static PreambleResult -MiddlemanPreamble_objc_msgSend(CallArguments* aArguments) +static void +MM_Alloc(MiddlemanCallContext& aCx) { - auto obj = aArguments->Arg<0, id>(); - auto message = aArguments->Arg<1, const char*>(); - - // Fake object value which allows null checks in the caller to pass. - static const size_t FakeId = 1; - - // Ignore uses of NSAutoreleasePool after diverging from the recording. - // These are not performed in the middleman because the middleman has its - // own autorelease pool, and because the middleman can process calls from - // multiple threads which will cause these messages to behave differently. - // release messages are also ignored, as for CFRelease. - if ((!strcmp(message, "alloc") && obj == (id) objc_lookUpClass("NSAutoreleasePool")) || - (!strcmp(message, "init") && obj == (id) FakeId) || - !strcmp(message, "drain") || - !strcmp(message, "release")) { - // Fake a return value in case the caller null checks it. - aArguments->Rval() = 1; - return PreambleResult::Veto; + if (aCx.mPhase == MiddlemanCallPhase::MiddlemanInput) { + // Refuse to allocate NSAutoreleasePools in the middleman: the order in + // which middleman calls happen does not guarantee the pools will be + // created and released in LIFO order, as the pools require. Instead, + // allocate an NSString instead so we have something to release later. + // Messages sent to NSAutoreleasePools will all be skipped in the + // middleman, so no one should notice this subterfuge. + auto& obj = aCx.mArguments->Arg<0, id>(); + if (obj == (id) objc_lookUpClass("NSAutoreleasePool")) { + obj = (id) objc_lookUpClass("NSString"); + } } - // Other messages will be handled by MM_objc_msgSend. - return PreambleResult::Redirect; + MM_CreateCFTypeRval(aCx); } static void @@ -1255,15 +1248,19 @@ struct ObjCMessageInfo // arguments / return values. static ObjCMessageInfo gObjCMiddlemanCallMessages[] = { // Generic - { "alloc", MM_CreateCFTypeRval }, + { "alloc", MM_Alloc }, { "init", MM_AutoreleaseCFTypeRval }, { "performSelector:withObject:", MM_PerformSelector }, + { "release", MM_SkipInMiddleman }, { "respondsToSelector:", MM_CString<2> }, // NSAppearance { "_drawInRect:context:options:", MM_Compose, MM_CFTypeArg<2>, MM_CFTypeArg<3>> }, + // NSAutoreleasePool + { "drain", MM_SkipInMiddleman }, + // NSArray { "count" }, { "objectAtIndex:", MM_AutoreleaseCFTypeRval }, @@ -2088,7 +2085,7 @@ static SystemRedirection gSystemRedirections[] = { { "objc_autoreleasePoolPop" }, { "objc_autoreleasePoolPush", RR_ScalarRval }, { "objc_msgSend", - RR_objc_msgSend, Preamble_objc_msgSend, MM_objc_msgSend, MiddlemanPreamble_objc_msgSend }, + RR_objc_msgSend, Preamble_objc_msgSend, MM_objc_msgSend }, ///////////////////////////////////////////////////////////////////////////// // Cocoa and CoreFoundation library functions