Bug 2027823 - Pay attention to storage partitioning for sessionStorage cloning in window.open - r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D290965
This commit is contained in:
Benjamin VanderSloot
2026-04-29 21:11:07 +00:00
committed by bvandersloot@mozilla.com
parent a0f07ee021
commit 3de9960e92
11 changed files with 190 additions and 37 deletions
@@ -22,15 +22,6 @@ add_task(async function () {
`cross-origin site and then navigating back`
);
await SpecialPowers.pushPrefEnv({
set: [
[
"privacy.partition.always_partition_third_party_non_cookie_storage",
false,
],
],
});
BrowserTestUtils.startLoadingURIString(browser, URL1);
await BrowserTestUtils.browserLoaded(browser);
@@ -104,8 +95,8 @@ add_task(async function () {
await SpecialPowers.spawn(
browser,
[ORIGIN2, ORIGIN1, URL1, key, value],
async (ORIGIN, iframeORIGIN, iframeURL, key, value) => {
[ORIGIN2, ORIGIN1, URL1, key],
async (ORIGIN, iframeORIGIN, iframeURL, key) => {
is(content.window.origin, ORIGIN, `Navigate to ${ORIGIN} as expected`);
let iframe = content.document.createElement("iframe");
@@ -115,32 +106,20 @@ add_task(async function () {
await content.SpecialPowers.spawn(
iframe,
[iframeORIGIN, key, value],
async function (ORIGIN, key, value) {
[iframeORIGIN, key],
async function (ORIGIN, key) {
is(
content.window.origin,
ORIGIN,
`Navigate to ${ORIGIN} as expected`
);
// Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5)
// Acquire storage access permission here so that the iframe has
// first-party access to the sessionStorage. Without this, it is
// isolated and this test will always fail
SpecialPowers.wrap(content.document).notifyUserGestureActivation();
await SpecialPowers.addPermission(
"storageAccessAPI",
true,
content.window.location.href
);
await SpecialPowers.wrap(content.document).requestStorageAccess();
let value1 = content.window.sessionStorage.getItem(key);
is(
value1,
value,
null,
`SessionStorage for ${key} in ${content.window.origin} is ` +
`preserved`
`partitioned and should not see the top-level value`
);
}
);
@@ -0,0 +1,19 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script type="text/javascript">
window.onload = function() {
const bc = SpecialPowers.wrap(BroadcastChannel)
.unpartitionedTestingChannel("sessionStorageTargetBlank");
bc.postMessage({
key1: sessionStorage.getItem("key1"),
length: sessionStorage.length,
hasOpener: !!window.opener,
});
bc.close();
parent.close();
};
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,19 @@
<!DOCTYPE HTML>
<html>
<!--
This page is opened as a popup in xorigin mode by test_sessionStorageTargetBlank.html.
It embeds file_targetBlankChild.html in an iframe from the test origin so it
runs in the same storage partition as the test.
-->
<body>
<script>
let params = new URLSearchParams(location.search);
let origin = params.get("origin");
let iframe = document.createElement("iframe");
let path = location.pathname.replace(
"file_targetBlankChild_xorigin_wrapper.html",
"file_targetBlankChild.html");
iframe.src = origin + path;
document.body.appendChild(iframe);
</script>
</html>
@@ -2,6 +2,8 @@
support-files = [
"file_http.html",
"file_https.html",
"file_targetBlankChild.html",
"file_targetBlankChild_xorigin_wrapper.html",
"frameEqual.html",
"frameNotEqual.html",
"frameReplace.html",
@@ -31,4 +33,10 @@ skip-if = [
"http3",
]
["test_sessionStorageTargetBlank.html"]
skip-if = [
"http2",
"http3",
]
["test_sessionStorageUsage.html"]
@@ -16,8 +16,6 @@ async function doNextTest()
// Make sure we do not unpartition storage inappropriately
await SpecialPowers.pushPrefEnv({
set: [
["privacy.partition.always_partition_third_party_non_cookie_storage", true],
["privacy.partition.always_partition_third_party_non_cookie_storage.exempt_sessionstorage", false],
["privacy.restrict3rdpartystorage.heuristic.window_open", false],
],
});
@@ -0,0 +1,79 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>sessionStorage cloning with target=_blank and noopener</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript">
SimpleTest.waitForExplicitFinish();
async function runTest() {
await SpecialPowers.pushPrefEnv({
set: [
["test.events.async.enabled", false],
],
});
if (isXOrigin) {
let path = location.pathname.replace(
"test_sessionStorageTargetBlank.html",
"file_targetBlankChild_xorigin_wrapper.html");
let wrapperUrl = "http://mochi.xorigin-test:8888" + path +
"?origin=" + encodeURIComponent(location.origin);
document.getElementById("link-opener").href = wrapperUrl;
document.getElementById("link-noopener").href = wrapperUrl;
}
sessionStorage.clear();
sessionStorage.setItem("key1", "value1");
// Test 1: target="_blank" (implicit noopener) — sessionStorage should NOT
// be cloned because target=_blank implies noopener per the HTML spec.
let bc = SpecialPowers.wrap(BroadcastChannel)
.unpartitionedTestingChannel("sessionStorageTargetBlank");
let resultPromise = new Promise(resolve => {
bc.onmessage = e => {
resolve(e.data);
}
});
synthesizeMouseAtCenter(document.getElementById("link-opener"), {});
let result = await resultPromise;
bc.close();
is(result.hasOpener, false, "target=_blank: should not have opener (implicit noopener)");
is(result.key1, null, "target=_blank: key1 should not be present");
is(result.length, 0, "target=_blank: should have 0 items");
// Test 2: rel="noopener" target="_blank" — sessionStorage should NOT be cloned
bc = SpecialPowers.wrap(BroadcastChannel)
.unpartitionedTestingChannel("sessionStorageTargetBlank");
resultPromise = new Promise(resolve => {
bc.onmessage = e => {
resolve(e.data);
}
});
synthesizeMouseAtCenter(document.getElementById("link-noopener"), {});
result = await resultPromise;
bc.close();
is(result.hasOpener, false, "noopener target=_blank: should not have opener");
is(result.key1, null, "noopener target=_blank: key1 should not be present");
is(result.length, 0, "noopener target=_blank: should have 0 items");
sessionStorage.clear();
await SpecialPowers.popPrefEnv();
SimpleTest.finish();
}
</script>
</head>
<body onload="runTest();">
<a id="link-opener" href="file_targetBlankChild.html" target="_blank">Open with opener</a>
<br/>
<a id="link-noopener" href="file_targetBlankChild.html" rel="noopener" target="_blank">Open without opener</a>
</body>
</html>
@@ -1318,9 +1318,15 @@ nsresult nsWindowWatcher::OpenWindowInternal(
targetDocShell->GetBrowsingContext()->GetSessionStorageManager();
if (parentStorageManager && newStorageManager) {
nsCOMPtr<nsIPrincipal> storagePrincipal;
if (parentDoc) {
storagePrincipal = parentDoc->EffectiveStoragePrincipal();
} else {
storagePrincipal = subjectPrincipal;
}
RefPtr<Storage> storage;
parentStorageManager->GetStorage(
parentInnerWin, subjectPrincipal, subjectPrincipal,
parentInnerWin, subjectPrincipal, storagePrincipal,
targetBC->UsePrivateBrowsing(), getter_AddRefs(storage));
if (storage) {
newStorageManager->CloneStorage(storage);
@@ -3,11 +3,13 @@
<!--
This page is opened in a new window by test_storage_copied.html.
We need to return the sessionStorage value for the item "test-item",
by way of postMessage.
by way of postMessage to the opener.
-->
<head>
<body>Opened!</body>
<script>
window.postMessage(window.sessionStorage.getItem("test-item"), "*");
let target = window.opener || window.parent;
target.postMessage(
window.sessionStorage.getItem("test-item"), "*");
</script>
</html>
@@ -0,0 +1,24 @@
<!DOCTYPE HTML>
<html>
<!--
This page is opened as a popup in xorigin mode by test_storage_copied.html.
It embeds file_storage_copied.html in an iframe from the test origin so it
runs in the same storage partition as the test, then relays the result to
the opener.
-->
<body>Wrapper!</body>
<script>
let params = new URLSearchParams(location.search);
let origin = params.get("origin");
let iframe = document.createElement("iframe");
let path = location.pathname.replace(
"file_storage_copied_xorigin_wrapper.html", "file_storage_copied.html");
iframe.src = origin + path;
window.addEventListener("message", function(event) {
if (window.opener) {
window.opener.postMessage(event.data, "*");
}
});
document.body.appendChild(iframe);
</script>
</html>
@@ -10,4 +10,7 @@ skip-if = [
support-files = ["file_named_window.html"]
["test_storage_copied.html"]
support-files = ["file_storage_copied.html"]
support-files = [
"file_storage_copied.html",
"file_storage_copied_xorigin_wrapper.html",
]
@@ -15,9 +15,9 @@ same domain as the opener.
<script type="application/javascript">
"use strict";
function waitForMessage(win) {
function waitForMessage() {
return new Promise(resolve => {
win.addEventListener("message", function(event) {
window.addEventListener("message", function(event) {
resolve(event.data);
}, {once: true});
});
@@ -33,8 +33,24 @@ same domain as the opener.
]});
window.sessionStorage.setItem("test-item", TEST_VALUE);
let win = window.open("file_storage_copied.html", "my_window");
let data = await waitForMessage(win);
let popupUrl;
if (isXOrigin) {
// In xorigin mode, this test runs in a third-party iframe
// (mochi.test inside mochi.xorigin-test). Open the popup on the
// top-level origin so it can embed a frame with the same partition
// key, allowing the cloned sessionStorage to be read.
let path = location.pathname.replace(
"test_storage_copied.html", "file_storage_copied_xorigin_wrapper.html");
popupUrl = "http://mochi.xorigin-test:8888" + path +
"?origin=" + encodeURIComponent(location.origin);
} else {
popupUrl = "file_storage_copied.html";
}
let loadPromise = waitForMessage();
let win = window.open(popupUrl, "my_window");
let data = await loadPromise;
is(data, TEST_VALUE, "Should have cloned the test value");
win.close();
});