Bug 1778492 - Add an origin trial for coep: credentialless r=emilio,necko-reviewers,kershaw

Differential Revision: https://phabricator.services.mozilla.com/D151381
This commit is contained in:
Sean Feng 2022-07-27 20:33:19 +00:00
parent bda035b7f5
commit 288cbe9f46
33 changed files with 973 additions and 26 deletions

View File

@ -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) {

View File

@ -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();

View File

@ -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

View File

@ -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);
});

View File

@ -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");
});

View File

@ -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");
});

View File

@ -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");
});

View File

@ -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"
);
});

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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
}
}

View File

@ -2211,6 +2211,8 @@ interface nsIDOMWindowUtils : nsISupports {
*/
void resetMobileViewportManager();
bool isCoepCredentialless();
/**
* NOTE: Currently works only on GTK+.
*/

View File

@ -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);

View File

@ -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);

View File

@ -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,
})
}

View File

@ -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

View File

@ -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>

View File

@ -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());

View File

@ -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);

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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;
};

View File

@ -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;

View File

@ -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.

View File

@ -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;
}

View File

@ -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__

View File

@ -166,6 +166,7 @@ struct LoadInfoArgs
StoragePermissionState storagePermission;
bool isMetaRefresh;
CrossOriginEmbedderPolicy loadingEmbedderPolicy;
bool originTrialCoepCredentiallessEnabledForTopLevel;
nsIURI unstrippedURI;
};

View File

@ -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;
}

View File

@ -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 =

View File

@ -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() {

View File

@ -442,7 +442,7 @@ interface nsIHttpChannelInternal : nsISupports
bool hasCrossOriginOpenerPolicyMismatch();
[noscript]
nsILoadInfo_CrossOriginEmbedderPolicy getResponseEmbedderPolicy();
nsILoadInfo_CrossOriginEmbedderPolicy getResponseEmbedderPolicy(in boolean aIsOriginTrialCoepCredentiallessEnabled);
[noscript, notxpcom, nostdcall]
void DoDiagnosticAssertWhenOnStopNotCalledOnDestroy();