tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit a315c687a53916f96d862e83021db8543395b2cc
parent d904c4b2823340b08533984f19f9189b9bba6ee0
Author: jim <zijin@ualberta.ca>
Date:   Mon,  6 Oct 2025 21:48:52 +0000

Bug 1886894 - removeLocalStorage now removes both localStorage and sessionStorage, a unit test is added. r=robwu

Differential Revision: https://phabricator.services.mozilla.com/D256723

Diffstat:
Mtoolkit/components/extensions/parent/ext-browsingData.js | 24++++++++++++++++++++++++
Mtoolkit/components/extensions/test/mochitest/mochitest-common.toml | 2++
Atoolkit/components/extensions/test/mochitest/test_ext_browsingData_sessionStorage.html | 261+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 287 insertions(+), 0 deletions(-)

diff --git a/toolkit/components/extensions/parent/ext-browsingData.js b/toolkit/components/extensions/parent/ext-browsingData.js @@ -210,6 +210,27 @@ const clearLocalStorage = async function (options) { message: "Firefox does not support clearing localStorage with 'since'.", }); } + const notifySessionStorage = function (hostname, cookieStoreId) { + if (hostname || cookieStoreId) { + const entry = Cc["@mozilla.org/clear-by-site-entry;1"].createInstance( + Ci.nsIClearBySiteEntry + ); + + //TODO: currently, passing cookieStoreId with empty hostname is not supported because + // CreateReversedDomain will reject empty string. + entry.schemelessSite = hostname || ""; + + entry.patternJSON = cookieStoreId + ? JSON.stringify( + getOriginAttributesPatternForCookieStoreId(cookieStoreId) + ) + : ""; + + Services.obs.notifyObservers(entry, "browser:purge-sessionStorage"); + } else { + Services.obs.notifyObservers(null, "browser:purge-sessionStorage"); + } + }; // The legacy LocalStorage implementation that will eventually be removed // depends on this observer notification. Some other subsystems like @@ -222,9 +243,12 @@ const clearLocalStorage = async function (options) { "extension:purge-localStorage", hostname ); + + notifySessionStorage(hostname, options.cookieStoreId); } } else { Services.obs.notifyObservers(null, "extension:purge-localStorage"); + notifySessionStorage(null, options.cookieStoreId); } if (Services.domStorageManager.nextGenLocalStorageEnabled) { diff --git a/toolkit/components/extensions/test/mochitest/mochitest-common.toml b/toolkit/components/extensions/test/mochitest/mochitest-common.toml @@ -159,6 +159,8 @@ skip-if = [ "http2", ] +["test_ext_browsingData_sessionStorage.html"] + ["test_ext_browsingData_settings.html"] ["test_ext_canvas_resistFingerprinting.html"] diff --git a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_sessionStorage.html b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_sessionStorage.html @@ -0,0 +1,261 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test browsingData.removeLocalStorage also removes sessionStorage</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<script type="text/javascript"> +"use strict"; + +add_task(async function setup() { + // make sure userContext is enabled. + return SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", true]], + }); +}); + +add_task(async function testLocalStorage() { + async function background() { + function waitForTabs() { + return new Promise(resolve => { + let tabs = {}; + + let listener = async (msg, { tab }) => { + if (msg !== "content-script-ready") { + return; + } + + tabs[tab.url] = tab; + if (Object.keys(tabs).length == 3) { + browser.runtime.onMessage.removeListener(listener); + resolve(tabs); + } + }; + browser.runtime.onMessage.addListener(listener); + }); + } + + function sendMessageToTabs(tabs, message) { + return Promise.all( + Object.values(tabs).map(tab => { + return browser.tabs.sendMessage(tab.id, message); + }) + ); + } + + let tabs = await waitForTabs(); + + browser.test.assertRejects( + browser.browsingData.removeLocalStorage({ since: Date.now() }), + "Firefox does not support clearing localStorage with 'since'.", + "Expected error received when using unimplemented parameter 'since'." + ); + + await sendMessageToTabs(tabs, "resetSessionStorage"); + + await browser.browsingData.removeLocalStorage({ + hostnames: ["example.com"], + }); + await browser.tabs.sendMessage(tabs["https://example.com/"].id, "checkSessionStorageCleared"); + await browser.tabs.sendMessage(tabs["https://example.net/"].id, "checkSessionStorageSet"); + ////TODO: Legacy sessionStorage implementation would not recognize the difference between example.com and test1.example.com, temporarily use example.org instead. + await browser.tabs.sendMessage(tabs["https://test1.example.org/"].id, "checkSessionStorageSet"); + + + await sendMessageToTabs(tabs, "resetSessionStorage"); + await sendMessageToTabs(tabs, "checkSessionStorageSet"); + await browser.browsingData.removeLocalStorage({}); + await sendMessageToTabs(tabs, "checkSessionStorageCleared"); + + await sendMessageToTabs(tabs, "resetSessionStorage"); + await sendMessageToTabs(tabs, "checkSessionStorageSet"); + await browser.browsingData.remove({}, { localStorage: true }); + await sendMessageToTabs(tabs, "checkSessionStorageCleared"); + + + // Can only delete cookieStoreId with LSNG enabled. + if (SpecialPowers.Services.domStorageManager.nextGenLocalStorageEnabled) { + await sendMessageToTabs(tabs, "resetSessionStorage"); + await sendMessageToTabs(tabs, "checkSessionStorageSet"); + // TODO: passing only the cookieStoreId is not supported, it will not delete anything. + await browser.browsingData.removeLocalStorage({ + cookieStoreId: "firefox-container-1", + }); + await browser.tabs.sendMessage(tabs["https://example.com/"].id, "checkSessionStorageSet"); + await browser.tabs.sendMessage(tabs["https://example.net/"].id, "checkSessionStorageSet"); + + // TODO: containers support is lacking on GeckoView (Bug 1643740) + if (!navigator.userAgent.includes("Android")) { + await browser.tabs.sendMessage(tabs["https://test1.example.org/"].id, "checkSessionStorageSet"); + } + + await sendMessageToTabs(tabs, "resetSessionStorage"); + await sendMessageToTabs(tabs, "checkSessionStorageSet"); + + // Happy path: Passing both hostname and cookieStoreId would clear the sessionStorage. + await browser.browsingData.removeLocalStorage({ + cookieStoreId: "firefox-container-1", + hostnames: ["example.org"], + }); + + await browser.tabs.sendMessage(tabs["https://example.com/"].id, "checkSessionStorageSet"); + await browser.tabs.sendMessage(tabs["https://example.net/"].id, "checkSessionStorageSet"); + await browser.tabs.sendMessage(tabs["https://test1.example.org/"].id, "checkSessionStorageCleared"); + + await sendMessageToTabs(tabs, "resetSessionStorage"); + await sendMessageToTabs(tabs, "checkSessionStorageSet"); + // Hostname doesn't match, so nothing cleared. + await browser.browsingData.removeLocalStorage({ + cookieStoreId: "firefox-container-1", + hostnames: ["example.net"], + }); + await sendMessageToTabs(tabs, "checkSessionStorageSet"); + + await sendMessageToTabs(tabs, "resetSessionStorage"); + await sendMessageToTabs(tabs, "checkSessionStorageSet"); + // Deleting private browsing mode data is silently ignored. + await browser.browsingData.removeLocalStorage({ + cookieStoreId: "firefox-private", + }); + await sendMessageToTabs(tabs, "checkSessionStorageSet"); + } else { + await browser.test.assertRejects( + browser.browsingData.removeLocalStorage({ + cookieStoreId: "firefox-container-1", + }), + "Firefox does not support clearing localStorage with 'cookieStoreId'.", + "removeLocalStorage with cookieStoreId requires LSNG" + ); + } + + await browser.browsingData.removeLocalStorage({}); + + browser.test.notifyPass("done"); + } + + function contentScript() { + browser.runtime.onMessage.addListener(async msg => { + if (msg === "resetSessionStorage") { + sessionStorage.clear(); + sessionStorage.setItem("key", "value"); + } else if (msg === "checkSessionStorageSet") { + browser.test.assertEq( + "value", + sessionStorage.getItem("key"), + `checkSessionStorageSet: ${location.href}` + ); + } else if (msg === "checkSessionStorageCleared") { + browser.test.assertEq( + null, + sessionStorage.getItem("key"), + `checkSessionStorageCleared: ${location.href}` + ); + } + }); + browser.runtime.sendMessage("content-script-ready"); + } + + // This extension is responsible for opening tabs with a specified + // cookieStoreId, we use a separate extension to make sure that browsingData + // works without the cookies permission. + let openTabsExtension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + manifest: { + name: "Open tabs", + browser_specific_settings: { gecko: { id: "open-tabs@tests.mozilla.org" }, }, + permissions: ["cookies"], + }, + async background() { + const TABS = [ + { url: "https://example.com" }, + { url: "https://example.net" }, + { + url: "https://test1.example.org", + cookieStoreId: 'firefox-container-1', + }, + ]; + + function awaitLoad(tabId) { + return new Promise(resolve => { + browser.tabs.onUpdated.addListener(function listener(tabId_, changed) { + if (tabId == tabId_ && changed.status == "complete") { + browser.tabs.onUpdated.removeListener(listener); + resolve(); + } + }); + }); + } + + let tabs = []; + let loaded = []; + for (let options of TABS) { + let tab = await browser.tabs.create(options); + loaded.push(awaitLoad(tab.id)); + tabs.push(tab); + } + + await Promise.all(loaded); + + browser.test.onMessage.addListener(async msg => { + if (msg === "cleanup") { + const tabIds = tabs.map(tab => tab.id); + let removedTabs = 0; + browser.tabs.onRemoved.addListener(tabId => { + browser.test.log(`Removing tab ${tabId}.`); + if (tabIds.includes(tabId)) { + removedTabs++; + if (removedTabs == tabIds.length) { + browser.test.sendMessage("done"); + } + } + }); + await browser.tabs.remove(tabIds); + } + }); + } + }); + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + background, + manifest: { + name: "Test Extension", + browser_specific_settings: { gecko: { id: "localStorage@tests.mozilla.org" } }, + permissions: ["browsingData", "tabs"], + content_scripts: [ + { + matches: [ + "https://example.com/", + "https://example.net/", + "https://test1.example.org/", + ], + js: ["content-script.js"], + run_at: "document_end", + }, + ], + }, + files: { + "content-script.js": contentScript, + }, + }); + + await openTabsExtension.startup(); + + await extension.startup(); + await extension.awaitFinish("done"); + await extension.unload(); + + await openTabsExtension.sendMessage("cleanup"); + await openTabsExtension.awaitMessage("done"); + await openTabsExtension.unload(); +}); +</script> +</body> +</html>