diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /browser/components/contextualidentity/test | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/contextualidentity/test')
37 files changed, 3780 insertions, 0 deletions
diff --git a/browser/components/contextualidentity/test/browser/blank.html b/browser/components/contextualidentity/test/browser/blank.html new file mode 100644 index 0000000000..bcc2e389b8 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/blank.html @@ -0,0 +1,2 @@ +<!doctype html> +This page intentionally left blank. diff --git a/browser/components/contextualidentity/test/browser/browser.toml b/browser/components/contextualidentity/test/browser/browser.toml new file mode 100644 index 0000000000..242466b10d --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser.toml @@ -0,0 +1,89 @@ +[DEFAULT] +support-files = [ + "head.js", + "empty_file.html", + "file_reflect_cookie_into_title.html", + "favicon-normal32.png", + "file_set_storages.html", + "serviceworker.html", + "worker.js", + "blank.html", +] + +["browser_aboutURLs.js"] +skip-if = ["os == 'linux' && bits == 64 && !debug"] # Bug 1731442 + +["browser_blobUrl.js"] + +["browser_broadcastchannel.js"] + +["browser_count_and_remove.js"] + +["browser_eme.js"] + +["browser_favicon.js"] + +["browser_forgetAPI_EME_forgetThisSite.js"] + +["browser_forgetAPI_cookie_getCookiesWithOriginAttributes.js"] + +["browser_forgetAPI_quota_clearStoragesForPrincipal.js"] +https_first_disabled = true +skip-if = ["verify"] + +["browser_forgetaboutsite.js"] +skip-if = ["true"] # Bug 1541885 + +["browser_guessusercontext.js"] + +["browser_imageCache.js"] +skip-if = ["verify && debug && os == 'win'"] + +["browser_middleClick.js"] +skip-if = [ + "verify && debug && os == 'linux'", + "apple_silicon", # Disabled due to bleedover with other tests when run in regular suites; passes in "failures" jobs + "os == 'linux' && socketprocess_networking", +] + +["browser_newtabButton.js"] + +["browser_originattrs_reopenin.js"] +https_first_disabled = true + +["browser_relatedTab.js"] + +["browser_reopenIn.js"] +https_first_disabled = true + +["browser_restore_getCookiesWithOriginAttributes.js"] + +["browser_saveLink.js"] +skip-if = ["verify && !debug && os == 'win'"] +support-files = [ + "saveLink.sjs", + "!/toolkit/content/tests/browser/common/mockTransfer.js", +] + +["browser_serviceworkers.js"] + +["browser_switchTab_across_user_context.js"] + +["browser_tab_color_update.js"] + +["browser_usercontext.js"] + +["browser_usercontextid_new_window.js"] + +["browser_usercontextid_tabdrop.js"] +https_first_disabled = true +skip-if = [ + "os == 'mac'", # Intermittent failure - bug 1268276 + "os == 'win'", # Intermittent failure - bug 1268276 +] + +["browser_windowName.js"] +tags = "openwindow" + +["browser_windowOpen.js"] +tags = "openwindow" diff --git a/browser/components/contextualidentity/test/browser/browser_aboutURLs.js b/browser/components/contextualidentity/test/browser/browser_aboutURLs.js new file mode 100644 index 0000000000..b2e43bfe65 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_aboutURLs.js @@ -0,0 +1,70 @@ +"use strict"; + +// For some about: URLs, they will take more time to load and cause timeout. +// See Bug 1270998. +requestLongerTimeout(2); + +add_task(async function () { + let aboutURLs = []; + + // List of about: URLs that may cause problem, so we skip them in this test. + let skipURLs = [ + // about:addons triggers an assertion in NS_CompareLoadInfoAndLoadContext: + // "The value of mUserContextId in the loadContext and in the loadInfo are not the same" + // This is due to a fetch request that has the default user context. Since + // the fetch request omits credentials, the user context doesn't matter. + "addons", + // about:credits and about:logins will initiate network request. + "credits", + "logins", + // about:telemetry will fetch Telemetry asynchronously and takes longer, + // so we skip this for now. + "telemetry", + // about:downloads causes a shutdown leak with stylo-chrome. bug 1419943. + "downloads", + // about:debugging requires specific wait code for internal pending RDP requests. + "debugging", + "debugging-new", + // about:protections uses RPM to send a message as soon as the page loads, + // the page is destoryed before getting a response. + "protections", + ]; + + for (let cid in Cc) { + let result = cid.match( + /@mozilla.org\/network\/protocol\/about;1\?what\=(.*)$/ + ); + if (!result) { + continue; + } + + let aboutType = result[1]; + let contract = "@mozilla.org/network/protocol/about;1?what=" + aboutType; + try { + let am = Cc[contract].getService(Ci.nsIAboutModule); + let uri = Services.io.newURI("about:" + aboutType); + let flags = am.getURIFlags(uri); + if ( + !(flags & Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT) && + !skipURLs.includes(aboutType) + ) { + aboutURLs.push(aboutType); + } + } catch (e) { + // getService might have thrown if the component doesn't actually + // implement nsIAboutModule + } + } + + for (let url of aboutURLs) { + info("Loading about:" + url); + let tab = BrowserTestUtils.addTab(gBrowser, "about:" + url, { + userContextId: 1, + }); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + ok(true, "Done loading about:" + url); + + BrowserTestUtils.removeTab(tab); + } +}); diff --git a/browser/components/contextualidentity/test/browser/browser_blobUrl.js b/browser/components/contextualidentity/test/browser/browser_blobUrl.js new file mode 100644 index 0000000000..636ffcc301 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_blobUrl.js @@ -0,0 +1,92 @@ +"use strict"; + +// Here we want to test that blob URLs are not available cross containers. + +const BASE_URI = + "http://mochi.test:8888/browser/browser/components/" + + "contextualidentity/test/browser/empty_file.html"; + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", true]], + }); +}); + +add_task(async function test() { + info("Creating a tab with UCI = 1..."); + let tab1 = BrowserTestUtils.addTab(gBrowser, BASE_URI, { userContextId: 1 }); + is(tab1.getAttribute("usercontextid"), "1", "New tab has UCI equal 1"); + + let browser1 = gBrowser.getBrowserForTab(tab1); + await BrowserTestUtils.browserLoaded(browser1); + + let blobURL; + + info("Creating a blob URL..."); + await SpecialPowers.spawn(browser1, [], function () { + return Promise.resolve( + content.window.URL.createObjectURL(new content.window.Blob([123])) + ); + }).then(newURL => { + blobURL = newURL; + }); + + info("Blob URL: " + blobURL); + + info("Creating a tab with UCI = 2..."); + let tab2 = BrowserTestUtils.addTab(gBrowser, BASE_URI, { userContextId: 2 }); + is(tab2.getAttribute("usercontextid"), "2", "New tab has UCI equal 2"); + + let browser2 = gBrowser.getBrowserForTab(tab2); + await BrowserTestUtils.browserLoaded(browser2); + + await SpecialPowers.spawn(browser2, [blobURL], function (url) { + return new Promise(resolve => { + var xhr = new content.window.XMLHttpRequest(); + xhr.onerror = function () { + resolve("SendErrored"); + }; + xhr.onload = function () { + resolve("SendLoaded"); + }; + xhr.open("GET", url); + xhr.send(); + }); + }).then(status => { + is( + status, + "SendErrored", + "Using a blob URI from one user context id in another should not work" + ); + }); + + info("Creating a tab with UCI = 1..."); + let tab3 = BrowserTestUtils.addTab(gBrowser, BASE_URI, { userContextId: 1 }); + is(tab3.getAttribute("usercontextid"), "1", "New tab has UCI equal 1"); + + let browser3 = gBrowser.getBrowserForTab(tab3); + await BrowserTestUtils.browserLoaded(browser3); + + await SpecialPowers.spawn(browser3, [blobURL], function (url) { + return new Promise(resolve => { + var xhr = new content.window.XMLHttpRequest(); + xhr.open("GET", url); + try { + xhr.send(); + resolve("SendSucceeded"); + } catch (e) { + resolve("SendThrew"); + } + }); + }).then(status => { + is( + status, + "SendSucceeded", + "Using a blob URI within a single user context id should work" + ); + }); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); + BrowserTestUtils.removeTab(tab3); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_broadcastchannel.js b/browser/components/contextualidentity/test/browser/browser_broadcastchannel.js new file mode 100644 index 0000000000..49d52e436e --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_broadcastchannel.js @@ -0,0 +1,70 @@ +const BASE_ORIGIN = "http://example.com"; +const URI = + BASE_ORIGIN + + "/browser/browser/components/contextualidentity/test/browser/empty_file.html"; + +async function runTestForReceiver(receiver) { + let channelName = "contextualidentity-broadcastchannel"; + + // reflect the received message on title + await SpecialPowers.spawn(receiver.browser, [channelName], function (name) { + content.window.testPromise = new content.window.Promise(resolve => { + content.window.bc = new content.window.BroadcastChannel(name); + content.window.bc.onmessage = function (e) { + content.document.title += e.data; + resolve(); + }; + }); + }); + + let sender1 = await openTabInUserContext(URI, 1); + let sender2 = await openTabInUserContext(URI, 2); + sender1.message = "Message from user context #1"; + sender2.message = "Message from user context #2"; + + // send a message from a tab in different user context first + // then send a message from a tab in the same user context + for (let sender of [sender1, sender2]) { + await SpecialPowers.spawn( + sender.browser, + [{ name: channelName, message: sender.message }], + function (opts) { + let bc = new content.window.BroadcastChannel(opts.name); + bc.postMessage(opts.message); + } + ); + } + + // Since sender1 sends before sender2, if the title is exactly + // sender2's message, sender1's message must've been blocked + await SpecialPowers.spawn( + receiver.browser, + [sender2.message], + async function (message) { + await content.window.testPromise.then(function () { + is( + content.document.title, + message, + "should only receive messages from the same user context" + ); + }); + } + ); + + gBrowser.removeTab(sender1.tab); + gBrowser.removeTab(sender2.tab); +} + +add_setup(async function () { + // make sure userContext is enabled. + await SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", true]], + }); +}); + +add_task(async function test() { + info("Checking broadcast channel with browser tab receiver"); + let receiver = await openTabInUserContext(URI, 2); + await runTestForReceiver(receiver); + gBrowser.removeTab(receiver.tab); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_count_and_remove.js b/browser/components/contextualidentity/test/browser/browser_count_and_remove.js new file mode 100644 index 0000000000..8f044db94e --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_count_and_remove.js @@ -0,0 +1,107 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function openTabInUserContext(userContextId) { + let tab = BrowserTestUtils.addTab(gBrowser, "about:blank", { userContextId }); + gBrowser.selectedTab = tab; +} + +add_setup(async function () { + // make sure userContext is enabled. + await SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", true]], + }); +}); + +add_task(async function test() { + is( + ContextualIdentityService.countContainerTabs(), + 0, + "0 container tabs by default." + ); + + openTabInUserContext(1); + is( + ContextualIdentityService.countContainerTabs(), + 1, + "1 container tab created" + ); + is( + ContextualIdentityService.countContainerTabs(1), + 1, + "1 container tab created with id 1" + ); + is( + ContextualIdentityService.countContainerTabs(2), + 0, + "0 container tabs created with id 2" + ); + + openTabInUserContext(1); + is( + ContextualIdentityService.countContainerTabs(), + 2, + "2 container tabs created" + ); + is( + ContextualIdentityService.countContainerTabs(1), + 2, + "2 container tabs created with id 1" + ); + is( + ContextualIdentityService.countContainerTabs(2), + 0, + "0 container tabs created with id 2" + ); + + openTabInUserContext(2); + is( + ContextualIdentityService.countContainerTabs(), + 3, + "3 container tab created" + ); + is( + ContextualIdentityService.countContainerTabs(1), + 2, + "2 container tabs created with id 1" + ); + is( + ContextualIdentityService.countContainerTabs(2), + 1, + "1 container tab created with id 2" + ); + + await ContextualIdentityService.closeContainerTabs(1); + is( + ContextualIdentityService.countContainerTabs(), + 1, + "1 container tab created" + ); + is( + ContextualIdentityService.countContainerTabs(1), + 0, + "0 container tabs created with id 1" + ); + is( + ContextualIdentityService.countContainerTabs(2), + 1, + "1 container tab created with id 2" + ); + + await ContextualIdentityService.closeContainerTabs(); + is( + ContextualIdentityService.countContainerTabs(), + 0, + "0 container tabs at the end." + ); + is( + ContextualIdentityService.countContainerTabs(1), + 0, + "0 container tabs at the end with id 1." + ); + is( + ContextualIdentityService.countContainerTabs(2), + 0, + "0 container tabs at the end with id 2." + ); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_eme.js b/browser/components/contextualidentity/test/browser/browser_eme.js new file mode 100644 index 0000000000..5b0cd8a940 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_eme.js @@ -0,0 +1,210 @@ +/* + * Bug 1283325 - A test case to test the EME is originAttributes aware or not. + */ +const CC = Components.Constructor; + +const TEST_HOST = "example.com"; +const TEST_URL = + "http://" + + TEST_HOST + + "/browser/browser/components/contextualidentity/test/browser/"; + +const TESTKEY = { + initDataType: "keyids", + initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"], "type":"persistent-license"}', + kid: "LwVHf8JLtPrv2GUXFW2v_A", + key: "97b9ddc459c8d5ff23c1f2754c95abe8", + sessionType: "persistent-license", +}; + +const USER_ID_DEFAULT = 0; +const USER_ID_PERSONAL = 1; + +function HexToBase64(hex) { + var bin = ""; + for (var i = 0; i < hex.length; i += 2) { + bin += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); + } + return window + .btoa(bin) + .replace(/=/g, "") + .replace(/\+/g, "-") + .replace(/\//g, "_"); +} + +function Base64ToHex(str) { + var bin = window.atob(str.replace(/-/g, "+").replace(/_/g, "/")); + var res = ""; + for (var i = 0; i < bin.length; i++) { + res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2); + } + return res; +} + +function ByteArrayToHex(array) { + let bin = String.fromCharCode.apply(null, new Uint8Array(array)); + let res = ""; + + for (let i = 0; i < bin.length; i++) { + res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2); + } + + return res; +} + +function generateKeyObject(aKid, aKey) { + let keyObj = { + kty: "oct", + kid: aKid, + k: HexToBase64(aKey), + }; + + return new TextEncoder().encode( + JSON.stringify({ + keys: [keyObj], + }) + ); +} + +function generateKeyInfo(aData) { + let keyInfo = { + initDataType: aData.initDataType, + initData: new TextEncoder().encode(aData.initData), + sessionType: aData.sessionType, + keyObj: generateKeyObject(aData.kid, aData.key), + }; + + return keyInfo; +} + +add_setup(async function () { + // Make sure userContext is enabled. + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.userContext.enabled", true], + ["media.mediasource.enabled", true], + ["media.mediasource.webm.enabled", true], + ["media.clearkey.persistent-license.enabled", true], + ], + }); +}); + +add_task(async function test() { + // Open a tab with the default container. + let defaultContainer = await openTabInUserContext( + TEST_URL + "empty_file.html", + USER_ID_DEFAULT + ); + + // Generate the key info for the default container. + let keyInfo = generateKeyInfo(TESTKEY); + + // Update the media key for the default container. + let result = await SpecialPowers.spawn( + defaultContainer.browser, + [keyInfo], + async function (aKeyInfo) { + let access = await content.navigator.requestMediaKeySystemAccess( + "org.w3.clearkey", + [ + { + initDataTypes: [aKeyInfo.initDataType], + videoCapabilities: [{ contentType: "video/webm" }], + sessionTypes: ["persistent-license"], + persistentState: "required", + }, + ] + ); + let mediaKeys = await access.createMediaKeys(); + let session = mediaKeys.createSession(aKeyInfo.sessionType); + let res = {}; + + // Insert the media key. + await new Promise(resolve => { + session.addEventListener("message", function (event) { + session + .update(aKeyInfo.keyObj) + .then(() => { + resolve(); + }) + .catch(() => { + ok(false, "Update the media key fail."); + resolve(); + }); + }); + + session.generateRequest(aKeyInfo.initDataType, aKeyInfo.initData); + }); + + let map = session.keyStatuses; + + is(map.size, 1, "One media key has been added."); + + if (map.size === 1) { + res.keyId = map.keys().next().value; + res.sessionId = session.sessionId; + } + + // Close the session. + session.close(); + await session.closed; + + return res; + } + ); + + // Check the media key ID. + is( + ByteArrayToHex(result.keyId), + Base64ToHex(TESTKEY.kid), + "The key Id of the default container is correct." + ); + + // Store the sessionId for the further checking. + keyInfo.sessionId = result.sessionId; + + // Open a tab with personal container. + let personalContainer = await openTabInUserContext( + TEST_URL + "empty_file.html", + USER_ID_PERSONAL + ); + + await SpecialPowers.spawn( + personalContainer.browser, + [keyInfo], + async function (aKeyInfo) { + let access = await content.navigator.requestMediaKeySystemAccess( + "org.w3.clearkey", + [ + { + initDataTypes: [aKeyInfo.initDataType], + videoCapabilities: [{ contentType: "video/webm" }], + sessionTypes: ["persistent-license"], + persistentState: "required", + }, + ] + ); + let mediaKeys = await access.createMediaKeys(); + let session = mediaKeys.createSession(aKeyInfo.sessionType); + + // First, load the session to check that mediakeys do not share with + // default container. + await session.load(aKeyInfo.sessionId); + + let map = session.keyStatuses; + + // Check that there is no media key here. + is( + map.size, + 0, + "No media key should be here for the personal container." + ); + } + ); + + // Close default container tab. + BrowserTestUtils.removeTab(defaultContainer.tab); + + // Close personal container tab. + BrowserTestUtils.removeTab(personalContainer.tab); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_favicon.js b/browser/components/contextualidentity/test/browser/browser_favicon.js new file mode 100644 index 0000000000..8d29aff28f --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_favicon.js @@ -0,0 +1,136 @@ +/* + * Bug 1270678 - A test case to test does the favicon obey originAttributes. + */ + +let { HttpServer } = ChromeUtils.importESModule( + "resource://testing-common/httpd.sys.mjs" +); + +const USER_CONTEXTS = ["default", "personal", "work"]; + +let gHttpServer = null; +let gUserContextId; +let gFaviconData; + +function getIconFile() { + new Promise(resolve => { + NetUtil.asyncFetch( + { + uri: "http://www.example.com/browser/browser/components/contextualidentity/test/browser/favicon-normal32.png", + loadUsingSystemPrincipal: true, + contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON, + }, + function (inputStream, status) { + let size = inputStream.available(); + gFaviconData = NetUtil.readInputStreamToString(inputStream, size); + resolve(); + } + ); + }); +} + +function loadIndexHandler(metadata, response) { + response.setStatusLine(metadata.httpVersion, 200, "Ok"); + response.setHeader("Content-Type", "text/html", false); + let body = ` + <!DOCTYPE HTML> + <html> + <head> + <meta charset='utf-8'> + <title>Favicon Test</title> + </head> + <body> + Favicon!! + </body> + </html>`; + response.bodyOutputStream.write(body, body.length); +} + +function loadFaviconHandler(metadata, response) { + let expectedCookie = "userContext=" + USER_CONTEXTS[gUserContextId]; + + if (metadata.hasHeader("Cookie")) { + is( + metadata.getHeader("Cookie"), + expectedCookie, + "The cookie has matched with the expected cookie." + ); + } else { + ok(false, "The request should have a cookie."); + } + + response.setStatusLine(metadata.httpVersion, 200, "Ok"); + response.setHeader("Content-Type", "image/png", false); + response.bodyOutputStream.write(gFaviconData, gFaviconData.length); +} + +add_setup(async function () { + // Make sure userContext is enabled. + await SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", true]], + }); + + // Create a http server for the image cache test. + if (!gHttpServer) { + gHttpServer = new HttpServer(); + gHttpServer.registerPathHandler("/", loadIndexHandler); + gHttpServer.registerPathHandler("/favicon.png", loadFaviconHandler); + gHttpServer.start(-1); + } +}); + +registerCleanupFunction(() => { + gHttpServer.stop(() => { + gHttpServer = null; + }); +}); + +add_task(async function test() { + waitForExplicitFinish(); + + // First, get the icon data. + await getIconFile(); + + let serverPort = gHttpServer.identity.primaryPort; + let testURL = "http://localhost:" + serverPort + "/"; + let testFaviconURL = "http://localhost:" + serverPort + "/favicon.png"; + + for (let userContextId of Object.keys(USER_CONTEXTS)) { + gUserContextId = userContextId; + + // Load the page in 3 different contexts and set a cookie + // which should only be visible in that context. + + // Open our tab in the given user context. + let tabInfo = await openTabInUserContext(testURL, userContextId); + + // Write a cookie according to the userContext. + await SpecialPowers.spawn( + tabInfo.browser, + [{ userContext: USER_CONTEXTS[userContextId] }], + function (arg) { + content.document.cookie = "userContext=" + arg.userContext; + } + ); + + let pageURI = NetUtil.newURI(testURL); + let favIconURI = NetUtil.newURI(testFaviconURL); + + await new Promise(resolve => { + PlacesUtils.favicons.setAndFetchFaviconForPage( + pageURI, + favIconURI, + true, + PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, + { + onComplete() { + resolve(); + }, + }, + tabInfo.browser.contentPrincipal + ); + }); + + BrowserTestUtils.removeTab(tabInfo.tab); + } +}); diff --git a/browser/components/contextualidentity/test/browser/browser_forgetAPI_EME_forgetThisSite.js b/browser/components/contextualidentity/test/browser/browser_forgetAPI_EME_forgetThisSite.js new file mode 100644 index 0000000000..24a4c51118 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_forgetAPI_EME_forgetThisSite.js @@ -0,0 +1,246 @@ +/* + * Bug 1278037 - A Test case for checking whether forgetting APIs are working for the media key. + */ + +const CC = Components.Constructor; + +const TEST_HOST = "example.com"; +const TEST_URL = + "http://" + + TEST_HOST + + "/browser/browser/components/contextualidentity/test/browser/"; + +const USER_CONTEXTS = ["default", "personal"]; + +const TEST_EME_KEY = { + initDataType: "keyids", + initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"], "type":"persistent-license"}', + kid: "LwVHf8JLtPrv2GUXFW2v_A", + key: "97b9ddc459c8d5ff23c1f2754c95abe8", + sessionType: "persistent-license", +}; + +// +// Support functions. +// + +function HexToBase64(hex) { + var bin = ""; + for (var i = 0; i < hex.length; i += 2) { + bin += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); + } + return window + .btoa(bin) + .replace(/=/g, "") + .replace(/\+/g, "-") + .replace(/\//g, "_"); +} + +function Base64ToHex(str) { + var bin = window.atob(str.replace(/-/g, "+").replace(/_/g, "/")); + var res = ""; + for (var i = 0; i < bin.length; i++) { + res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2); + } + return res; +} + +function ByteArrayToHex(array) { + let bin = String.fromCharCode.apply(null, new Uint8Array(array)); + let res = ""; + + for (let i = 0; i < bin.length; i++) { + res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2); + } + + return res; +} + +function generateKeyObject(aKid, aKey) { + let keyObj = { + kty: "oct", + kid: aKid, + k: HexToBase64(aKey), + }; + + return new TextEncoder().encode( + JSON.stringify({ + keys: [keyObj], + }) + ); +} + +function generateKeyInfo(aData) { + let keyInfo = { + initDataType: aData.initDataType, + initData: new TextEncoder().encode(aData.initData), + sessionType: aData.sessionType, + keyObj: generateKeyObject(aData.kid, aData.key), + }; + + return keyInfo; +} + +// Setup a EME key for the given browser, and return the sessionId. +async function setupEMEKey(browser) { + // Generate the key info. + let keyInfo = generateKeyInfo(TEST_EME_KEY); + + // Setup the EME key. + let result = await SpecialPowers.spawn( + browser, + [keyInfo], + async function (aKeyInfo) { + let access = await content.navigator.requestMediaKeySystemAccess( + "org.w3.clearkey", + [ + { + initDataTypes: [aKeyInfo.initDataType], + videoCapabilities: [{ contentType: "video/webm" }], + sessionTypes: ["persistent-license"], + persistentState: "required", + }, + ] + ); + let mediaKeys = await access.createMediaKeys(); + let session = mediaKeys.createSession(aKeyInfo.sessionType); + let res = {}; + + // Insert the EME key. + await new Promise(resolve => { + session.addEventListener("message", function (event) { + session + .update(aKeyInfo.keyObj) + .then(() => { + resolve(); + }) + .catch(() => { + ok(false, "Update the EME key fail."); + resolve(); + }); + }); + + session.generateRequest(aKeyInfo.initDataType, aKeyInfo.initData); + }); + + let map = session.keyStatuses; + + is(map.size, 1, "One EME key has been added."); + + if (map.size === 1) { + res.keyId = map.keys().next().value; + res.sessionId = session.sessionId; + } + + // Close the session. + session.close(); + await session.closed; + + return res; + } + ); + + // Check the EME key ID. + is( + ByteArrayToHex(result.keyId), + Base64ToHex(TEST_EME_KEY.kid), + "The key Id is correct." + ); + return result.sessionId; +} + +// Check whether the EME key has been cleared. +async function checkEMEKey(browser, emeSessionId) { + // Generate the key info. + let keyInfo = generateKeyInfo(TEST_EME_KEY); + keyInfo.sessionId = emeSessionId; + + await SpecialPowers.spawn(browser, [keyInfo], async function (aKeyInfo) { + let access = await content.navigator.requestMediaKeySystemAccess( + "org.w3.clearkey", + [ + { + initDataTypes: [aKeyInfo.initDataType], + videoCapabilities: [{ contentType: "video/webm" }], + sessionTypes: ["persistent-license"], + persistentState: "required", + }, + ] + ); + let mediaKeys = await access.createMediaKeys(); + let session = mediaKeys.createSession(aKeyInfo.sessionType); + + // First, load the session with the sessionId. + await session.load(aKeyInfo.sessionId); + + let map = session.keyStatuses; + + // Check that there is no media key here. + is( + map.size, + 0, + "No media key should be here after forgetThisSite() was called." + ); + }); +} + +// +// Test functions. +// + +add_setup(async function () { + // Make sure userContext is enabled. + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.userContext.enabled", true], + ["media.mediasource.enabled", true], + ["media.mediasource.webm.enabled", true], + ["media.clearkey.persistent-license.enabled", true], + ], + }); +}); + +add_task(async function test_EME_forgetThisSite() { + let tabs = []; + let emeSessionIds = []; + + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // Open our tab in the given user context. + tabs[userContextId] = await openTabInUserContext( + TEST_URL + "empty_file.html", + userContextId + ); + + // Setup EME Key. + emeSessionIds[userContextId] = await setupEMEKey( + tabs[userContextId].browser + ); + + // Close this tab. + BrowserTestUtils.removeTab(tabs[userContextId].tab); + } + + // Clear all EME data for a given domain with originAttributes pattern. + let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].getService( + Ci.mozIGeckoMediaPluginChromeService + ); + mps.forgetThisSite(TEST_HOST, JSON.stringify({})); + + // Open tabs again to check EME keys have been cleared. + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // Open our tab in the given user context. + tabs[userContextId] = await openTabInUserContext( + TEST_URL + "empty_file.html", + userContextId + ); + + // Check whether EME Key has been cleared. + await checkEMEKey( + tabs[userContextId].browser, + emeSessionIds[userContextId] + ); + + // Close this tab. + BrowserTestUtils.removeTab(tabs[userContextId].tab); + } +}); diff --git a/browser/components/contextualidentity/test/browser/browser_forgetAPI_cookie_getCookiesWithOriginAttributes.js b/browser/components/contextualidentity/test/browser/browser_forgetAPI_cookie_getCookiesWithOriginAttributes.js new file mode 100644 index 0000000000..c314b20008 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_forgetAPI_cookie_getCookiesWithOriginAttributes.js @@ -0,0 +1,82 @@ +/* + * Bug 1278037 - A Test case for checking whether forgetting APIs are working for cookies. + */ + +const CC = Components.Constructor; + +const TEST_HOST = "example.com"; +const TEST_URL = + "http://" + + TEST_HOST + + "/browser/browser/components/contextualidentity/test/browser/"; + +const USER_CONTEXTS = ["default", "personal"]; + +// +// Support functions. +// + +function getCookiesForOA(host, userContextId) { + return Services.cookies.getCookiesFromHost(host, { userContextId }); +} + +// +// Test functions. +// + +add_setup(async function () { + // Make sure userContext is enabled. + await SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", true]], + }); +}); + +add_task(async function test_cookie_getCookiesWithOriginAttributes() { + let tabs = []; + let cookieName = "userContextId"; + + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // Load the page in 2 different contexts and set a cookie + // which should only be visible in that context. + let value = USER_CONTEXTS[userContextId]; + + // Open our tab in the given user context. + tabs[userContextId] = await openTabInUserContext( + TEST_URL + "file_reflect_cookie_into_title.html?" + value, + userContextId + ); + + // Close this tab. + BrowserTestUtils.removeTab(tabs[userContextId].tab); + } + + // Check that cookies have been set properly. + for (let userContextId of Object.keys(USER_CONTEXTS)) { + let cookies = getCookiesForOA(TEST_HOST, userContextId); + ok(cookies.length, "Cookies available"); + + let foundCookie = cookies[0]; + is(foundCookie.name, cookieName, "Check cookie name"); + is(foundCookie.value, USER_CONTEXTS[userContextId], "Check cookie value"); + } + + // Using getCookiesWithOriginAttributes() to get all cookies for a certain + // domain by using the originAttributes pattern, and clear all these cookies. + for (let cookie of Services.cookies.getCookiesWithOriginAttributes( + JSON.stringify({}), + TEST_HOST + )) { + Services.cookies.remove( + cookie.host, + cookie.name, + cookie.path, + cookie.originAttributes + ); + } + + // Check that whether cookies has been cleared. + for (let userContextId of Object.keys(USER_CONTEXTS)) { + let cookies = getCookiesForOA(TEST_HOST, userContextId); + ok(!cookies.length, "No Cookie should be here"); + } +}); diff --git a/browser/components/contextualidentity/test/browser/browser_forgetAPI_quota_clearStoragesForPrincipal.js b/browser/components/contextualidentity/test/browser/browser_forgetAPI_quota_clearStoragesForPrincipal.js new file mode 100644 index 0000000000..aab262a117 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_forgetAPI_quota_clearStoragesForPrincipal.js @@ -0,0 +1,152 @@ +/* + * Bug 1278037 - A Test case for checking whether forgetting APIs are working for the quota manager. + */ + +const CC = Components.Constructor; + +const TEST_HOST = "example.com"; +const TEST_URL = + "http://" + + TEST_HOST + + "/browser/browser/components/contextualidentity/test/browser/"; + +const USER_CONTEXTS = ["default", "personal"]; + +// +// Support functions. +// + +// Setup an entry for the indexedDB. +async function setupIndexedDB(browser) { + await SpecialPowers.spawn( + browser, + [{ input: "TestForgetAPIs" }], + async function (arg) { + let request = content.indexedDB.open("idb", 1); + + request.onerror = function () { + throw new Error("error opening db connection"); + }; + + request.onupgradeneeded = event => { + let db = event.target.result; + let store = db.createObjectStore("obj", { keyPath: "id" }); + store.createIndex("userContext", "userContext", { unique: false }); + }; + + let db = await new Promise(resolve => { + request.onsuccess = event => { + resolve(event.target.result); + }; + }); + + // Add an entry into the indexedDB. + let transaction = db.transaction(["obj"], "readwrite"); + let store = transaction.objectStore("obj"); + store.add({ id: 1, userContext: arg.input }); + + await new Promise(resolve => { + transaction.oncomplete = () => { + resolve(); + }; + }); + + // Check the indexedDB has been set properly. + transaction = db.transaction(["obj"], "readonly"); + store = transaction.objectStore("obj"); + let getRequest = store.get(1); + await new Promise(resolve => { + getRequest.onsuccess = () => { + let res = getRequest.result; + is(res.userContext, arg.input, "Check the indexedDB value"); + resolve(); + }; + }); + } + ); +} + +// Check whether the indexedDB has been cleared. +async function checkIndexedDB(browser) { + await SpecialPowers.spawn(browser, [], async function () { + let request = content.indexedDB.open("idb", 1); + + let db = await new Promise(done => { + request.onsuccess = event => { + done(event.target.result); + }; + }); + + try { + db.transaction(["obj"], "readonly"); + ok(false, "The indexedDB should not exist"); + } catch (e) { + is(e.name, "NotFoundError", "The indexedDB does not exist as expected"); + } + + db.close(); + + content.indexedDB.deleteDatabase("idb"); + }); +} + +// +// Test functions. +// + +add_setup(async function () { + // Make sure userContext is enabled. + await SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", true]], + }); +}); + +add_task(async function test_quota_clearStoragesForPrincipal() { + let tabs = []; + + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // Open our tab in the given user context. + tabs[userContextId] = await openTabInUserContext( + TEST_URL + "empty_file.html", + userContextId + ); + + // Setup an entry for the indexedDB. + await setupIndexedDB(tabs[userContextId].browser); + + // Close this tab. + BrowserTestUtils.removeTab(tabs[userContextId].tab); + } + + // Using quota manager to clear all indexed DB for a given domain. + let caUtils = {}; + Services.scriptloader.loadSubScript( + "chrome://global/content/contentAreaUtils.js", + caUtils + ); + let httpURI = caUtils.makeURI("http://" + TEST_HOST); + let httpPrincipal = Services.scriptSecurityManager.createContentPrincipal( + httpURI, + {} + ); + let clearRequest = Services.qms.clearStoragesForOriginPrefix(httpPrincipal); + await new Promise(resolve => { + clearRequest.callback = () => { + resolve(); + }; + }); + + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // Open our tab in the given user context. + tabs[userContextId] = await openTabInUserContext( + TEST_URL + "empty_file.html", + userContextId + ); + + // Check whether indexed DB has been cleared. + await checkIndexedDB(tabs[userContextId].browser); + + // Close this tab. + BrowserTestUtils.removeTab(tabs[userContextId].tab); + } +}); diff --git a/browser/components/contextualidentity/test/browser/browser_forgetaboutsite.js b/browser/components/contextualidentity/test/browser/browser_forgetaboutsite.js new file mode 100644 index 0000000000..79975fff8c --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_forgetaboutsite.js @@ -0,0 +1,414 @@ +/* + * Bug 1238183 - Test cases for forgetAboutSite with userContextId. + */ + +const CC = Components.Constructor; + +let { ForgetAboutSite } = ChromeUtils.importESModule( + "resource://gre/modules/ForgetAboutSite.sys.mjs" +); +let { HttpServer } = ChromeUtils.importESModule( + "resource://testing-common/httpd.sys.mjs" +); + +const USER_CONTEXTS = ["default", "personal"]; +const TEST_HOST = "example.com"; +const TEST_URL = + "http://" + + TEST_HOST + + "/browser/browser/components/contextualidentity/test/browser/"; +const COOKIE_NAME = "userContextId"; + +// Counter for image load hits. +let gHits = 0; + +let gHttpServer = null; + +function imageHandler(metadata, response) { + // A 1x1 PNG image. + // Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain) + const IMAGE = atob( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" + + "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=" + ); + gHits++; + response.setHeader("Cache-Control", "max-age=10000", false); + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "image/png", false); + response.write(IMAGE); +} + +function loadImagePageHandler(metadata, response) { + response.setHeader("Cache-Control", "max-age=10000", false); + response.setStatusLine(metadata.httpVersion, 200, "Ok"); + response.setHeader("Content-Type", "text/html", false); + let body = + "<!DOCTYPE HTML>\ + <html>\ + <head>\ + <meta charset='utf-8'>\ + <title>Load Image</title>\ + </head>\ + <body>\ + <img src='image.png'>\ + </body>\ + </html>"; + response.bodyOutputStream.write(body, body.length); +} + +function getCookiesForOA(host, userContextId) { + return Services.cookies.getCookiesFromHost(host, { userContextId }); +} + +function createURI(uri) { + return Services.io.newURI(uri); +} + +function getCacheStorage(where, lci) { + if (!lci) { + lci = Services.loadContextInfo.default; + } + switch (where) { + case "disk": + return Services.cache2.diskCacheStorage(lci); + case "memory": + return Services.cache2.memoryCacheStorage(lci); + case "pin": + return Services.cache2.pinningCacheStorage(lci); + } + return null; +} + +function OpenCacheEntry(key, where, flags, lci) { + return new Promise(resolve => { + key = createURI(key); + function CacheListener() {} + CacheListener.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsICacheEntryOpenCallback"]), + + onCacheEntryCheck(entry) { + return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; + }, + + onCacheEntryAvailable(entry, isnew, status) { + resolve(); + }, + + run() { + let storage = getCacheStorage(where, lci); + storage.asyncOpenURI(key, "", flags, this); + }, + }; + + new CacheListener().run(); + }); +} + +// +// Test functions. +// + +// Cookies +async function test_cookie_cleared() { + let tabs = []; + + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // Load the page in 2 different contexts and set a cookie + // which should only be visible in that context. + let value = USER_CONTEXTS[userContextId]; + + // Open our tab in the given user context. + tabs[userContextId] = await openTabInUserContext( + TEST_URL + "file_reflect_cookie_into_title.html?" + value, + userContextId + ); + + // Close this tab. + BrowserTestUtils.removeTab(tabs[userContextId].tab); + } + // Check that cookies have been set properly. + for (let userContextId of Object.keys(USER_CONTEXTS)) { + let cookies = getCookiesForOA(TEST_HOST, userContextId); + ok(cookies.length, "Cookies available"); + + let foundCookie = cookies[0]; + Assert.equal(foundCookie.name, COOKIE_NAME, "Check cookie name"); + Assert.equal( + foundCookie.value, + USER_CONTEXTS[userContextId], + "Check cookie value" + ); + } + + // Forget the site. + await ForgetAboutSite.removeDataFromDomain(TEST_HOST); + + // Check that whether cookies has been cleared or not. + for (let userContextId of Object.keys(USER_CONTEXTS)) { + let cookies = getCookiesForOA(TEST_HOST, userContextId); + ok(!cookies.length, "No Cookie should be here"); + } +} + +// Cache +async function test_cache_cleared() { + // First, add some caches. + for (let userContextId of Object.keys(USER_CONTEXTS)) { + await OpenCacheEntry( + "http://" + TEST_HOST + "/", + "disk", + Ci.nsICacheStorage.OPEN_NORMALLY, + Services.loadContextInfo.custom(false, { userContextId }) + ); + + await OpenCacheEntry( + "http://" + TEST_HOST + "/", + "memory", + Ci.nsICacheStorage.OPEN_NORMALLY, + Services.loadContextInfo.custom(false, { userContextId }) + ); + } + + // Check that caches have been set correctly. + for (let userContextId of Object.keys(USER_CONTEXTS)) { + let mem = getCacheStorage( + "memory", + Services.loadContextInfo.custom(false, { userContextId }) + ); + let disk = getCacheStorage( + "disk", + Services.loadContextInfo.custom(false, { userContextId }) + ); + + Assert.ok( + mem.exists(createURI("http://" + TEST_HOST + "/"), ""), + "The memory cache has been set correctly" + ); + Assert.ok( + disk.exists(createURI("http://" + TEST_HOST + "/"), ""), + "The disk cache has been set correctly" + ); + } + + // Forget the site. + await ForgetAboutSite.removeDataFromDomain(TEST_HOST); + + // Check that do caches be removed or not? + for (let userContextId of Object.keys(USER_CONTEXTS)) { + let mem = getCacheStorage( + "memory", + Services.loadContextInfo.custom(false, { userContextId }) + ); + let disk = getCacheStorage( + "disk", + Services.loadContextInfo.custom(false, { userContextId }) + ); + + Assert.ok( + !mem.exists(createURI("http://" + TEST_HOST + "/"), ""), + "The memory cache is cleared" + ); + Assert.ok( + !disk.exists(createURI("http://" + TEST_HOST + "/"), ""), + "The disk cache is cleared" + ); + } +} + +// Image Cache +async function test_image_cache_cleared() { + let tabs = []; + + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // Open our tab in the given user context to cache image. + tabs[userContextId] = await openTabInUserContext( + "http://localhost:" + + gHttpServer.identity.primaryPort + + "/loadImage.html", + userContextId + ); + BrowserTestUtils.removeTab(tabs[userContextId].tab); + } + + let expectedHits = USER_CONTEXTS.length; + + // Check that image cache works with the userContextId. + is( + gHits, + expectedHits, + "The image should be loaded" + expectedHits + "times." + ); + + // Reset the cache count. + gHits = 0; + + // Forget the site. + await ForgetAboutSite.removeDataFromDomain("localhost"); + + // Load again. + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // Open our tab in the given user context to cache image. + tabs[userContextId] = await openTabInUserContext( + "http://localhost:" + + gHttpServer.identity.primaryPort + + "/loadImage.html", + userContextId + ); + BrowserTestUtils.removeTab(tabs[userContextId].tab); + } + + // Check that image cache was cleared and the server gets another two hits. + is( + gHits, + expectedHits, + "The image should be loaded" + expectedHits + "times." + ); +} + +// Offline Storage +async function test_storage_cleared() { + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // Load the page in 2 different contexts and set the local storage + // which should only be visible in that context. + let value = USER_CONTEXTS[userContextId]; + + // Open our tab in the given user context. + let tabInfo = await openTabInUserContext( + TEST_URL + "file_set_storages.html?" + value, + userContextId + ); + + // Check that the storages has been set correctly. + await SpecialPowers.spawn( + tabInfo.browser, + [{ userContext: USER_CONTEXTS[userContextId] }], + async function (arg) { + // Check that the local storage has been set correctly. + Assert.equal( + content.localStorage.getItem("userContext"), + arg.userContext, + "Check the local storage value" + ); + + // Check that the session storage has been set correctly. + Assert.equal( + content.sessionStorage.getItem("userContext"), + arg.userContext, + "Check the session storage value" + ); + + // Check that the indexedDB has been set correctly. + let request = content.indexedDB.open("idb", 1); + + let db = await new Promise(done => { + request.onsuccess = event => { + done(event.target.result); + }; + }); + + let transaction = db.transaction(["obj"], "readonly"); + let store = transaction.objectStore("obj"); + let storeRequest = store.get(1); + + await new Promise(done => { + storeRequest.onsuccess = event => { + let res = storeRequest.result; + Assert.equal( + res.userContext, + arg.userContext, + "Check the indexedDB value" + ); + done(); + }; + }); + } + ); + + // Close this tab. + BrowserTestUtils.removeTab(tabInfo.tab); + } + + // Forget the site. + await ForgetAboutSite.removeDataFromDomain(TEST_HOST); + + // Open the tab again without setting the localStorage and check that the + // local storage has been cleared or not. + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // Open our tab in the given user context without setting local storage. + let tabInfo = await openTabInUserContext( + TEST_URL + "file_set_storages.html", + userContextId + ); + + // Check that do storages be cleared or not. + await SpecialPowers.spawn(tabInfo.browser, [], async function () { + // Check that does the local storage be cleared or not. + Assert.ok( + !content.localStorage.getItem("userContext"), + "The local storage has been cleared" + ); + + // Check that does the session storage be cleared or not. + Assert.ok( + !content.sessionStorage.getItem("userContext"), + "The session storage has been cleared" + ); + + // Check that does the indexedDB be cleared or not. + let request = content.indexedDB.open("idb", 1); + + let db = await new Promise(done => { + request.onsuccess = event => { + done(event.target.result); + }; + }); + try { + db.transaction(["obj"], "readonly"); + Assert.ok(false, "The indexedDB should not exist"); + } catch (e) { + Assert.equal( + e.name, + "NotFoundError", + "The indexedDB does not exist as expected" + ); + } + }); + + // Close the tab. + BrowserTestUtils.removeTab(tabInfo.tab); + } +} + +add_setup(async function () { + // Make sure userContext is enabled. + await SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", true]], + }); + + // Create a http server for the image cache test. + if (!gHttpServer) { + gHttpServer = new HttpServer(); + gHttpServer.registerPathHandler("/image.png", imageHandler); + gHttpServer.registerPathHandler("/loadImage.html", loadImagePageHandler); + gHttpServer.start(-1); + } +}); + +let tests = [ + test_cookie_cleared, + test_cache_cleared, + test_image_cache_cleared, + test_storage_cleared, +]; + +add_task(async function test() { + for (let i = 0; i < tests.length; i++) { + add_task(tests[i]); + } +}); + +registerCleanupFunction(() => { + gHttpServer.stop(() => { + gHttpServer = null; + }); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_guessusercontext.js b/browser/components/contextualidentity/test/browser/browser_guessusercontext.js new file mode 100644 index 0000000000..69461e67b5 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_guessusercontext.js @@ -0,0 +1,115 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const DEFAULT = Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID; +const PERSONAL = 1; +const WORK = 2; +const HOST_MOCHI = makeURI( + "http://mochi.test:8888/browser/browser/components/contextualidentity/test/browser/blank.html" +); +const HOST_EXAMPLE = makeURI( + "https://example.com/browser/browser/components/contextualidentity/test/browser/blank.html" +); + +const { + URILoadingHelper: { guessUserContextId }, +} = ChromeUtils.importESModule("resource:///modules/URILoadingHelper.sys.mjs"); + +async function openTabInUserContext(uri, userContextId, win = window) { + let { gBrowser } = win; + let tab = BrowserTestUtils.addTab(gBrowser, uri, { userContextId }); + gBrowser.selectedTab = tab; + tab.ownerGlobal.focus(); + await BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab)); + return tab; +} + +registerCleanupFunction(async function cleanup() { + Services.prefs.clearUserPref( + "browser.link.force_default_user_context_id_for_external_opens" + ); + while (gBrowser.tabs.length > 1) { + gBrowser.removeTab(gBrowser.selectedTab, { animate: false }); + } +}); + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", true]], + }); +}); + +add_task(async function test() { + is(guessUserContextId(null), null, "invalid uri - null"); + is(guessUserContextId(HOST_EXAMPLE), null, "no tabs - null"); + await openTabInUserContext(HOST_EXAMPLE.spec, PERSONAL); + is(guessUserContextId(HOST_EXAMPLE), PERSONAL, "one tab - matches container"); + is(guessUserContextId(HOST_MOCHI), null, "one tab - doesn't match container"); + + await openTabInUserContext(HOST_MOCHI.spec, PERSONAL); + is(guessUserContextId(HOST_MOCHI), PERSONAL, "one tab - matches container"); + await openTabInUserContext(HOST_MOCHI.spec); + await openTabInUserContext(HOST_MOCHI.spec); + is( + guessUserContextId(HOST_MOCHI), + DEFAULT, + "can guess guess default container" + ); + + await openTabInUserContext(HOST_EXAMPLE.spec, WORK); + is(guessUserContextId(HOST_EXAMPLE), PERSONAL, "same number - use first"); + await openTabInUserContext(HOST_EXAMPLE.spec, WORK); + is(guessUserContextId(HOST_EXAMPLE), WORK, "multiple per host - max"); + + let win = await BrowserTestUtils.openNewBrowserWindow(); + await openTabInUserContext(HOST_EXAMPLE.spec, PERSONAL, win); + await openTabInUserContext(HOST_EXAMPLE.spec, PERSONAL, win); + is(guessUserContextId(HOST_EXAMPLE), PERSONAL, "count across windows"); + + await BrowserTestUtils.closeWindow(win); + is(guessUserContextId(HOST_EXAMPLE), WORK, "forgets closed window"); + + // Check the opener flow more directly + openURIFromExternal(HOST_EXAMPLE.spec + "?new"); + is( + gBrowser.selectedTab.getAttribute("usercontextid"), + WORK.toString(), + "opener flow" + ); + is(guessUserContextId(HOST_EXAMPLE), WORK, "still the most common"); + is( + guessUserContextId(HOST_MOCHI), + DEFAULT, + "still matches default container" + ); + + // Force into default with the pref from https://bugzilla.mozilla.org/show_bug.cgi?id=1692124 + Services.prefs.setBoolPref( + "browser.link.force_default_user_context_id_for_external_opens", + true + ); + openURIFromExternal(HOST_EXAMPLE.spec + "?new"); + is( + gBrowser.selectedTab.getAttribute("usercontextid"), + "", + "opener flow with default user context ID forced by pref" + ); +}); + +async function openURIFromExternal(spec) { + let browsingContext = window.browserDOMWindow.openURI( + makeURI(spec), + null, + Ci.nsIBrowserDOMWindow.OPEN_NEWTAB, + Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL, + Services.scriptSecurityManager.getSystemPrincipal() + ); + await BrowserTestUtils.browserLoaded(browsingContext.embedderElement); + is( + browsingContext.embedderElement, + gBrowser.selectedBrowser, + "opener selected" + ); +} diff --git a/browser/components/contextualidentity/test/browser/browser_imageCache.js b/browser/components/contextualidentity/test/browser/browser_imageCache.js new file mode 100644 index 0000000000..ec3645d9a4 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_imageCache.js @@ -0,0 +1,57 @@ +let { HttpServer } = ChromeUtils.importESModule( + "resource://testing-common/httpd.sys.mjs" +); + +const NUM_USER_CONTEXTS = 3; + +let gHits = 0; + +let server = new HttpServer(); +server.registerPathHandler("/image.png", imageHandler); +server.registerPathHandler("/file.html", fileHandler); +server.start(-1); + +let BASE_URI = "http://localhost:" + server.identity.primaryPort; +let IMAGE_URI = BASE_URI + "/image.png"; +let FILE_URI = BASE_URI + "/file.html"; + +function imageHandler(metadata, response) { + gHits++; + response.setHeader("Cache-Control", "max-age=10000", false); + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "image/png", false); + var body = atob( + "iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=" + ); + response.bodyOutputStream.write(body, body.length); +} + +function fileHandler(metadata, response) { + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/html", false); + let body = `<html><body><image src=${IMAGE_URI}></body></html>`; + response.bodyOutputStream.write(body, body.length); +} + +add_setup(async function () { + // make sure userContext is enabled. + await SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", true]], + }); +}); + +add_task(async function test() { + for ( + let userContextId = 0; + userContextId < NUM_USER_CONTEXTS; + userContextId++ + ) { + let { tab } = await openTabInUserContext(FILE_URI, userContextId); + gBrowser.removeTab(tab); + } + is( + gHits, + NUM_USER_CONTEXTS, + "should get an image request for each user contexts" + ); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_middleClick.js b/browser/components/contextualidentity/test/browser/browser_middleClick.js new file mode 100644 index 0000000000..9b9fbcb737 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_middleClick.js @@ -0,0 +1,50 @@ +"use strict"; + +const BASE_ORIGIN = "http://example.com"; +const URI = + BASE_ORIGIN + + "/browser/browser/components/contextualidentity/test/browser/empty_file.html"; + +add_task(async function () { + info("Opening a new container tab..."); + + let tab = BrowserTestUtils.addTab(gBrowser, URI, { userContextId: 1 }); + gBrowser.selectedTab = tab; + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + info("Create a HTMLAnchorElement..."); + await SpecialPowers.spawn(browser, [URI], function (uri) { + let anchor = content.document.createElement("a"); + anchor.setAttribute("id", "clickMe"); + anchor.setAttribute("href", uri); + anchor.appendChild(content.document.createTextNode("click me!")); + content.document.body.appendChild(anchor); + }); + + info("Synthesize a mouse click and wait for a new tab..."); + let newTab = await new Promise((resolve, reject) => { + gBrowser.tabContainer.addEventListener( + "TabOpen", + function (openEvent) { + resolve(openEvent.target); + }, + { once: true } + ); + + BrowserTestUtils.synthesizeMouseAtCenter( + "#clickMe", + { button: 1 }, + browser + ); + }); + + is(newTab.getAttribute("usercontextid"), "1", "Correct UserContextId?"); + + // newTab shouldn't be closed in the same event tick as TabOpen. + await TestUtils.waitForTick(); + + BrowserTestUtils.removeTab(tab); + BrowserTestUtils.removeTab(newTab); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_newtabButton.js b/browser/components/contextualidentity/test/browser/browser_newtabButton.js new file mode 100644 index 0000000000..02f50f27d6 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_newtabButton.js @@ -0,0 +1,214 @@ +"use strict"; + +// Testing that when the user opens the add tab menu and clicks menu items +// the correct context id is opened + +function findPopup(browser = gBrowser) { + return browser.tabContainer.querySelector(".new-tab-popup"); +} + +function findContextPopup() { + return document.querySelector("#new-tab-button-popup"); +} + +add_task(async function test_containers_no_left_click() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.userContext.enabled", true], + ["privacy.userContext.newTabContainerOnLeftClick.enabled", false], + ], + }); + + let newTabButton = gBrowser.tabContainer.newTabButton; + ok(newTabButton, "New tab button exists"); + ok(!newTabButton.hidden, "New tab button is visible"); + let popup = findPopup(); + ok(popup, "new tab should have a popup"); + + // Test context menu + let contextMenu = findContextPopup(); + is(contextMenu.state, "closed", "Context menu is initally closed."); + + let popupshown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown"); + + let target = gBrowser.tabContainer.newTabButton; + EventUtils.synthesizeMouseAtCenter(target, { type: "contextmenu" }); + await popupshown; + + is(contextMenu.state, "open", "Context menu is open."); + let contextIdItems = contextMenu.querySelectorAll("menuitem"); + // 4 + default + manage containers + is(contextIdItems.length, 6, "Has 6 menu items"); + contextMenu.hidePopup(); +}); + +add_task(async function test_containers_with_left_click() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.userContext.enabled", true], + ["privacy.userContext.newTabContainerOnLeftClick.enabled", true], + ], + }); + + let newTabButton = gBrowser.tabContainer.newTabButton; + ok(newTabButton, "New tab button exists"); + ok(!newTabButton.hidden, "New tab button is visible"); + + await BrowserTestUtils.waitForCondition( + () => !!findPopup(), + "Wait for popup to exist" + ); + let popup = findPopup(); + + let popupShownPromise = BrowserTestUtils.waitForEvent(popup, "popupshown"); + let popupHiddenPromise = BrowserTestUtils.waitForEvent(popup, "popuphidden"); + EventUtils.synthesizeMouseAtCenter(newTabButton, { type: "mousedown" }); + await popupShownPromise; + let contextIdItems = popup.querySelectorAll("menuitem"); + // 4 + default + manage containers + is(contextIdItems.length, 6, "Has 6 menu items"); + popup.hidePopup(); + await popupHiddenPromise; + + for (let i = 0; i <= 4; i++) { + popupShownPromise = BrowserTestUtils.waitForEvent(popup, "popupshown"); + EventUtils.synthesizeMouseAtCenter(newTabButton, { type: "mousedown" }); + + await popupShownPromise; + let contextIdItem = popup.querySelector( + `menuitem[data-usercontextid="${i}"]` + ); + + ok(contextIdItem, `User context id ${i} exists`); + + // waitForNewTab doesn't work for default tabs due to a different code path that doesn't cause a load event + let waitForTabPromise = BrowserTestUtils.waitForEvent( + gBrowser.tabContainer, + "TabOpen" + ); + EventUtils.synthesizeMouseAtCenter(contextIdItem, {}); + + let tabEvent = await waitForTabPromise; + let tab = tabEvent.target; + if (i > 0) { + is( + tab.getAttribute("usercontextid"), + "" + i, + `New tab has UCI equal ${i}` + ); + } else { + ok(!tab.hasAttribute("usercontextid"), `New tab has no UCI`); + } + BrowserTestUtils.removeTab(tab); + } + + // Test context menu + let contextMenu = findContextPopup(); + is(contextMenu.state, "closed", "Context menu is initally closed."); + + let popupshown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown"); + + let target = gBrowser.tabContainer.newTabButton; + EventUtils.synthesizeMouseAtCenter(target, { type: "contextmenu" }); + await popupshown; + + is(contextMenu.state, "open", "Context menu is open."); + contextIdItems = contextMenu.querySelectorAll("menuitem"); + // 4 + default + manage containers + is(contextIdItems.length, 6, "Has 6 menu items"); + contextMenu.hidePopup(); +}); + +add_task(async function test_no_containers_no_left_click() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.userContext.enabled", false], + ["privacy.userContext.newTabContainerOnLeftClick.enabled", false], + ], + }); + + let newTabButton = gBrowser.tabContainer.newTabButton; + ok(newTabButton, "New tab button exists"); + ok(!newTabButton.hidden, "New tab button is visible"); + let popup = findPopup(); + ok(!popup, "new tab should not have a popup"); + + // Test context menu + let contextMenu = findContextPopup(); + let target = gBrowser.tabContainer.newTabButton; + EventUtils.synthesizeMouseAtCenter(target, { type: "contextmenu" }); + is(contextMenu.state, "closed", "Context menu does not open."); +}); + +add_task(async function test_private_mode() { + let privateWindow = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + let privateDocument = privateWindow.document; + let { tabContainer } = privateWindow.gBrowser; + let newTab = tabContainer.newTabButton; + let newTab2 = privateDocument.getElementById("new-tab-button"); + // Check to ensure we are talking about the right button + ok(!!newTab.clientWidth, "new tab button should not be hidden"); + ok(!newTab2.clientWidth, "overflow new tab button should be hidden"); + let popup = findPopup(privateWindow.gBrowser); + ok(!popup, "new tab should not have a popup"); + await BrowserTestUtils.closeWindow(privateWindow); +}); + +add_task(async function test_opening_container_tab_context() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.userContext.enabled", true], + ["privacy.userContext.newTabContainerOnLeftClick.enabled", true], + ], + }); + + let newTabButton = gBrowser.tabContainer.newTabButton; + ok(newTabButton, "New tab button exists"); + ok(!newTabButton.hidden, "New tab button is visible"); + + let popup = findContextPopup(); + + let popupShownPromise = BrowserTestUtils.waitForEvent(popup, "popupshown"); + let popupHiddenPromise = BrowserTestUtils.waitForEvent(popup, "popuphidden"); + EventUtils.synthesizeMouseAtCenter(newTabButton, { type: "contextmenu" }); + await popupShownPromise; + let contextIdItems = popup.querySelectorAll("menuitem"); + // 4 + default + manage containers + is(contextIdItems.length, 6, "Has 6 menu items"); + popup.hidePopup(); + await popupHiddenPromise; + + for (let i = 0; i <= 4; i++) { + popupShownPromise = BrowserTestUtils.waitForEvent(popup, "popupshown"); + EventUtils.synthesizeMouseAtCenter(newTabButton, { type: "contextmenu" }); + + await popupShownPromise; + let contextIdItem = popup.querySelector( + `menuitem[data-usercontextid="${i}"]` + ); + + ok(contextIdItem, `User context id ${i} exists`); + + // waitForNewTab doesn't work for default tabs due to a different code path that doesn't cause a load event + let waitForTabPromise = BrowserTestUtils.waitForEvent( + gBrowser.tabContainer, + "TabOpen" + ); + popup.activateItem(contextIdItem); + + let tabEvent = await waitForTabPromise; + let tab = tabEvent.target; + if (i > 0) { + is( + tab.getAttribute("usercontextid"), + "" + i, + `New tab has UCI equal ${i}` + ); + } else { + ok(!tab.hasAttribute("usercontextid"), `New tab has no UCI`); + } + BrowserTestUtils.removeTab(tab); + } +}); diff --git a/browser/components/contextualidentity/test/browser/browser_originattrs_reopenin.js b/browser/components/contextualidentity/test/browser/browser_originattrs_reopenin.js new file mode 100644 index 0000000000..9ee3c0490a --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_originattrs_reopenin.js @@ -0,0 +1,184 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../../../base/content/test/tabs/helper_origin_attrs_testing.js */ +loadTestSubscript( + "../../../../base/content/test/tabs/helper_origin_attrs_testing.js" +); + +const PATH = + "/browser/browser/components/contextualidentity/test/browser/blank.html"; + +const URI_EXAMPLECOM = "https://example.com/" + PATH; +const URI_EXAMPLEORG = "https://example.org/" + PATH; + +var TEST_CASES = [ + URI_EXAMPLECOM, + URI_EXAMPLEORG, + "about:preferences", + "about:config", +]; + +var remoteTypes; + +var xulFrameLoaderCreatedCounter = {}; + +function handleEventLocal(aEvent) { + if (aEvent.type != "XULFrameLoaderCreated") { + return; + } + // Ignore <browser> element in about:preferences and any other special pages + if ("gBrowser" in aEvent.target.ownerGlobal) { + xulFrameLoaderCreatedCounter.numCalledSoFar++; + } +} +const NUM_PAGES_OPEN_FOR_EACH_TEST_CASE = 5; +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.userContext.enabled", true], + // don't preload tabs so we don't have extra XULFrameLoaderCreated events + // firing + ["browser.newtab.preload", false], + ], + }); + + requestLongerTimeout(5); +}); + +function setupRemoteTypes() { + remoteTypes = { + regular: { "about:preferences": null, "about:config": null }, + 1: { "about:preferences": null, "about:config": null }, + 2: { "about:preferences": null, "about:config": null }, + 3: { "about:preferences": null, "about:config": null }, + }; + if (gFissionBrowser) { + remoteTypes.regular[URI_EXAMPLECOM] = "webIsolated=https://example.com"; + remoteTypes.regular[URI_EXAMPLEORG] = "webIsolated=https://example.org"; + remoteTypes["1"][URI_EXAMPLECOM] = + "webIsolated=https://example.com^userContextId=1"; + remoteTypes["1"][URI_EXAMPLEORG] = + "webIsolated=https://example.org^userContextId=1"; + remoteTypes["2"][URI_EXAMPLECOM] = + "webIsolated=https://example.com^userContextId=2"; + remoteTypes["2"][URI_EXAMPLEORG] = + "webIsolated=https://example.org^userContextId=2"; + remoteTypes["3"][URI_EXAMPLECOM] = + "webIsolated=https://example.com^userContextId=3"; + remoteTypes["3"][URI_EXAMPLEORG] = + "webIsolated=https://example.org^userContextId=3"; + } else { + remoteTypes.regular[URI_EXAMPLECOM] = "web"; + remoteTypes.regular[URI_EXAMPLEORG] = "web"; + remoteTypes["1"][URI_EXAMPLECOM] = "web"; + remoteTypes["1"][URI_EXAMPLEORG] = "web"; + remoteTypes["2"][URI_EXAMPLECOM] = "web"; + remoteTypes["2"][URI_EXAMPLEORG] = "web"; + remoteTypes["3"][URI_EXAMPLECOM] = "web"; + remoteTypes["3"][URI_EXAMPLEORG] = "web"; + } +} + +add_task(async function testReopen() { + setupRemoteTypes(); + /** + * Open a regular tab + * For each url + * navigate regular tab to that url + * pid_seen = [regular tab pid] + * For each container + * reopen regular tab in that container + * verify remote type + * reopen container tab in regular tab + * Close all the tabs + * This tests that behaviour of reopening tabs is correct + */ + + let regularPage = await openURIInRegularTab("about:blank"); + var currRemoteType; + + for (const uri of TEST_CASES) { + // Navigate regular tab to a test-uri + let loaded = BrowserTestUtils.browserLoaded( + regularPage.tab.linkedBrowser, + false, + uri + ); + BrowserTestUtils.startLoadingURIString(regularPage.tab.linkedBrowser, uri); + await loaded; + info(`Start Opened ${uri} in a regular tab`); + currRemoteType = regularPage.tab.linkedBrowser.remoteType; + is(currRemoteType, remoteTypes.regular[uri], "correct remote type"); + + let containerTabs = []; + + // Reopen this test-uri in different containers and ensure pids are different + for (var userCtxId = 1; userCtxId <= NUM_USER_CONTEXTS; userCtxId++) { + // Prepare the reopen menu + let reopenMenu = await openReopenMenuForTab(regularPage.tab); + + // Add a listener for XULFrameLoaderCreated + initXulFrameLoaderCreatedCounter(xulFrameLoaderCreatedCounter); + regularPage.tab.ownerGlobal.gBrowser.addEventListener( + "XULFrameLoaderCreated", + handleEventLocal + ); + + // Reopen the page in a container + let containerTab = await openTabInContainer( + gBrowser, + uri, + reopenMenu, + userCtxId.toString() + ); + info(`Re-opened ${uri} in a container tab ${userCtxId}`); + + // Check correct remote type + currRemoteType = containerTab.linkedBrowser.remoteType; + is( + currRemoteType, + remoteTypes[userCtxId.toString()][uri], + "correct remote type" + ); + + // Check that XULFrameLoaderCreated has fired off correct number of times + is( + xulFrameLoaderCreatedCounter.numCalledSoFar, + 1, + `XULFrameLoaderCreated was fired once when reopening ${uri} in container ${userCtxId}` + ); + regularPage.tab.ownerGlobal.gBrowser.removeEventListener( + "XULFrameLoaderCreated", + handleEventLocal + ); + + // Save the tab so we can close it later + containerTabs.push(containerTab); + } + let containedTabsOpenedInNoContainer = []; + + for (const containerTab of containerTabs) { + info(`About to re-open ${uri} in a regular, no-container tab`); + let reopenMenu = await openReopenMenuForTab(containerTab); + let reopenedInNoContainerTab = await openTabInContainer( + gBrowser, + uri, + reopenMenu, + "0" + ); + info(`Re-opened ${uri} in a regular, no-container tab`); + // Check remote type + currRemoteType = reopenedInNoContainerTab.linkedBrowser.remoteType; + is(currRemoteType, remoteTypes.regular[uri], "correct remote type"); + containedTabsOpenedInNoContainer.push(reopenedInNoContainerTab); + } + + // Close tabs + for (const tab of containerTabs.concat(containedTabsOpenedInNoContainer)) { + BrowserTestUtils.removeTab(tab); + } + } + BrowserTestUtils.removeTab(regularPage.tab); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_relatedTab.js b/browser/components/contextualidentity/test/browser/browser_relatedTab.js new file mode 100644 index 0000000000..3451d3d16e --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_relatedTab.js @@ -0,0 +1,94 @@ +"use strict"; + +/* + * Bug 1325014 - Adding tab related to current tab inherits current tab's container usercontextid unless otherwise specified + */ + +add_task(async function () { + let tab = BrowserTestUtils.addTab(gBrowser, "about:blank", { + userContextId: 1, + }); + let ReferrerInfo = Components.Constructor( + "@mozilla.org/referrer-info;1", + "nsIReferrerInfo", + "init" + ); + + gBrowser.selectedTab = tab; + let relatedTab = BrowserTestUtils.addTab(gBrowser, "about:blank", { + relatedToCurrent: true, + }); + is( + relatedTab.getAttribute("usercontextid"), + "1", + "Related tab (relatedToCurrent) inherits current tab's usercontextid" + ); + is( + relatedTab.linkedBrowser.contentPrincipal.userContextId, + 1, + "Related tab's browser actually inherits the current tab's usercontextid" + ); + BrowserTestUtils.removeTab(relatedTab); + + gBrowser.selectedTab = tab; + relatedTab = BrowserTestUtils.addTab(gBrowser, "about:blank", { + relatedToCurrent: true, + userContextId: 2, + }); + is( + relatedTab.getAttribute("usercontextid"), + "2", + "Related tab (relatedToCurrent) with overridden usercontextid" + ); + is( + relatedTab.linkedBrowser.contentPrincipal.userContextId, + 2, + "Related tab's browser actually gets overridden usercontextid" + ); + BrowserTestUtils.removeTab(relatedTab); + + gBrowser.selectedTab = tab; + let referrerInfo = new ReferrerInfo( + Ci.nsIReferrerInfo.EMPTY, + true, + gBrowser.currentURI + ); + relatedTab = BrowserTestUtils.addTab(gBrowser, "about:blank", { + referrerInfo, + }); + is( + relatedTab.getAttribute("usercontextid"), + "1", + "Related tab (referrer) inherits current tab's usercontextid" + ); + is( + relatedTab.linkedBrowser.contentPrincipal.userContextId, + 1, + "Related tab's browser (referrer) actually inherits the current tab's usercontextid" + ); + BrowserTestUtils.removeTab(relatedTab); + + gBrowser.selectedTab = tab; + referrerInfo = new ReferrerInfo( + Ci.nsIReferrerInfo.EMPTY, + true, + gBrowser.currentURI + ); + relatedTab = BrowserTestUtils.addTab(gBrowser, "about:blank", { + referrerInfo, + userContextId: 2, + }); + is( + relatedTab.getAttribute("usercontextid"), + "2", + "Related tab (referrer) with overridden usercontextid" + ); + is( + relatedTab.linkedBrowser.contentPrincipal.userContextId, + 2, + "Related tab's browser (referrer) actually gets overridden usercontextid" + ); + BrowserTestUtils.removeTab(relatedTab); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_reopenIn.js b/browser/components/contextualidentity/test/browser/browser_reopenIn.js new file mode 100644 index 0000000000..9bda63b201 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_reopenIn.js @@ -0,0 +1,164 @@ +"use strict"; + +const TEST_HOST = "example.com"; +const TEST_URL = + "http://" + + TEST_HOST + + "/browser/browser/components/contextualidentity/test/browser/"; + +async function openTabMenuFor(tab) { + let tabMenu = tab.ownerDocument.getElementById("tabContextMenu"); + + let tabMenuShown = BrowserTestUtils.waitForEvent(tabMenu, "popupshown"); + EventUtils.synthesizeMouseAtCenter( + tab, + { type: "contextmenu" }, + tab.ownerGlobal + ); + await tabMenuShown; + + return tabMenu; +} + +async function openReopenMenuForTab(tab) { + await openTabMenuFor(tab); + + let reopenItem = tab.ownerDocument.getElementById( + "context_reopenInContainer" + ); + ok(!reopenItem.hidden, "Reopen in Container item should be shown"); + + let reopenMenu = reopenItem.getElementsByTagName("menupopup")[0]; + let reopenMenuShown = BrowserTestUtils.waitForEvent(reopenMenu, "popupshown"); + reopenItem.openMenu(true); + await reopenMenuShown; + + return reopenMenu; +} + +function checkMenuItem(reopenMenu, shown, hidden) { + for (let id of shown) { + ok( + reopenMenu.querySelector(`menuitem[data-usercontextid="${id}"]`), + `User context id ${id} should exist` + ); + } + for (let id of hidden) { + ok( + !reopenMenu.querySelector(`menuitem[data-usercontextid="${id}"]`), + `User context id ${id} shouldn't exist` + ); + } +} + +function openTabInContainer(gBrowser, reopenMenu, id) { + let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, TEST_URL, true); + let menuitem = reopenMenu.querySelector( + `menuitem[data-usercontextid="${id}"]` + ); + reopenMenu.activateItem(menuitem); + return tabPromise; +} + +add_task(async function testReopen() { + await SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", true]], + }); + + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + url: TEST_URL, + }); + ok( + !tab.hasAttribute("usercontextid"), + "Tab with No Container should be opened" + ); + + let reopenMenu = await openReopenMenuForTab(tab); + checkMenuItem(reopenMenu, [1, 2, 3, 4], [0]); + + let containerTab = await openTabInContainer(gBrowser, reopenMenu, "1"); + + is( + containerTab.getAttribute("usercontextid"), + "1", + "Tab with UCI=1 should be opened" + ); + is( + containerTab.linkedBrowser.currentURI.spec, + TEST_URL, + "Same page should be opened" + ); + + reopenMenu = await openReopenMenuForTab(containerTab); + checkMenuItem(reopenMenu, [0, 2, 3, 4], [1]); + + let noContainerTab = await openTabInContainer(gBrowser, reopenMenu, "0"); + + ok( + !noContainerTab.hasAttribute("usercontextid"), + "Tab with no UCI should be opened" + ); + is( + noContainerTab.linkedBrowser.currentURI.spec, + TEST_URL, + "Same page should be opened" + ); + + BrowserTestUtils.removeTab(tab); + BrowserTestUtils.removeTab(containerTab); + BrowserTestUtils.removeTab(noContainerTab); +}); + +add_task(async function testDisabled() { + await SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", false]], + }); + + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + url: TEST_URL, + }); + ok( + !tab.hasAttribute("usercontextid"), + "Tab with No Container should be opened" + ); + + let tabMenu = await openTabMenuFor(tab); + let reopenItem = document.getElementById("context_reopenInContainer"); + ok(reopenItem.hidden, "Reopen in Container item should be hidden"); + + // Close the tab menu. + tabMenu.hidePopup(); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function testPrivateMode() { + await SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", true]], + }); + + let privateWindow = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser: privateWindow.gBrowser, + url: TEST_URL, + }); + ok( + !tab.hasAttribute("usercontextid"), + "Tab with No Container should be opened" + ); + + let tabMenu = await openTabMenuFor(tab); + let reopenItem = privateWindow.document.getElementById( + "context_reopenInContainer" + ); + ok(reopenItem.hidden, "Reopen in Container item should be hidden"); + + // Close the tab menu. + tabMenu.hidePopup(); + + await BrowserTestUtils.closeWindow(privateWindow); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_restore_getCookiesWithOriginAttributes.js b/browser/components/contextualidentity/test/browser/browser_restore_getCookiesWithOriginAttributes.js new file mode 100644 index 0000000000..b3cacfbb21 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_restore_getCookiesWithOriginAttributes.js @@ -0,0 +1,109 @@ +/* + * Bug 1334587 - A Test case for checking whether forgetting APIs are working for cookies. + */ + +const CC = Components.Constructor; + +const TEST_HOST = "example.com"; +const TEST_URL = + "http://" + + TEST_HOST + + "/browser/browser/components/contextualidentity/test/browser/"; + +const USER_CONTEXTS = ["default", "personal", "work"]; + +const DELETE_CONTEXT = 1; +const COOKIE_NAME = "userContextId"; + +// +// Support functions. +// + +function getCookiesForOA(host, userContextId) { + return Services.cookies.getCookiesFromHost(host, { userContextId }); +} + +// +// Test functions. +// + +add_setup(async function () { + // Make sure userContext is enabled. + await SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", true]], + }); +}); + +function checkCookies(ignoreContext = null) { + for (let userContextId of Object.keys(USER_CONTEXTS)) { + if (ignoreContext && userContextId === String(ignoreContext)) { + continue; + } + let cookies = getCookiesForOA(TEST_HOST, userContextId); + ok(cookies.length, "Cookies available"); + + let foundCookie = cookies[0]; + is(foundCookie.name, COOKIE_NAME, "Check cookie name"); + is(foundCookie.value, USER_CONTEXTS[userContextId], "Check cookie value"); + } +} + +function deleteCookies(onlyContext = null) { + // Using getCookiesWithOriginAttributes() to get all cookies for a certain + // domain by using the originAttributes pattern, and clear all these cookies. + for (let cookie of Services.cookies.getCookiesWithOriginAttributes( + JSON.stringify({}), + TEST_HOST + )) { + if (!onlyContext || cookie.originAttributes.userContextId == onlyContext) { + Services.cookies.remove( + cookie.host, + cookie.name, + cookie.path, + cookie.originAttributes + ); + } + } +} + +add_task(async function test_cookie_getCookiesWithOriginAttributes() { + let tabs = []; + + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // Load the page in different contexts and set a cookie + // which should only be visible in that context. + let value = USER_CONTEXTS[userContextId]; + + // Open our tab in the given user context. + tabs[userContextId] = await openTabInUserContext( + TEST_URL + "file_reflect_cookie_into_title.html?" + value, + userContextId + ); + + // Close this tab. + BrowserTestUtils.removeTab(tabs[userContextId].tab); + } + + // Check that cookies have been set properly. + for (let userContextId of Object.keys(USER_CONTEXTS)) { + let cookies = getCookiesForOA(TEST_HOST, userContextId); + ok(cookies.length, "Cookies available"); + + let foundCookie = cookies[0]; + is(foundCookie.name, COOKIE_NAME, "Check cookie name"); + is(foundCookie.value, USER_CONTEXTS[userContextId], "Check cookie value"); + } + checkCookies(); + + deleteCookies(DELETE_CONTEXT); + + checkCookies(DELETE_CONTEXT); + + deleteCookies(); + + // Check that whether cookies has been cleared. + for (let userContextId of Object.keys(USER_CONTEXTS)) { + let cookies = getCookiesForOA(TEST_HOST, userContextId); + ok(!cookies.length, "No Cookie should be here"); + } +}); diff --git a/browser/components/contextualidentity/test/browser/browser_saveLink.js b/browser/components/contextualidentity/test/browser/browser_saveLink.js new file mode 100644 index 0000000000..65f60f7eef --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_saveLink.js @@ -0,0 +1,134 @@ +"use strict"; + +const BASE_ORIGIN = "https://example.com"; +const URI = + BASE_ORIGIN + + "/browser/browser/components/contextualidentity/test/browser/saveLink.sjs"; + +let MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); + +add_setup(async function () { + info("Setting the prefs."); + + // make sure userContext is enabled. + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.userContext.enabled", true], + // This test does a redirect from https to http and it checks the + // cookies. This is incompatible with the cookie SameSite schemeful + // feature and we need to disable it. + ["network.cookie.sameSite.schemeful", false], + // This Test trys to download mixed content + // so we need to make sure that this is not blocked + ["dom.block_download_insecure", false], + ], + }); +}); + +add_task(async function test() { + info("Let's open a new window"); + let win = await BrowserTestUtils.openNewBrowserWindow(); + + info("Opening tab with UCI: 1"); + let tab = BrowserTestUtils.addTab(win.gBrowser, URI + "?UCI=1", { + userContextId: 1, + }); + + // select tab and make sure its browser is focused + win.gBrowser.selectedTab = tab; + tab.ownerGlobal.focus(); + + info("Waiting to load content"); + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + let popupShownPromise = BrowserTestUtils.waitForEvent( + win.document, + "popupshown" + ); + + await BrowserTestUtils.synthesizeMouseAtCenter( + "#fff", + { type: "contextmenu", button: 2 }, + win.gBrowser.selectedBrowser + ); + info("Right clicked!"); + + await popupShownPromise; + info("Context menu opened"); + + info("Let's create a temporary dir"); + let tempDir = createTemporarySaveDirectory(); + let destFile; + + MockFilePicker.displayDirectory = tempDir; + MockFilePicker.showCallback = fp => { + info("MockFilePicker showCallback"); + + let fileName = fp.defaultString; + destFile = tempDir.clone(); + destFile.append(fileName); + + MockFilePicker.setFiles([destFile]); + MockFilePicker.filterIndex = 1; // kSaveAsType_URL + + info("MockFilePicker showCallback done"); + }; + + let transferCompletePromise = new Promise(resolve => { + function onTransferComplete(downloadSuccess) { + ok(downloadSuccess, "File should have been downloaded successfully"); + resolve(); + } + + mockTransferCallback = onTransferComplete; + mockTransferRegisterer.register(); + }); + + registerCleanupFunction(function () { + mockTransferRegisterer.unregister(); + MockFilePicker.cleanup(); + tempDir.remove(true); + }); + + // Select "Save Link As" option from context menu + let saveLinkCommand = win.document.getElementById("context-savelink"); + info("saveLinkCommand: " + saveLinkCommand); + saveLinkCommand.doCommand(); + + let contextMenu = win.document.getElementById("contentAreaContextMenu"); + let popupHiddenPromise = BrowserTestUtils.waitForEvent( + contextMenu, + "popuphidden" + ); + contextMenu.hidePopup(); + await popupHiddenPromise; + info("popup hidden"); + + await transferCompletePromise; + + // Let's query the SJS to know if the download happened with the correct cookie. + let response = await fetch(URI + "?result=1"); + let text = await response.text(); + is(text, "Result:UCI=1", "Correct cookie used: -" + text + "-"); + + info("Closing the window"); + await BrowserTestUtils.closeWindow(win); +}); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", + this +); + +function createTemporarySaveDirectory() { + let saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile); + saveDir.append("testsavedir"); + if (!saveDir.exists()) { + info("create testsavedir!"); + saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + } + info("return from createTempSaveDir: " + saveDir.path); + return saveDir; +} diff --git a/browser/components/contextualidentity/test/browser/browser_serviceworkers.js b/browser/components/contextualidentity/test/browser/browser_serviceworkers.js new file mode 100644 index 0000000000..4f42c55d73 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_serviceworkers.js @@ -0,0 +1,121 @@ +let swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService( + Ci.nsIServiceWorkerManager +); + +const BASE_ORIGIN = "https://example.com"; +const URI = + BASE_ORIGIN + + "/browser/browser/components/contextualidentity/test/browser/serviceworker.html"; +const NUM_USER_CONTEXTS = 3; + +// opens `uri' in a new tab with the provided userContextId and focuses it. +// returns the newly opened tab +function openTabInUserContext(uri, userContextId) { + // open the tab in the correct userContextId + let tab = BrowserTestUtils.addTab(gBrowser, uri, { userContextId }); + + // select tab and make sure its browser is focused + gBrowser.selectedTab = tab; + tab.ownerGlobal.focus(); + + return tab; +} + +add_setup(async function () { + // make sure userContext is enabled. + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.userContext.enabled", true], + ["dom.ipc.processCount", 1], + ], + }); +}); + +let infos = []; + +add_task(async function test() { + // Open the same URI in multiple user contexts, and make sure we have a + // separate service worker in each of the contexts + for ( + let userContextId = 0; + userContextId < NUM_USER_CONTEXTS; + userContextId++ + ) { + // Open a tab in given user contexts + let tab = openTabInUserContext(URI, userContextId); + + // wait for tab load + await BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab)); + + // remove the tab + gBrowser.removeTab(tab); + } + + if (!allRegistered()) { + await promiseAllRegistered(); + } + ok(true, "all service workers are registered"); + + // Unregistered all service workers added in this test + for (let info of infos) { + await promiseUnregister(info); + } +}); + +function allRegistered() { + let results = []; + let registrations = swm.getAllRegistrations(); + for (let i = 0; i < registrations.length; i++) { + let info = registrations.queryElementAt( + i, + Ci.nsIServiceWorkerRegistrationInfo + ); + let principal = info.principal; + if (principal.originNoSuffix === BASE_ORIGIN) { + results[principal.userContextId] = true; + infos[principal.userContextId] = info; + } + } + for ( + let userContextId = 0; + userContextId < NUM_USER_CONTEXTS; + userContextId++ + ) { + if (!results[userContextId]) { + return false; + } + } + return true; +} + +function promiseAllRegistered() { + return new Promise(function (resolve) { + let listener = { + onRegister() { + if (allRegistered()) { + swm.removeListener(listener); + resolve(); + } + }, + }; + swm.addListener(listener); + }); +} + +function promiseUnregister(info) { + return new Promise(function (resolve) { + swm.unregister( + info.principal, + { + unregisterSucceeded(aState) { + ok(aState, "ServiceWorkerRegistration exists"); + resolve(); + }, + unregisterFailed(aState) { + ok(false, "unregister should succeed"); + }, + }, + info.scope + ); + }); +} diff --git a/browser/components/contextualidentity/test/browser/browser_switchTab_across_user_context.js b/browser/components/contextualidentity/test/browser/browser_switchTab_across_user_context.js new file mode 100644 index 0000000000..fc299f4cbd --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_switchTab_across_user_context.js @@ -0,0 +1,141 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(this, "UrlbarTestUtils", () => { + const { UrlbarTestUtils: module } = ChromeUtils.importESModule( + "resource://testing-common/UrlbarTestUtils.sys.mjs" + ); + module.init(this); + return module; +}); + +add_setup(async () => { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.userContext.enabled", true], + ["browser.urlbar.switchTabs.searchAllContainers", true], + ], + }); +}); + +add_task(async function test_switch_tab() { + let urlA = "https://example.com/"; + let urlB = "https://www.mozilla.org/"; + + let contextIdTabA = 1; + let contextIdTabB = 2; + let contextIdTabC = 3; + + let { tab: tabA } = await openTabInUserContext(urlA, contextIdTabA); + let { tab: tabB } = await openTabInUserContext(urlA, contextIdTabB); + let { tab: tabC } = await openTabInUserContext(urlB, contextIdTabC); + + let searchContext = await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + waitForFocus, + value: "exa", + }); + // Check if results contain correct tab switch rows + ok( + searchContext.results.find(result => { + return ( + result.type == UrlbarUtils.RESULT_TYPE.TAB_SWITCH && + result.payload.url == "https://example.com/" && + result.payload.userContextId == contextIdTabA + ); + }), + "Switch tab row for user context A is present in results." + ); + + let resultIndex = -1; + let tabSwitchRow = searchContext.results.find(result => { + resultIndex += 1; + return ( + result.type == UrlbarUtils.RESULT_TYPE.TAB_SWITCH && + result.payload.url == "https://example.com/" && + result.payload.userContextId == contextIdTabB + ); + }); + + Assert.notEqual( + tabSwitchRow, + undefined, + "Urlbar results contain the switch to tab from another container." + ); + let element = UrlbarTestUtils.getRowAt(window, resultIndex); + is( + element.querySelectorAll(".urlbarView-action.urlbarView-userContext") + .length, + 1, + "Has switch to tab with user-context chiclet" + ); + let tabSwitchDonePromise = BrowserTestUtils.waitForEvent( + window, + "TabSwitchDone" + ); + EventUtils.synthesizeMouseAtCenter(element, {}); + await tabSwitchDonePromise; + + is(gBrowser.selectedTab, tabB, "Correct tab is selected after switch."); + is( + gBrowser.selectedTab.userContextId, + contextIdTabB, + "Tab has correct user context id." + ); + + window.gBrowser.removeTab(tabA); + window.gBrowser.removeTab(tabB); + window.gBrowser.removeTab(tabC); +}); + +add_task(async function test_chiclet_disabled_on_update() { + // When result rows of tab switches across containers are reused for + // other result types, make sure that the user-context chiclet is removed. + + let urlA = "https://example.com/"; + + await PlacesTestUtils.addBookmarkWithDetails({ + uri: "https://exomple.com/", + title: "Exomple", + }); + await PlacesTestUtils.addBookmarkWithDetails({ + uri: "https://exooomple.com/", + title: "Exooomple", + }); + + let contextIdTabA = 2; + let contextIdTabB = 3; + + let initialTab = gBrowser.selectedTab; + let { tab: tabA } = await openTabInUserContext(urlA, contextIdTabA); + let { tab: tabB } = await openTabInUserContext(urlA, contextIdTabB); + + await BrowserTestUtils.switchTab(gBrowser, initialTab); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + waitForFocus, + value: "exa", + }); + + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + waitForFocus, + value: "exo", + }); + + let row = UrlbarTestUtils.getRowAt(window, 1); + Assert.equal( + row._elements["user-context"], + undefined, + "Row doesnt contain user-context chiclet." + ); + + window.gBrowser.removeTab(tabA); + window.gBrowser.removeTab(tabB); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_tab_color_update.js b/browser/components/contextualidentity/test/browser/browser_tab_color_update.js new file mode 100644 index 0000000000..e0b498fb96 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_tab_color_update.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +async function openTabInUserContext(uri, userContextId) { + // open the tab in the correct userContextId + let tab = BrowserTestUtils.addTab(gBrowser, uri, { userContextId }); + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser, false, uri); + return tab; +} + +/** + * When a container's colour changes, the tab colour should update. + */ +add_task(async function test_tab_color_updates() { + const kId = 2; + let tab = await openTabInUserContext("https://example.com/", kId); + let contextIdInfo = ContextualIdentityService.getPublicIdentityFromId(kId); + ok( + tab.classList.contains("identity-color-" + contextIdInfo.color), + `Should use color ${contextIdInfo.color} for tab` + ); + + // The container has a localized name which isn't in the contextIdInfo object. + // We need to pass a valid name for the update to go through, so make one up. + let name = "Foo"; + // Don't hardcode a colour so changes in defaults won't cause the following + // test to fail or be a no-op. + let otherColor = contextIdInfo.color == "green" ? "orange" : "green"; + registerCleanupFunction(() => ContextualIdentityService.resetDefault()); + ContextualIdentityService.update(kId, name, contextIdInfo.icon, otherColor); + + ok( + tab.classList.contains("identity-color-" + otherColor), + `Should use color ${otherColor} for tab after update` + ); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_usercontext.js b/browser/components/contextualidentity/test/browser/browser_usercontext.js new file mode 100644 index 0000000000..dafdf95394 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_usercontext.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const USER_CONTEXTS = ["default", "personal", "work"]; + +const BASE_URI = + "http://mochi.test:8888/browser/browser/components/" + + "contextualidentity/test/browser/file_reflect_cookie_into_title.html"; + +// opens `uri' in a new tab with the provided userContextId and focuses it. +// returns the newly opened tab +function openTabInUserContext(uri, userContextId) { + // open the tab in the correct userContextId + let tab = BrowserTestUtils.addTab(gBrowser, uri, { userContextId }); + + // select tab and make sure its browser is focused + gBrowser.selectedTab = tab; + tab.ownerGlobal.focus(); + + return tab; +} + +add_setup(async function () { + // make sure userContext is enabled. + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.userContext.enabled", true], + ["dom.ipc.processCount", 1], + ], + }); +}); + +add_task(async function test() { + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // load the page in 3 different contexts and set a cookie + // which should only be visible in that context + let cookie = USER_CONTEXTS[userContextId]; + + // open our tab in the given user context + let tab = openTabInUserContext(BASE_URI + "?" + cookie, userContextId); + + // wait for tab load + await BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab)); + + // remove the tab + gBrowser.removeTab(tab); + } + + { + // Set a cookie in a different context so we can detect if that affects + // cross-context properly. If we don't do that, we get an UNEXPECTED-PASS + // for the localStorage case for the last tab we set. + let tab = openTabInUserContext(BASE_URI + "?foo", 9999); + await BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab)); + gBrowser.removeTab(tab); + } + + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // Load the page without setting the cookie this time + let expectedContext = USER_CONTEXTS[userContextId]; + + let tab = openTabInUserContext(BASE_URI, userContextId); + + // wait for load + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // get the title + let title = browser.contentTitle.trim().split("|"); + + // check each item in the title and validate it meets expectatations + for (let part of title) { + let [storageMethodName, value] = part.split("="); + is( + value, + expectedContext, + "the title reflects the expected contextual identity of " + + expectedContext + + " for method " + + storageMethodName + + ": " + + value + ); + } + + gBrowser.removeTab(tab); + } +}); diff --git a/browser/components/contextualidentity/test/browser/browser_usercontextid_new_window.js b/browser/components/contextualidentity/test/browser/browser_usercontextid_new_window.js new file mode 100644 index 0000000000..5fd4c6f3c2 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_usercontextid_new_window.js @@ -0,0 +1,93 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the content of new browser windows have the expected +// userContextId when it passed as the window arguments. + +const TEST_URI = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://mochi.test:8888" + ) + "empty_file.html"; + +function openWindowWithUserContextId(userContextId, isPrivate) { + let flags = "chrome,dialog=no,all"; + if (isPrivate) { + flags += ",private"; + } + + let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); + + let urlSupports = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + urlSupports.data = TEST_URI; + args.appendElement(urlSupports); + + args.appendElement(null); + args.appendElement(null); + args.appendElement(null); + args.appendElement(null); + + let userContextIdSupports = Cc[ + "@mozilla.org/supports-PRUint32;1" + ].createInstance(Ci.nsISupportsPRUint32); + userContextIdSupports.data = userContextId; + args.appendElement(userContextIdSupports); + + args.appendElement(Services.scriptSecurityManager.getSystemPrincipal()); + args.appendElement(Services.scriptSecurityManager.getSystemPrincipal()); + args.appendElement(Services.scriptSecurityManager.getSystemPrincipal()); + + let windowPromise = BrowserTestUtils.waitForNewWindow({ url: TEST_URI }); + Services.ww.openWindow( + null, + AppConstants.BROWSER_CHROME_URL, + "_blank", + flags, + args + ); + return windowPromise; +} + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["privacy.userContext.enabled", true]], + }); +}); + +add_task(async function test_new_window() { + let win = await openWindowWithUserContextId(1, false); + + await SpecialPowers.spawn(win.gBrowser.selectedBrowser, [TEST_URI], url => { + Assert.equal(content.document.URL, url, "expected document URL"); + let { originAttributes } = content.document.nodePrincipal; + Assert.equal(originAttributes.userContextId, 1, "expected userContextId"); + Assert.equal( + originAttributes.privateBrowsingId, + 0, + "expected non-private context" + ); + }); + + await BrowserTestUtils.closeWindow(win); +}); + +add_task(async function test_new_private_window() { + let win = await openWindowWithUserContextId(1, true); + + await SpecialPowers.spawn(win.gBrowser.selectedBrowser, [TEST_URI], url => { + Assert.equal(content.document.URL, url, "expected document URL"); + let { originAttributes } = content.document.nodePrincipal; + Assert.equal(originAttributes.userContextId, 1, "expected userContextId"); + Assert.equal( + originAttributes.privateBrowsingId, + 1, + "expected private context" + ); + }); + + await BrowserTestUtils.closeWindow(win); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_usercontextid_tabdrop.js b/browser/components/contextualidentity/test/browser/browser_usercontextid_tabdrop.js new file mode 100644 index 0000000000..bddf39eb87 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_usercontextid_tabdrop.js @@ -0,0 +1,181 @@ +"use strict"; + +let EventUtils = {}; +Services.scriptloader.loadSubScript( + "chrome://mochikit/content/tests/SimpleTest/EventUtils.js", + EventUtils +); + +/** + * Dragging an URL to a tab without userContextId set. + */ +add_task(async function () { + let tab = BrowserTestUtils.addTab(gBrowser, "http://example.com/"); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + let awaitDrop = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "drop"); + let newTabPromise = BrowserTestUtils.waitForNewTab( + gBrowser, + "http://test1.example.com/", + true + ); + + // A drop type of "link" onto an existing tab would normally trigger a + // load in that same tab, but tabbrowser code in _getDragTargetTab treats + // drops on the outer edges of a tab differently (loading a new tab + // instead). Make events created by synthesizeDrop have all of their + // coordinates set to 0 (screenX/screenY), so they're treated as drops + // on the outer edge of the tab, thus they open new tabs. + let event = { + clientX: 0, + clientY: 0, + screenX: 0, + screenY: 0, + }; + EventUtils.synthesizeDrop( + tab, + tab, + [[{ type: "text/plain", data: "http://test1.example.com/" }]], + "link", + window, + undefined, + event + ); + + await awaitDrop; + + let tab2 = await newTabPromise; + Assert.ok( + !tab2.hasAttribute("usercontextid"), + "Tab shouldn't have usercontextid attribute" + ); + + await SpecialPowers.spawn(tab2.linkedBrowser, [], async function () { + Assert.equal(content.document.documentURI, "http://test1.example.com/"); + Assert.equal( + content.document.nodePrincipal.originAttributes.userContextId, + 0 + ); + + // referrer is empty when urls are dragged to new or existing tabs. + // If this changes in the future, it would be okay to send the referrer + // in this case because we are creating a new tab with the default + // usercontextid as the original tab. + Assert.equal(content.document.referrer, "", "referrer should be empty"); + }); + + BrowserTestUtils.removeTab(tab); + BrowserTestUtils.removeTab(tab2); +}); + +/** + * When dragging an URL to a new tab, the new tab should have the same + * userContextId as the original tab. + */ +add_task(async function () { + let tab = BrowserTestUtils.addTab(gBrowser, "http://example.com/", { + userContextId: 1, + }); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + let awaitDrop = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "drop"); + let newTabPromise = BrowserTestUtils.waitForNewTab( + gBrowser, + "http://test1.example.com/", + true + ); + + // A drop type of "link" onto an existing tab would normally trigger a + // load in that same tab, but tabbrowser code in _getDragTargetTab treats + // drops on the outer edges of a tab differently (loading a new tab + // instead). Make events created by synthesizeDrop have all of their + // coordinates set to 0 (screenX/screenY), so they're treated as drops + // on the outer edge of the tab, thus they open new tabs. + let event = { + clientX: 0, + clientY: 0, + screenX: 0, + screenY: 0, + }; + EventUtils.synthesizeDrop( + tab, + tab, + [[{ type: "text/plain", data: "http://test1.example.com/" }]], + "link", + window, + undefined, + event + ); + + await awaitDrop; + + let tab2 = await newTabPromise; + Assert.equal(tab2.getAttribute("usercontextid"), 1); + + await SpecialPowers.spawn(tab2.linkedBrowser, [], async function () { + Assert.equal(content.document.documentURI, "http://test1.example.com/"); + Assert.equal( + content.document.nodePrincipal.originAttributes.userContextId, + 1 + ); + + // referrer is empty when urls are dragged to new or existing tabs. + // If this changes in the future, it would be okay to send the referrer + // in this case because we are creating a new tab with the same + // usercontextid as the original tab. + Assert.equal(content.document.referrer, "", "referrer should be empty"); + }); + + BrowserTestUtils.removeTab(tab); + BrowserTestUtils.removeTab(tab2); +}); + +/** + * When dragging a URL from one tab or link on a tab to an existing tab, the + * existing tab should not change its userContextId. + * Ex: if you drag a link from tab 1 with userContext 1 to tab 2 with + * userContext 2, the link will open in tab 2 with userContext 2. + */ +add_task(async function () { + let tab = BrowserTestUtils.addTab(gBrowser, "http://example.com/", { + userContextId: 1, + }); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + let tab2 = BrowserTestUtils.addTab(gBrowser, "http://example.org/", { + userContextId: 2, + }); + await BrowserTestUtils.browserLoaded(tab2.linkedBrowser); + + let awaitDrop = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "drop"); + + EventUtils.synthesizeDrop( + tab, + tab2, + [[{ type: "text/plain", data: "http://test1.example.com/" }]], + "link", + window + ); + + await awaitDrop; + Assert.equal(tab2.getAttribute("usercontextid"), 2); + + await BrowserTestUtils.browserLoaded(tab2.linkedBrowser); + + await SpecialPowers.spawn(tab2.linkedBrowser, [], async function () { + Assert.equal(content.document.documentURI, "http://test1.example.com/"); + Assert.equal( + content.document.nodePrincipal.originAttributes.userContextId, + 2 + ); + + // referrer is empty when urls are dragged to new or existing tabs. + // If this changes in the future, we should ensure that we are not sending + // a referrer for this case! When opening links across user contexts, we + // don't want the referrer to follow the user from one context to another. + Assert.equal(content.document.referrer, "", "referrer should be empty"); + }); + + BrowserTestUtils.removeTab(tab); + BrowserTestUtils.removeTab(tab2); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_windowName.js b/browser/components/contextualidentity/test/browser/browser_windowName.js new file mode 100644 index 0000000000..5ba2cc0e0a --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_windowName.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const USER_CONTEXTS = ["default", "personal", "work"]; + +const BASE_URI = + "http://mochi.test:8888/browser/browser/components/" + + "contextualidentity/test/browser/empty_file.html"; + +add_setup(async function () { + // make sure userContext is enabled. + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.userContext.enabled", true], + ["browser.link.open_newwindow", 3], + ], + }); +}); + +add_task(async function test() { + info("Creating first tab..."); + let tab1 = BrowserTestUtils.addTab(gBrowser, BASE_URI + "?old", { + userContextId: 1, + }); + let browser1 = gBrowser.getBrowserForTab(tab1); + await BrowserTestUtils.browserLoaded(browser1); + await SpecialPowers.spawn(browser1, [], function (opts) { + content.window.name = "tab-1"; + }); + + info("Creating second tab..."); + let tab2 = BrowserTestUtils.addTab(gBrowser, BASE_URI + "?old", { + userContextId: 2, + }); + let browser2 = gBrowser.getBrowserForTab(tab2); + await BrowserTestUtils.browserLoaded(browser2); + await SpecialPowers.spawn(browser2, [], function (opts) { + content.window.name = "tab-2"; + }); + + // Let's try to open a window from tab1 with a name 'tab-2'. + info("Opening a window from the first tab..."); + await SpecialPowers.spawn( + browser1, + [{ url: BASE_URI + "?new" }], + async function (opts) { + await new content.window.wrappedJSObject.Promise(resolve => { + let w = content.window.wrappedJSObject.open(opts.url, "tab-2"); + w.onload = function () { + resolve(); + }; + }); + } + ); + + is(browser1.contentTitle, "?old", "Tab1 title must be 'old'"); + is(browser1.contentPrincipal.userContextId, 1, "Tab1 UCI must be 1"); + + is(browser2.contentTitle, "?old", "Tab2 title must be 'old'"); + is(browser2.contentPrincipal.userContextId, 2, "Tab2 UCI must be 2"); + + let found = false; + for (let i = 0; i < gBrowser.tabs.length; ++i) { + let tab = gBrowser.tabs[i]; + let browser = gBrowser.getBrowserForTab(tab); + if (browser.contentTitle == "?new") { + is(browser.contentPrincipal.userContextId, 1, "Tab3 UCI must be 1"); + isnot(browser, browser1, "Tab3 is not browser 1"); + isnot(browser, browser2, "Tab3 is not browser 2"); + gBrowser.removeTab(tab); + found = true; + break; + } + } + + ok(found, "We have tab3"); + + gBrowser.removeTab(tab1); + gBrowser.removeTab(tab2); +}); diff --git a/browser/components/contextualidentity/test/browser/browser_windowOpen.js b/browser/components/contextualidentity/test/browser/browser_windowOpen.js new file mode 100644 index 0000000000..d550b03ed8 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_windowOpen.js @@ -0,0 +1,41 @@ +"use strict"; + +// Here we want to test that a new opened window shows the same UI of the +// parent one if this has been loaded from a particular container. + +const BASE_URI = + "http://mochi.test:8888/browser/browser/components/" + + "contextualidentity/test/browser/empty_file.html"; + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.userContext.enabled", true], + ["browser.link.open_newwindow", 2], + ], + }); +}); + +add_task(async function test() { + info("Creating a tab with UCI = 1..."); + let tab = BrowserTestUtils.addTab(gBrowser, BASE_URI, { userContextId: 1 }); + is(tab.getAttribute("usercontextid"), "1", "New tab has UCI equal 1"); + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + info("Opening a new window from this tab..."); + let newWinPromise = BrowserTestUtils.waitForNewWindow({ url: BASE_URI }); + SpecialPowers.spawn(browser, [BASE_URI], function (url) { + content.window.newWindow = content.window.open(url, "_blank"); + }); + + let newWin = await newWinPromise; + let newTab = newWin.gBrowser.selectedTab; + + is(newTab.getAttribute("usercontextid"), "1", "New tab has UCI equal 1"); + + info("Closing the new window and tab..."); + await BrowserTestUtils.closeWindow(newWin); + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/components/contextualidentity/test/browser/empty_file.html b/browser/components/contextualidentity/test/browser/empty_file.html new file mode 100644 index 0000000000..c6d11dcd57 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/empty_file.html @@ -0,0 +1,5 @@ +<html><body> +<script> +document.title = window.location.search; +</script> +</body></html> diff --git a/browser/components/contextualidentity/test/browser/favicon-normal32.png b/browser/components/contextualidentity/test/browser/favicon-normal32.png Binary files differnew file mode 100644 index 0000000000..5535363c94 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/favicon-normal32.png diff --git a/browser/components/contextualidentity/test/browser/file_reflect_cookie_into_title.html b/browser/components/contextualidentity/test/browser/file_reflect_cookie_into_title.html new file mode 100644 index 0000000000..cc05453507 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/file_reflect_cookie_into_title.html @@ -0,0 +1,22 @@ +<html> + <head> + <meta charset="UTF-8"> + <title>title not set</title> + <script> + // if we have a query string, use it to set the cookie and localStorage + if (window.location.search.length) { + let context_name = window.location.search.substr(1); + document.cookie = "userContextId=" + context_name; + localStorage.setItem("userContext", context_name); + } + + // get the cookie + let [name, val] = document.cookie.split("="); + + // set the title to reflect the cookie and local storage values we find + document.title = "cookie=" + val + "|" + + "local=" + localStorage.getItem("userContext"); + </script> + </head> + <body></body> +</html> diff --git a/browser/components/contextualidentity/test/browser/file_set_storages.html b/browser/components/contextualidentity/test/browser/file_set_storages.html new file mode 100644 index 0000000000..96c46f9062 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/file_set_storages.html @@ -0,0 +1,41 @@ +<html> + <head> + <meta charset="UTF-8"> + <title>Bug 1238183</title> + </head> + <body> + <script type="application/javascript"> + "use strict"; + + // if we have a query string, use it to set storages + if (window.location.search.length) { + let context_name = window.location.search.substr(1); + localStorage.setItem("userContext", context_name); + sessionStorage.setItem("userContext", context_name); + + let request = indexedDB.open("idb", 1); + + request.onerror = function() { + throw new Error("error opening db connection"); + }; + + request.onupgradeneeded = event => { + let db = event.target.result; + let store = db.createObjectStore("obj", { keyPath: "id" }); + store.createIndex("userContext", "userContext", { unique: false }); + }; + + request.onsuccess = event => { + let db = request.result; + let transaction = db.transaction(["obj"], "readwrite"); + let store = transaction.objectStore("obj"); + store.add({id: 1, userContext: context_name}); + + transaction.oncomplete = () => { + db.close(); + }; + }; + } + </script> + </body> +</html> diff --git a/browser/components/contextualidentity/test/browser/head.js b/browser/components/contextualidentity/test/browser/head.js new file mode 100644 index 0000000000..9bbffc3616 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/head.js @@ -0,0 +1,68 @@ +async function openTabMenuFor(tab) { + let tabMenu = tab.ownerDocument.getElementById("tabContextMenu"); + + let tabMenuShown = BrowserTestUtils.waitForEvent(tabMenu, "popupshown"); + EventUtils.synthesizeMouseAtCenter( + tab, + { type: "contextmenu" }, + tab.ownerGlobal + ); + await tabMenuShown; + + return tabMenu; +} + +async function openReopenMenuForTab(tab) { + await openTabMenuFor(tab); + + let reopenItem = tab.ownerDocument.getElementById( + "context_reopenInContainer" + ); + ok(!reopenItem.hidden, "Reopen in Container item should be shown"); + + const menuPopup = reopenItem.menupopup; + const menuPopupPromise = BrowserTestUtils.waitForEvent( + menuPopup, + "popupshown" + ); + info(`About to open a popup`); + reopenItem.openMenu(true); + info(`Waiting for the menu popup promise`); + await menuPopupPromise; + info(`Awaited menu popup promise`); + return menuPopup; +} + +function openTabInContainer(gBrowser, url, reopenMenu, id) { + let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url, true); + let menuitem = reopenMenu.querySelector( + `menuitem[data-usercontextid="${id}"]` + ); + info(`about to activate item`); + reopenMenu.activateItem(menuitem); + return tabPromise; +} + +function loadTestSubscript(filePath) { + Services.scriptloader.loadSubScript(new URL(filePath, gTestPath).href, this); +} + +/** + * Opens `uri' in a new tab with the provided userContextId and focuses it. + * + * @param {string} uri The uri which should be opened in the new tab. + * @param {number} userContextId The id of the user context in which the tab + * should be opened. + * @returns {object} Keys are `tab` (the newly-opened tab) and `browser` (the + * browser associated with the tab). + */ +async function openTabInUserContext(uri, userContextId) { + let tab = BrowserTestUtils.addTab(gBrowser, uri, { userContextId }); + + gBrowser.selectedTab = tab; + tab.ownerGlobal.focus(); + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + return { tab, browser }; +} diff --git a/browser/components/contextualidentity/test/browser/saveLink.sjs b/browser/components/contextualidentity/test/browser/saveLink.sjs new file mode 100644 index 0000000000..754072d787 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/saveLink.sjs @@ -0,0 +1,53 @@ +const HTTP_ORIGIN = "http://example.com"; +const HTTPS_ORIGIN = "https://example.com"; +const URI_PATH = + "/browser/browser/components/contextualidentity/test/browser/saveLink.sjs"; + +function handleRequest(aRequest, aResponse) { + var params = new URLSearchParams(aRequest.queryString); + + // This is the first request, where we set the cookie. + if (params.has("UCI")) { + aResponse.setStatusLine(aRequest.httpVersion, 200); + aResponse.setHeader("Set-Cookie", "UCI=" + params.get("UCI")); + aResponse.write( + "<html><body><a href='" + + HTTPS_ORIGIN + + URI_PATH + + "?redirect=1' id='fff'>this is a link</a></body></html>" + ); + return; + } + + // Second request. This is the save-as content, but we make a redirect to see + // if we are able to follow it. + if (params.has("redirect")) { + aResponse.setStatusLine(aRequest.httpVersion, 302, "Found"); + aResponse.setHeader( + "Location", + HTTP_ORIGIN + URI_PATH + "?download=1", + false + ); + aResponse.write("Redirect!"); + return; + } + + // This is the 3rd request where we offer the content to be saved. + if (params.has("download")) { + setState("downloadUCI", aRequest.getHeader("Cookie")); + aResponse.setStatusLine(aRequest.httpVersion, 200); + aResponse.write("All Good!"); + return; + } + + // This is the last request to check that the download happened with the correct cookie + if (params.has("result")) { + aResponse.setStatusLine(aRequest.httpVersion, 200); + aResponse.write("Result:" + getState("downloadUCI")); + return; + } + + // We should not be here! + aResponse.setStatusLine(aRequest.httpVersion, 500); + aResponse.write("ERROR!!!"); +} diff --git a/browser/components/contextualidentity/test/browser/serviceworker.html b/browser/components/contextualidentity/test/browser/serviceworker.html new file mode 100644 index 0000000000..11edd001a2 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/serviceworker.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <script> + navigator.serviceWorker.register("worker.js"); + </script> + </head> + <body> + This is a test page. + </body> +<html> diff --git a/browser/components/contextualidentity/test/browser/worker.js b/browser/components/contextualidentity/test/browser/worker.js new file mode 100644 index 0000000000..2aba167d18 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/worker.js @@ -0,0 +1 @@ +// empty worker, always succeed! |