From 3ae5ab32635c3ca24d22db974686727de8311d87 Mon Sep 17 00:00:00 2001
From: Sean Feng <sefeng@mozilla.com>
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, a=dsmith

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 b34ad3970c1ec..3c074b0915484 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 972be59520940..8cee829af918a 100644
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -4706,6 +4706,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 8705dea1ab1c1..6fd8244288aa9 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 0000000000000..851ab974190e7
--- /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 0000000000000..c8c5c0078b832
--- /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 0000000000000..b7817ec1e0a9f
--- /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 0000000000000..53970a097fd22
--- /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 0000000000000..8afe9149df227
--- /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 0000000000000..72d0d738ecdcb
--- /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 0000000000000..a9e2197d1843b
--- /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 0000000000000..d01a3bb6a39df
--- /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 = `<!DOCTYPE HTML>
+<head>
+  <!-- Created with: mktoken --origin 'https://example.com' --feature CoepCredentialless --expiry 'Wed, 01 Jan 3000 01:00:00 +0100' --sign test-keys/test-ecdsa.pkcs8 -->
+  <meta http-equiv="origin-trial" content="Az+DK2Kczk8Xz1cAlD+TkvPZmuM2uJZ2CFefbp2hLuCU9FbUqxWTyQ2tEYr50r0syKELcOZLAPaABw8aYTLHn5YAAABUeyJvcmlnaW4iOiJodHRwczovL2V4YW1wbGUuY29tIiwiZmVhdHVyZSI6IkNvZXBDcmVkZW50aWFsbGVzcyIsImV4cGlyeSI6MzI1MDM2ODAwMDB9">
+</head>
+<html>
+  <body>Hello World</body>
+</html>`;
+
+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 0000000000000..3290a09995f4e
--- /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 579043a768c50..51e3288c4cc8f 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 c8377ea036d28..e9c4eaddcbd8d 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 5f62c39b75abe..d1de36a202675 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 b28fc659ff8bf..f850fe52458db 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 4c1103a1f5885..68b834f5e0355 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 979f497c84bcd..9afd4a645c165 100644
--- a/dom/origin-trials/tests/test_meta_simple.html
+++ b/dom/origin-trials/tests/test_meta_simple.html
@@ -3,6 +3,8 @@
 <meta http-equiv="origin-trial" content="AyGdETIKWLLqe+chG57f74gZcjYSfbdYAapEq7DA49E6CmaYaPmaoXh/4tAe5XJJJdwwpFVal7hz/irC+Wvp1HgAAABLeyJvcmlnaW4iOiJodHRwczovL2V4YW1wbGUuY29tIiwiZmVhdHVyZSI6IlRlc3RUcmlhbCIsImV4cGlyeSI6MzI1MDM2ODAwMDB9">
 <!-- Created with: mktoken --origin 'https://example.com' --feature OffscreenCanvas --expiry 'Wed, 01 Jan 3000 01:00:00 +0100' --sign test-keys/test-ecdsa.pkcs8 -->
 <meta http-equiv="origin-trial" content="Ay92n3CdO5VIYbmQB7t7r7e4c34nT1k9zbX5ON2JthrXaOFxLn5NieN7ITlKhPbmPSLA4qoS+TBdshqEUwmVaIwAAABReyJvcmlnaW4iOiJodHRwczovL2V4YW1wbGUuY29tIiwiZmVhdHVyZSI6Ik9mZnNjcmVlbkNhbnZhcyIsImV4cGlyeSI6MzI1MDM2ODAwMDB9">
+<!-- Created with: mktoken --origin 'https://example.com' --feature CoepCredentialless --expiry 'Wed, 01 Jan 3000 01:00:00 +0100' --sign test-keys/test-ecdsa.pkcs8 -->
+<meta http-equiv="origin-trial" content="Az+DK2Kczk8Xz1cAlD+TkvPZmuM2uJZ2CFefbp2hLuCU9FbUqxWTyQ2tEYr50r0syKELcOZLAPaABw8aYTLHn5YAAABUeyJvcmlnaW4iOiJodHRwczovL2V4YW1wbGUuY29tIiwiZmVhdHVyZSI6IkNvZXBDcmVkZW50aWFsbGVzcyIsImV4cGlyeSI6MzI1MDM2ODAwMDB9">
 <script src="/tests/SimpleTest/SimpleTest.js"></script>
 <script src="common.js"></script>
 <script>
@@ -10,4 +12,7 @@
   add_task(function() {
     ok(!!self.OffscreenCanvas, "OffscreenCanvas trial works.");
   });
+  add_task(function() {
+    ok(!!SpecialPowers.DOMWindowUtils.isCoepCredentialless(), "CoepCredentialless trial works.");
+  });
 </script>
diff --git a/dom/workers/loader/CacheLoadHandler.cpp b/dom/workers/loader/CacheLoadHandler.cpp
index 872dd7a3ce2f9..c1ea3c29bd685 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 06bcf91b24266..7b844d66011c2 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 df63b2b4b1235..b7a0e54a05155 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 987ebb4f79345..ead3626d4fe4c 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 9277c804444af..817102b2c5349 100644
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -513,6 +513,11 @@ LoadInfo::LoadInfo(dom::WindowGlobalParent* aParentWGP,
   RefPtr<WindowContext> 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<nsIContentSecurityPolicy> 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 21a1ff8bb50e0..213ab30006bf1 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<nsIURI> mUnstrippedURI;
 };
 
diff --git a/netwerk/base/TRRLoadInfo.cpp b/netwerk/base/TRRLoadInfo.cpp
index 58048ad6156eb..7be1c78e40c92 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 9c638e8494f08..0088b129d5325 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 5881400258f4f..3fcb9b6803dad 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<nsISFVService> sfv = GetSFVService();
 
   nsCOMPtr<nsISFVItem> 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 9228eaf344478..e5c3c2176c1e6 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 9d6a7474e69a7..aa962e54aab31 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 7b9f8fcee27e0..079ec1bc7f952 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 add5850010ca5..61f65c2a14fb5 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 8c1d528b10efb..09c273d469f85 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 2f91ba8bc1464..8173ef67b46cc 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();
-- 
GitLab