From 288cbe9f46ba00a44f9804128550c71db52f5e8b Mon Sep 17 00:00:00 2001 From: Sean Feng Date: Wed, 27 Jul 2022 20:33:19 +0000 Subject: [PATCH] Bug 1778492 - Add an origin trial for coep: credentialless r=emilio,necko-reviewers,kershaw Differential Revision: https://phabricator.services.mozilla.com/D151381 --- dom/base/Document.cpp | 12 +- dom/base/nsDOMWindowUtils.cpp | 12 ++ dom/fetch/tests/browser.ini | 21 +++ ..._origin_trial_coep_credentialless_cache.js | 142 +++++++++++++++ ...rigin_trial_coep_credentialless_fetch_1.js | 134 ++++++++++++++ ...rigin_trial_coep_credentialless_fetch_2.js | 134 ++++++++++++++ ...rigin_trial_coep_credentialless_fetch_3.js | 139 +++++++++++++++ ...origin_trial_coep_credentialless_worker.js | 164 ++++++++++++++++++ dom/fetch/tests/credentialless_resource.sjs | 21 +++ dom/fetch/tests/credentialless_worker.sjs | 25 +++ .../tests/open_credentialless_document.sjs | 23 +++ dom/fetch/tests/store_header.sjs | 23 +++ dom/interfaces/base/nsIDOMWindowUtils.idl | 2 + dom/origin-trials/OriginTrials.cpp | 19 +- dom/origin-trials/OriginTrials.h | 4 +- dom/origin-trials/ffi/lib.rs | 2 + dom/origin-trials/tests/mochitest.ini | 1 + dom/origin-trials/tests/test_meta_simple.html | 5 + dom/workers/loader/CacheLoadHandler.cpp | 4 +- .../loader/ScriptResponseHeaderProcessor.cpp | 4 +- ipc/glue/BackgroundUtils.cpp | 8 +- modules/libpref/init/StaticPrefList.yaml | 8 + netwerk/base/LoadInfo.cpp | 26 +++ netwerk/base/LoadInfo.h | 3 + netwerk/base/TRRLoadInfo.cpp | 12 ++ netwerk/base/nsILoadInfo.idl | 5 + netwerk/base/nsNetUtil.cpp | 12 +- netwerk/base/nsNetUtil.h | 6 +- netwerk/ipc/NeckoChannelParams.ipdlh | 1 + .../protocol/http/ClassifierDummyChannel.cpp | 1 + netwerk/protocol/http/HttpBaseChannel.cpp | 23 ++- netwerk/protocol/http/HttpBaseChannel.h | 1 + .../protocol/http/nsIHttpChannelInternal.idl | 2 +- 33 files changed, 973 insertions(+), 26 deletions(-) create mode 100644 dom/fetch/tests/browser_origin_trial_coep_credentialless_cache.js create mode 100644 dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_1.js create mode 100644 dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_2.js create mode 100644 dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_3.js create mode 100644 dom/fetch/tests/browser_origin_trial_coep_credentialless_worker.js create mode 100644 dom/fetch/tests/credentialless_resource.sjs create mode 100644 dom/fetch/tests/credentialless_worker.sjs create mode 100644 dom/fetch/tests/open_credentialless_document.sjs create mode 100644 dom/fetch/tests/store_header.sjs diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index b34ad3970c1e..3c074b091548 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -3998,7 +3998,8 @@ nsresult Document::InitCOEP(nsIChannel* aChannel) { nsILoadInfo::CrossOriginEmbedderPolicy policy = nsILoadInfo::EMBEDDER_POLICY_NULL; - if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(&policy))) { + if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy( + mTrials.IsEnabled(OriginTrial::CoepCredentialless), &policy))) { mEmbedderPolicy = Some(policy); } @@ -6874,6 +6875,15 @@ void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) { if (aHeaderField == nsGkAtoms::origin_trial) { mTrials.UpdateFromToken(aData, NodePrincipal()); + if (mTrials.IsEnabled(OriginTrial::CoepCredentialless)) { + InitCOEP(mChannel); + + WindowContext* ctx = GetWindowContext(); + MOZ_ASSERT(ctx); + if (mEmbedderPolicy) { + Unused << ctx->SetEmbedderPolicy(mEmbedderPolicy.value()); + } + } } if (aHeaderField == nsGkAtoms::headerDefaultStyle) { diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 47e6aa9724fd..a9201a271a9a 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -4711,6 +4711,18 @@ nsDOMWindowUtils::IsCssPropertyRecordedInUseCounter(const nsACString& aPropName, return knownProp ? NS_OK : NS_ERROR_FAILURE; } +NS_IMETHODIMP +nsDOMWindowUtils::IsCoepCredentialless(bool* aResult) { + Document* doc = GetDocument(); + if (!doc) { + return NS_ERROR_FAILURE; + } + + *aResult = IsCoepCredentiallessEnabled( + doc->Trials().IsEnabled(OriginTrial::CoepCredentialless)); + return NS_OK; +} + NS_IMETHODIMP nsDOMWindowUtils::GetLayersId(uint64_t* aOutLayersId) { nsIWidget* widget = GetWidget(); diff --git a/dom/fetch/tests/browser.ini b/dom/fetch/tests/browser.ini index 8705dea1ab1c..6fd8244288aa 100644 --- a/dom/fetch/tests/browser.ini +++ b/dom/fetch/tests/browser.ini @@ -1,2 +1,23 @@ [DEFAULT] [browser_blobFromFile.js] +[browser_origin_trial_coep_credentialless_fetch_1.js] +support-files = + open_credentialless_document.sjs + store_header.sjs +[browser_origin_trial_coep_credentialless_fetch_2.js] +support-files = + open_credentialless_document.sjs + store_header.sjs +[browser_origin_trial_coep_credentialless_fetch_3.js] +support-files = + open_credentialless_document.sjs + store_header.sjs +[browser_origin_trial_coep_credentialless_worker.js] +support-files = + open_credentialless_document.sjs + store_header.sjs + credentialless_worker.sjs +[browser_origin_trial_coep_credentialless_cache.js] +support-files = + open_credentialless_document.sjs + credentialless_resource.sjs diff --git a/dom/fetch/tests/browser_origin_trial_coep_credentialless_cache.js b/dom/fetch/tests/browser_origin_trial_coep_credentialless_cache.js new file mode 100644 index 000000000000..851ab974190e --- /dev/null +++ b/dom/fetch/tests/browser_origin_trial_coep_credentialless_cache.js @@ -0,0 +1,142 @@ +const TOP_LEVEL_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "open_credentialless_document.sjs"; + +const SAME_ORIGIN = "https://example.com"; +const CROSS_ORIGIN = "https://test1.example.com"; + +const USE_CREDENTIALLESS = true; +const NO_CREDENTIALLESS = false; + +const RESOURCE_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://test1.example.com" + ) + "credentialless_resource.sjs"; + +async function store(storer, url, requestCredentialMode) { + await SpecialPowers.spawn( + storer.linkedBrowser, + [url, requestCredentialMode], + async function(url, requestCredentialMode) { + const cache = await content.caches.open("v1"); + const fetchRequest = new content.Request(url, { + mode: "no-cors", + credentials: requestCredentialMode, + }); + + const fetchResponse = await content.fetch(fetchRequest); + content.wrappedJSObject.console.log(fetchResponse.headers); + await cache.put(fetchRequest, fetchResponse); + } + ); +} + +async function retrieve(retriever, resourceURL) { + return await SpecialPowers.spawn( + retriever.linkedBrowser, + [resourceURL], + async function(url) { + const cache = await content.caches.open("v1"); + try { + await cache.match(url); + return "retrieved"; + } catch (error) { + return "error"; + } + } + ); +} + +async function testCache( + storer, + storeRequestCredentialMode, + resourceCOEP, + retriever, + expectation +) { + const resourceURL = RESOURCE_URL + "?" + resourceCOEP; + + await store(storer, resourceURL, storeRequestCredentialMode); + const result = await retrieve(retriever, resourceURL); + + is(result, expectation); +} + +add_task(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.coep.credentialless", false], + ["dom.origin-trials.enabled", true], + ["dom.origin-trials.test-key.enabled", true], + ], + }); + + const noneTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TOP_LEVEL_URL + ); + const requireCorpTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TOP_LEVEL_URL + "?requirecorp" + ); + const credentiallessTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TOP_LEVEL_URL + "?credentialless" + ); + + await testCache(noneTab, "include", "", noneTab, "retrieved"); + await testCache(noneTab, "include", "", credentiallessTab, "error"); + await testCache(noneTab, "omit", "", credentiallessTab, "retrieved"); + await testCache( + noneTab, + "include", + "corp_cross_origin", + credentiallessTab, + "retrieved" + ); + await testCache(noneTab, "include", "", requireCorpTab, "error"); + await testCache( + noneTab, + "include", + "corp_cross_origin", + requireCorpTab, + "retrieved" + ); + await testCache(credentiallessTab, "include", "", noneTab, "retrieved"); + await testCache( + credentiallessTab, + "include", + "", + credentiallessTab, + "retrieved" + ); + await testCache(credentiallessTab, "include", "", requireCorpTab, "error"); + await testCache( + requireCorpTab, + "include", + "corp_cross_origin", + noneTab, + "retrieved" + ); + await testCache( + requireCorpTab, + "include", + "corp_cross_origin", + credentiallessTab, + "retrieved" + ); + await testCache( + requireCorpTab, + "include", + "corp_cross_origin", + requireCorpTab, + "retrieved" + ); + + await BrowserTestUtils.removeTab(noneTab); + await BrowserTestUtils.removeTab(requireCorpTab); + await BrowserTestUtils.removeTab(credentiallessTab); +}); diff --git a/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_1.js b/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_1.js new file mode 100644 index 000000000000..c8c5c0078b83 --- /dev/null +++ b/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_1.js @@ -0,0 +1,134 @@ +const TOP_LEVEL_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "open_credentialless_document.sjs"; + +const SAME_ORIGIN = "https://example.com"; +const CROSS_ORIGIN = "https://test1.example.com"; + +const USE_CREDENTIALLESS = true; +const NO_CREDENTIALLESS = false; + +const GET_STATE_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "store_header.sjs?getstate"; + +async function addCookieToOrigin(origin) { + const fetchRequestURL = + getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) + + "store_header.sjs?addcookie"; + + const addcookieTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + fetchRequestURL + ); + + await SpecialPowers.spawn(addcookieTab.linkedBrowser, [], async function() { + content.document.cookie = "coep=credentialless; SameSite=None; Secure"; + }); + await BrowserTestUtils.removeTab(addcookieTab); +} + +async function testOrigin( + fetchOrigin, + isCredentialless, + fetchRequestMode, + fetchRequestCrendentials, + expectedCookieResult +) { + let topLevelUrl = TOP_LEVEL_URL; + if (isCredentialless) { + topLevelUrl += "?credentialless"; + } + + const noCredentiallessTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + topLevelUrl + ); + + const fetchRequestURL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + fetchOrigin + ) + "store_header.sjs?checkheader"; + + await SpecialPowers.spawn( + noCredentiallessTab.linkedBrowser, + [ + fetchRequestURL, + fetchRequestMode, + fetchRequestCrendentials, + GET_STATE_URL, + expectedCookieResult, + ], + async function( + fetchRequestURL, + fetchRequestMode, + fetchRequestCrendentials, + getStateURL, + expectedCookieResult + ) { + // When store_header.sjs receives this request, it will store + // whether it has received the cookie as a shared state. + await content.fetch(fetchRequestURL, { + mode: fetchRequestMode, + credentials: fetchRequestCrendentials, + }); + + // This request is used to get the saved state from the + // previous fetch request. + const response = await content.fetch(getStateURL, { + mode: "cors", + }); + const text = await response.text(); + is(text, expectedCookieResult); + } + ); + + await BrowserTestUtils.removeTab(noCredentiallessTab); +} + +async function doTest( + origin, + fetchRequestMode, + fetchRequestCrendentials, + expectedCookieResultForNoCredentialless, + expectedCookieResultForCredentialless +) { + await testOrigin( + origin, + USE_CREDENTIALLESS, + fetchRequestMode, + fetchRequestCrendentials, + expectedCookieResultForCredentialless + ); + await testOrigin( + origin, + NO_CREDENTIALLESS, + fetchRequestMode, + fetchRequestCrendentials, + expectedCookieResultForNoCredentialless + ); +} + +add_task(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.coep.credentialless", false], + ["dom.origin-trials.enabled", true], + ["dom.origin-trials.test-key.enabled", true], + ], + }); + + await addCookieToOrigin(SAME_ORIGIN); + await addCookieToOrigin(CROSS_ORIGIN); + + // Cookies never sent with omit + await doTest(SAME_ORIGIN, "no-cors", "omit", "noCookie", "noCookie"); + await doTest(SAME_ORIGIN, "cors", "omit", "noCookie", "noCookie"); + await doTest(CROSS_ORIGIN, "no-cors", "omit", "noCookie", "noCookie"); + await doTest(CROSS_ORIGIN, "cors", "omit", "noCookie", "noCookie"); +}); diff --git a/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_2.js b/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_2.js new file mode 100644 index 000000000000..b7817ec1e0a9 --- /dev/null +++ b/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_2.js @@ -0,0 +1,134 @@ +const TOP_LEVEL_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "open_credentialless_document.sjs"; + +const SAME_ORIGIN = "https://example.com"; +const CROSS_ORIGIN = "https://test1.example.com"; + +const USE_CREDENTIALLESS = true; +const NO_CREDENTIALLESS = false; + +const GET_STATE_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "store_header.sjs?getstate"; + +async function addCookieToOrigin(origin) { + const fetchRequestURL = + getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) + + "store_header.sjs?addcookie"; + + const addcookieTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + fetchRequestURL + ); + + await SpecialPowers.spawn(addcookieTab.linkedBrowser, [], async function() { + content.document.cookie = "coep=credentialless; SameSite=None; Secure"; + }); + await BrowserTestUtils.removeTab(addcookieTab); +} + +async function testOrigin( + fetchOrigin, + isCredentialless, + fetchRequestMode, + fetchRequestCrendentials, + expectedCookieResult +) { + let topLevelUrl = TOP_LEVEL_URL; + if (isCredentialless) { + topLevelUrl += "?credentialless"; + } + + const noCredentiallessTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + topLevelUrl + ); + + const fetchRequestURL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + fetchOrigin + ) + "store_header.sjs?checkheader"; + + await SpecialPowers.spawn( + noCredentiallessTab.linkedBrowser, + [ + fetchRequestURL, + fetchRequestMode, + fetchRequestCrendentials, + GET_STATE_URL, + expectedCookieResult, + ], + async function( + fetchRequestURL, + fetchRequestMode, + fetchRequestCrendentials, + getStateURL, + expectedCookieResult + ) { + // When store_header.sjs receives this request, it will store + // whether it has received the cookie as a shared state. + await content.fetch(fetchRequestURL, { + mode: fetchRequestMode, + credentials: fetchRequestCrendentials, + }); + + // This request is used to get the saved state from the + // previous fetch request. + const response = await content.fetch(getStateURL, { + mode: "cors", + }); + const text = await response.text(); + is(text, expectedCookieResult); + } + ); + + await BrowserTestUtils.removeTab(noCredentiallessTab); +} + +async function doTest( + origin, + fetchRequestMode, + fetchRequestCrendentials, + expectedCookieResultForNoCredentialless, + expectedCookieResultForCredentialless +) { + await testOrigin( + origin, + USE_CREDENTIALLESS, + fetchRequestMode, + fetchRequestCrendentials, + expectedCookieResultForCredentialless + ); + await testOrigin( + origin, + NO_CREDENTIALLESS, + fetchRequestMode, + fetchRequestCrendentials, + expectedCookieResultForNoCredentialless + ); +} + +add_task(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.coep.credentialless", false], + ["dom.origin-trials.enabled", true], + ["dom.origin-trials.test-key.enabled", true], + ], + }); + + await addCookieToOrigin(SAME_ORIGIN); + await addCookieToOrigin(CROSS_ORIGIN); + + // Same-origin request contains Cookies. + await doTest(SAME_ORIGIN, "no-cors", "include", "hasCookie", "hasCookie"); + await doTest(SAME_ORIGIN, "cors", "include", "hasCookie", "hasCookie"); + await doTest(SAME_ORIGIN, "no-cors", "same-origin", "hasCookie", "hasCookie"); + await doTest(SAME_ORIGIN, "cors", "same-origin", "hasCookie", "hasCookie"); +}); diff --git a/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_3.js b/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_3.js new file mode 100644 index 000000000000..53970a097fd2 --- /dev/null +++ b/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_3.js @@ -0,0 +1,139 @@ +const TOP_LEVEL_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "open_credentialless_document.sjs"; + +const SAME_ORIGIN = "https://example.com"; +const CROSS_ORIGIN = "https://test1.example.com"; + +const USE_CREDENTIALLESS = true; +const NO_CREDENTIALLESS = false; + +const GET_STATE_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "store_header.sjs?getstate"; + +async function addCookieToOrigin(origin) { + const fetchRequestURL = + getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) + + "store_header.sjs?addcookie"; + + const addcookieTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + fetchRequestURL + ); + + await SpecialPowers.spawn(addcookieTab.linkedBrowser, [], async function() { + content.document.cookie = "coep=credentialless; SameSite=None; Secure"; + }); + await BrowserTestUtils.removeTab(addcookieTab); +} + +async function testOrigin( + fetchOrigin, + isCredentialless, + fetchRequestMode, + fetchRequestCrendentials, + expectedCookieResult +) { + let topLevelUrl = TOP_LEVEL_URL; + if (isCredentialless) { + topLevelUrl += "?credentialless"; + } + + const noCredentiallessTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + topLevelUrl + ); + + const fetchRequestURL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + fetchOrigin + ) + "store_header.sjs?checkheader"; + + await SpecialPowers.spawn( + noCredentiallessTab.linkedBrowser, + [ + fetchRequestURL, + fetchRequestMode, + fetchRequestCrendentials, + GET_STATE_URL, + expectedCookieResult, + ], + async function( + fetchRequestURL, + fetchRequestMode, + fetchRequestCrendentials, + getStateURL, + expectedCookieResult + ) { + // When store_header.sjs receives this request, it will store + // whether it has received the cookie as a shared state. + await content.fetch(fetchRequestURL, { + mode: fetchRequestMode, + credentials: fetchRequestCrendentials, + }); + + // This request is used to get the saved state from the + // previous fetch request. + const response = await content.fetch(getStateURL, { + mode: "cors", + }); + const text = await response.text(); + is(text, expectedCookieResult); + } + ); + + await BrowserTestUtils.removeTab(noCredentiallessTab); +} + +async function doTest( + origin, + fetchRequestMode, + fetchRequestCrendentials, + expectedCookieResultForNoCredentialless, + expectedCookieResultForCredentialless +) { + await testOrigin( + origin, + USE_CREDENTIALLESS, + fetchRequestMode, + fetchRequestCrendentials, + expectedCookieResultForCredentialless + ); + await testOrigin( + origin, + NO_CREDENTIALLESS, + fetchRequestMode, + fetchRequestCrendentials, + expectedCookieResultForNoCredentialless + ); +} + +add_task(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.coep.credentialless", false], + ["dom.origin-trials.enabled", true], + ["dom.origin-trials.test-key.enabled", true], + ], + }); + + await addCookieToOrigin(SAME_ORIGIN); + await addCookieToOrigin(CROSS_ORIGIN); + + // Cross-origin CORS requests contains Cookies, if credentials mode is set to + // 'include'. This does not depends on COEP. + await doTest(CROSS_ORIGIN, "cors", "include", "hasCookie", "hasCookie"); + await doTest(CROSS_ORIGIN, "cors", "same-origin", "noCookie", "noCookie"); + + // Cross-origin no-CORS requests includes Cookies when: + // 1. credentials mode is 'include' + // 2. COEP: is not credentialless. + await doTest(CROSS_ORIGIN, "no-cors", "include", "hasCookie", "noCookie"); + await doTest(CROSS_ORIGIN, "no-cors", "same-origin", "noCookie", "noCookie"); +}); diff --git a/dom/fetch/tests/browser_origin_trial_coep_credentialless_worker.js b/dom/fetch/tests/browser_origin_trial_coep_credentialless_worker.js new file mode 100644 index 000000000000..8afe9149df22 --- /dev/null +++ b/dom/fetch/tests/browser_origin_trial_coep_credentialless_worker.js @@ -0,0 +1,164 @@ +const TOP_LEVEL_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "open_credentialless_document.sjs"; + +const WORKER_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "credentialless_worker.sjs"; + +const GET_STATE_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "store_header.sjs?getstate"; + +const SAME_ORIGIN = "https://example.com"; +const CROSS_ORIGIN = "https://test1.example.com"; + +const WORKER_USES_CREDENTIALLESS = "credentialless"; +const WORKER_NOT_USE_CREDENTIALLESS = ""; + +async function addCookieToOrigin(origin) { + const fetchRequestURL = + getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) + + "store_header.sjs?addcookie"; + + const addcookieTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + fetchRequestURL + ); + + await SpecialPowers.spawn(addcookieTab.linkedBrowser, [], async function() { + content.document.cookie = "coep=credentialless; SameSite=None; Secure"; + }); + await BrowserTestUtils.removeTab(addcookieTab); +} + +async function testOrigin( + fetchOrigin, + isCredentialless, + workerUsesCredentialless, + expectedCookieResult +) { + let topLevelUrl = TOP_LEVEL_URL; + if (isCredentialless) { + topLevelUrl += "?credentialless"; + } + const noCredentiallessTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + topLevelUrl + ); + + const fetchRequestURL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + fetchOrigin + ) + "store_header.sjs?checkheader"; + + let workerScriptURL = WORKER_URL + "?" + workerUsesCredentialless; + + await SpecialPowers.spawn( + noCredentiallessTab.linkedBrowser, + [fetchRequestURL, GET_STATE_URL, workerScriptURL, expectedCookieResult], + async function( + fetchRequestURL, + getStateURL, + workerScriptURL, + expectedCookieResult + ) { + const worker = new content.Worker(workerScriptURL, {}); + + // When the worker receives this message, it'll send + // a fetch request to fetchRequestURL, and fetchRequestURL + // will store whether it has received the cookie as a + // shared state. + worker.postMessage(fetchRequestURL); + + if (expectedCookieResult == "error") { + await new Promise(r => { + worker.onerror = function() { + ok(true, "worker has error"); + r(); + }; + }); + } else { + await new Promise(r => { + worker.addEventListener("message", async function() { + // This request is used to get the saved state from the + // previous fetch request. + const response = await content.fetch(getStateURL, { + mode: "cors", + }); + const text = await response.text(); + is(text, expectedCookieResult); + r(); + }); + }); + } + } + ); + await BrowserTestUtils.removeTab(noCredentiallessTab); +} + +async function dedicatedWorkerTest( + origin, + workerCOEP, + expectedCookieResultForNoCredentialless, + expectedCookieResultForCredentialless +) { + await testOrigin( + origin, + false, + workerCOEP, + expectedCookieResultForNoCredentialless + ); + await testOrigin( + origin, + true, + workerCOEP, + expectedCookieResultForCredentialless + ); +} + +add_task(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.coep.credentialless", false], // Explicitly set credentialless to false because we want to test origin trial + ["dom.origin-trials.enabled", true], + ["dom.origin-trials.test-key.enabled", true], + ], + }); + + await addCookieToOrigin(SAME_ORIGIN); + await addCookieToOrigin(CROSS_ORIGIN); + + await dedicatedWorkerTest( + SAME_ORIGIN, + WORKER_NOT_USE_CREDENTIALLESS, + "hasCookie", + "error" + ); + await dedicatedWorkerTest( + SAME_ORIGIN, + WORKER_USES_CREDENTIALLESS, + "hasCookie", + "hasCookie" + ); + + await dedicatedWorkerTest( + CROSS_ORIGIN, + WORKER_NOT_USE_CREDENTIALLESS, + "hasCookie", + "error" + ); + await dedicatedWorkerTest( + CROSS_ORIGIN, + WORKER_USES_CREDENTIALLESS, + "noCookie", + "noCookie" + ); +}); diff --git a/dom/fetch/tests/credentialless_resource.sjs b/dom/fetch/tests/credentialless_resource.sjs new file mode 100644 index 000000000000..72d0d738ecdc --- /dev/null +++ b/dom/fetch/tests/credentialless_resource.sjs @@ -0,0 +1,21 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; +// small red image +const IMG_BYTES = atob( + "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" + + "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" +); + +function handleRequest(request, response) { + response.seizePower(); + response.write("HTTP/1.1 200 OK\r\n"); + response.write("Content-Type: image/png\r\n"); + if (request.queryString === "corp_cross_origin") { + response.write("Cross-Origin-Resource-Policy: cross-origin\r\n"); + } + response.write("\r\n"); + response.write(IMG_BYTES); + response.finish(); +} diff --git a/dom/fetch/tests/credentialless_worker.sjs b/dom/fetch/tests/credentialless_worker.sjs new file mode 100644 index 000000000000..a9e2197d1843 --- /dev/null +++ b/dom/fetch/tests/credentialless_worker.sjs @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const WORKER = ` + onmessage = function(event) { + fetch(event.data, { + mode: "no-cors", + credentials: "include" + }).then(function() { + postMessage("fetch done"); + }); + } +`; + +function handleRequest(request, response) { + if (request.queryString === "credentialless") { + response.setHeader("Cross-Origin-Embedder-Policy", "credentialless", true); + } + + response.setHeader("Content-Type", "application/javascript", false); + response.setStatusLine(request.httpVersion, "200", "Found"); + response.write(WORKER); +} diff --git a/dom/fetch/tests/open_credentialless_document.sjs b/dom/fetch/tests/open_credentialless_document.sjs new file mode 100644 index 000000000000..d01a3bb6a39d --- /dev/null +++ b/dom/fetch/tests/open_credentialless_document.sjs @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const HTML = ` + + + + + + Hello World +`; + +function handleRequest(request, response) { + if (request.queryString == "credentialless") { + response.setHeader("Cross-Origin-Embedder-Policy", "credentialless"); + } else if (request.queryString === "requirecorp") { + response.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); + } + response.setHeader("Content-Type", "text/html;charset=utf-8", false); + response.setStatusLine(request.httpVersion, "200", "Found"); + response.write(HTML); +} diff --git a/dom/fetch/tests/store_header.sjs b/dom/fetch/tests/store_header.sjs new file mode 100644 index 000000000000..3290a09995f4 --- /dev/null +++ b/dom/fetch/tests/store_header.sjs @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const key = "store_header"; +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/plain"); + response.setHeader("Access-Control-Allow-Origin", "https://example.com"); + response.setHeader("Access-Control-Allow-Credentials", "true"); + + if (request.queryString === "getstate") { + response.write(getSharedState(key)); + } else if (request.queryString === "checkheader") { + if (request.hasHeader("Cookie")) { + setSharedState(key, "hasCookie"); + } else { + setSharedState(key, "noCookie"); + } + } else { + // This is the first request which sets the cookie + } +} diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index 579043a768c5..51e3288c4cc8 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -2211,6 +2211,8 @@ interface nsIDOMWindowUtils : nsISupports { */ void resetMobileViewportManager(); + bool isCoepCredentialless(); + /** * NOTE: Currently works only on GTK+. */ diff --git a/dom/origin-trials/OriginTrials.cpp b/dom/origin-trials/OriginTrials.cpp index c8377ea036d2..e9c4eaddcbd8 100644 --- a/dom/origin-trials/OriginTrials.cpp +++ b/dom/origin-trials/OriginTrials.cpp @@ -212,6 +212,8 @@ static int32_t PrefState(OriginTrial aTrial) { return StaticPrefs::dom_origin_trials_test_trial_state(); case OriginTrial::OffscreenCanvas: return StaticPrefs::dom_origin_trials_offscreen_canvas_state(); + case OriginTrial::CoepCredentialless: + return StaticPrefs::dom_origin_trials_coep_credentialless_state(); case OriginTrial::MAX: MOZ_ASSERT_UNREACHABLE("Unknown trial!"); break; @@ -219,13 +221,7 @@ static int32_t PrefState(OriginTrial aTrial) { return 0; } -bool OriginTrials::IsEnabled(JSContext* aCx, JSObject* aObject, - OriginTrial aTrial) { - if (nsContentUtils::ThreadsafeIsSystemCaller(aCx)) { - return true; - } - LOG("OriginTrials::IsEnabled(%d)\n", int(aTrial)); - +bool OriginTrials::IsEnabled(OriginTrial aTrial) const { switch (PrefState(aTrial)) { case 1: return true; @@ -235,6 +231,15 @@ bool OriginTrials::IsEnabled(JSContext* aCx, JSObject* aObject, break; } + return mEnabledTrials.contains(aTrial); +} + +bool OriginTrials::IsEnabled(JSContext* aCx, JSObject* aObject, + OriginTrial aTrial) { + if (nsContentUtils::ThreadsafeIsSystemCaller(aCx)) { + return true; + } + LOG("OriginTrials::IsEnabled(%d)\n", int(aTrial)); nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); MOZ_ASSERT(global); return global && global->Trials().IsEnabled(aTrial); diff --git a/dom/origin-trials/OriginTrials.h b/dom/origin-trials/OriginTrials.h index 5f62c39b75ab..d1de36a20267 100644 --- a/dom/origin-trials/OriginTrials.h +++ b/dom/origin-trials/OriginTrials.h @@ -40,9 +40,7 @@ class OriginTrials final { void UpdateFromToken(const nsAString& aBase64EncodedToken, nsIPrincipal* aPrincipal); - bool IsEnabled(OriginTrial aTrial) const { - return mEnabledTrials.contains(aTrial); - } + bool IsEnabled(OriginTrial aTrial) const; // Checks whether a given origin trial is enabled for a given call. static bool IsEnabled(JSContext*, JSObject*, OriginTrial); diff --git a/dom/origin-trials/ffi/lib.rs b/dom/origin-trials/ffi/lib.rs index b28fc659ff8b..f850fe52458d 100644 --- a/dom/origin-trials/ffi/lib.rs +++ b/dom/origin-trials/ffi/lib.rs @@ -10,6 +10,7 @@ pub enum OriginTrial { // NOTE(emilio): 0 is reserved for WebIDL usage. TestTrial = 1, OffscreenCanvas = 2, + CoepCredentialless = 3, MAX, } @@ -19,6 +20,7 @@ impl OriginTrial { Some(match s { "TestTrial" => Self::TestTrial, "OffscreenCanvas" => Self::OffscreenCanvas, + "CoepCredentialless" => Self::CoepCredentialless, _ => return None, }) } diff --git a/dom/origin-trials/tests/mochitest.ini b/dom/origin-trials/tests/mochitest.ini index 4c1103a1f588..68b834f5e035 100644 --- a/dom/origin-trials/tests/mochitest.ini +++ b/dom/origin-trials/tests/mochitest.ini @@ -2,6 +2,7 @@ prefs = dom.origin-trials.enabled=true dom.origin-trials.test-key.enabled=true + browser.tabs.remote.coep.credentialless=false support-files = test_header_simple.html^headers^ common.js diff --git a/dom/origin-trials/tests/test_meta_simple.html b/dom/origin-trials/tests/test_meta_simple.html index 979f497c84bc..9afd4a645c16 100644 --- a/dom/origin-trials/tests/test_meta_simple.html +++ b/dom/origin-trials/tests/test_meta_simple.html @@ -3,6 +3,8 @@ + + diff --git a/dom/workers/loader/CacheLoadHandler.cpp b/dom/workers/loader/CacheLoadHandler.cpp index 872dd7a3ce2f..c1ea3c29bd68 100644 --- a/dom/workers/loader/CacheLoadHandler.cpp +++ b/dom/workers/loader/CacheLoadHandler.cpp @@ -382,7 +382,9 @@ void CacheLoadHandler::ResolvedCallback(JSContext* aCx, headers->Get("cross-origin-embedder-policy"_ns, coepHeader, IgnoreErrors()); nsILoadInfo::CrossOriginEmbedderPolicy coep = - NS_GetCrossOriginEmbedderPolicyFromHeader(coepHeader); + NS_GetCrossOriginEmbedderPolicyFromHeader( + coepHeader, + mWorkerPrivate->Trials().IsEnabled(OriginTrial::CoepCredentialless)); rv = ScriptResponseHeaderProcessor::ProcessCrossOriginEmbedderPolicyHeader( mWorkerPrivate, coep, mLoader->IsMainScript()); diff --git a/dom/workers/loader/ScriptResponseHeaderProcessor.cpp b/dom/workers/loader/ScriptResponseHeaderProcessor.cpp index 06bcf91b2426..7b844d66011c 100644 --- a/dom/workers/loader/ScriptResponseHeaderProcessor.cpp +++ b/dom/workers/loader/ScriptResponseHeaderProcessor.cpp @@ -49,7 +49,9 @@ nsresult ScriptResponseHeaderProcessor::ProcessCrossOriginEmbedderPolicyHeader( } nsILoadInfo::CrossOriginEmbedderPolicy coep; - MOZ_TRY(httpChannel->GetResponseEmbedderPolicy(&coep)); + MOZ_TRY(httpChannel->GetResponseEmbedderPolicy( + mWorkerPrivate->Trials().IsEnabled(OriginTrial::CoepCredentialless), + &coep)); return ProcessCrossOriginEmbedderPolicyHeader(mWorkerPrivate, coep, mIsMainScript); diff --git a/ipc/glue/BackgroundUtils.cpp b/ipc/glue/BackgroundUtils.cpp index df63b2b4b123..b7a0e54a0515 100644 --- a/ipc/glue/BackgroundUtils.cpp +++ b/ipc/glue/BackgroundUtils.cpp @@ -537,7 +537,9 @@ nsresult LoadInfoToLoadInfoArgs(nsILoadInfo* aLoadInfo, aLoadInfo->GetIsFromObjectOrEmbed(), cookieJarSettingsArgs, aLoadInfo->GetRequestBlockingReason(), maybeCspToInheritInfo, aLoadInfo->GetStoragePermission(), aLoadInfo->GetIsMetaRefresh(), - aLoadInfo->GetLoadingEmbedderPolicy(), unstrippedURI)); + aLoadInfo->GetLoadingEmbedderPolicy(), + aLoadInfo->GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(), + unstrippedURI)); return NS_OK; } @@ -779,7 +781,9 @@ nsresult LoadInfoArgsToLoadInfo( loadInfoArgs.isInDevToolsContext(), loadInfoArgs.parserCreatedScript(), loadInfoArgs.storagePermission(), loadInfoArgs.isMetaRefresh(), loadInfoArgs.requestBlockingReason(), loadingContext, - loadInfoArgs.loadingEmbedderPolicy(), loadInfoArgs.unstrippedURI()); + loadInfoArgs.loadingEmbedderPolicy(), + loadInfoArgs.originTrialCoepCredentiallessEnabledForTopLevel(), + loadInfoArgs.unstrippedURI()); if (loadInfoArgs.isFromProcessingFrameAttributes()) { loadInfo->SetIsFromProcessingFrameAttributes(); diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index d199f545c668..32f36680958f 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -1542,6 +1542,7 @@ type: RelaxedAtomicBool value: @IS_NIGHTLY_BUILD@ mirror: always + do_not_use_directly: true # When this pref is enabled top level loads with a mismatched # Cross-Origin-Opener-Policy header will be loaded in a separate process. @@ -2997,6 +2998,13 @@ value: 0 mirror: always +# Origin trial state for COEP: Credentialless. +# 0: normal, 1: always-enabled, 2: always-disabled +- name: dom.origin-trials.coep-credentialless.state + type: RelaxedAtomicInt32 + value: 0 + mirror: always + # Is support for Window.paintWorklet enabled? - name: dom.paintWorklet.enabled type: bool diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp index 9277c804444a..817102b2c534 100644 --- a/netwerk/base/LoadInfo.cpp +++ b/netwerk/base/LoadInfo.cpp @@ -513,6 +513,11 @@ LoadInfo::LoadInfo(dom::WindowGlobalParent* aParentWGP, RefPtr ctx = WindowContext::GetById(mInnerWindowID); if (ctx) { mLoadingEmbedderPolicy = ctx->GetEmbedderPolicy(); + + if (Document* document = ctx->GetDocument()) { + mIsOriginTrialCoepCredentiallessEnabledForTopLevel = + document->Trials().IsEnabled(OriginTrial::CoepCredentialless); + } } } @@ -601,6 +606,8 @@ LoadInfo::LoadInfo(const LoadInfo& rhs) mIsMediaInitialRequest(rhs.mIsMediaInitialRequest), mIsFromObjectOrEmbed(rhs.mIsFromObjectOrEmbed), mLoadingEmbedderPolicy(rhs.mLoadingEmbedderPolicy), + mIsOriginTrialCoepCredentiallessEnabledForTopLevel( + rhs.mIsOriginTrialCoepCredentiallessEnabledForTopLevel), mUnstrippedURI(rhs.mUnstrippedURI) {} LoadInfo::LoadInfo( @@ -640,6 +647,7 @@ LoadInfo::LoadInfo( nsILoadInfo::StoragePermissionState aStoragePermission, bool aIsMetaRefresh, uint32_t aRequestBlockingReason, nsINode* aLoadingContext, nsILoadInfo::CrossOriginEmbedderPolicy aLoadingEmbedderPolicy, + bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel, nsIURI* aUnstrippedURI) : mLoadingPrincipal(aLoadingPrincipal), mTriggeringPrincipal(aTriggeringPrincipal), @@ -707,6 +715,8 @@ LoadInfo::LoadInfo( mIsMetaRefresh(aIsMetaRefresh), mLoadingEmbedderPolicy(aLoadingEmbedderPolicy), + mIsOriginTrialCoepCredentiallessEnabledForTopLevel( + aIsOriginTrialCoepCredentiallessEnabledForTopLevel), mUnstrippedURI(aUnstrippedURI) { // Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal MOZ_ASSERT(mLoadingPrincipal || @@ -2104,6 +2114,22 @@ LoadInfo::SetLoadingEmbedderPolicy( return NS_OK; } +NS_IMETHODIMP +LoadInfo::GetIsOriginTrialCoepCredentiallessEnabledForTopLevel( + bool* aIsOriginTrialCoepCredentiallessEnabledForTopLevel) { + *aIsOriginTrialCoepCredentiallessEnabledForTopLevel = + mIsOriginTrialCoepCredentiallessEnabledForTopLevel; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsOriginTrialCoepCredentiallessEnabledForTopLevel( + bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel) { + mIsOriginTrialCoepCredentiallessEnabledForTopLevel = + aIsOriginTrialCoepCredentiallessEnabledForTopLevel; + return NS_OK; +} + already_AddRefed LoadInfo::GetCsp() { // Before querying the CSP from the client we have to check if the // triggeringPrincipal originates from an addon and potentially diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h index 21a1ff8bb50e..213ab30006bf 100644 --- a/netwerk/base/LoadInfo.h +++ b/netwerk/base/LoadInfo.h @@ -232,6 +232,7 @@ class LoadInfo final : public nsILoadInfo { bool aIsMetaRefresh, uint32_t aRequestBlockingReason, nsINode* aLoadingContext, nsILoadInfo::CrossOriginEmbedderPolicy aLoadingEmbedderPolicy, + bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel, nsIURI* aUnstrippedURI); LoadInfo(const LoadInfo& rhs); @@ -359,6 +360,8 @@ class LoadInfo final : public nsILoadInfo { nsILoadInfo::CrossOriginEmbedderPolicy mLoadingEmbedderPolicy = nsILoadInfo::EMBEDDER_POLICY_NULL; + bool mIsOriginTrialCoepCredentiallessEnabledForTopLevel = false; + nsCOMPtr mUnstrippedURI; }; diff --git a/netwerk/base/TRRLoadInfo.cpp b/netwerk/base/TRRLoadInfo.cpp index 58048ad6156e..7be1c78e40c9 100644 --- a/netwerk/base/TRRLoadInfo.cpp +++ b/netwerk/base/TRRLoadInfo.cpp @@ -746,6 +746,18 @@ TRRLoadInfo::SetLoadingEmbedderPolicy( return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +TRRLoadInfo::GetIsOriginTrialCoepCredentiallessEnabledForTopLevel( + bool* aIsOriginTrialCoepCredentiallessEnabledForTopLevel) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsOriginTrialCoepCredentiallessEnabledForTopLevel( + bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel) { + return NS_ERROR_NOT_IMPLEMENTED; +} + NS_IMETHODIMP TRRLoadInfo::GetUnstrippedURI(nsIURI** aURI) { return NS_ERROR_NOT_IMPLEMENTED; diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl index 9c638e8494f0..0088b129d532 100644 --- a/netwerk/base/nsILoadInfo.idl +++ b/netwerk/base/nsILoadInfo.idl @@ -1394,6 +1394,11 @@ interface nsILoadInfo : nsISupports [infallible] attribute nsILoadInfo_CrossOriginEmbedderPolicy loadingEmbedderPolicy; + /** + * This attribute will be true if the top level document has COEP: + * credentialless enabled in Origin Trial. + */ + [infallible] attribute boolean isOriginTrialCoepCredentiallessEnabledForTopLevel; /** * This attribute will be true if this is a load triggered by a media * element. diff --git a/netwerk/base/nsNetUtil.cpp b/netwerk/base/nsNetUtil.cpp index 5881400258f4..3fcb9b6803da 100644 --- a/netwerk/base/nsNetUtil.cpp +++ b/netwerk/base/nsNetUtil.cpp @@ -2605,7 +2605,8 @@ nsresult NS_MaybeOpenChannelUsingAsyncOpen(nsIChannel* aChannel, } nsILoadInfo::CrossOriginEmbedderPolicy -NS_GetCrossOriginEmbedderPolicyFromHeader(const nsACString& aHeader) { +NS_GetCrossOriginEmbedderPolicyFromHeader( + const nsACString& aHeader, bool aIsOriginTrialCoepCredentiallessEnabled) { nsCOMPtr sfv = GetSFVService(); nsCOMPtr item; @@ -2634,7 +2635,8 @@ NS_GetCrossOriginEmbedderPolicyFromHeader(const nsACString& aHeader) { if (embedderPolicy.EqualsLiteral("require-corp")) { return nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP; } else if (embedderPolicy.EqualsLiteral("credentialless") && - StaticPrefs::browser_tabs_remote_coep_credentialless()) { + IsCoepCredentiallessEnabled( + aIsOriginTrialCoepCredentiallessEnabled)) { return nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS; } @@ -3901,3 +3903,9 @@ void CheckForBrokenChromeURL(nsILoadInfo* aLoadInfo, nsIURI* aURI) { printf_stderr("Missing chrome or resource URL: %s\n", spec.get()); } } + +bool IsCoepCredentiallessEnabled(bool aIsOriginTrialCoepCredentiallessEnabled) { + return StaticPrefs:: + browser_tabs_remote_coep_credentialless_DoNotUseDirectly() || + aIsOriginTrialCoepCredentiallessEnabled; +} diff --git a/netwerk/base/nsNetUtil.h b/netwerk/base/nsNetUtil.h index 9228eaf34447..e5c3c2176c1e 100644 --- a/netwerk/base/nsNetUtil.h +++ b/netwerk/base/nsNetUtil.h @@ -59,6 +59,7 @@ class nsIIncrementalStreamLoaderObserver; namespace mozilla { class Encoding; class OriginAttributes; +class OriginTrials; namespace dom { class ClientInfo; class PerformanceStorage; @@ -831,7 +832,8 @@ nsresult NS_MaybeOpenChannelUsingAsyncOpen(nsIChannel* aChannel, * See: https://mikewest.github.io/corpp/#parsing */ nsILoadInfo::CrossOriginEmbedderPolicy -NS_GetCrossOriginEmbedderPolicyFromHeader(const nsACString& aHeader); +NS_GetCrossOriginEmbedderPolicyFromHeader( + const nsACString& aHeader, bool aIsOriginTrialCoepCredentiallessEnabled); /** Given the first (disposition) token from a Content-Disposition header, * tell whether it indicates the content is inline or attachment @@ -1069,4 +1071,6 @@ nsresult NS_HasRootDomain(const nsACString& aInput, const nsACString& aHost, void CheckForBrokenChromeURL(nsILoadInfo* aLoadInfo, nsIURI* aURI); +bool IsCoepCredentiallessEnabled(bool aIsOriginTrialCoepCredentiallessEnabled); + #endif // !nsNetUtil_h__ diff --git a/netwerk/ipc/NeckoChannelParams.ipdlh b/netwerk/ipc/NeckoChannelParams.ipdlh index 9d6a7474e69a..aa962e54aab3 100644 --- a/netwerk/ipc/NeckoChannelParams.ipdlh +++ b/netwerk/ipc/NeckoChannelParams.ipdlh @@ -166,6 +166,7 @@ struct LoadInfoArgs StoragePermissionState storagePermission; bool isMetaRefresh; CrossOriginEmbedderPolicy loadingEmbedderPolicy; + bool originTrialCoepCredentiallessEnabledForTopLevel; nsIURI unstrippedURI; }; diff --git a/netwerk/protocol/http/ClassifierDummyChannel.cpp b/netwerk/protocol/http/ClassifierDummyChannel.cpp index 7b9f8fcee27e..079ec1bc7f95 100644 --- a/netwerk/protocol/http/ClassifierDummyChannel.cpp +++ b/netwerk/protocol/http/ClassifierDummyChannel.cpp @@ -772,6 +772,7 @@ NS_IMETHODIMP ClassifierDummyChannel::IsThirdPartySocialTrackingResource( void ClassifierDummyChannel::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() {} NS_IMETHODIMP ClassifierDummyChannel::GetResponseEmbedderPolicy( + bool aIsOriginTrialCoepCredentiallessEnabled, nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) { return NS_ERROR_NOT_IMPLEMENTED; } diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index add5850010ca..61f65c2a14fb 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -17,6 +17,7 @@ #include "ReferrerInfo.h" #include "mozIRemoteLazyInputStream.h" #include "mozIThirdPartyUtil.h" +#include "mozilla/LoadInfo.h" #include "mozilla/AntiTrackingUtils.h" #include "mozilla/BasePrincipal.h" #include "mozilla/BinarySearch.h" @@ -2403,7 +2404,11 @@ nsresult HttpBaseChannel::ProcessCrossOriginEmbedderPolicyHeader() { nsILoadInfo::CrossOriginEmbedderPolicy resultPolicy = nsILoadInfo::EMBEDDER_POLICY_NULL; - rv = GetResponseEmbedderPolicy(&resultPolicy); + bool isCoepCredentiallessEnabled; + rv = mLoadInfo->GetIsOriginTrialCoepCredentiallessEnabledForTopLevel( + &isCoepCredentiallessEnabled); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetResponseEmbedderPolicy(isCoepCredentiallessEnabled, &resultPolicy); if (NS_FAILED(rv)) { return NS_OK; } @@ -2469,8 +2474,7 @@ nsresult HttpBaseChannel::ProcessCrossOriginResourcePolicyHeader() { if (StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) { if (content.IsEmpty()) { if (mLoadInfo->GetLoadingEmbedderPolicy() == - nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS && - StaticPrefs::browser_tabs_remote_coep_credentialless()) { + nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) { bool requestIncludesCredentials = false; nsresult rv = GetCorsIncludeCredentials(&requestIncludesCredentials); if (NS_FAILED(rv)) { @@ -5670,6 +5674,7 @@ void HttpBaseChannel::SetIPv4Disabled() { mCaps |= NS_HTTP_DISABLE_IPV4; } void HttpBaseChannel::SetIPv6Disabled() { mCaps |= NS_HTTP_DISABLE_IPV6; } NS_IMETHODIMP HttpBaseChannel::GetResponseEmbedderPolicy( + bool aIsOriginTrialCoepCredentiallessEnabled, nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) { *aOutPolicy = nsILoadInfo::EMBEDDER_POLICY_NULL; if (!mResponseHead) { @@ -5684,8 +5689,8 @@ NS_IMETHODIMP HttpBaseChannel::GetResponseEmbedderPolicy( nsAutoCString content; Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Embedder_Policy, content); - - *aOutPolicy = NS_GetCrossOriginEmbedderPolicyFromHeader(content); + *aOutPolicy = NS_GetCrossOriginEmbedderPolicyFromHeader( + content, aIsOriginTrialCoepCredentiallessEnabled); return NS_OK; } @@ -5747,11 +5752,15 @@ NS_IMETHODIMP HttpBaseChannel::ComputeCrossOriginOpenerPolicy( } else if (openerPolicy.EqualsLiteral("same-origin-allow-popups")) { policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS; } - if (policy == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN) { nsILoadInfo::CrossOriginEmbedderPolicy coep = nsILoadInfo::EMBEDDER_POLICY_NULL; - if (NS_SUCCEEDED(GetResponseEmbedderPolicy(&coep)) && + bool isCoepCredentiallessEnabled; + rv = mLoadInfo->GetIsOriginTrialCoepCredentiallessEnabledForTopLevel( + &isCoepCredentiallessEnabled); + NS_ENSURE_SUCCESS(rv, rv); + if (NS_SUCCEEDED( + GetResponseEmbedderPolicy(isCoepCredentiallessEnabled, &coep)) && (coep == nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP || coep == nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS)) { policy = diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index 8c1d528b10ef..09c273d469f8 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -330,6 +330,7 @@ class HttpBaseChannel : public nsHashPropertyBag, nsILoadInfo::CrossOriginOpenerPolicy* aOutPolicy) override; NS_IMETHOD HasCrossOriginOpenerPolicyMismatch(bool* aIsMismatch) override; NS_IMETHOD GetResponseEmbedderPolicy( + bool aIsOriginTrialCoepCredentiallessEnabled, nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) override; inline void CleanRedirectCacheChainIfNecessary() { diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl index 2f91ba8bc146..8173ef67b46c 100644 --- a/netwerk/protocol/http/nsIHttpChannelInternal.idl +++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl @@ -442,7 +442,7 @@ interface nsIHttpChannelInternal : nsISupports bool hasCrossOriginOpenerPolicyMismatch(); [noscript] - nsILoadInfo_CrossOriginEmbedderPolicy getResponseEmbedderPolicy(); + nsILoadInfo_CrossOriginEmbedderPolicy getResponseEmbedderPolicy(in boolean aIsOriginTrialCoepCredentiallessEnabled); [noscript, notxpcom, nostdcall] void DoDiagnosticAssertWhenOnStopNotCalledOnDestroy();