diff options
Diffstat (limited to '')
5 files changed, 196 insertions, 89 deletions
diff --git a/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs b/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs index f2a5d5e645..8424bebf4a 100644 --- a/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs +++ b/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs @@ -22,7 +22,6 @@ ChromeUtils.defineESModuleGetters(lazy, { "chrome://remote/content/shared/NavigationManager.sys.mjs", NavigationListener: "chrome://remote/content/shared/listeners/NavigationListener.sys.mjs", - OwnershipModel: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs", PollPromise: "chrome://remote/content/shared/Sync.sys.mjs", pprint: "chrome://remote/content/shared/Format.sys.mjs", print: "chrome://remote/content/shared/PDF.sys.mjs", @@ -857,12 +856,6 @@ class BrowsingContextModule extends Module { * @param {number=} options.maxNodeCount * The maximum amount of nodes which is going to be returned. * Defaults to return all the found nodes. - * @param {OwnershipModel=} options.ownership - * The ownership model to use for the serialization - * of the DOM nodes. Defaults to `OwnershipModel.None`. - * @property {string=} sandbox - * The name of the sandbox. If the value is null or empty - * string, the default realm will be used. * @property {SerializationOptions=} serializationOptions * An object which holds the information of how the DOM nodes * should be serialized. @@ -884,8 +877,6 @@ class BrowsingContextModule extends Module { context: contextId, locator, maxNodeCount = null, - ownership = lazy.OwnershipModel.None, - sandbox = null, serializationOptions, startNodes = null, } = options; @@ -923,19 +914,6 @@ class BrowsingContextModule extends Module { }, maxNodeCountErrorMsg)(maxNodeCount); } - const ownershipTypes = Object.values(lazy.OwnershipModel); - lazy.assert.that( - ownership => ownershipTypes.includes(ownership), - `Expected "ownership" to be one of ${ownershipTypes}, got ${ownership}` - )(ownership); - - if (sandbox != null) { - lazy.assert.string( - sandbox, - `Expected "sandbox" to be a string, got ${sandbox}` - ); - } - const serializationOptionsWithDefaults = lazy.setDefaultAndAssertSerializationOptions(serializationOptions); @@ -961,8 +939,6 @@ class BrowsingContextModule extends Module { params: { locator, maxNodeCount, - resultOwnership: ownership, - sandbox, serializationOptions: serializationOptionsWithDefaults, startNodes, }, diff --git a/remote/webdriver-bidi/modules/root/network.sys.mjs b/remote/webdriver-bidi/modules/root/network.sys.mjs index 238b9f3640..6850e3f372 100644 --- a/remote/webdriver-bidi/modules/root/network.sys.mjs +++ b/remote/webdriver-bidi/modules/root/network.sys.mjs @@ -340,6 +340,9 @@ class NetworkModule extends Module { * of any intercept, the request will be suspended. * * @param {object=} options + * @param {Array<string>=} options.contexts + * The list of browsing context ids where this intercept should be used. + * Optional, defaults to null. * @param {Array<InterceptPhase>} options.phases * The phases where this intercept should be checked. * @param {Array<URLPattern>=} options.urlPatterns @@ -353,7 +356,34 @@ class NetworkModule extends Module { * Raised if an argument is of an invalid type or value. */ addIntercept(options = {}) { - const { phases, urlPatterns = [] } = options; + const { contexts = null, phases, urlPatterns = [] } = options; + + if (contexts !== null) { + lazy.assert.array( + contexts, + `Expected "contexts" to be an array, got ${contexts}` + ); + + if (!options.contexts.length) { + throw new lazy.error.InvalidArgumentError( + `Expected "contexts" to contain at least one item, got an empty array` + ); + } + + for (const contextId of contexts) { + lazy.assert.string( + contextId, + `Expected elements of "contexts" to be a string, got ${contextId}` + ); + const context = this.#getBrowsingContext(contextId); + + if (context.parent) { + throw new lazy.error.InvalidArgumentError( + `Context with id ${contextId} is not a top-level browsing context` + ); + } + } + } lazy.assert.array( phases, @@ -386,6 +416,7 @@ class NetworkModule extends Module { const interceptId = lazy.generateUUID(); this.#interceptMap.set(interceptId, { + contexts, phases, urlPatterns: parsedPatterns, }); @@ -1122,6 +1153,23 @@ class NetworkModule extends Module { return challenges; } + #getBrowsingContext(contextId) { + const context = lazy.TabManager.getBrowsingContextById(contextId); + if (context === null) { + throw new lazy.error.NoSuchFrameError( + `Browsing Context with id ${contextId} not found` + ); + } + + if (!context.currentWindowGlobal) { + throw new lazy.error.NoSuchFrameError( + `No window found for BrowsingContext with id ${contextId}` + ); + } + + return context; + } + #getContextInfo(browsingContext) { return { contextId: browsingContext.id, @@ -1129,11 +1177,7 @@ class NetworkModule extends Module { }; } - #getSuspendMarkerText(requestData, phase) { - return `Request (id: ${requestData.request}) suspended by WebDriver BiDi in ${phase} phase`; - } - - #getNetworkIntercepts(event, requestData) { + #getNetworkIntercepts(event, requestData, contextId) { const intercepts = []; let phase; @@ -1153,8 +1197,23 @@ class NetworkModule extends Module { return intercepts; } + // Retrieve the top browsing context id for this network event. + const browsingContext = lazy.TabManager.getBrowsingContextById(contextId); + const topLevelContextId = lazy.TabManager.getIdForBrowsingContext( + browsingContext.top + ); + const url = requestData.url; for (const [interceptId, intercept] of this.#interceptMap) { + if ( + intercept.contexts !== null && + !intercept.contexts.includes(topLevelContextId) + ) { + // Skip this intercept if the event's context does not match the list + // of contexts for this intercept. + continue; + } + if (intercept.phases.includes(phase)) { const urlPatterns = intercept.urlPatterns; if ( @@ -1196,6 +1255,10 @@ class NetworkModule extends Module { return navigation ? navigation.navigationId : null; } + #getSuspendMarkerText(requestData, phase) { + return `Request (id: ${requestData.request}) suspended by WebDriver BiDi in ${phase} phase`; + } + #onAuthRequired = (name, data) => { const { authCallbacks, @@ -1580,7 +1643,11 @@ class NetworkModule extends Module { #processNetworkEvent(eventName, data) { const { contextId, navigation, redirectCount, requestData, timestamp } = data; - const intercepts = this.#getNetworkIntercepts(eventName, requestData); + const intercepts = this.#getNetworkIntercepts( + eventName, + requestData, + contextId + ); const isBlocked = !!intercepts.length; const baseParameters = { diff --git a/remote/webdriver-bidi/modules/root/session.sys.mjs b/remote/webdriver-bidi/modules/root/session.sys.mjs index a34ca514e3..8ecf7d7724 100644 --- a/remote/webdriver-bidi/modules/root/session.sys.mjs +++ b/remote/webdriver-bidi/modules/root/session.sys.mjs @@ -76,16 +76,10 @@ class SessionModule extends Module { const { events, contexts: contextIds = null } = params; // Check input types until we run schema validation. - lazy.assert.array(events, "events: array value expected"); - events.forEach(name => { - lazy.assert.string(name, `${name}: string value expected`); - }); + this.#assertNonEmptyArrayWithStrings(events, "events"); if (contextIds !== null) { - lazy.assert.array(contextIds, "contexts: array value expected"); - contextIds.forEach(contextId => { - lazy.assert.string(contextId, `${contextId}: string value expected`); - }); + this.#assertNonEmptyArrayWithStrings(contextIds, "contexts"); } const listeners = this.#updateEventMap(events, contextIds, true); @@ -113,15 +107,9 @@ class SessionModule extends Module { const { events, contexts: contextIds = null } = params; // Check input types until we run schema validation. - lazy.assert.array(events, "events: array value expected"); - events.forEach(name => { - lazy.assert.string(name, `${name}: string value expected`); - }); + this.#assertNonEmptyArrayWithStrings(events, "events"); if (contextIds !== null) { - lazy.assert.array(contextIds, "contexts: array value expected"); - contextIds.forEach(contextId => { - lazy.assert.string(contextId, `${contextId}: string value expected`); - }); + this.#assertNonEmptyArrayWithStrings(contextIds, "contexts"); } const listeners = this.#updateEventMap(events, contextIds, false); @@ -139,6 +127,23 @@ class SessionModule extends Module { } } + #assertNonEmptyArrayWithStrings(array, variableName) { + lazy.assert.array( + array, + `Expected "${variableName}" to be an array, got ${array}` + ); + lazy.assert.that( + array => !!array.length, + `Expected "${variableName}" array to have at least one item` + )(array); + array.forEach(item => { + lazy.assert.string( + item, + `Expected elements of "${variableName}" to be a string, got ${item}` + ); + }); + } + #getBrowserIdForContextId(contextId) { const context = lazy.TabManager.getBrowsingContextById(contextId); if (!context) { diff --git a/remote/webdriver-bidi/modules/root/storage.sys.mjs b/remote/webdriver-bidi/modules/root/storage.sys.mjs index 3eced2da4c..34f909aa28 100644 --- a/remote/webdriver-bidi/modules/root/storage.sys.mjs +++ b/remote/webdriver-bidi/modules/root/storage.sys.mjs @@ -16,6 +16,15 @@ ChromeUtils.defineESModuleGetters(lazy, { "chrome://remote/content/shared/UserContextManager.sys.mjs", }); +const PREF_COOKIE_BEHAVIOR = "network.cookie.cookieBehavior"; +const PREF_COOKIE_OPTIN_PARTITIONING = + "network.cookie.cookieBehavior.optInPartitioning"; + +// This is a static preference, so it cannot be modified during runtime and we can cache its value. +ChromeUtils.defineLazyGetter(lazy, "cookieBehaviorOptInPartitioning", () => + Services.prefs.getBoolPref(PREF_COOKIE_OPTIN_PARTITIONING) +); + const CookieFieldsMapping = { domain: "host", expiry: "expiry", @@ -603,10 +612,22 @@ class StorageModule extends Module { if (partitionSpec.type === PartitionType.Context) { const { context: contextId } = partitionSpec; const browsingContext = this.#getBrowsingContext(contextId); + const principal = Services.scriptSecurityManager.createContentPrincipal( + browsingContext.currentURI, + {} + ); // Define browsing context’s associated storage partition as combination of user context id - // and the origin of the document in this browsing context. + // and the origin of the document in this browsing context. We also add here `isThirdPartyURI` + // which is required to filter out third-party cookies in case they are not allowed. return { + // In case we have the browsing context of an iframe here, we perform a check + // if the URI of the top context is considered third-party to the URI of the iframe principal. + // It's considered a third-party if base domains or hosts (in case one or both base domains + // can not be determined) do not match. + isThirdPartyURI: browsingContext.parent + ? principal.isThirdPartyURI(browsingContext.top.currentURI) + : false, sourceOrigin: browsingContext.currentURI.prePath, userContext: browsingContext.originAttributes.userContextId, }; @@ -640,6 +661,9 @@ class StorageModule extends Module { ); } + // This key is not used for partitioning and was required to only filter out third-party cookies. + delete partitionKey.isThirdPartyURI; + return partitionKey; } @@ -742,44 +766,61 @@ class StorageModule extends Module { // Prepare the data in the format required for the platform API. const originAttributes = this.#getOriginAttributes(storagePartitionKey); - // In case we want to get the cookies for a certain `sourceOrigin`, - // we have to separately retrieve cookies for a hostname built from `sourceOrigin`, - // and with `partitionKey` equal an empty string to retrieve the cookies that which were set - // by this hostname but without `partitionKey`, e.g. with `document.cookie`. - if (storagePartitionKey.sourceOrigin) { - const url = new URL(storagePartitionKey.sourceOrigin); - const hostname = url.hostname; - - const principal = Services.scriptSecurityManager.createContentPrincipal( - Services.io.newURI(url), - {} + // Retrieve the cookies which exactly match a built partition attributes. + const cookiesWithOriginAttributes = + Services.cookies.getCookiesWithOriginAttributes( + JSON.stringify(originAttributes) ); - const isSecureProtocol = principal.isOriginPotentiallyTrustworthy; - - // We want to keep `userContext` id here, if it's present, - // but set the `partitionKey` to an empty string. - const cookiesMatchingHostname = - Services.cookies.getCookiesWithOriginAttributes( - JSON.stringify({ ...originAttributes, partitionKey: "" }), - hostname + + const isFirstPartyOrCrossSiteAllowed = + !storagePartitionKey.isThirdPartyURI || + this.#shouldIncludeCrossSiteCookie(); + + // Check if we accessing the first party storage or cross-site cookies are allowed. + if (isFirstPartyOrCrossSiteAllowed) { + // In case we want to get the cookies for a certain `sourceOrigin`, + // we have to separately retrieve cookies for a hostname built from `sourceOrigin`, + // and with `partitionKey` equal an empty string to retrieve the cookies that which were set + // by this hostname but without `partitionKey`, e.g. with `document.cookie`. + if (storagePartitionKey.sourceOrigin) { + const url = new URL(storagePartitionKey.sourceOrigin); + const hostname = url.hostname; + + const principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI(url), + {} ); + const isSecureProtocol = principal.isOriginPotentiallyTrustworthy; + + // We want to keep `userContext` id here, if it's present, + // but set the `partitionKey` to an empty string. + const cookiesMatchingHostname = + Services.cookies.getCookiesWithOriginAttributes( + JSON.stringify({ ...originAttributes, partitionKey: "" }), + hostname + ); + for (const cookie of cookiesMatchingHostname) { + // Ignore secure cookies for non-secure protocols. + if (cookie.isSecure && !isSecureProtocol) { + continue; + } + store.push(cookie); + } + } - for (const cookie of cookiesMatchingHostname) { - // Ignore secure cookies for non-secure protocols. - if (cookie.isSecure && !isSecureProtocol) { - continue; + store = store.concat(cookiesWithOriginAttributes); + } + // If we're trying to access the store in the third party context and + // the preferences imply that we shouldn't include cross site cookies, + // but we should include partitioned cookies, add only partitioned cookies. + else if (this.#shouldIncludePartitionedCookies()) { + for (const cookie of cookiesWithOriginAttributes) { + if (cookie.isPartitioned) { + store.push(cookie); } - store.push(cookie); } } - // Add the cookies which exactly match a built partition attributes. - store = store.concat( - Services.cookies.getCookiesWithOriginAttributes( - JSON.stringify(originAttributes) - ) - ); - return store; } @@ -856,6 +897,30 @@ class StorageModule extends Module { return cookie; } + + #shouldIncludeCrossSiteCookie() { + const cookieBehavior = Services.prefs.getIntPref(PREF_COOKIE_BEHAVIOR); + + if ( + cookieBehavior === Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN || + cookieBehavior === + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN + ) { + return false; + } + + return true; + } + + #shouldIncludePartitionedCookies() { + const cookieBehavior = Services.prefs.getIntPref(PREF_COOKIE_BEHAVIOR); + + return ( + cookieBehavior === + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN && + lazy.cookieBehaviorOptInPartitioning + ); + } } export const storage = StorageModule; diff --git a/remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs b/remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs index adf821601d..ef61954284 100644 --- a/remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs +++ b/remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs @@ -17,6 +17,7 @@ ChromeUtils.defineESModuleGetters(lazy, { "chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs", OriginType: "chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs", + OwnershipModel: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs", PollPromise: "chrome://remote/content/shared/Sync.sys.mjs", }); @@ -425,16 +426,9 @@ class BrowsingContextModule extends WindowGlobalBiDiModule { } _locateNodes(params = {}) { - const { - locator, - maxNodeCount, - resultOwnership, - sandbox, - serializationOptions, - startNodes, - } = params; + const { locator, maxNodeCount, serializationOptions, startNodes } = params; - const realm = this.messageHandler.getRealm({ sandboxName: sandbox }); + const realm = this.messageHandler.getRealm(); const contextNodes = []; if (startNodes === null) { @@ -482,7 +476,7 @@ class BrowsingContextModule extends WindowGlobalBiDiModule { this.serialize( returnedNode, serializationOptions, - resultOwnership, + lazy.OwnershipModel.None, realm, { seenNodeIds } ) |