summaryrefslogtreecommitdiffstats
path: root/browser/components/privatebrowsing/test
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/privatebrowsing/test')
-rw-r--r--browser/components/privatebrowsing/test/browser/browser.toml123
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_oa_private_browsing_window.js64
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_DownloadLastDirWithCPS.js445
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about.js266
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutSessionRestore.js25
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_cookie_banners_promo.js107
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_default_pin_promo.js110
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_default_promo.js224
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_focus_promo.js89
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus.js459
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus_dismiss.js139
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus_impressions.js126
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus_messaging.js247
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_search_banner.js317
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_beacon.js46
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_blobUrl.js69
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js94
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_certexceptionsui.js65
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cleanup.js46
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js101
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html33
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_context_and_chromeFlags.js69
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_crh.js48
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js133
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js146
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_toggle.js118
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js322
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html13
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_history_shift_click.js69
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_last_private_browsing_context_exited.js66
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_lastpbcontextexited.js63
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage.js28
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after.js46
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page.html11
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page2.html10
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page1.html10
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page2.html10
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_newtab_from_popup.js71
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_noSessionRestoreMenuOption.js29
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_nonbrowser.js21
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_opendir.js175
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.html8
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js59
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js82
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler.js71
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler_page.html13
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_rememberprompt.js90
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_resetPBM.js824
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_sidebar.js88
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_theming.js46
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js102
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_urlbarfocus.js44
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle.js140
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle_page.html9
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_xrprompt_page.html11
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoom.js46
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoomrestore.js80
-rw-r--r--browser/components/privatebrowsing/test/browser/empty_file.html1
-rw-r--r--browser/components/privatebrowsing/test/browser/file_favicon.html11
-rw-r--r--browser/components/privatebrowsing/test/browser/file_favicon.pngbin0 -> 344 bytes
-rw-r--r--browser/components/privatebrowsing/test/browser/file_favicon.png^headers^1
-rw-r--r--browser/components/privatebrowsing/test/browser/file_triggeringprincipal_oa.html10
-rw-r--r--browser/components/privatebrowsing/test/browser/head.js163
-rw-r--r--browser/components/privatebrowsing/test/browser/title.sjs23
64 files changed, 6575 insertions, 0 deletions
diff --git a/browser/components/privatebrowsing/test/browser/browser.toml b/browser/components/privatebrowsing/test/browser/browser.toml
new file mode 100644
index 0000000000..2c37cd8a48
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser.toml
@@ -0,0 +1,123 @@
+[DEFAULT]
+tags = "openwindow"
+support-files = [
+ "browser_privatebrowsing_concurrent_page.html",
+ "browser_privatebrowsing_geoprompt_page.html",
+ "browser_privatebrowsing_xrprompt_page.html",
+ "browser_privatebrowsing_localStorage_before_after_page.html",
+ "browser_privatebrowsing_localStorage_before_after_page2.html",
+ "browser_privatebrowsing_localStorage_page1.html",
+ "browser_privatebrowsing_localStorage_page2.html",
+ "browser_privatebrowsing_placesTitleNoUpdate.html",
+ "browser_privatebrowsing_protocolhandler_page.html",
+ "browser_privatebrowsing_windowtitle_page.html",
+ "head.js",
+ "title.sjs",
+ "empty_file.html",
+ "file_favicon.html",
+ "file_favicon.png",
+ "file_favicon.png^headers^",
+ "file_triggeringprincipal_oa.html",
+]
+
+["browser_oa_private_browsing_window.js"]
+
+["browser_privatebrowsing_DownloadLastDirWithCPS.js"]
+
+["browser_privatebrowsing_about.js"]
+skip-if = ["verify"]
+tags = "trackingprotection"
+
+["browser_privatebrowsing_aboutSessionRestore.js"]
+
+["browser_privatebrowsing_about_cookie_banners_promo.js"]
+
+["browser_privatebrowsing_about_default_pin_promo.js"]
+skip-if = [
+ "os == 'mac'",
+ "os == 'linux'",
+ "os == 'win' && msix", # We don't support pinning in MSIX builds
+]
+
+["browser_privatebrowsing_about_default_promo.js"]
+
+["browser_privatebrowsing_about_focus_promo.js"]
+
+["browser_privatebrowsing_about_nimbus.js"]
+
+["browser_privatebrowsing_about_nimbus_dismiss.js"]
+
+["browser_privatebrowsing_about_nimbus_impressions.js"]
+
+["browser_privatebrowsing_about_nimbus_messaging.js"]
+
+["browser_privatebrowsing_about_search_banner.js"]
+
+["browser_privatebrowsing_beacon.js"]
+
+["browser_privatebrowsing_blobUrl.js"]
+
+["browser_privatebrowsing_cache.js"]
+
+["browser_privatebrowsing_certexceptionsui.js"]
+
+["browser_privatebrowsing_cleanup.js"]
+
+["browser_privatebrowsing_concurrent.js"]
+skip-if = ["release_or_beta"]
+
+["browser_privatebrowsing_context_and_chromeFlags.js"]
+
+["browser_privatebrowsing_crh.js"]
+
+["browser_privatebrowsing_downloadLastDir.js"]
+skip-if = ["verify"]
+
+["browser_privatebrowsing_downloadLastDir_c.js"]
+
+["browser_privatebrowsing_downloadLastDir_toggle.js"]
+
+["browser_privatebrowsing_favicon.js"]
+
+["browser_privatebrowsing_history_shift_click.js"]
+
+["browser_privatebrowsing_last_private_browsing_context_exited.js"]
+
+["browser_privatebrowsing_lastpbcontextexited.js"]
+
+["browser_privatebrowsing_localStorage.js"]
+
+["browser_privatebrowsing_localStorage_before_after.js"]
+
+["browser_privatebrowsing_newtab_from_popup.js"]
+
+["browser_privatebrowsing_noSessionRestoreMenuOption.js"]
+
+["browser_privatebrowsing_nonbrowser.js"]
+
+["browser_privatebrowsing_opendir.js"]
+
+["browser_privatebrowsing_placesTitleNoUpdate.js"]
+
+["browser_privatebrowsing_placestitle.js"]
+
+["browser_privatebrowsing_protocolhandler.js"]
+
+["browser_privatebrowsing_rememberprompt.js"]
+tags = "geolocation xr"
+
+["browser_privatebrowsing_resetPBM.js"]
+
+["browser_privatebrowsing_sidebar.js"]
+
+["browser_privatebrowsing_theming.js"]
+
+["browser_privatebrowsing_ui.js"]
+
+["browser_privatebrowsing_urlbarfocus.js"]
+
+["browser_privatebrowsing_windowtitle.js"]
+
+["browser_privatebrowsing_zoom.js"]
+
+["browser_privatebrowsing_zoomrestore.js"]
diff --git a/browser/components/privatebrowsing/test/browser/browser_oa_private_browsing_window.js b/browser/components/privatebrowsing/test/browser/browser_oa_private_browsing_window.js
new file mode 100644
index 0000000000..a1b9420171
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_oa_private_browsing_window.js
@@ -0,0 +1,64 @@
+"use strict";
+
+const PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+const TEST_PAGE = PATH + "file_triggeringprincipal_oa.html";
+const DUMMY_PAGE = PATH + "empty_file.html";
+
+add_task(
+ async function test_principal_right_click_open_link_in_new_private_win() {
+ await BrowserTestUtils.withNewTab(TEST_PAGE, async function (browser) {
+ let promiseNewWindow = BrowserTestUtils.waitForNewWindow({
+ url: DUMMY_PAGE,
+ });
+
+ // simulate right-click open link in new private window
+ BrowserTestUtils.waitForEvent(document, "popupshown", false, event => {
+ document.getElementById("context-openlinkprivate").doCommand();
+ event.target.hidePopup();
+ return true;
+ });
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#checkPrincipalOA",
+ { type: "contextmenu", button: 2 },
+ gBrowser.selectedBrowser
+ );
+ let privateWin = await promiseNewWindow;
+
+ await SpecialPowers.spawn(
+ privateWin.gBrowser.selectedBrowser,
+ [{ DUMMY_PAGE, TEST_PAGE }],
+ // eslint-disable-next-line no-shadow
+ async function ({ DUMMY_PAGE, TEST_PAGE }) {
+ // eslint-disable-line
+
+ let channel = content.docShell.currentDocumentChannel;
+ is(
+ channel.URI.spec,
+ DUMMY_PAGE,
+ "sanity check to ensure we check principal for right URI"
+ );
+
+ let triggeringPrincipal = channel.loadInfo.triggeringPrincipal;
+ ok(
+ triggeringPrincipal.isContentPrincipal,
+ "sanity check to ensure principal is a contentPrincipal"
+ );
+ is(
+ triggeringPrincipal.spec,
+ TEST_PAGE,
+ "test page must be the triggering page"
+ );
+ is(
+ triggeringPrincipal.originAttributes.privateBrowsingId,
+ 1,
+ "must have correct privateBrowsingId"
+ );
+ }
+ );
+ await BrowserTestUtils.closeWindow(privateWin);
+ });
+ }
+);
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_DownloadLastDirWithCPS.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_DownloadLastDirWithCPS.js
new file mode 100644
index 0000000000..eeaeee033c
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_DownloadLastDirWithCPS.js
@@ -0,0 +1,445 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+var gTests;
+function test() {
+ waitForExplicitFinish();
+ requestLongerTimeout(2);
+ runTest().catch(ex => ok(false, ex));
+}
+
+/*
+ * ================
+ * Helper functions
+ * ================
+ */
+
+function createWindow(aOptions) {
+ return new Promise(resolve => whenNewWindowLoaded(aOptions, resolve));
+}
+
+function getFile(downloadLastDir, aURI) {
+ return downloadLastDir.getFileAsync(aURI);
+}
+
+function setFile(downloadLastDir, aURI, aValue) {
+ downloadLastDir.setFile(aURI, aValue);
+ return new Promise(resolve => executeSoon(resolve));
+}
+
+function clearHistoryAndWait() {
+ clearHistory();
+ return new Promise(resolve => executeSoon(_ => executeSoon(resolve)));
+}
+
+/*
+ * ===================
+ * Function with tests
+ * ===================
+ */
+
+async function runTest() {
+ let { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+ );
+ let { DownloadLastDir } = ChromeUtils.importESModule(
+ "resource://gre/modules/DownloadLastDir.sys.mjs"
+ );
+
+ let tmpDir = FileUtils.getDir("TmpD", []);
+ let dir1 = newDirectory();
+ let dir2 = newDirectory();
+ let dir3 = newDirectory();
+
+ let uri1 = Services.io.newURI("http://test1.com/");
+ let uri2 = Services.io.newURI("http://test2.com/");
+ let uri3 = Services.io.newURI("http://test3.com/");
+ let uri4 = Services.io.newURI("http://test4.com/");
+
+ // cleanup functions registration
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.download.lastDir.savePerSite");
+ Services.prefs.clearUserPref("browser.download.lastDir");
+ [dir1, dir2, dir3].forEach(dir => dir.remove(true));
+ win.close();
+ pbWin.close();
+ });
+
+ function checkDownloadLastDir(gDownloadLastDir, aLastDir) {
+ is(
+ gDownloadLastDir.file.path,
+ aLastDir.path,
+ "gDownloadLastDir should point to the expected last directory"
+ );
+ return getFile(gDownloadLastDir, uri1);
+ }
+
+ function checkDownloadLastDirNull(gDownloadLastDir) {
+ is(gDownloadLastDir.file, null, "gDownloadLastDir should be null");
+ return getFile(gDownloadLastDir, uri1);
+ }
+
+ /*
+ * ================================
+ * Create a regular and a PB window
+ * ================================
+ */
+
+ let win = await createWindow({ private: false });
+ let pbWin = await createWindow({ private: true });
+
+ let downloadLastDir = new DownloadLastDir(win);
+ let pbDownloadLastDir = new DownloadLastDir(pbWin);
+
+ /*
+ * ==================
+ * Beginning of tests
+ * ==================
+ */
+
+ is(
+ typeof downloadLastDir,
+ "object",
+ "downloadLastDir should be a valid object"
+ );
+ is(downloadLastDir.file, null, "LastDir pref should be null to start with");
+
+ // set up last dir
+ await setFile(downloadLastDir, null, tmpDir);
+ is(
+ downloadLastDir.file.path,
+ tmpDir.path,
+ "LastDir should point to the tmpDir"
+ );
+ isnot(
+ downloadLastDir.file,
+ tmpDir,
+ "downloadLastDir.file should not be pointing to tmpDir"
+ );
+
+ // set uri1 to dir1, all should now return dir1
+ // also check that a new object is returned
+ await setFile(downloadLastDir, uri1, dir1);
+ is(
+ downloadLastDir.file.path,
+ dir1.path,
+ "downloadLastDir should return dir1"
+ );
+ isnot(
+ downloadLastDir.file,
+ dir1,
+ "downloadLastDir.file should not return dir1"
+ );
+ is(
+ (await getFile(downloadLastDir, uri1)).path,
+ dir1.path,
+ "uri1 should return dir1"
+ ); // set in CPS
+ isnot(
+ await getFile(downloadLastDir, uri1),
+ dir1,
+ "getFile on uri1 should not return dir1"
+ );
+ is(
+ (await getFile(downloadLastDir, uri2)).path,
+ dir1.path,
+ "uri2 should return dir1"
+ ); // fallback
+ isnot(
+ await getFile(downloadLastDir, uri2),
+ dir1,
+ "getFile on uri2 should not return dir1"
+ );
+ is(
+ (await getFile(downloadLastDir, uri3)).path,
+ dir1.path,
+ "uri3 should return dir1"
+ ); // fallback
+ isnot(
+ await getFile(downloadLastDir, uri3),
+ dir1,
+ "getFile on uri3 should not return dir1"
+ );
+ is(
+ (await getFile(downloadLastDir, uri4)).path,
+ dir1.path,
+ "uri4 should return dir1"
+ ); // fallback
+ isnot(
+ await getFile(downloadLastDir, uri4),
+ dir1,
+ "getFile on uri4 should not return dir1"
+ );
+
+ // set uri2 to dir2, all except uri1 should now return dir2
+ await setFile(downloadLastDir, uri2, dir2);
+ is(
+ downloadLastDir.file.path,
+ dir2.path,
+ "downloadLastDir should point to dir2"
+ );
+ is(
+ (await getFile(downloadLastDir, uri1)).path,
+ dir1.path,
+ "uri1 should return dir1"
+ ); // set in CPS
+ is(
+ (await getFile(downloadLastDir, uri2)).path,
+ dir2.path,
+ "uri2 should return dir2"
+ ); // set in CPS
+ is(
+ (await getFile(downloadLastDir, uri3)).path,
+ dir2.path,
+ "uri3 should return dir2"
+ ); // fallback
+ is(
+ (await getFile(downloadLastDir, uri4)).path,
+ dir2.path,
+ "uri4 should return dir2"
+ ); // fallback
+
+ // set uri3 to dir3, all except uri1 and uri2 should now return dir3
+ await setFile(downloadLastDir, uri3, dir3);
+ is(
+ downloadLastDir.file.path,
+ dir3.path,
+ "downloadLastDir should point to dir3"
+ );
+ is(
+ (await getFile(downloadLastDir, uri1)).path,
+ dir1.path,
+ "uri1 should return dir1"
+ ); // set in CPS
+ is(
+ (await getFile(downloadLastDir, uri2)).path,
+ dir2.path,
+ "uri2 should return dir2"
+ ); // set in CPS
+ is(
+ (await getFile(downloadLastDir, uri3)).path,
+ dir3.path,
+ "uri3 should return dir3"
+ ); // set in CPS
+ is(
+ (await getFile(downloadLastDir, uri4)).path,
+ dir3.path,
+ "uri4 should return dir4"
+ ); // fallback
+
+ // set uri1 to dir2, all except uri3 should now return dir2
+ await setFile(downloadLastDir, uri1, dir2);
+ is(
+ downloadLastDir.file.path,
+ dir2.path,
+ "downloadLastDir should point to dir2"
+ );
+ is(
+ (await getFile(downloadLastDir, uri1)).path,
+ dir2.path,
+ "uri1 should return dir2"
+ ); // set in CPS
+ is(
+ (await getFile(downloadLastDir, uri2)).path,
+ dir2.path,
+ "uri2 should return dir2"
+ ); // set in CPS
+ is(
+ (await getFile(downloadLastDir, uri3)).path,
+ dir3.path,
+ "uri3 should return dir3"
+ ); // set in CPS
+ is(
+ (await getFile(downloadLastDir, uri4)).path,
+ dir2.path,
+ "uri4 should return dir2"
+ ); // fallback
+
+ await clearHistoryAndWait();
+
+ // check clearHistory removes all data
+ is(downloadLastDir.file, null, "clearHistory removes all data");
+ is(await getFile(downloadLastDir, uri1), null, "uri1 should point to null");
+ is(await getFile(downloadLastDir, uri2), null, "uri2 should point to null");
+ is(await getFile(downloadLastDir, uri3), null, "uri3 should point to null");
+ is(await getFile(downloadLastDir, uri4), null, "uri4 should point to null");
+
+ await setFile(downloadLastDir, null, tmpDir);
+
+ // check data set outside PB mode is remembered
+ is(
+ (await checkDownloadLastDir(pbDownloadLastDir, tmpDir)).path,
+ tmpDir.path,
+ "uri1 should return the expected last directory"
+ );
+ is(
+ (await checkDownloadLastDir(downloadLastDir, tmpDir)).path,
+ tmpDir.path,
+ "uri1 should return the expected last directory"
+ );
+ await clearHistoryAndWait();
+
+ await setFile(downloadLastDir, uri1, dir1);
+
+ // check data set using CPS outside PB mode is remembered
+ is(
+ (await checkDownloadLastDir(pbDownloadLastDir, dir1)).path,
+ dir1.path,
+ "uri1 should return the expected last directory"
+ );
+ is(
+ (await checkDownloadLastDir(downloadLastDir, dir1)).path,
+ dir1.path,
+ "uri1 should return the expected last directory"
+ );
+ await clearHistoryAndWait();
+
+ // check data set inside PB mode is forgotten
+ await setFile(pbDownloadLastDir, null, tmpDir);
+
+ is(
+ (await checkDownloadLastDir(pbDownloadLastDir, tmpDir)).path,
+ tmpDir.path,
+ "uri1 should return the expected last directory"
+ );
+ is(
+ await checkDownloadLastDirNull(downloadLastDir),
+ null,
+ "uri1 should return the expected last directory"
+ );
+
+ await clearHistoryAndWait();
+
+ // check data set using CPS inside PB mode is forgotten
+ await setFile(pbDownloadLastDir, uri1, dir1);
+
+ is(
+ (await checkDownloadLastDir(pbDownloadLastDir, dir1)).path,
+ dir1.path,
+ "uri1 should return the expected last directory"
+ );
+ is(
+ await checkDownloadLastDirNull(downloadLastDir),
+ null,
+ "uri1 should return the expected last directory"
+ );
+
+ // check data set outside PB mode but changed inside is remembered correctly
+ await setFile(downloadLastDir, uri1, dir1);
+ await setFile(pbDownloadLastDir, uri1, dir2);
+ is(
+ (await checkDownloadLastDir(pbDownloadLastDir, dir2)).path,
+ dir2.path,
+ "uri1 should return the expected last directory"
+ );
+ is(
+ (await checkDownloadLastDir(downloadLastDir, dir1)).path,
+ dir1.path,
+ "uri1 should return the expected last directory"
+ );
+
+ /*
+ * ====================
+ * Create new PB window
+ * ====================
+ */
+
+ // check that the last dir store got cleared in a new PB window
+ pbWin.close();
+ // And give it time to close
+ await new Promise(resolve => executeSoon(resolve));
+
+ pbWin = await createWindow({ private: true });
+ pbDownloadLastDir = new DownloadLastDir(pbWin);
+
+ is(
+ (await checkDownloadLastDir(pbDownloadLastDir, dir1)).path,
+ dir1.path,
+ "uri1 should return the expected last directory"
+ );
+
+ await clearHistoryAndWait();
+
+ // check clearHistory inside PB mode clears data outside PB mode
+ await setFile(pbDownloadLastDir, uri1, dir2);
+
+ await clearHistoryAndWait();
+
+ is(
+ await checkDownloadLastDirNull(downloadLastDir),
+ null,
+ "uri1 should return the expected last directory"
+ );
+ is(
+ await checkDownloadLastDirNull(pbDownloadLastDir),
+ null,
+ "uri1 should return the expected last directory"
+ );
+
+ // check that disabling CPS works
+ Services.prefs.setBoolPref("browser.download.lastDir.savePerSite", false);
+
+ await setFile(downloadLastDir, uri1, dir1);
+ is(downloadLastDir.file.path, dir1.path, "LastDir should be set to dir1");
+ is(
+ (await getFile(downloadLastDir, uri1)).path,
+ dir1.path,
+ "uri1 should return dir1"
+ );
+ is(
+ (await getFile(downloadLastDir, uri2)).path,
+ dir1.path,
+ "uri2 should return dir1"
+ );
+ is(
+ (await getFile(downloadLastDir, uri3)).path,
+ dir1.path,
+ "uri3 should return dir1"
+ );
+ is(
+ (await getFile(downloadLastDir, uri4)).path,
+ dir1.path,
+ "uri4 should return dir1"
+ );
+
+ downloadLastDir.setFile(uri2, dir2);
+ is(downloadLastDir.file.path, dir2.path, "LastDir should be set to dir2");
+ is(
+ (await getFile(downloadLastDir, uri1)).path,
+ dir2.path,
+ "uri1 should return dir2"
+ );
+ is(
+ (await getFile(downloadLastDir, uri2)).path,
+ dir2.path,
+ "uri2 should return dir2"
+ );
+ is(
+ (await getFile(downloadLastDir, uri3)).path,
+ dir2.path,
+ "uri3 should return dir2"
+ );
+ is(
+ (await getFile(downloadLastDir, uri4)).path,
+ dir2.path,
+ "uri4 should return dir2"
+ );
+
+ Services.prefs.clearUserPref("browser.download.lastDir.savePerSite");
+
+ // check that passing null to setFile clears the stored value
+ await setFile(downloadLastDir, uri3, dir3);
+ is(
+ (await getFile(downloadLastDir, uri3)).path,
+ dir3.path,
+ "LastDir should be set to dir3"
+ );
+ await setFile(downloadLastDir, uri3, null);
+ is(await getFile(downloadLastDir, uri3), null, "uri3 should return null");
+
+ await clearHistoryAndWait();
+
+ finish();
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about.js
new file mode 100644
index 0000000000..af8bac9727
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about.js
@@ -0,0 +1,266 @@
+/* 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/. */
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
+});
+
+ChromeUtils.defineLazyGetter(this, "UrlbarTestUtils", () => {
+ const { UrlbarTestUtils: module } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlbarTestUtils.sys.mjs"
+ );
+ module.init(this);
+ return module;
+});
+
+/**
+ * Clicks the given link and checks this opens the given URI in the new tab.
+ *
+ * This function does not return to the previous page.
+ */
+async function testLinkOpensUrl({ win, tab, elementId, expectedUrl }) {
+ let loadedPromise = BrowserTestUtils.waitForNewTab(win.gBrowser, url =>
+ url.startsWith(expectedUrl)
+ );
+ await SpecialPowers.spawn(tab, [elementId], async function (elemId) {
+ content.document.getElementById(elemId).click();
+ });
+ await loadedPromise;
+ is(
+ win.gBrowser.selectedBrowser.currentURI.spec,
+ expectedUrl,
+ `Clicking ${elementId} opened ${expectedUrl} in the same tab.`
+ );
+}
+
+let expectedEngineAlias;
+let expectedIconURL;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault", true],
+ // Enable suggestions in this test. Otherwise, the behaviour of the
+ // content search box changes.
+ ["browser.search.suggest.enabled", true],
+ ],
+ });
+
+ const originalPrivateDefault = await Services.search.getDefaultPrivate();
+ // We have to use a built-in engine as we are currently hard-coding the aliases.
+ const privateEngine = await Services.search.getEngineByName("DuckDuckGo");
+ await Services.search.setDefaultPrivate(
+ privateEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ expectedEngineAlias = privateEngine.aliases[0];
+ expectedIconURL = privateEngine.getIconURL();
+
+ registerCleanupFunction(async () => {
+ await Services.search.setDefaultPrivate(
+ originalPrivateDefault,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ });
+});
+
+/**
+ * Tests the private-browsing-myths link in "about:privatebrowsing".
+ */
+add_task(async function test_myths_link() {
+ Services.prefs.setCharPref("app.support.baseURL", "https://example.com/");
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("app.support.baseURL");
+ });
+
+ let { win, tab } = await openAboutPrivateBrowsing();
+
+ await testLinkOpensUrl({
+ win,
+ tab,
+ elementId: "private-browsing-myths",
+ expectedUrl: "https://example.com/private-browsing-myths",
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+function urlBarHasHiddenFocus(win) {
+ return win.gURLBar.focused && !win.gURLBar.hasAttribute("focused");
+}
+
+function urlBarHasNormalFocus(win) {
+ return win.gURLBar.hasAttribute("focused");
+}
+
+/**
+ * Tests that we have the correct icon displayed.
+ */
+add_task(async function test_search_icon() {
+ let { win, tab } = await openAboutPrivateBrowsing();
+
+ await SpecialPowers.spawn(tab, [expectedIconURL], async function (iconURL) {
+ is(
+ content.document.body.getAttribute("style"),
+ `--newtab-search-icon: url(${iconURL});`,
+ "Should have the correct icon URL for the logo"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * Tests the search hand-off on character keydown in "about:privatebrowsing".
+ */
+add_task(async function test_search_handoff_on_keydown() {
+ let { win, tab } = await openAboutPrivateBrowsing();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ let btn = content.document.getElementById("search-handoff-button");
+ btn.click();
+ ok(btn.classList.contains("focused"), "in-content search has focus styles");
+ });
+ ok(urlBarHasHiddenFocus(win), "Urlbar has hidden focus");
+
+ // Expect two searches, one to enter search mode and then another in search
+ // mode.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(win);
+
+ await new Promise(r => EventUtils.synthesizeKey("f", {}, win, r));
+ await SpecialPowers.spawn(tab, [], async function () {
+ ok(
+ content.document
+ .getElementById("search-handoff-button")
+ .classList.contains("disabled"),
+ "in-content search is disabled"
+ );
+ });
+ await searchPromise;
+ ok(urlBarHasNormalFocus(win), "Urlbar has normal focus");
+ is(win.gURLBar.value, "f", "url bar has search text");
+
+ // Close the popup.
+ await UrlbarTestUtils.promisePopupClose(win);
+
+ // Hitting ESC should reshow the in-content search
+ await new Promise(r => EventUtils.synthesizeKey("KEY_Escape", {}, win, r));
+ await SpecialPowers.spawn(tab, [], async function () {
+ ok(
+ !content.document
+ .getElementById("search-handoff-button")
+ .classList.contains("disabled"),
+ "in-content search is not disabled"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * Tests the search hand-off on composition start in "about:privatebrowsing".
+ */
+add_task(async function test_search_handoff_on_composition_start() {
+ let { win, tab } = await openAboutPrivateBrowsing();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ content.document.getElementById("search-handoff-button").click();
+ });
+ ok(urlBarHasHiddenFocus(win), "Urlbar has hidden focus");
+ await new Promise(r =>
+ EventUtils.synthesizeComposition({ type: "compositionstart" }, win, r)
+ );
+ ok(urlBarHasNormalFocus(win), "Urlbar has normal focus");
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * Tests the search hand-off on paste in "about:privatebrowsing".
+ */
+add_task(async function test_search_handoff_on_paste() {
+ let { win, tab } = await openAboutPrivateBrowsing();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ content.document.getElementById("search-handoff-button").click();
+ });
+ ok(urlBarHasHiddenFocus(win), "Urlbar has hidden focus");
+ var helper = SpecialPowers.Cc[
+ "@mozilla.org/widget/clipboardhelper;1"
+ ].getService(SpecialPowers.Ci.nsIClipboardHelper);
+ helper.copyString("words");
+
+ // Expect two searches, one to enter search mode and then another in search
+ // mode.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(win);
+
+ await new Promise(r =>
+ EventUtils.synthesizeKey("v", { accelKey: true }, win, r)
+ );
+
+ await searchPromise;
+
+ ok(urlBarHasNormalFocus(win), "Urlbar has normal focus");
+ is(win.gURLBar.value, "words", "Urlbar has search text");
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * Tests that handoff enters search mode when suggestions are disabled.
+ */
+add_task(async function test_search_handoff_search_mode() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.searches", false]],
+ });
+
+ let { win, tab } = await openAboutPrivateBrowsing();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ let btn = content.document.getElementById("search-handoff-button");
+ btn.click();
+ ok(btn.classList.contains("focused"), "in-content search has focus styles");
+ });
+ ok(urlBarHasHiddenFocus(win), "Urlbar has hidden focus");
+
+ // Expect two searches, one to enter search mode and then another in search
+ // mode.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(win);
+
+ await new Promise(r => EventUtils.synthesizeKey("f", {}, win, r));
+ await SpecialPowers.spawn(tab, [], async function () {
+ ok(
+ content.document
+ .getElementById("search-handoff-button")
+ .classList.contains("disabled"),
+ "in-content search is disabled"
+ );
+ });
+ await searchPromise;
+ ok(urlBarHasNormalFocus(win), "Urlbar has normal focus");
+ await UrlbarTestUtils.assertSearchMode(win, {
+ engineName: "DuckDuckGo",
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ entry: "handoff",
+ });
+ is(win.gURLBar.value, "f", "url bar has search text");
+
+ // Close the popup.
+ await UrlbarTestUtils.exitSearchMode(win);
+ await UrlbarTestUtils.promisePopupClose(win);
+
+ // Hitting ESC should reshow the in-content search
+ await new Promise(r => EventUtils.synthesizeKey("KEY_Escape", {}, win, r));
+ await SpecialPowers.spawn(tab, [], async function () {
+ ok(
+ !content.document
+ .getElementById("search-handoff-button")
+ .classList.contains("disabled"),
+ "in-content search is not disabled"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutSessionRestore.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutSessionRestore.js
new file mode 100644
index 0000000000..a838a7e52d
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutSessionRestore.js
@@ -0,0 +1,25 @@
+/* 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/. */
+
+// This test checks that the session restore button from about:sessionrestore
+// is disabled in private mode
+add_task(async function testNoSessionRestoreButton() {
+ // Opening, then closing, a private window shouldn't create session data.
+ (await BrowserTestUtils.openNewBrowserWindow({ private: true })).close();
+
+ let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ let tab = BrowserTestUtils.addTab(win.gBrowser, "about:sessionrestore");
+ let browser = tab.linkedBrowser;
+
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ Assert.ok(
+ content.document.getElementById("errorTryAgain").disabled,
+ "The Restore about:sessionrestore button should be disabled"
+ );
+ });
+
+ win.close();
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_cookie_banners_promo.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_cookie_banners_promo.js
new file mode 100644
index 0000000000..2f9c87e1e1
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_cookie_banners_promo.js
@@ -0,0 +1,107 @@
+const { ASRouter } = ChromeUtils.importESModule(
+ "resource:///modules/asrouter/ASRouter.sys.mjs"
+);
+
+const promoImgSrc = "chrome://browser/content/assets/cookie-banners-begone.svg";
+
+async function resetState() {
+ await Promise.all([ASRouter.resetMessageState(), ASRouter.unblockAll()]);
+}
+
+add_setup(async function setup() {
+ registerCleanupFunction(resetState);
+ await resetState();
+});
+
+add_task(async function test_cookie_banners_promo_user_set_prefs() {
+ await resetState();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.promo.cookiebanners.enabled", true],
+ // The message's targeting is looking for the following prefs not being 0
+ ["cookiebanners.service.mode", 0],
+ ["cookiebanners.service.mode.privateBrowsing", 0],
+ ],
+ });
+ await ASRouter.onPrefChange();
+
+ const { win, tab } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab, [promoImgSrc], async function (imgSrc) {
+ const promoImage = content.document.querySelector(
+ ".promo-image-large > img"
+ );
+ Assert.notStrictEqual(
+ promoImage?.src,
+ imgSrc,
+ "Cookie banner reduction promo is not shown"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_cookie_banners_promo() {
+ await resetState();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.promo.cookiebanners.enabled", true],
+ ["cookiebanners.service.mode.privateBrowsing", 1],
+ ],
+ });
+ await ASRouter.onPrefChange();
+
+ const sandbox = sinon.createSandbox();
+ const expectedUrl = Services.urlFormatter.formatURL(
+ "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/cookie-banner-reduction"
+ );
+
+ const { win, tab } = await openTabAndWaitForRender();
+ let triedToOpenTab = new Promise(resolve => {
+ sandbox.stub(win, "openLinkIn").callsFake((url, where) => {
+ is(url, expectedUrl, "The link should open the expected URL");
+ is(
+ where,
+ "tabshifted",
+ "The link should open the expected URL in a new foreground tab"
+ );
+ resolve();
+ });
+ });
+
+ await SpecialPowers.spawn(tab, [promoImgSrc], async function (imgSrc) {
+ const promoImage = content.document.querySelector(
+ ".promo-image-large > img"
+ );
+ Assert.strictEqual(
+ promoImage?.src,
+ imgSrc,
+ "Cookie banner reduction promo is shown"
+ );
+ let linkEl = content.document.getElementById("private-browsing-promo-link");
+ linkEl.click();
+ });
+
+ await triedToOpenTab;
+ sandbox.restore();
+
+ ok(true, "The link was clicked and the new tab opened");
+
+ let { win: win2, tab: tab2 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab2, [promoImgSrc], async function (imgSrc) {
+ const promoImage = content.document.querySelector(
+ ".promo-image-large > img"
+ );
+ Assert.notStrictEqual(
+ promoImage?.src,
+ imgSrc,
+ "Cookie banner reduction promo is no longer shown after clicking the link"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win2);
+ await BrowserTestUtils.closeWindow(win);
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_default_pin_promo.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_default_pin_promo.js
new file mode 100644
index 0000000000..bc62556b12
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_default_pin_promo.js
@@ -0,0 +1,110 @@
+/* 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/. */
+
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+
+const sandbox = sinon.createSandbox();
+
+add_setup(async function () {
+ ASRouter.resetMessageState();
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.promo.pin.enabled", true]],
+ });
+ await ASRouter.onPrefChange();
+ // Stub out the doesAppNeedPin to true so that Pin Promo targeting evaluates true
+
+ sandbox.stub(ShellService, "doesAppNeedPin").withArgs(true).returns(true);
+ registerCleanupFunction(async () => {
+ sandbox.restore();
+ });
+});
+
+add_task(async function test_pin_promo() {
+ let { win: win1, tab: tab1 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab1, [], async function () {
+ const promoContainer = content.document.querySelector(".promo");
+ const promoHeader = content.document.getElementById("promo-header");
+
+ ok(promoContainer, "Pin promo is shown");
+ is(
+ promoHeader.getAttribute("data-l10n-id"),
+ "about-private-browsing-pin-promo-header",
+ "Correct default values are shown"
+ );
+ });
+
+ let { win: win2 } = await openTabAndWaitForRender();
+ let { win: win3 } = await openTabAndWaitForRender();
+ let { win: win4, tab: tab4 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab4, [], async function () {
+ is(
+ content.document.getElementById(".private-browsing-promo-link"),
+ null,
+ "should no longer render the promo after 3 impressions"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win1);
+ await BrowserTestUtils.closeWindow(win2);
+ await BrowserTestUtils.closeWindow(win3);
+ await BrowserTestUtils.closeWindow(win4);
+});
+
+add_task(async function test_pin_promo_mr2022_holdback() {
+ ASRouter.resetMessageState();
+ // Set majorRelease2022 feature onboarding variable fallback pref
+ // for inMr2022Holdback targeting to evaluate true
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.majorrelease.onboarding", false]],
+ });
+ await ASRouter.onPrefChange();
+ let { win: win1, tab: tab1 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab1, [], async function () {
+ const promoContainer = content.document.querySelector(".promo");
+ const promoButton = content.document.querySelector(
+ "#private-browsing-promo-link"
+ );
+
+ ok(promoContainer, "Promo is shown");
+
+ Assert.equal(
+ promoButton.getAttribute("data-l10n-id"),
+ "about-private-browsing-focus-promo-cta",
+ "Pin Promo not shown for holdback user"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win1);
+});
+
+add_task(async function test_pin_promo_mr2022_not_holdback() {
+ ASRouter.resetMessageState();
+ // Set majorRelease2022 feature onboarding variable fallback pref
+ // for inMr2022Holdback targeting to evaluate false
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.majorrelease.onboarding", true]],
+ });
+ await ASRouter.onPrefChange();
+ let { win: win1, tab: tab1 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab1, [], async function () {
+ const promoContainer = content.document.querySelector(".promo");
+ const promoHeader = content.document.getElementById("promo-header");
+
+ ok(promoContainer, "Promo is shown");
+
+ is(
+ promoHeader.getAttribute("data-l10n-id"),
+ "about-private-browsing-pin-promo-header",
+ "Pin Promo is shown"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win1);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_default_promo.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_default_promo.js
new file mode 100644
index 0000000000..82c6fea011
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_default_promo.js
@@ -0,0 +1,224 @@
+/* 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/. */
+
+const PromoInfo = {
+ FOCUS: { enabledPref: "browser.promo.focus.enabled" },
+ VPN: { enabledPref: "browser.vpn_promo.enabled" },
+ PIN: { enabledPref: "browser.promo.pin.enabled" },
+ COOKIE_BANNERS: { enabledPref: "browser.promo.cookiebanners.enabled" },
+};
+
+const sandbox = sinon.createSandbox();
+
+async function resetState() {
+ await Promise.all([
+ ASRouter.resetMessageState(),
+ ASRouter.resetGroupsState(),
+ ASRouter.unblockAll(),
+ sandbox.restore(),
+ ]);
+}
+
+add_setup(async function () {
+ registerCleanupFunction(resetState);
+ await resetState();
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.promo.pin.enabled", false]],
+ });
+ await ASRouter.onPrefChange();
+});
+
+add_task(async function test_privatebrowsing_asrouter_messages_state() {
+ await resetState();
+ let pinPromoMessage = ASRouter.state.messages.find(
+ m => m.id === "PB_NEWTAB_PIN_PROMO"
+ );
+ Assert.ok(pinPromoMessage, "Pin Promo message found");
+
+ const initialMessages = JSON.parse(JSON.stringify(ASRouter.state.messages));
+
+ let { win, tab } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ const promoContainer = content.document.querySelector(".promo");
+ ok(promoContainer, "Focus promo is shown");
+ });
+
+ Assert.equal(
+ ASRouter.state.messages.filter(m => m.id === "PB_NEWTAB_PIN_PROMO").length,
+ 0,
+ "Pin Promo message removed from state when Promotype Pin is disabled"
+ );
+
+ for (let msg of initialMessages) {
+ let shouldPersist =
+ msg.template !== "pb_newtab" ||
+ Services.prefs.getBoolPref(
+ PromoInfo[msg.content?.promoType]?.enabledPref,
+ true
+ );
+ Assert.equal(
+ !!ASRouter.state.messages.find(m => m.id === msg.id),
+ shouldPersist,
+ shouldPersist
+ ? "Message persists in ASRouter state"
+ : "Promo message with disabled promoType removed from ASRouter state"
+ );
+ }
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_default_promo() {
+ await resetState();
+
+ let { win: win1, tab: tab1 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab1, [], async function () {
+ const promoContainer = content.document.querySelector(".promo"); // container which is present if promo is enabled and should show
+ const promoHeader = content.document.getElementById("promo-header");
+
+ ok(promoContainer, "Focus promo is shown");
+ is(
+ promoHeader.getAttribute("data-l10n-id"),
+ "about-private-browsing-focus-promo-header-c",
+ "Correct default values are shown"
+ );
+ });
+
+ let { win: win2 } = await openTabAndWaitForRender();
+ let { win: win3 } = await openTabAndWaitForRender();
+
+ let { win: win4, tab: tab4 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab4, [], async function () {
+ is(
+ content.document.querySelector(".promo button"),
+ null,
+ "should no longer render the promo after 3 impressions"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win1);
+ await BrowserTestUtils.closeWindow(win2);
+ await BrowserTestUtils.closeWindow(win3);
+ await BrowserTestUtils.closeWindow(win4);
+});
+
+// Verify that promos are correctly removed if blocked in another tab.
+// See handlePromoOnPreload() in aboutPrivateBrowsing.js
+add_task(async function test_remove_promo_from_prerendered_tab_if_blocked() {
+ await resetState();
+
+ const { win, tab: tab1 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab1, [], async function () {
+ // container which is present if promo message is not blocked
+ const promoContainer = content.document.querySelector(".promo");
+ ok(promoContainer, "Focus promo is shown in tab 1");
+ });
+
+ // Open a new background tab (tab 2) while the promo message is unblocked
+ win.openTrustedLinkIn(win.BROWSER_NEW_TAB_URL, "tabshifted");
+
+ // Block the promo in tab 1
+ await SpecialPowers.spawn(tab1, [], async function () {
+ content.document.getElementById("dismiss-btn").click();
+ await ContentTaskUtils.waitForCondition(() => {
+ return !content.document.querySelector(".promo");
+ }, "The promo container is removed.");
+ });
+
+ // Switch to tab 2, invoking the `visibilitychange` handler in
+ // handlePromoOnPreload()
+ await BrowserTestUtils.switchTab(win.gBrowser, win.gBrowser.tabs[1]);
+
+ // Verify that the promo has now been removed from tab 2
+ await SpecialPowers.spawn(
+ win.gBrowser.tabs[1].linkedBrowser,
+ [],
+ // The timing may be weird in Chaos Mode, so wait for it to be removed
+ // instead of a single assertion.
+ async function () {
+ await ContentTaskUtils.waitForCondition(
+ () => !content.document.querySelector(".promo"),
+ "Focus promo is not shown in a new tab after being dismissed in another tab"
+ );
+ }
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+// Test that some default content is rendered while waiting for ASRouter to
+// return a message.
+add_task(async function test_default_content_deferred_message_load() {
+ await resetState();
+
+ let messageRequestedPromiseResolver;
+ const messageRequestedPromise = new Promise(resolve => {
+ messageRequestedPromiseResolver = resolve;
+ });
+ let messageReadyPromiseResolver;
+ const messageReadyPromise = new Promise(resolve => {
+ messageReadyPromiseResolver = resolve;
+ });
+ // Force ASRouter to "hang" until we resolve the promise so we can test what
+ // happens when there is a delay in loading the message.
+ const sendMessageStub = sandbox
+ .stub(ASRouter, "sendPBNewTabMessage")
+ .callsFake(async (...args) => {
+ messageRequestedPromiseResolver();
+ await messageReadyPromise;
+ return sendMessageStub.wrappedMethod.apply(ASRouter, args);
+ });
+
+ const { win, tab } = await openAboutPrivateBrowsing();
+ await messageRequestedPromise;
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ const promoContainer = content.document.querySelector(".promo");
+ ok(
+ promoContainer && !promoContainer.classList.contains("promo-visible"),
+ "Focus promo is hidden but not removed"
+ );
+ const infoContainer = content.document.querySelector(".info");
+ ok(infoContainer && !infoContainer.hidden, "Info container is shown");
+ const infoTitle = content.document.getElementById("info-title");
+ ok(infoTitle && infoTitle.hidden, "Info title is hidden");
+ const infoBody = content.document.getElementById("info-body");
+ ok(infoBody, "Info body is shown");
+ is(
+ infoBody.getAttribute("data-l10n-id"),
+ "about-private-browsing-info-description-private-window",
+ "Info body has the correct Fluent id"
+ );
+ await ContentTaskUtils.waitForCondition(
+ () => infoBody.textContent,
+ "Info body has been translated"
+ );
+ const infoLink = content.document.getElementById("private-browsing-myths");
+ ok(infoLink, "Info link is shown");
+ is(
+ infoLink.getAttribute("data-l10n-id"),
+ "about-private-browsing-learn-more-link",
+ "Info link has the correct Fluent id"
+ );
+ await ContentTaskUtils.waitForCondition(
+ () => infoLink.textContent && infoLink.href,
+ "Info body has been translated"
+ );
+ });
+
+ messageReadyPromiseResolver();
+ await messageReadyPromise;
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ const promoContainer = content.document.querySelector(".promo");
+ return promoContainer?.classList.contains("promo-visible");
+ }, "The promo container is shown.");
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_focus_promo.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_focus_promo.js
new file mode 100644
index 0000000000..7e5c6540b0
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_focus_promo.js
@@ -0,0 +1,89 @@
+const { Region } = ChromeUtils.importESModule(
+ "resource://gre/modules/Region.sys.mjs"
+);
+const { ASRouter } = ChromeUtils.importESModule(
+ "resource:///modules/asrouter/ASRouter.sys.mjs"
+);
+
+const initialHomeRegion = Region._home;
+const intialCurrentRegion = Region._current;
+const initialLocale = Services.locale.appLocaleAsBCP47;
+
+// Helper to run tests for specific regions
+async function setupRegions(home, current) {
+ Region._setHomeRegion(home || "");
+ Region._setCurrentRegion(current || "");
+}
+
+// Helper to run tests for specific locales
+function setLocale(locale) {
+ Services.locale.availableLocales = [locale];
+ Services.locale.requestedLocales = [locale];
+}
+
+add_task(async function test_focus_promo_in_allowed_region() {
+ ASRouter.resetMessageState();
+
+ const allowedRegion = "ES"; // Spain
+ setupRegions(allowedRegion, allowedRegion);
+
+ const { win, tab } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ const promoContainer = content.document.querySelector(".promo"); // container which is present if promo is enabled and should show
+
+ ok(promoContainer, "Focus promo is shown for allowed region");
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ setupRegions(initialHomeRegion, intialCurrentRegion); // revert changes to regions
+});
+
+add_task(async function test_focus_promo_in_disallowed_region() {
+ ASRouter.resetMessageState();
+
+ const disallowedRegion = "CN"; // China
+ setupRegions(disallowedRegion);
+
+ const { win, tab } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ const promoContainer = content.document.querySelector(".promo"); // container which is removed if promo is disabled and/or should not show
+
+ ok(!promoContainer, "Focus promo is not shown for disallowed region");
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ setupRegions(initialHomeRegion, intialCurrentRegion); // revert changes to regions
+});
+
+add_task(
+ async function test_klar_promo_in_certain_regions_with_English_locale() {
+ const testLocale = "en-US"; // US English
+ setLocale(testLocale);
+
+ const testRegion = async region => {
+ setupRegions(region);
+ ASRouter.resetMessageState();
+ const { win, tab } = await openTabAndWaitForRender();
+ await SpecialPowers.spawn(tab, [], async function () {
+ const buttonText = content.document.querySelector(
+ "#private-browsing-promo-link"
+ ).textContent;
+ Assert.equal(
+ buttonText,
+ "Download Firefox Klar",
+ "The promo button text reads 'Download Firefox Klar'"
+ );
+ });
+ await BrowserTestUtils.closeWindow(win);
+ };
+
+ await testRegion("AT"); // Austria
+ await testRegion("DE"); // Germany
+ await testRegion("CH"); // Switzerland
+
+ setupRegions(initialHomeRegion, intialCurrentRegion); // revert changes to regions
+ setLocale(initialLocale); // revert changes to locale
+ }
+);
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus.js
new file mode 100644
index 0000000000..489c9c91b2
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus.js
@@ -0,0 +1,459 @@
+/* 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/. */
+
+requestLongerTimeout(2);
+
+add_task(async function test_experiment_plain_text() {
+ const defaultMessageContent = (await PanelTestProvider.getMessages()).find(
+ m => m.template === "pb_newtab"
+ ).content;
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: "PB_NEWTAB_MESSAGING_SYSTEM",
+ template: "pb_newtab",
+ content: {
+ ...defaultMessageContent,
+ infoTitle: "Hello world",
+ infoTitleEnabled: true,
+ infoBody: "This is some text",
+ infoLinkText: "This is a link",
+ infoIcon: "chrome://branding/content/about-logo.png",
+ promoTitle: "Promo title",
+ promoLinkText: "Promo link",
+ promoLinkType: "link",
+ promoButton: {
+ action: {
+ type: "OPEN_URL",
+ data: {
+ args: "https://example.com",
+ where: "tabshifted",
+ },
+ },
+ },
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ let { win, tab } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ const infoContainer = content.document.querySelector(".info");
+ const infoTitle = content.document.getElementById("info-title");
+ const infoBody = content.document.getElementById("info-body");
+ const infoLink = content.document.getElementById("private-browsing-myths");
+ const promoText = content.document.getElementById(
+ "private-browsing-promo-text"
+ );
+ const promoLink = content.document.getElementById(
+ "private-browsing-promo-link"
+ );
+
+ // Check experiment values are rendered
+ ok(!infoContainer.hidden, ".info container should be visible");
+ ok(
+ infoContainer.style.backgroundImage.includes(
+ "chrome://branding/content/about-logo.png"
+ ),
+ "should render icon"
+ );
+ is(infoTitle.textContent, "Hello world", "should render infoTitle");
+ is(infoBody.textContent, "This is some text", "should render infoBody");
+ is(infoLink.textContent, "This is a link", "should render infoLink");
+ is(promoText.textContent, "Promo title", "should render promoTitle");
+ is(promoLink.textContent, "Promo link", "should render promoLinkText");
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ await doExperimentCleanup();
+});
+
+add_task(async function test_experiment_info_disabled() {
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: "PB_NEWTAB_MESSAGING_SYSTEM",
+ template: "pb_newtab",
+ content: {
+ infoEnabled: false,
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ let { win, tab } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ ok(
+ content.document.querySelector(".info").hidden,
+ "should hide .info element"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ await doExperimentCleanup();
+});
+
+add_task(async function test_experiment_promo_disabled() {
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: "PB_NEWTAB_MESSAGING_SYSTEM",
+ template: "pb_newtab",
+ content: {
+ promoEnabled: false,
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ let { win, tab } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ is(
+ content.document.querySelector(".promo"),
+ undefined,
+ "should remove .promo element"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ await doExperimentCleanup();
+});
+
+add_task(async function test_experiment_format_urls() {
+ const LOCALE = Services.locale.appLocaleAsBCP47;
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: "PB_NEWTAB_MESSAGING_SYSTEM",
+ template: "pb_newtab",
+ content: {
+ infoEnabled: true,
+ promoEnabled: true,
+ infoLinkUrl: "http://foo.mozilla.com/%LOCALE%",
+ promoButton: {
+ action: {
+ data: {
+ args: "http://bar.mozilla.com/%LOCALE%",
+ where: "tabshifted",
+ },
+ type: "OPEN_URL",
+ },
+ },
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ let { win, tab } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab, [LOCALE], async function (locale) {
+ is(
+ content.document.querySelector(".info a").getAttribute("href"),
+ "http://foo.mozilla.com/" + locale,
+ "should format the infoLinkUrl url"
+ );
+
+ ok(
+ content.document.querySelector(".promo button"),
+ "should render promo button"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ await doExperimentCleanup();
+});
+
+add_task(async function test_experiment_click_info_telemetry() {
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: "PB_NEWTAB_MESSAGING_SYSTEM_CLICK_INFO_TELEM",
+ template: "pb_newtab",
+ content: {
+ infoEnabled: true,
+ infoLinkUrl: "http://example.com",
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ // Required for `mach test --verify`
+ Services.telemetry.clearEvents();
+
+ let { win, tab } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab, [], () => {
+ const el = content.document.querySelector(".info a");
+ el.click();
+ });
+
+ let event = await waitForTelemetryEvent("aboutprivatebrowsing");
+
+ ok(
+ event[2] == "click" && event[3] == "info_link",
+ "recorded telemetry for info link"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ await doExperimentCleanup();
+});
+
+add_task(async function test_experiment_click_promo_telemetry() {
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: `PB_NEWTAB_MESSAGING_SYSTEM_PROMO_TELEM_${Math.random()}`,
+ template: "pb_newtab",
+ content: {
+ promoEnabled: true,
+ promoLinkType: "link",
+ promoButton: {
+ action: {
+ type: "OPEN_URL",
+ data: {
+ args: "https://example.com",
+ where: "tabshifted",
+ },
+ },
+ },
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ let { win, tab } = await openTabAndWaitForRender();
+
+ Services.telemetry.clearEvents();
+
+ await SpecialPowers.spawn(tab, [], () => {
+ is(
+ content.document
+ .querySelector(".promo-cta button")
+ .classList.contains("promo-link"),
+ true,
+ "Should have a button styled as a link"
+ );
+
+ const el = content.document.querySelector(".promo button");
+ el.click();
+ });
+
+ let event = await waitForTelemetryEvent("aboutprivatebrowsing");
+
+ ok(
+ event[2] == "click" && event[3] == "promo_link",
+ "recorded telemetry for promo link"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ await doExperimentCleanup();
+});
+
+add_task(async function test_experiment_bottom_promo() {
+ const defaultMessageContent = (await PanelTestProvider.getMessages()).find(
+ m => m.template === "pb_newtab"
+ ).content;
+
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: "PB_NEWTAB_MESSAGING_SYSTEM",
+ template: "pb_newtab",
+ content: {
+ ...defaultMessageContent,
+ promoEnabled: true,
+ promoLinkType: "button",
+ promoSectionStyle: "bottom",
+ promoHeader: "Need more privacy?",
+ infoTitleEnabled: true,
+ promoTitleEnabled: false,
+ promoImageLarge: "",
+ promoImageSmall: "chrome://browser/content/assets/vpn-logo.svg",
+ promoButton: {
+ action: {
+ data: {
+ args: "http://bar.example.com/%LOCALE%",
+ where: "tabshifted",
+ },
+ type: "OPEN_URL",
+ },
+ },
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ let { win, tab } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ is(
+ content.document
+ .querySelector(".promo-cta button")
+ .classList.contains("primary"),
+ true,
+ "Should have a button CTA"
+ );
+ is(
+ content.document.querySelector(".promo-image-small img").src,
+ "chrome://browser/content/assets/vpn-logo.svg",
+ "Should have logo image"
+ );
+ ok(
+ content.document.querySelector(".promo.bottom"),
+ "Should have .bottom for the promo section"
+ );
+ const infoTitle = content.document.getElementById("info-title");
+ ok(
+ infoTitle && !infoTitle.hidden,
+ "Should render info title if infoTitleEnabled is true"
+ );
+ ok(
+ !content.document.querySelector("#private-browsing-promo-text"),
+ "Should not render promo title if promoTitleEnabled is false"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ await doExperimentCleanup();
+});
+
+add_task(async function test_experiment_below_search_promo() {
+ const defaultMessageContent = (await PanelTestProvider.getMessages()).find(
+ m => m.template === "pb_newtab"
+ ).content;
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: "PB_NEWTAB_MESSAGING_SYSTEM",
+ template: "pb_newtab",
+ content: {
+ ...defaultMessageContent,
+ promoEnabled: true,
+ promoLinkType: "button",
+ promoSectionStyle: "below-search",
+ promoHeader: "Need more privacy?",
+ promoTitle:
+ "Mozilla VPN. Security, reliability and speed — on every device, anywhere you go.",
+ promoImageLarge: "chrome://browser/content/assets/moz-vpn.svg",
+ promoImageSmall: "chrome://browser/content/assets/vpn-logo.svg",
+ infoTitleEnabled: false,
+ promoButton: {
+ action: {
+ data: {
+ args: "https://foo.example.com",
+ where: "tabshifted",
+ },
+ type: "OPEN_URL",
+ },
+ },
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ let { win, tab } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ is(
+ content.document
+ .querySelector(".promo-cta button")
+ .classList.contains("primary"),
+ true,
+ "Should have a button CTA"
+ );
+ is(
+ content.document.querySelector(".promo-image-small img").src,
+ "chrome://browser/content/assets/vpn-logo.svg",
+ "Should have logo image"
+ );
+ is(
+ content.document.querySelector(".promo-image-large img").src,
+ "chrome://browser/content/assets/moz-vpn.svg",
+ "Should have a product image"
+ );
+ ok(
+ content.document.querySelector(".promo.below-search"),
+ "Should have .below-search for the promo section"
+ );
+ ok(
+ content.document.getElementById("info-title").hidden,
+ "Should not render info title if infoTitleEnabled is false"
+ );
+ ok(
+ content.document.querySelector("#private-browsing-promo-text"),
+ "Should render promo title if promoTitleEnabled is true"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ await doExperimentCleanup();
+});
+
+add_task(async function test_experiment_top_promo() {
+ const defaultMessageContent = (await PanelTestProvider.getMessages()).find(
+ m => m.template === "pb_newtab"
+ ).content;
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: `PB_NEWTAB_MESSAGING_SYSTEM_DISMISS_${Math.random()}`,
+ template: "pb_newtab",
+ content: {
+ ...defaultMessageContent,
+ promoEnabled: true,
+ promoLinkType: "button",
+ promoSectionStyle: "top",
+ promoHeader: "Need more privacy?",
+ promoTitle:
+ "Mozilla VPN. Security, reliability and speed — on every device, anywhere you go.",
+ promoImageLarge: "chrome://browser/content/assets/moz-vpn.svg",
+ promoImageSmall: "chrome://browser/content/assets/vpn-logo.svg",
+ infoTitleEnabled: false,
+ promoButton: {
+ action: {
+ data: {
+ args: "https://foo.example.com",
+ where: "tabshifted",
+ },
+ type: "OPEN_URL",
+ },
+ },
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ let { win, tab } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ is(
+ content.document.querySelector(".promo-image-small img").src,
+ "chrome://browser/content/assets/vpn-logo.svg",
+ "Should have logo image"
+ );
+ is(
+ content.document.querySelector(".promo-image-large img").src,
+ "chrome://browser/content/assets/moz-vpn.svg",
+ "Should have a product image"
+ );
+ ok(
+ content.document.querySelector(".promo.top"),
+ "Should have .below-search for the promo section"
+ );
+ ok(
+ content.document.getElementById("info-title").hidden,
+ "Should hide info title if infoTitleEnabled is false"
+ );
+ ok(
+ content.document.querySelector("#private-browsing-promo-text"),
+ "Should render promo title if promoTitleEnabled is true"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ await doExperimentCleanup();
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus_dismiss.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus_dismiss.js
new file mode 100644
index 0000000000..bfe5708a5b
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus_dismiss.js
@@ -0,0 +1,139 @@
+/* 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/. */
+
+add_setup(async function () {
+ ASRouter.resetMessageState();
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.promo.pin.enabled", false]],
+ });
+ await ASRouter.onPrefChange();
+});
+
+add_task(async function test_experiment_messaging_system_dismiss() {
+ const LOCALE = Services.locale.appLocaleAsBCP47;
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: `PB_NEWTAB_MESSAGING_SYSTEM_${Math.random()}`,
+ template: "pb_newtab",
+ content: {
+ hideDefault: true,
+ promoEnabled: true,
+ infoEnabled: true,
+ infoBody: "fluent:about-private-browsing-info-title",
+ promoLinkText: "fluent:about-private-browsing-prominent-cta",
+ infoLinkUrl: "http://foo.example.com/%LOCALE%",
+ promoLinkType: "link",
+ promoButton: {
+ action: {
+ data: {
+ args: "http://bar.example.com/%LOCALE%",
+ where: "tabshifted",
+ },
+ type: "OPEN_URL",
+ },
+ },
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ let { win: win1, tab: tab1 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab1, [LOCALE], async function (locale) {
+ content.document.querySelector("#dismiss-btn").click();
+ info("button clicked");
+ });
+
+ let telemetryEvent = await waitForTelemetryEvent("aboutprivatebrowsing");
+
+ ok(
+ telemetryEvent[2] == "click" && telemetryEvent[3] == "dismiss_button",
+ "recorded the dismiss button click"
+ );
+
+ let { win: win2, tab: tab2 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab2, [], async function () {
+ is(
+ content.document.querySelector(".promo button"),
+ null,
+ "should no longer render the experiment message after dismissing"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win1);
+ await BrowserTestUtils.closeWindow(win2);
+ await doExperimentCleanup();
+});
+
+add_task(async function test_experiment_messaging_show_default_on_dismiss() {
+ registerCleanupFunction(() => {
+ ASRouter.resetMessageState();
+ });
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: `PB_NEWTAB_MESSAGING_SYSTEM_${Math.random()}`,
+ template: "pb_newtab",
+ content: {
+ hideDefault: false,
+ promoEnabled: true,
+ infoEnabled: true,
+ infoBody: "fluent:about-private-browsing-info-title",
+ promoLinkText: "fluent:about-private-browsing-prominent-cta",
+ infoLinkUrl: "http://foo.example.com",
+ promoLinkType: "link",
+ promoButton: {
+ action: {
+ data: {
+ args: "http://bar.example.com",
+ where: "tabshifted",
+ },
+ type: "OPEN_URL",
+ },
+ },
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ let { win: win1, tab: tab1 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab1, [], async function () {
+ ok(
+ content.document.querySelector(".promo"),
+ "should render the promo experiment message"
+ );
+
+ content.document.querySelector("#dismiss-btn").click();
+ info("button clicked");
+ });
+
+ let telemetryEvent = await waitForTelemetryEvent("aboutprivatebrowsing");
+
+ ok(
+ telemetryEvent[2] == "click" && telemetryEvent[3] == "dismiss_button",
+ "recorded the dismiss button click"
+ );
+
+ let { win: win2, tab: tab2 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab2, [], async function () {
+ const promoHeader = content.document.getElementById("promo-header");
+ ok(
+ content.document.querySelector(".promo"),
+ "should render the default promo message after dismissing experiment promo"
+ );
+ is(
+ promoHeader.getAttribute("data-l10n-id"),
+ "about-private-browsing-focus-promo-header-c",
+ "Correct default values are shown"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win1);
+ await BrowserTestUtils.closeWindow(win2);
+ await doExperimentCleanup();
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus_impressions.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus_impressions.js
new file mode 100644
index 0000000000..ac42caa2dd
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus_impressions.js
@@ -0,0 +1,126 @@
+/* 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/. */
+
+/* Tests that use TelemetryTestUtils.assertEvents (at the very least, those with
+ * `{ process: "content" }`) seem to be super flaky and intermittent-prone when they
+ * share a file with other telemetry tests, so each one gets its own file.
+ */
+
+add_task(async function test_experiment_messaging_system_impressions() {
+ registerCleanupFunction(() => {
+ ASRouter.resetMessageState();
+ });
+ const LOCALE = Services.locale.appLocaleAsBCP47;
+ let experimentId = `pb_newtab_${Math.random()}`;
+
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: experimentId,
+ template: "pb_newtab",
+ content: {
+ hideDefault: true,
+ promoEnabled: true,
+ infoEnabled: true,
+ infoBody: "fluent:about-private-browsing-info-title",
+ promoLinkText: "fluent:about-private-browsing-prominent-cta",
+ infoLinkUrl: "http://foo.example.com/%LOCALE%",
+ promoButton: {
+ action: {
+ data: {
+ args: "https://bar.example.com/%LOCALE%",
+ where: "tabshifted",
+ },
+ type: "OPEN_URL",
+ },
+ },
+ },
+ frequency: {
+ lifetime: 2,
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ Services.telemetry.clearEvents();
+
+ let { win: win1, tab: tab1 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab1, [LOCALE], async function (locale) {
+ is(
+ content.document
+ .querySelector(".promo button")
+ .classList.contains("primary"),
+ true,
+ "should render the promo button as a button"
+ );
+ });
+
+ let event = await waitForTelemetryEvent("normandy", experimentId);
+
+ ok(
+ event[1] == "normandy" &&
+ event[2] == "expose" &&
+ event[3] == "nimbus_experiment" &&
+ event[4].includes(experimentId) &&
+ event[5].featureId == "pbNewtab",
+ "recorded telemetry for expose"
+ );
+
+ Services.telemetry.clearEvents();
+
+ let { win: win2, tab: tab2 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab2, [LOCALE], async function (locale) {
+ is(
+ content.document
+ .querySelector(".promo button")
+ .classList.contains("primary"),
+ true,
+ "should render the promo button as a button"
+ );
+ });
+
+ let event2 = await waitForTelemetryEvent("normandy", experimentId);
+
+ ok(
+ event2[1] == "normandy" &&
+ event2[2] == "expose" &&
+ event2[3] == "nimbus_experiment" &&
+ event2[4].includes(experimentId) &&
+ event2[5].featureId == "pbNewtab",
+ "recorded telemetry for expose"
+ );
+
+ Services.telemetry.clearEvents();
+
+ let { win: win3, tab: tab3 } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab3, [], async function () {
+ is(
+ content.document.querySelector(".promo button"),
+ null,
+ "should no longer render the experiment message after 2 impressions"
+ );
+ });
+
+ // Verify that the telemetry events array does not
+ // contain an expose event for pbNewtab
+ info("Should not have promo expose");
+ TelemetryTestUtils.assertEvents([], {
+ category: "normandy",
+ method: "expose",
+ object: "nimbus_experiment",
+ extra_keys: {
+ featureId: "pbNewtab",
+ },
+ });
+
+ Services.telemetry.clearEvents();
+
+ await BrowserTestUtils.closeWindow(win1);
+ await BrowserTestUtils.closeWindow(win2);
+ await BrowserTestUtils.closeWindow(win3);
+ await doExperimentCleanup();
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus_messaging.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus_messaging.js
new file mode 100644
index 0000000000..1463cee961
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus_messaging.js
@@ -0,0 +1,247 @@
+/* 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/. */
+
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+
+add_task(async function test_experiment_messaging_system() {
+ const LOCALE = Services.locale.appLocaleAsBCP47;
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: "PB_NEWTAB_MESSAGING_SYSTEM",
+ template: "pb_newtab",
+ content: {
+ hideDefault: true,
+ promoEnabled: true,
+ infoEnabled: true,
+ infoBody: "fluent:about-private-browsing-info-title",
+ promoLinkText: "fluent:about-private-browsing-prominent-cta",
+ infoLinkUrl: "http://foo.example.com/%LOCALE%",
+ promoButton: {
+ action: {
+ data: {
+ args: "http://bar.example.com/%LOCALE%",
+ where: "tabshifted",
+ },
+ type: "OPEN_URL",
+ },
+ },
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ let { win, tab } = await openTabAndWaitForRender();
+
+ await SpecialPowers.spawn(tab, [LOCALE], async function (locale) {
+ const infoBody = content.document.getElementById("info-body");
+ const promoLink = content.document.getElementById(
+ "private-browsing-promo-link"
+ );
+
+ // Check experiment values are rendered
+ is(
+ infoBody.getAttribute("data-l10n-id"),
+ "about-private-browsing-info-title",
+ "should render infoBody with fluent"
+ );
+ is(
+ promoLink.getAttribute("data-l10n-id"),
+ "about-private-browsing-prominent-cta",
+ "should render promoLinkText with fluent"
+ );
+ is(
+ content.document.querySelector(".info a").getAttribute("href"),
+ "http://foo.example.com/" + locale,
+ "should format the infoLinkUrl url"
+ );
+ is(
+ content.document.querySelector(".info a").getAttribute("target"),
+ "_blank",
+ "should open info url in new tab"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ await doExperimentCleanup();
+});
+
+add_task(async function test_experiment_promo_action() {
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: "PB_NEWTAB_TEST_URL",
+ template: "pb_newtab",
+ content: {
+ hideDefault: true,
+ promoEnabled: true,
+ infoEnabled: true,
+ infoBody: "fluent:about-private-browsing-info-title",
+ promoLinkText: "fluent:about-private-browsing-prominent-cta",
+ infoLinkUrl: "http://foo.example.com/%LOCALE%",
+ promoLinkType: "button",
+ promoButton: {
+ action: {
+ data: {
+ args: "https://foo.example.com",
+ where: "tabshifted",
+ },
+ type: "OPEN_URL",
+ },
+ },
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ let { win, tab } = await openTabAndWaitForRender();
+ const sandbox = sinon.createSandbox();
+ registerCleanupFunction(() => {
+ ASRouter.resetMessageState();
+ sandbox.restore();
+ BrowserTestUtils.closeWindow(win);
+ });
+
+ let windowGlobalParent =
+ win.gBrowser.selectedBrowser.browsingContext.currentWindowGlobal;
+ let aboutPrivateBrowsingActor = windowGlobalParent.getActor(
+ "AboutPrivateBrowsing"
+ );
+
+ let specialActionSpy = sandbox.spy(
+ aboutPrivateBrowsingActor,
+ "receiveMessage"
+ );
+
+ let expectedUrl = "https://foo.example.com";
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ ok(
+ content.document.querySelector(".promo"),
+ "should render the promo experiment message"
+ );
+
+ is(
+ content.document
+ .querySelector(".promo button")
+ .classList.contains("primary"),
+ true,
+ "should render the promo button styled as a button"
+ );
+
+ content.document.querySelector(".promo button").click();
+ info("promo button clicked");
+ });
+
+ Assert.equal(
+ specialActionSpy.callCount,
+ 1,
+ "Should be called by promo action"
+ );
+
+ let promoAction = specialActionSpy.firstCall.args[0].data;
+
+ Assert.equal(
+ promoAction.type,
+ "OPEN_URL",
+ "Should be called with promo button action"
+ );
+
+ Assert.equal(
+ promoAction.data.args,
+ expectedUrl,
+ "Should be called with right URL"
+ );
+
+ await doExperimentCleanup();
+});
+
+add_task(async function test_experiment_open_spotlight_action() {
+ let doExperimentCleanup = await setupMSExperimentWithMessage({
+ id: "PB_NEWTAB_TEST_SPOTLIGHT",
+ template: "pb_newtab",
+ content: {
+ hideDefault: true,
+ promoEnabled: true,
+ infoEnabled: true,
+ infoBody: "fluent:about-private-browsing-info-title",
+ promoLinkText: "fluent:about-private-browsing-prominent-cta",
+ infoLinkUrl: "http://foo.example.com/",
+ promoLinkType: "button",
+ promoButton: {
+ action: {
+ type: "SHOW_SPOTLIGHT",
+ data: {
+ content: {
+ template: "multistage",
+ screens: [
+ {
+ content: {
+ title: "Test",
+ subtitle: "Sub Title",
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ // Priority ensures this message is picked over the one in
+ // OnboardingMessageProvider
+ priority: 5,
+ targeting: "true",
+ });
+
+ let { win, tab } = await openTabAndWaitForRender();
+ const sandbox = sinon.createSandbox();
+ registerCleanupFunction(() => {
+ ASRouter.resetMessageState();
+ sandbox.restore();
+ BrowserTestUtils.closeWindow(win);
+ });
+
+ let windowGlobalParent =
+ win.gBrowser.selectedBrowser.browsingContext.currentWindowGlobal;
+ let aboutPrivateBrowsingActor = windowGlobalParent.getActor(
+ "AboutPrivateBrowsing"
+ );
+
+ let specialActionSpy = sandbox.spy(
+ aboutPrivateBrowsingActor,
+ "receiveMessage"
+ );
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ ok(
+ content.document.querySelector(".promo"),
+ "should render the promo experiment message"
+ );
+ content.document.querySelector(".promo button").click();
+ });
+
+ Assert.equal(
+ specialActionSpy.callCount,
+ 1,
+ "Should be called by promo action"
+ );
+
+ let promoAction = specialActionSpy.firstCall.args[0].data;
+
+ Assert.equal(
+ promoAction.type,
+ "SHOW_SPOTLIGHT",
+ "Should be called with promo button spotlight action"
+ );
+
+ Assert.equal(
+ promoAction.data.content.metrics,
+ "allow",
+ "Should be called with metrics property set as allow for experiments"
+ );
+
+ await doExperimentCleanup();
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_search_banner.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_search_banner.js
new file mode 100644
index 0000000000..4d8c2c407a
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_search_banner.js
@@ -0,0 +1,317 @@
+/* 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/. */
+
+// This test makes sure that about:privatebrowsing correctly shows the search
+// banner.
+
+const { AboutPrivateBrowsingParent } = ChromeUtils.importESModule(
+ "resource:///actors/AboutPrivateBrowsingParent.sys.mjs"
+);
+
+const PREF_UI_ENABLED = "browser.search.separatePrivateDefault.ui.enabled";
+const PREF_BANNER_SHOWN =
+ "browser.search.separatePrivateDefault.ui.banner.shown";
+const PREF_MAX_SEARCH_BANNER_SHOW_COUNT =
+ "browser.search.separatePrivateDefault.ui.banner.max";
+const MAX_SHOW_COUNT = 5;
+
+add_setup(async function () {
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_UI_ENABLED, false],
+ [PREF_BANNER_SHOWN, 0],
+ [PREF_MAX_SEARCH_BANNER_SHOW_COUNT, MAX_SHOW_COUNT],
+ ],
+ });
+
+ AboutPrivateBrowsingParent.setShownThisSession(false);
+});
+
+add_task(async function test_not_shown_if_pref_off() {
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_UI_ENABLED, false],
+ [PREF_MAX_SEARCH_BANNER_SHOW_COUNT, 5],
+ ],
+ });
+
+ const { win, tab } = await openAboutPrivateBrowsing();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ content.document.documentElement.hasAttribute(
+ "SearchBannerInitialized"
+ ),
+ "Should have initialized"
+ );
+ ok(
+ content.document.getElementById("search-banner").hasAttribute("hidden"),
+ "should be hiding the in-content search banner"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_not_shown_if_max_count_0() {
+ // To avoid having to restart Firefox and slow down tests, we manually reset
+ // the session pref.
+ AboutPrivateBrowsingParent.setShownThisSession(false);
+
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_UI_ENABLED, true],
+ [PREF_MAX_SEARCH_BANNER_SHOW_COUNT, 0],
+ ],
+ });
+ const { win, tab } = await openAboutPrivateBrowsing();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ content.document.documentElement.hasAttribute(
+ "SearchBannerInitialized"
+ ),
+ "Should have initialized"
+ );
+ ok(
+ content.document.getElementById("search-banner").hasAttribute("hidden"),
+ "should be hiding the in-content search banner"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_show_banner_first() {
+ // To avoid having to restart Firefox and slow down tests, we manually reset
+ // the session pref.
+ AboutPrivateBrowsingParent.setShownThisSession(false);
+
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_UI_ENABLED, true],
+ [PREF_MAX_SEARCH_BANNER_SHOW_COUNT, MAX_SHOW_COUNT],
+ ],
+ });
+
+ let prefChanged = TestUtils.waitForPrefChange(PREF_BANNER_SHOWN);
+
+ const { win, tab } = await openAboutPrivateBrowsing();
+
+ Assert.equal(
+ await prefChanged,
+ 1,
+ "Should have incremented the amount of times shown."
+ );
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ content.document.documentElement.hasAttribute(
+ "SearchBannerInitialized"
+ ),
+ "Should have initialized"
+ );
+
+ ok(
+ !content.document.getElementById("search-banner").hasAttribute("hidden"),
+ "should be showing the in-content search banner"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+
+ const { win: win1, tab: tab1 } = await openAboutPrivateBrowsing();
+
+ await SpecialPowers.spawn(tab1, [], async function () {
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ content.document.documentElement.hasAttribute(
+ "SearchBannerInitialized"
+ ),
+ "Should have initialized"
+ );
+
+ ok(
+ content.document.getElementById("search-banner").hasAttribute("hidden"),
+ "should not be showing the banner in a second window."
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win1);
+
+ Assert.equal(
+ Services.prefs.getIntPref(PREF_BANNER_SHOWN, -1),
+ 1,
+ "Should not have changed the preference further"
+ );
+});
+
+add_task(async function test_show_banner_max_times() {
+ // We've already shown the UI once, so show it a few more times.
+ for (let i = 1; i < MAX_SHOW_COUNT; i++) {
+ // To avoid having to restart Firefox and slow down tests, we manually reset
+ // the session pref.
+ AboutPrivateBrowsingParent.setShownThisSession(false);
+
+ let prefChanged = TestUtils.waitForPrefChange(PREF_BANNER_SHOWN);
+ const { win, tab } = await openAboutPrivateBrowsing();
+
+ Assert.equal(
+ await prefChanged,
+ i + 1,
+ "Should have incremented the amount of times shown."
+ );
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ content.document.documentElement.hasAttribute(
+ "SearchBannerInitialized"
+ ),
+ "Should have initialized"
+ );
+
+ ok(
+ !content.document
+ .getElementById("search-banner")
+ .hasAttribute("hidden"),
+ "Should be showing the banner again"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ }
+
+ // Final time!
+
+ AboutPrivateBrowsingParent.setShownThisSession(false);
+
+ const { win, tab } = await openAboutPrivateBrowsing();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ content.document.documentElement.hasAttribute(
+ "SearchBannerInitialized"
+ ),
+ "Should have initialized"
+ );
+
+ ok(
+ content.document.getElementById("search-banner").hasAttribute("hidden"),
+ "should not be showing the banner again"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_show_banner_close_no_more() {
+ SpecialPowers.pushPrefEnv({
+ set: [[PREF_BANNER_SHOWN, 0]],
+ });
+
+ AboutPrivateBrowsingParent.setShownThisSession(false);
+
+ const { win, tab } = await openAboutPrivateBrowsing();
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ content.document.documentElement.hasAttribute(
+ "SearchBannerInitialized"
+ ),
+ "Should have initialized"
+ );
+
+ ok(
+ !content.document.getElementById("search-banner").hasAttribute("hidden"),
+ "should be showing the banner again before closing"
+ );
+
+ content.document.getElementById("search-banner-close-button").click();
+
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ ContentTaskUtils.isHidden(
+ content.document.getElementById("search-banner")
+ ),
+ "should have closed the in-content search banner after clicking close"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+
+ Assert.equal(
+ Services.prefs.getIntPref(PREF_BANNER_SHOWN, -1),
+ MAX_SHOW_COUNT,
+ "Should have set the shown preference to the maximum"
+ );
+});
+
+add_task(async function test_show_banner_open_preferences_and_no_more() {
+ SpecialPowers.pushPrefEnv({
+ set: [[PREF_BANNER_SHOWN, 0]],
+ });
+
+ AboutPrivateBrowsingParent.setShownThisSession(false);
+
+ const { win, tab } = await openAboutPrivateBrowsing();
+
+ // This is "borrowed" from the preferences test code, as waiting for the
+ // full preferences to load helps avoid leaking a window.
+ const finalPaneEvent = Services.prefs.getBoolPref(
+ "identity.fxaccounts.enabled"
+ )
+ ? "sync-pane-loaded"
+ : "privacy-pane-loaded";
+ let finalPrefPaneLoaded = TestUtils.topicObserved(finalPaneEvent, () => true);
+ const waitForInitialized = new Promise(resolve => {
+ tab.addEventListener(
+ "Initialized",
+ () => {
+ tab.contentWindow.addEventListener(
+ "load",
+ async function () {
+ await finalPrefPaneLoaded;
+ resolve();
+ },
+ { once: true }
+ );
+ },
+ { capture: true, once: true }
+ );
+ });
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ content.document.documentElement.hasAttribute(
+ "SearchBannerInitialized"
+ ),
+ "Should have initialized"
+ );
+
+ ok(
+ !content.document.getElementById("search-banner").hasAttribute("hidden"),
+ "should be showing the banner again before opening prefs"
+ );
+
+ content.document.getElementById("open-search-options-link").click();
+ });
+
+ info("Waiting for preference window load");
+ await waitForInitialized;
+
+ await BrowserTestUtils.closeWindow(win);
+
+ Assert.equal(
+ Services.prefs.getIntPref(PREF_BANNER_SHOWN, -1),
+ MAX_SHOW_COUNT,
+ "Should have set the shown preference to the maximum"
+ );
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_beacon.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_beacon.js
new file mode 100644
index 0000000000..034061a91a
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_beacon.js
@@ -0,0 +1,46 @@
+/* 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/. */
+
+const TEST_DOMAIN = "example.com";
+const TEST_TOP = `https://${TEST_DOMAIN}`;
+const TEST_URL = `${TEST_TOP}/browser/browser/components/privatebrowsing/test/browser/title.sjs`;
+
+add_task(async function () {
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ privateWin.gBrowser,
+ TEST_TOP
+ );
+
+ // Create a promise to wait the http response of the beacon request.
+ let promise = BrowserUtils.promiseObserved(
+ "http-on-examine-response",
+ subject => {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ let url = channel.URI.spec;
+
+ return url == TEST_URL;
+ }
+ );
+
+ // Open a tab and send a beacon.
+ await SpecialPowers.spawn(tab.linkedBrowser, [TEST_URL], async url => {
+ content.navigator.sendBeacon(url);
+ });
+
+ // Close the entire private window directly.
+ await BrowserTestUtils.closeWindow(privateWin);
+
+ // Wait the response.
+ await promise;
+
+ const cookies = Services.cookies.getCookiesFromHost(TEST_DOMAIN, {
+ privateBrowsingId: 1,
+ });
+
+ is(cookies.length, 0, "No cookies after close the private window.");
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_blobUrl.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_blobUrl.js
new file mode 100644
index 0000000000..f5fd40d4ed
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_blobUrl.js
@@ -0,0 +1,69 @@
+"use strict";
+
+// Here we want to test that blob URLs are not available between private and
+// non-private browsing.
+
+const BASE_URI =
+ "http://mochi.test:8888/browser/browser/components/" +
+ "privatebrowsing/test/browser/empty_file.html";
+
+add_task(async function test() {
+ const loaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ BASE_URI
+ );
+ BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, BASE_URI);
+ await loaded;
+
+ let blobURL;
+ info("Creating a blob URL...");
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
+ return Promise.resolve(
+ content.window.URL.createObjectURL(
+ new Blob([123], { type: "text/plain" })
+ )
+ );
+ }).then(newURL => {
+ blobURL = newURL;
+ });
+
+ info("Blob URL: " + blobURL);
+
+ info("Creating a private window...");
+
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let privateTab = privateWin.gBrowser.selectedBrowser;
+
+ const privateTabLoaded = BrowserTestUtils.browserLoaded(
+ privateTab,
+ false,
+ BASE_URI
+ );
+ BrowserTestUtils.startLoadingURIString(privateTab, BASE_URI);
+ await privateTabLoaded;
+
+ await SpecialPowers.spawn(privateTab, [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"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(privateWin);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
new file mode 100644
index 0000000000..de6aa1f6ba
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
@@ -0,0 +1,94 @@
+/* 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/. */
+
+// Check about:cache after private browsing
+// This test covers MozTrap test 6047
+// bug 880621
+
+var tmp = {};
+
+function test() {
+ waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [["privacy.partition.network_state", false]],
+ },
+ function () {
+ Sanitizer.sanitize(["cache"], { ignoreTimespan: false });
+
+ getStorageEntryCount("regular", function (nrEntriesR1) {
+ is(nrEntriesR1, 0, "Disk cache reports 0KB and has no entries");
+
+ get_cache_for_private_window();
+ });
+ }
+ );
+}
+
+function getStorageEntryCount(device, goon) {
+ var storage;
+ switch (device) {
+ case "private":
+ storage = Services.cache2.diskCacheStorage(
+ Services.loadContextInfo.private
+ );
+ break;
+ case "regular":
+ storage = Services.cache2.diskCacheStorage(
+ Services.loadContextInfo.default
+ );
+ break;
+ default:
+ throw new Error(`Unknown device ${device} at getStorageEntryCount`);
+ }
+
+ var visitor = {
+ entryCount: 0,
+ onCacheStorageInfo(aEntryCount, aConsumption) {},
+ onCacheEntryInfo(uri) {
+ var urispec = uri.asciiSpec;
+ info(device + ":" + urispec + "\n");
+ if (urispec.match(/^https:\/\/example.com\//)) {
+ ++this.entryCount;
+ }
+ },
+ onCacheEntryVisitCompleted() {
+ goon(this.entryCount);
+ },
+ };
+
+ storage.asyncVisitStorage(visitor, true);
+}
+
+function get_cache_for_private_window() {
+ let win = whenNewWindowLoaded({ private: true }, function () {
+ executeSoon(function () {
+ ok(true, "The private window got loaded");
+
+ let tab = BrowserTestUtils.addTab(win.gBrowser, "https://example.com");
+ win.gBrowser.selectedTab = tab;
+ let newTabBrowser = win.gBrowser.getBrowserForTab(tab);
+
+ BrowserTestUtils.browserLoaded(newTabBrowser).then(function () {
+ executeSoon(function () {
+ getStorageEntryCount("private", function (nrEntriesP) {
+ Assert.greaterOrEqual(
+ nrEntriesP,
+ 1,
+ "Memory cache reports some entries from example.org domain"
+ );
+
+ getStorageEntryCount("regular", function (nrEntriesR2) {
+ is(nrEntriesR2, 0, "Disk cache reports 0KB and has no entries");
+
+ win.close();
+ finish();
+ });
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_certexceptionsui.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_certexceptionsui.js
new file mode 100644
index 0000000000..9b796613a9
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_certexceptionsui.js
@@ -0,0 +1,65 @@
+/* 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/. */
+
+// This test makes sure that certificate exceptions UI behaves correctly
+// in private browsing windows, based on whether it's opened from the prefs
+// window or from the SSL error page (see bug 461627).
+
+function test() {
+ const EXCEPTIONS_DLG_URL = "chrome://pippki/content/exceptionDialog.xhtml";
+ const EXCEPTIONS_DLG_FEATURES = "chrome,centerscreen";
+ const INVALID_CERT_LOCATION = "https://nocert.example.com/";
+ waitForExplicitFinish();
+
+ // open a private browsing window
+ var pbWin = OpenBrowserWindow({ private: true });
+ pbWin.addEventListener(
+ "load",
+ function () {
+ doTest();
+ },
+ { once: true }
+ );
+
+ // Test the certificate exceptions dialog
+ function doTest() {
+ let params = {
+ exceptionAdded: false,
+ location: INVALID_CERT_LOCATION,
+ prefetchCert: true,
+ };
+ function testCheckbox() {
+ win.removeEventListener("load", testCheckbox);
+ Services.obs.addObserver(function onCertUI(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(onCertUI, "cert-exception-ui-ready");
+ ok(win.gCert, "The certificate information should be available now");
+
+ let checkbox = win.document.getElementById("permanent");
+ ok(
+ checkbox.hasAttribute("disabled"),
+ "the permanent checkbox should be disabled when handling the private browsing mode"
+ );
+ ok(
+ !checkbox.hasAttribute("checked"),
+ "the permanent checkbox should not be checked when handling the private browsing mode"
+ );
+ win.close();
+ cleanup();
+ }, "cert-exception-ui-ready");
+ }
+ var win = pbWin.openDialog(
+ EXCEPTIONS_DLG_URL,
+ "",
+ EXCEPTIONS_DLG_FEATURES,
+ params
+ );
+ win.addEventListener("load", testCheckbox);
+ }
+
+ function cleanup() {
+ // close the private browsing window
+ pbWin.close();
+ finish();
+ }
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cleanup.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cleanup.js
new file mode 100644
index 0000000000..39e41589b4
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cleanup.js
@@ -0,0 +1,46 @@
+/* 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/. */
+
+"use strict";
+
+const DOMAIN = "http://example.com/";
+const PATH = "browser/browser/components/privatebrowsing/test/browser/";
+const TOP_PAGE = DOMAIN + PATH + "empty_file.html";
+
+add_task(async () => {
+ // Create a private browsing window.
+ let privateWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ let privateTab = privateWindow.gBrowser.selectedBrowser;
+ BrowserTestUtils.startLoadingURIString(privateTab, TOP_PAGE);
+ await BrowserTestUtils.browserLoaded(privateTab);
+
+ let observerExited = {
+ observe(aSubject, aTopic, aData) {
+ ok(false, "Notification received!");
+ },
+ };
+ Services.obs.addObserver(observerExited, "last-pb-context-exited");
+
+ let popup = BrowserTestUtils.waitForNewWindow();
+
+ await SpecialPowers.spawn(privateTab, [], () => {
+ content.window.open("empty_file.html", "_blank", "width=300,height=300");
+ });
+
+ popup = await popup;
+ ok(!!popup, "Popup shown");
+
+ await BrowserTestUtils.closeWindow(privateWindow);
+ Services.obs.removeObserver(observerExited, "last-pb-context-exited");
+
+ let notificationPromise = TestUtils.topicObserved("last-pb-context-exited");
+
+ popup.close();
+
+ await notificationPromise;
+ ok(true, "Notification received!");
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js
new file mode 100644
index 0000000000..0029cdc852
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js
@@ -0,0 +1,101 @@
+/* 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/. */
+
+// Test opening two tabs that share a localStorage, but keep one in private mode.
+// Ensure that values from one don't leak into the other, and that values from
+// earlier private storage sessions aren't visible later.
+
+// Step 1: create new tab, load a page that sets test=value in non-private storage
+// Step 2: create a new tab, load a page that sets test2=value2 in private storage
+// Step 3: load a page in the tab from step 1 that checks the value of test2 is value2 and the total count in non-private storage is 1
+// Step 4: load a page in the tab from step 2 that checks the value of test is value and the total count in private storage is 1
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.ipc.processCount", 1]],
+ });
+});
+
+add_task(async function test() {
+ let prefix =
+ "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html";
+
+ function getElts(browser) {
+ return browser.contentTitle.split("|");
+ }
+
+ // Step 1
+ let non_private_browser = gBrowser.selectedBrowser;
+ let url = prefix + "?action=set&name=test&value=value&initial=true";
+ BrowserTestUtils.startLoadingURIString(non_private_browser, url);
+ await BrowserTestUtils.browserLoaded(non_private_browser, false, url);
+
+ // Step 2
+ let private_window = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let private_browser = private_window.gBrowser.selectedBrowser;
+ url = prefix + "?action=set&name=test2&value=value2";
+ BrowserTestUtils.startLoadingURIString(private_browser, url);
+ await BrowserTestUtils.browserLoaded(private_browser, false, url);
+
+ // Step 3
+ url = prefix + "?action=get&name=test2";
+ BrowserTestUtils.startLoadingURIString(non_private_browser, url);
+ await BrowserTestUtils.browserLoaded(non_private_browser, false, url);
+ let elts = await getElts(non_private_browser);
+ isnot(elts[0], "value2", "public window shouldn't see private storage");
+ is(elts[1], "1", "public window should only see public items");
+
+ // Step 4
+ url = prefix + "?action=get&name=test";
+ BrowserTestUtils.startLoadingURIString(private_browser, url);
+ await BrowserTestUtils.browserLoaded(private_browser, false, url);
+ elts = await getElts(private_browser);
+ isnot(elts[0], "value", "private window shouldn't see public storage");
+ is(elts[1], "1", "private window should only see private items");
+
+ // Reopen the private window again, without privateBrowsing, which should clear the
+ // the private storage.
+ private_window.close();
+ private_window = await BrowserTestUtils.openNewBrowserWindow({
+ private: false,
+ });
+ private_browser = null;
+ await new Promise(resolve => Cu.schedulePreciseGC(resolve));
+ private_browser = private_window.gBrowser.selectedBrowser;
+
+ url = prefix + "?action=get&name=test2";
+ BrowserTestUtils.startLoadingURIString(private_browser, url);
+ await BrowserTestUtils.browserLoaded(private_browser, false, url);
+ elts = await getElts(private_browser);
+ isnot(
+ elts[0],
+ "value2",
+ "public window shouldn't see cleared private storage"
+ );
+ is(elts[1], "1", "public window should only see public items");
+
+ // Making it private again should clear the storage and it shouldn't
+ // be able to see the old private storage as well.
+ private_window.close();
+ private_window = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ private_browser = null;
+ await new Promise(resolve => Cu.schedulePreciseGC(resolve));
+ private_browser = private_window.gBrowser.selectedBrowser;
+
+ url = prefix + "?action=set&name=test3&value=value3";
+ BrowserTestUtils.startLoadingURIString(private_browser, url);
+ await BrowserTestUtils.browserLoaded(private_browser, false, url);
+ elts = await getElts(private_browser);
+ is(elts[1], "1", "private window should only see new private items");
+
+ // Cleanup.
+ url = prefix + "?final=true";
+ BrowserTestUtils.startLoadingURIString(non_private_browser, url);
+ await BrowserTestUtils.browserLoaded(non_private_browser, false, url);
+ private_window.close();
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html
new file mode 100644
index 0000000000..96d3b74c7c
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html
@@ -0,0 +1,33 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+ var oGetVars = {};
+
+ if (window.location.search.length > 1) {
+ for (var aItKey, nKeyId = 0, aCouples = window.location.search.substr(1).split("&");
+ nKeyId < aCouples.length;
+ nKeyId++) {
+ aItKey = aCouples[nKeyId].split("=");
+ oGetVars[unescape(aItKey[0])] = aItKey.length > 1 ? unescape(aItKey[1]) : "";
+ }
+ }
+
+ if (oGetVars.initial == "true") {
+ localStorage.clear();
+ }
+
+ if (oGetVars.action == "set") {
+ localStorage.setItem(oGetVars.name, oGetVars.value);
+ document.title = localStorage.getItem(oGetVars.name) + "|" + localStorage.length;
+ } else if (oGetVars.action == "get") {
+ document.title = localStorage.getItem(oGetVars.name) + "|" + localStorage.length;
+ }
+
+ if (oGetVars.final == "true") {
+ localStorage.clear();
+ }
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_context_and_chromeFlags.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_context_and_chromeFlags.js
new file mode 100644
index 0000000000..66e8dea359
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_context_and_chromeFlags.js
@@ -0,0 +1,69 @@
+"use strict";
+
+/**
+ * Given some window in the parent process, ensure that
+ * the nsIAppWindow has the CHROME_PRIVATE_WINDOW chromeFlag,
+ * and that the usePrivateBrowsing property is set to true on
+ * both the window's nsILoadContext, as well as on the initial
+ * browser's content docShell nsILoadContext.
+ *
+ * @param win (nsIDOMWindow)
+ * An nsIDOMWindow in the parent process.
+ * @return Promise
+ */
+function assertWindowIsPrivate(win) {
+ let winDocShell = win.docShell;
+ let chromeFlags = winDocShell.treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIAppWindow).chromeFlags;
+
+ if (!win.gBrowser.selectedBrowser.hasContentOpener) {
+ Assert.ok(
+ chromeFlags & Ci.nsIWebBrowserChrome.CHROME_PRIVATE_WINDOW,
+ "Should have the private window chrome flag"
+ );
+ }
+
+ let loadContext = winDocShell.QueryInterface(Ci.nsILoadContext);
+ Assert.ok(
+ loadContext.usePrivateBrowsing,
+ "The parent window should be using private browsing"
+ );
+
+ return SpecialPowers.spawn(
+ win.gBrowser.selectedBrowser,
+ [],
+ async function () {
+ let contentLoadContext = docShell.QueryInterface(Ci.nsILoadContext);
+ Assert.ok(
+ contentLoadContext.usePrivateBrowsing,
+ "Content docShell should be using private browsing"
+ );
+ }
+ );
+}
+
+/**
+ * Tests that chromeFlags bits and the nsILoadContext.usePrivateBrowsing
+ * attribute are properly set when opening a new private browsing
+ * window.
+ */
+add_task(async function test_context_and_chromeFlags() {
+ let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ await assertWindowIsPrivate(win);
+
+ let browser = win.gBrowser.selectedBrowser;
+
+ let newWinPromise = BrowserTestUtils.waitForNewWindow({
+ url: "https://example.com/",
+ });
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.open("https://example.com", "_blank", "width=100,height=100");
+ });
+
+ let win2 = await newWinPromise;
+ await assertWindowIsPrivate(win2);
+
+ await BrowserTestUtils.closeWindow(win2);
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_crh.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_crh.js
new file mode 100644
index 0000000000..ddfb53f1a8
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_crh.js
@@ -0,0 +1,48 @@
+/* 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/. */
+
+// This test makes sure that the Clear Recent History menu item and command
+// is disabled inside the private browsing mode.
+
+add_task(async function test() {
+ function checkDisableOption(aPrivateMode, aWindow) {
+ let crhCommand = aWindow.document.getElementById("Tools:Sanitize");
+ ok(crhCommand, "The clear recent history command should exist");
+
+ is(
+ PrivateBrowsingUtils.isWindowPrivate(aWindow),
+ aPrivateMode,
+ "PrivateBrowsingUtils should report the correct per-window private browsing status"
+ );
+ is(
+ crhCommand.hasAttribute("disabled"),
+ aPrivateMode,
+ "Clear Recent History command should be disabled according to the private browsing mode"
+ );
+ }
+
+ let testURI = "http://mochi.test:8888/";
+
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let privateBrowser = privateWin.gBrowser.selectedBrowser;
+ BrowserTestUtils.startLoadingURIString(privateBrowser, testURI);
+ await BrowserTestUtils.browserLoaded(privateBrowser);
+
+ info("Test on private window");
+ checkDisableOption(true, privateWin);
+
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ let browser = win.gBrowser.selectedBrowser;
+ BrowserTestUtils.startLoadingURIString(browser, testURI);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Test on public window");
+ checkDisableOption(false, win);
+
+ // Cleanup
+ await BrowserTestUtils.closeWindow(privateWin);
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js
new file mode 100644
index 0000000000..dd358bee73
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js
@@ -0,0 +1,133 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+ );
+ let { DownloadLastDir } = ChromeUtils.importESModule(
+ "resource://gre/modules/DownloadLastDir.sys.mjs"
+ );
+ let MockFilePicker = SpecialPowers.MockFilePicker;
+ let launcher = {
+ source: Services.io.newURI("http://test1.com/file"),
+ };
+
+ MockFilePicker.init(window);
+ MockFilePicker.returnValue = Ci.nsIFilePicker.returnOK;
+
+ let prefs = Services.prefs.getBranch("browser.download.");
+ let launcherDialog = Cc["@mozilla.org/helperapplauncherdialog;1"].getService(
+ Ci.nsIHelperAppLauncherDialog
+ );
+ let tmpDir = FileUtils.getDir("TmpD", []);
+ let dir1 = newDirectory();
+ let dir2 = newDirectory();
+ let dir3 = newDirectory();
+ let file1 = newFileInDirectory(dir1);
+ let file2 = newFileInDirectory(dir2);
+ let file3 = newFileInDirectory(dir3);
+
+ // cleanup functions registration
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.download.lastDir");
+ [dir1, dir2, dir3].forEach(dir => dir.remove(true));
+ MockFilePicker.cleanup();
+ });
+ prefs.setComplexValue("lastDir", Ci.nsIFile, tmpDir);
+
+ function testOnWindow(aPrivate, aCallback) {
+ whenNewWindowLoaded({ private: aPrivate }, function (win) {
+ let gDownloadLastDir = new DownloadLastDir(win);
+ aCallback(win, gDownloadLastDir);
+ gDownloadLastDir.cleanupPrivateFile();
+ });
+ }
+
+ function testDownloadDir(
+ aWin,
+ gDownloadLastDir,
+ aFile,
+ aDisplayDir,
+ aLastDir,
+ aGlobalLastDir,
+ aCallback
+ ) {
+ // Check lastDir preference.
+ is(
+ prefs.getComplexValue("lastDir", Ci.nsIFile).path,
+ aDisplayDir.path,
+ "LastDir should be the expected display dir"
+ );
+ // Check gDownloadLastDir value.
+ is(
+ gDownloadLastDir.file.path,
+ aDisplayDir.path,
+ "gDownloadLastDir should be the expected display dir"
+ );
+
+ MockFilePicker.setFiles([aFile]);
+ MockFilePicker.displayDirectory = null;
+
+ launcher.saveDestinationAvailable = function (file) {
+ ok(!!file, "promptForSaveToFile correctly returned a file");
+
+ // File picker should start with expected display dir.
+ is(
+ MockFilePicker.displayDirectory.path,
+ aDisplayDir.path,
+ "File picker should start with browser.download.lastDir"
+ );
+ // browser.download.lastDir should be modified on not private windows
+ is(
+ prefs.getComplexValue("lastDir", Ci.nsIFile).path,
+ aLastDir.path,
+ "LastDir should be the expected last dir"
+ );
+ // gDownloadLastDir should be usable outside of private windows
+ is(
+ gDownloadLastDir.file.path,
+ aGlobalLastDir.path,
+ "gDownloadLastDir should be the expected global last dir"
+ );
+
+ launcher.saveDestinationAvailable = null;
+ aWin.close();
+ aCallback();
+ };
+
+ launcherDialog.promptForSaveToFileAsync(launcher, aWin, "", "", false);
+ }
+
+ testOnWindow(false, function (win, downloadDir) {
+ testDownloadDir(win, downloadDir, file1, tmpDir, dir1, dir1, function () {
+ testOnWindow(true, function (win1, downloadDir1) {
+ testDownloadDir(
+ win1,
+ downloadDir1,
+ file2,
+ dir1,
+ dir1,
+ dir2,
+ function () {
+ testOnWindow(false, function (win2, downloadDir2) {
+ testDownloadDir(
+ win2,
+ downloadDir2,
+ file3,
+ dir1,
+ dir3,
+ dir3,
+ finish
+ );
+ });
+ }
+ );
+ });
+ });
+ });
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
new file mode 100644
index 0000000000..04e510096a
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
@@ -0,0 +1,146 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+ );
+ let { DownloadLastDir } = ChromeUtils.importESModule(
+ "resource://gre/modules/DownloadLastDir.sys.mjs"
+ );
+ let MockFilePicker = SpecialPowers.MockFilePicker;
+
+ MockFilePicker.init(window);
+ MockFilePicker.returnValue = Ci.nsIFilePicker.returnOK;
+
+ let validateFileNameToRestore = validateFileName;
+ let prefs = Services.prefs.getBranch("browser.download.");
+ let tmpDir = FileUtils.getDir("TmpD", []);
+ let dir1 = newDirectory();
+ let dir2 = newDirectory();
+ let dir3 = newDirectory();
+ let file1 = newFileInDirectory(dir1);
+ let file2 = newFileInDirectory(dir2);
+ let file3 = newFileInDirectory(dir3);
+
+ // cleanup function registration
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.download.lastDir");
+ [dir1, dir2, dir3].forEach(dir => dir.remove(true));
+ MockFilePicker.cleanup();
+ validateFileName = validateFileNameToRestore;
+ });
+
+ // Overwrite validateFileName to validate everything
+ validateFileName = foo => foo;
+
+ let params = {
+ fileInfo: new FileInfo(
+ "test.txt",
+ "test.txt",
+ "test",
+ "txt",
+ "http://mozilla.org/test.txt"
+ ),
+ contentType: "text/plain",
+ saveMode: SAVEMODE_FILEONLY,
+ saveAsType: kSaveAsType_Complete,
+ file: null,
+ };
+
+ prefs.setComplexValue("lastDir", Ci.nsIFile, tmpDir);
+
+ function testOnWindow(aPrivate, aCallback) {
+ whenNewWindowLoaded({ private: aPrivate }, function (win) {
+ let gDownloadLastDir = new DownloadLastDir(win);
+ aCallback(win, gDownloadLastDir);
+ });
+ }
+
+ function testDownloadDir(
+ aWin,
+ gDownloadLastDir,
+ aFile,
+ aDisplayDir,
+ aLastDir,
+ aGlobalLastDir,
+ aCallback
+ ) {
+ // Check lastDir preference.
+ is(
+ prefs.getComplexValue("lastDir", Ci.nsIFile).path,
+ aDisplayDir.path,
+ "LastDir should be the expected display dir"
+ );
+ // Check gDownloadLastDir value.
+ is(
+ gDownloadLastDir.file.path,
+ aDisplayDir.path,
+ "gDownloadLastDir should be the expected display dir"
+ );
+
+ MockFilePicker.setFiles([aFile]);
+ MockFilePicker.displayDirectory = null;
+ aWin
+ .promiseTargetFile(params)
+ .then(function () {
+ // File picker should start with expected display dir.
+ is(
+ MockFilePicker.displayDirectory.path,
+ aDisplayDir.path,
+ "File picker should start with browser.download.lastDir"
+ );
+ // browser.download.lastDir should be modified on not private windows
+ is(
+ prefs.getComplexValue("lastDir", Ci.nsIFile).path,
+ aLastDir.path,
+ "LastDir should be the expected last dir"
+ );
+ // gDownloadLastDir should be usable outside of private windows
+ is(
+ gDownloadLastDir.file.path,
+ aGlobalLastDir.path,
+ "gDownloadLastDir should be the expected global last dir"
+ );
+
+ gDownloadLastDir.cleanupPrivateFile();
+ aWin.close();
+ aCallback();
+ })
+ .catch(function () {
+ ok(false);
+ });
+ }
+
+ testOnWindow(false, function (win, downloadDir) {
+ testDownloadDir(win, downloadDir, file1, tmpDir, dir1, dir1, function () {
+ testOnWindow(true, function (win1, downloadDir1) {
+ testDownloadDir(
+ win1,
+ downloadDir1,
+ file2,
+ dir1,
+ dir1,
+ dir2,
+ function () {
+ testOnWindow(false, function (win2, downloadDir2) {
+ testDownloadDir(
+ win2,
+ downloadDir2,
+ file3,
+ dir1,
+ dir3,
+ dir3,
+ finish
+ );
+ });
+ }
+ );
+ });
+ });
+ });
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_toggle.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_toggle.js
new file mode 100644
index 0000000000..4b88bbddf9
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_toggle.js
@@ -0,0 +1,118 @@
+/**
+ * Tests how the browser remembers the last download folder
+ * from download to download, with a particular emphasis
+ * on how it behaves when private browsing windows open.
+ */
+add_task(async function test_downloads_last_dir_toggle() {
+ let tmpDir = FileUtils.getDir("TmpD", []);
+ let dir1 = newDirectory();
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.download.lastDir");
+ dir1.remove(true);
+ });
+
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ let gDownloadLastDir = new DownloadLastDir(win);
+ is(
+ typeof gDownloadLastDir,
+ "object",
+ "gDownloadLastDir should be a valid object"
+ );
+ is(
+ gDownloadLastDir.file,
+ null,
+ "gDownloadLastDir.file should be null to start with"
+ );
+
+ gDownloadLastDir.file = tmpDir;
+ is(
+ gDownloadLastDir.file.path,
+ tmpDir.path,
+ "LastDir should point to the temporary directory"
+ );
+ isnot(
+ gDownloadLastDir.file,
+ tmpDir,
+ "gDownloadLastDir.file should not be pointing to the tmpDir"
+ );
+
+ gDownloadLastDir.file = 1; // not an nsIFile
+ is(gDownloadLastDir.file, null, "gDownloadLastDir.file should be null");
+
+ gDownloadLastDir.file = tmpDir;
+ clearHistory();
+ is(gDownloadLastDir.file, null, "gDownloadLastDir.file should be null");
+
+ gDownloadLastDir.file = tmpDir;
+ await BrowserTestUtils.closeWindow(win);
+
+ info("Opening the first private window");
+ await testHelper({ private: true, expectedDir: tmpDir });
+ info("Opening a non-private window");
+ await testHelper({ private: false, expectedDir: tmpDir });
+ info("Opening a private window and setting download directory");
+ await testHelper({ private: true, setDir: dir1, expectedDir: dir1 });
+ info("Opening a non-private window and checking download directory");
+ await testHelper({ private: false, expectedDir: tmpDir });
+ info("Opening private window and clearing history");
+ await testHelper({ private: true, clearHistory: true, expectedDir: null });
+ info("Opening a non-private window and checking download directory");
+ await testHelper({ private: true, expectedDir: null });
+});
+
+/**
+ * Opens a new window and performs some test actions on it based
+ * on the options object that have been passed in.
+ *
+ * @param options (Object)
+ * An object with the following properties:
+ *
+ * clearHistory (bool, optional):
+ * Whether or not to simulate clearing session history.
+ * Defaults to false.
+ *
+ * setDir (nsIFile, optional):
+ * An nsIFile for setting the last download directory.
+ * If not set, the load download directory is not changed.
+ *
+ * expectedDir (nsIFile, expectedDir):
+ * An nsIFile for what we expect the last download directory
+ * should be. The nsIFile is not compared directly - only
+ * paths are compared. If expectedDir is not set, then the
+ * last download directory is expected to be null.
+ *
+ * @returns Promise
+ */
+async function testHelper(options) {
+ let win = await BrowserTestUtils.openNewBrowserWindow(options);
+ let gDownloadLastDir = new DownloadLastDir(win);
+
+ if (options.clearHistory) {
+ clearHistory();
+ }
+
+ if (options.setDir) {
+ gDownloadLastDir.file = options.setDir;
+ }
+
+ let expectedDir = options.expectedDir;
+
+ if (expectedDir) {
+ is(
+ gDownloadLastDir.file.path,
+ expectedDir.path,
+ "gDownloadLastDir should point to the expected last directory"
+ );
+ isnot(
+ gDownloadLastDir.file,
+ expectedDir,
+ "gDownloadLastDir.file should not be pointing to the last directory"
+ );
+ } else {
+ is(gDownloadLastDir.file, null, "gDownloadLastDir should be null");
+ }
+
+ gDownloadLastDir.cleanupPrivateFile();
+ await BrowserTestUtils.closeWindow(win);
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
new file mode 100644
index 0000000000..eea0ab07ca
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
@@ -0,0 +1,322 @@
+/* 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/. */
+
+// This test make sure that the favicon of the private browsing is isolated.
+
+const TEST_SITE = "https://example.com";
+const TEST_CACHE_SITE = "https://test1.example.com";
+const TEST_DIRECTORY =
+ "/browser/browser/components/privatebrowsing/test/browser/";
+
+const TEST_PAGE = TEST_SITE + TEST_DIRECTORY + "file_favicon.html";
+const TEST_CACHE_PAGE = TEST_CACHE_SITE + TEST_DIRECTORY + "file_favicon.html";
+const FAVICON_URI = TEST_SITE + TEST_DIRECTORY + "file_favicon.png";
+const FAVICON_CACHE_URI = TEST_CACHE_SITE + TEST_DIRECTORY + "file_favicon.png";
+
+let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+function clearAllImageCaches() {
+ let tools = SpecialPowers.Cc["@mozilla.org/image/tools;1"].getService(
+ SpecialPowers.Ci.imgITools
+ );
+ let imageCache = tools.getImgCacheForDocument(window.document);
+ imageCache.clearCache(true); // true=chrome
+ imageCache.clearCache(false); // false=content
+}
+
+function clearAllPlacesFavicons() {
+ let faviconService = Cc["@mozilla.org/browser/favicon-service;1"].getService(
+ Ci.nsIFaviconService
+ );
+
+ return new Promise(resolve => {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic === "places-favicons-expired") {
+ resolve();
+ Services.obs.removeObserver(observer, "places-favicons-expired");
+ }
+ },
+ };
+
+ Services.obs.addObserver(observer, "places-favicons-expired");
+ faviconService.expireAllFavicons();
+ });
+}
+
+function observeFavicon(aIsPrivate, aExpectedCookie, aPageURI) {
+ let attr = {};
+
+ if (aIsPrivate) {
+ attr.privateBrowsingId = 1;
+ }
+
+ let expectedPrincipal = Services.scriptSecurityManager.createContentPrincipal(
+ aPageURI,
+ attr
+ );
+
+ return new Promise(resolve => {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ // Make sure that the topic is 'http-on-modify-request'.
+ if (aTopic === "http-on-modify-request") {
+ // We check the privateBrowsingId for the originAttributes of the loading
+ // channel. All requests for the favicon should contain the correct
+ // privateBrowsingId. There are two requests for a favicon loading, one
+ // from the Places library and one from the XUL image. The difference
+ // of them is the loading principal. The Places will use the content
+ // principal and the XUL image will use the system principal.
+
+ let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ let reqLoadInfo = httpChannel.loadInfo;
+ let loadingPrincipal = reqLoadInfo.loadingPrincipal;
+
+ // Make sure this is a favicon request.
+ if (httpChannel.URI.spec !== FAVICON_URI) {
+ return;
+ }
+
+ // Check the privateBrowsingId.
+ if (aIsPrivate) {
+ is(
+ reqLoadInfo.originAttributes.privateBrowsingId,
+ 1,
+ "The loadInfo has correct privateBrowsingId"
+ );
+ } else {
+ is(
+ reqLoadInfo.originAttributes.privateBrowsingId,
+ 0,
+ "The loadInfo has correct privateBrowsingId"
+ );
+ }
+
+ ok(
+ loadingPrincipal.equals(expectedPrincipal),
+ "The loadingPrincipal of favicon loading from Places should be the content prinicpal"
+ );
+
+ let faviconCookie = httpChannel.getRequestHeader("cookie");
+
+ is(
+ faviconCookie,
+ aExpectedCookie,
+ "The cookie of the favicon loading is correct."
+ );
+ } else {
+ ok(false, "Received unexpected topic: ", aTopic);
+ }
+
+ resolve();
+ Services.obs.removeObserver(observer, "http-on-modify-request");
+ },
+ };
+
+ Services.obs.addObserver(observer, "http-on-modify-request");
+ });
+}
+
+function waitOnFaviconResponse(aFaviconURL) {
+ return new Promise(resolve => {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ if (
+ aTopic === "http-on-examine-response" ||
+ aTopic === "http-on-examine-cached-response"
+ ) {
+ let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ let loadInfo = httpChannel.loadInfo;
+
+ if (httpChannel.URI.spec !== aFaviconURL) {
+ return;
+ }
+
+ let result = {
+ topic: aTopic,
+ privateBrowsingId: loadInfo.originAttributes.privateBrowsingId,
+ };
+
+ resolve(result);
+ Services.obs.removeObserver(observer, "http-on-examine-response");
+ Services.obs.removeObserver(
+ observer,
+ "http-on-examine-cached-response"
+ );
+ }
+ },
+ };
+
+ Services.obs.addObserver(observer, "http-on-examine-response");
+ Services.obs.addObserver(observer, "http-on-examine-cached-response");
+ });
+}
+
+function waitOnFaviconLoaded(aFaviconURL) {
+ return PlacesTestUtils.waitForNotification("favicon-changed", events =>
+ events.some(e => e.faviconUrl == aFaviconURL)
+ );
+}
+
+async function assignCookies(aBrowser, aURL, aCookieValue) {
+ let tabInfo = await openTab(aBrowser, aURL);
+
+ await SpecialPowers.spawn(
+ tabInfo.browser,
+ [aCookieValue],
+ async function (value) {
+ content.document.cookie = value;
+ }
+ );
+
+ BrowserTestUtils.removeTab(tabInfo.tab);
+}
+
+async function openTab(aBrowser, aURL) {
+ let tab = BrowserTestUtils.addTab(aBrowser, aURL);
+
+ // Select tab and make sure its browser is focused.
+ aBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = aBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+ return { tab, browser };
+}
+
+registerCleanupFunction(async () => {
+ Services.cookies.removeAll();
+ clearAllImageCaches();
+ Services.cache2.clear();
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+});
+
+add_task(async function test_favicon_privateBrowsing() {
+ // Clear all image caches before running the test.
+ clearAllImageCaches();
+ // Clear all favicons in Places.
+ await clearAllPlacesFavicons();
+
+ // Create a private browsing window.
+ let privateWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let pageURI = Services.io.newURI(TEST_PAGE);
+
+ // Generate two random cookies for non-private window and private window
+ // respectively.
+ let cookies = [];
+ cookies.push(Math.random().toString());
+ cookies.push(Math.random().toString());
+
+ // Open a tab in private window and add a cookie into it.
+ await assignCookies(privateWindow.gBrowser, TEST_SITE, cookies[0]);
+
+ // Open a tab in non-private window and add a cookie into it.
+ await assignCookies(gBrowser, TEST_SITE, cookies[1]);
+
+ // Add the observer earlier in case we don't capture events in time.
+ let promiseObserveFavicon = observeFavicon(true, cookies[0], pageURI);
+
+ // The page must be bookmarked for favicon requests to go through in PB mode.
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: TEST_PAGE,
+ });
+
+ // Open a tab for the private window.
+ let tabInfo = await openTab(privateWindow.gBrowser, TEST_PAGE);
+
+ info("Waiting until favicon requests are all made in private window.");
+ await promiseObserveFavicon;
+
+ // Close the tab.
+ BrowserTestUtils.removeTab(tabInfo.tab);
+ // FIXME: We need to wait for the next event tick here to avoid observing
+ // the previous tab info in the next step (bug 1446725).
+ await new Promise(executeSoon);
+
+ // Add the observer earlier in case we don't capture events in time.
+ promiseObserveFavicon = observeFavicon(false, cookies[1], pageURI);
+
+ // Open a tab for the non-private window.
+ tabInfo = await openTab(gBrowser, TEST_PAGE);
+
+ info("Waiting until favicon requests are all made in non-private window.");
+ await promiseObserveFavicon;
+
+ // Close the tab.
+ BrowserTestUtils.removeTab(tabInfo.tab);
+ await BrowserTestUtils.closeWindow(privateWindow);
+});
+
+add_task(async function test_favicon_cache_privateBrowsing() {
+ // Clear all image caches and network cache before running the test.
+ clearAllImageCaches();
+
+ Services.cache2.clear();
+
+ // Clear all favicons in Places.
+ await clearAllPlacesFavicons();
+
+ // Add an observer for making sure the favicon has been loaded and cached.
+ let promiseFaviconLoaded = waitOnFaviconLoaded(FAVICON_CACHE_URI);
+ let promiseFaviconResponse = waitOnFaviconResponse(FAVICON_CACHE_URI);
+
+ // Open a tab for the non-private window.
+ let tabInfoNonPrivate = await openTab(gBrowser, TEST_CACHE_PAGE);
+
+ let response = await promiseFaviconResponse;
+
+ await promiseFaviconLoaded;
+
+ // Check that the favicon response has come from the network and it has the
+ // correct privateBrowsingId.
+ is(
+ response.topic,
+ "http-on-examine-response",
+ "The favicon image should be loaded through network."
+ );
+ is(
+ response.privateBrowsingId,
+ 0,
+ "We should observe the network response for the non-private tab."
+ );
+
+ // Create a private browsing window.
+ let privateWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ // The page must be bookmarked for favicon requests to go through in PB mode.
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: TEST_CACHE_PAGE,
+ });
+
+ promiseFaviconResponse = waitOnFaviconResponse(FAVICON_CACHE_URI);
+
+ // Open a tab for the private window.
+ let tabInfoPrivate = await openTab(privateWindow.gBrowser, TEST_CACHE_PAGE);
+
+ // Wait for the favicon response of the private tab.
+ response = await promiseFaviconResponse;
+
+ // Make sure the favicon is loaded through the network and its privateBrowsingId is correct.
+ is(
+ response.topic,
+ "http-on-examine-response",
+ "The favicon image should be loaded through the network again."
+ );
+ is(
+ response.privateBrowsingId,
+ 1,
+ "We should observe the network response for the private tab."
+ );
+
+ BrowserTestUtils.removeTab(tabInfoPrivate.tab);
+ BrowserTestUtils.removeTab(tabInfoNonPrivate.tab);
+ await BrowserTestUtils.closeWindow(privateWindow);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html
new file mode 100644
index 0000000000..01ed3f3d2c
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <title>Geolocation invoker</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ navigator.geolocation.getCurrentPosition(function(pos) {
+ // ignore
+ });
+ </script>
+ </body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_history_shift_click.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_history_shift_click.js
new file mode 100644
index 0000000000..793bcd1a5d
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_history_shift_click.js
@@ -0,0 +1,69 @@
+/* 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/. */
+
+add_task(async function () {
+ await testShiftClickOpensNewWindow("back-button");
+});
+
+add_task(async function () {
+ await testShiftClickOpensNewWindow("forward-button");
+});
+
+// Create new private browser, open new tab and set history state, then return the window
+async function createPrivateWindow() {
+ const privateWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ await BrowserTestUtils.openNewForegroundTab(
+ privateWindow.gBrowser,
+ "http://example.com"
+ );
+ await SpecialPowers.spawn(
+ privateWindow.gBrowser.selectedBrowser,
+ [],
+ async function () {
+ content.history.pushState({}, "first item", "first-item.html");
+ content.history.pushState({}, "second item", "second-item.html");
+ content.history.pushState({}, "third item", "third-item.html");
+ content.history.back();
+ }
+ );
+ await TestUtils.topicObserved("sessionstore-state-write-complete");
+
+ // Wait for the session data to be flushed before continuing the test
+ await new Promise(resolve =>
+ SessionStore.getSessionHistory(privateWindow.gBrowser.selectedTab, resolve)
+ );
+
+ info("Private window created");
+
+ return privateWindow;
+}
+
+async function testShiftClickOpensNewWindow(buttonId) {
+ const privateWindow = await createPrivateWindow();
+
+ const button = privateWindow.document.getElementById(buttonId);
+ // Wait for the new private window to be created after click
+ const newPrivateWindowPromise = BrowserTestUtils.waitForNewWindow();
+
+ EventUtils.synthesizeMouseAtCenter(button, { shiftKey: true }, privateWindow);
+
+ info("Waiting for new private browser to open");
+
+ const newPrivateWindow = await newPrivateWindowPromise;
+
+ ok(
+ PrivateBrowsingUtils.isBrowserPrivate(newPrivateWindow.gBrowser),
+ "New window is private"
+ );
+
+ // Cleanup
+ await Promise.all([
+ BrowserTestUtils.closeWindow(privateWindow),
+ BrowserTestUtils.closeWindow(newPrivateWindow),
+ ]);
+
+ info("Closed all windows");
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_last_private_browsing_context_exited.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_last_private_browsing_context_exited.js
new file mode 100644
index 0000000000..1fd28d4ca6
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_last_private_browsing_context_exited.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_no_notification_when_pb_autostart() {
+ let observedLastPBContext = false;
+ let observerExited = {
+ observe(aSubject, aTopic, aData) {
+ observedLastPBContext = true;
+ },
+ };
+ Services.obs.addObserver(observerExited, "last-pb-context-exited");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.privatebrowsing.autostart", true]],
+ });
+
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+
+ let browser = win.gBrowser.selectedTab.linkedBrowser;
+ ok(browser.browsingContext.usePrivateBrowsing, "should use private browsing");
+
+ await BrowserTestUtils.closeWindow(win);
+
+ await SpecialPowers.popPrefEnv();
+ Services.obs.removeObserver(observerExited, "last-pb-context-exited");
+ ok(!observedLastPBContext, "No last-pb-context-exited notification seen");
+});
+
+add_task(async function test_notification_when_about_preferences() {
+ let observedLastPBContext = false;
+ let observerExited = {
+ observe(aSubject, aTopic, aData) {
+ observedLastPBContext = true;
+ },
+ };
+ Services.obs.addObserver(observerExited, "last-pb-context-exited");
+
+ let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+
+ let browser = win.gBrowser.selectedTab.linkedBrowser;
+ ok(browser.browsingContext.usePrivateBrowsing, "should use private browsing");
+ ok(browser.browsingContext.isContent, "should be content browsing context");
+
+ let tab = await BrowserTestUtils.addTab(win.gBrowser, "about:preferences");
+ ok(
+ tab.linkedBrowser.browsingContext.usePrivateBrowsing,
+ "should use private browsing"
+ );
+ ok(
+ tab.linkedBrowser.browsingContext.isContent,
+ "should be content browsing context"
+ );
+
+ let tabClose = BrowserTestUtils.waitForTabClosing(win.gBrowser.selectedTab);
+ BrowserTestUtils.removeTab(win.gBrowser.selectedTab);
+ await tabClose;
+
+ ok(!observedLastPBContext, "No last-pb-context-exited notification seen");
+
+ await BrowserTestUtils.closeWindow(win);
+
+ Services.obs.removeObserver(observerExited, "last-pb-context-exited");
+ ok(observedLastPBContext, "No last-pb-context-exited notification seen");
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_lastpbcontextexited.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_lastpbcontextexited.js
new file mode 100644
index 0000000000..c46417933a
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_lastpbcontextexited.js
@@ -0,0 +1,63 @@
+/* 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/. */
+
+function test() {
+ // We need to open a new window for this so that its docshell would get destroyed
+ // when clearing the PB mode flag.
+ function runTest(aCloseWindow, aCallback) {
+ let newWin = OpenBrowserWindow({ private: true });
+ SimpleTest.waitForFocus(function () {
+ let expectedExiting = true;
+ let expectedExited = false;
+ let observerExiting = {
+ observe(aSubject, aTopic, aData) {
+ is(
+ aTopic,
+ "last-pb-context-exiting",
+ "Correct topic should be dispatched (exiting)"
+ );
+ is(expectedExiting, true, "notification not expected yet (exiting)");
+ expectedExited = true;
+ Services.obs.removeObserver(
+ observerExiting,
+ "last-pb-context-exiting"
+ );
+ },
+ };
+ let observerExited = {
+ observe(aSubject, aTopic, aData) {
+ is(
+ aTopic,
+ "last-pb-context-exited",
+ "Correct topic should be dispatched (exited)"
+ );
+ is(expectedExited, true, "notification not expected yet (exited)");
+ Services.obs.removeObserver(observerExited, "last-pb-context-exited");
+ aCallback();
+ },
+ };
+ Services.obs.addObserver(observerExiting, "last-pb-context-exiting");
+ Services.obs.addObserver(observerExited, "last-pb-context-exited");
+ expectedExiting = true;
+ aCloseWindow(newWin);
+ newWin = null;
+ SpecialPowers.forceGC();
+ }, newWin);
+ }
+
+ waitForExplicitFinish();
+
+ runTest(
+ function (newWin) {
+ // Simulate pressing the window close button
+ newWin.document.getElementById("cmd_closeWindow").doCommand();
+ },
+ function () {
+ runTest(function (newWin) {
+ // Simulate closing the last tab
+ newWin.document.getElementById("cmd_close").doCommand();
+ }, finish);
+ }
+ );
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage.js
new file mode 100644
index 0000000000..7cf07fd601
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage.js
@@ -0,0 +1,28 @@
+/* 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/. */
+
+add_task(async function test() {
+ requestLongerTimeout(2);
+ const page1 =
+ "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/" +
+ "browser_privatebrowsing_localStorage_page1.html";
+
+ let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+
+ win.gBrowser.selectedTab = BrowserTestUtils.addTab(win.gBrowser, page1);
+ let browser = win.gBrowser.selectedBrowser;
+ await BrowserTestUtils.browserLoaded(browser);
+
+ BrowserTestUtils.startLoadingURIString(
+ browser,
+ "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/" +
+ "browser_privatebrowsing_localStorage_page2.html"
+ );
+ await BrowserTestUtils.browserLoaded(browser);
+
+ is(browser.contentTitle, "2", "localStorage should contain 2 items");
+
+ // Cleanup
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after.js
new file mode 100644
index 0000000000..3537236068
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after.js
@@ -0,0 +1,46 @@
+/* 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/. */
+
+// Ensure that a storage instance used by both private and public sessions at different times does not
+// allow any data to leak due to cached values.
+
+// Step 1: Load browser_privatebrowsing_localStorage_before_after_page.html in a private tab, causing a storage
+// item to exist. Close the tab.
+// Step 2: Load the same page in a non-private tab, ensuring that the storage instance reports only one item
+// existing.
+
+add_task(async function test() {
+ let prefix =
+ "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/";
+
+ // Step 1.
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let testURL =
+ prefix + "browser_privatebrowsing_localStorage_before_after_page.html";
+ await BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser, testURL);
+
+ is(
+ privateWin.gBrowser.selectedBrowser.contentTitle,
+ "1",
+ "localStorage should contain 1 item"
+ );
+
+ // Step 2.
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ testURL =
+ prefix + "browser_privatebrowsing_localStorage_before_after_page2.html";
+ await BrowserTestUtils.openNewForegroundTab(win.gBrowser, testURL);
+
+ is(
+ win.gBrowser.selectedBrowser.contentTitle,
+ "null|0",
+ "localStorage should contain 0 items"
+ );
+
+ // Cleanup
+ await BrowserTestUtils.closeWindow(privateWin);
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page.html
new file mode 100644
index 0000000000..0fcb3f89be
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page.html
@@ -0,0 +1,11 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+ localStorage.clear();
+ localStorage.setItem("zzztest", "zzzvalue");
+ document.title = localStorage.length;
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page2.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page2.html
new file mode 100644
index 0000000000..4eccebdf48
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page2.html
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+ document.title = localStorage.getItem("zzztest", "zzzvalue") + "|" + localStorage.length;
+ localStorage.clear();
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page1.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page1.html
new file mode 100644
index 0000000000..ecf5507e0a
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page1.html
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+ localStorage.clear();
+ localStorage.setItem("test1", "value1");
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page2.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page2.html
new file mode 100644
index 0000000000..d49c7fea29
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page2.html
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+ localStorage.setItem("test2", "value2");
+ document.title = localStorage.length;
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_newtab_from_popup.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_newtab_from_popup.js
new file mode 100644
index 0000000000..a46c44b0b8
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_newtab_from_popup.js
@@ -0,0 +1,71 @@
+/**
+ * Tests that a popup window in private browsing window opens
+ * new tab links in the original private browsing window as
+ * new tabs.
+ *
+ * This is a regression test for bug 1202634.
+ */
+
+// We're able to sidestep some quote-escaping issues when
+// nesting data URI's by encoding the second data URI in
+// base64.
+const POPUP_BODY_BASE64 = btoa(`<a href="http://example.com/" target="_blank"
+ id="second">
+ Now click this
+ </a>`);
+const POPUP_LINK = `data:text/html;charset=utf-8;base64,${POPUP_BODY_BASE64}`;
+const WINDOW_BODY = `data:text/html,
+ <a href="%23" id="first"
+ onclick="window.open('${POPUP_LINK}', '_blank',
+ 'width=630,height=500')">
+ First click this.
+ </a>`;
+
+add_task(async function test_private_popup_window_opens_private_tabs() {
+ // allow top level data: URI navigations, otherwise clicking a data: link fails
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.data_uri.block_toplevel_data_uri_navigations", false]],
+ });
+ let privWin = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+
+ // Sanity check - this browser better be private.
+ ok(
+ PrivateBrowsingUtils.isWindowPrivate(privWin),
+ "Opened a private browsing window."
+ );
+
+ // First, open a private browsing window, and load our
+ // testing page.
+ let privBrowser = privWin.gBrowser.selectedBrowser;
+ BrowserTestUtils.startLoadingURIString(privBrowser, WINDOW_BODY);
+ await BrowserTestUtils.browserLoaded(privBrowser);
+
+ // Next, click on the link in the testing page, and ensure
+ // that a private popup window is opened.
+ let openedPromise = BrowserTestUtils.waitForNewWindow({ url: POPUP_LINK });
+
+ await BrowserTestUtils.synthesizeMouseAtCenter("#first", {}, privBrowser);
+ let popupWin = await openedPromise;
+ ok(
+ PrivateBrowsingUtils.isWindowPrivate(popupWin),
+ "Popup window was private."
+ );
+
+ // Now click on the link in the popup, and ensure that a new
+ // tab is opened in the original private browsing window.
+ let newTabPromise = BrowserTestUtils.waitForNewTab(privWin.gBrowser);
+ let popupBrowser = popupWin.gBrowser.selectedBrowser;
+ await BrowserTestUtils.synthesizeMouseAtCenter("#second", {}, popupBrowser);
+ let newPrivTab = await newTabPromise;
+
+ // Ensure that the newly created tab's browser is private.
+ ok(
+ PrivateBrowsingUtils.isBrowserPrivate(newPrivTab.linkedBrowser),
+ "Newly opened tab should be private."
+ );
+
+ // Clean up
+ BrowserTestUtils.removeTab(newPrivTab);
+ await BrowserTestUtils.closeWindow(popupWin);
+ await BrowserTestUtils.closeWindow(privWin);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_noSessionRestoreMenuOption.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_noSessionRestoreMenuOption.js
new file mode 100644
index 0000000000..17ea34d1aa
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_noSessionRestoreMenuOption.js
@@ -0,0 +1,29 @@
+"use strict";
+
+/**
+ * Tests that if we open a tab within a private browsing window, and then
+ * close that private browsing window, that subsequent private browsing
+ * windows do not allow the command for restoring the last session.
+ */
+add_task(async function test_no_session_restore_menu_option() {
+ let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ ok(true, "The first private window got loaded");
+ BrowserTestUtils.addTab(win.gBrowser, "about:mozilla");
+ await BrowserTestUtils.closeWindow(win);
+
+ win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ let srCommand = win.document.getElementById("Browser:RestoreLastSession");
+ ok(srCommand, "The Session Restore command should exist");
+ is(
+ PrivateBrowsingUtils.isWindowPrivate(win),
+ true,
+ "PrivateBrowsingUtils should report the correct per-window private browsing status"
+ );
+ is(
+ srCommand.hasAttribute("disabled"),
+ true,
+ "The Session Restore command should be disabled in private browsing mode"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_nonbrowser.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_nonbrowser.js
new file mode 100644
index 0000000000..24586d7464
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_nonbrowser.js
@@ -0,0 +1,21 @@
+"use strict";
+
+/**
+ * Tests that we fire the last-pb-context-exited observer notification
+ * when the last private browsing window closes, even if a chrome window
+ * was opened from that private browsing window.
+ */
+add_task(async function () {
+ let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ let chromeWin = win.open(
+ "chrome://browser/content/places/places.xhtml",
+ "_blank",
+ "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar"
+ );
+ await BrowserTestUtils.waitForEvent(chromeWin, "load");
+ let obsPromise = TestUtils.topicObserved("last-pb-context-exited");
+ await BrowserTestUtils.closeWindow(win);
+ await obsPromise;
+ Assert.ok(true, "Got the last-pb-context-exited notification");
+ chromeWin.close();
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_opendir.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_opendir.js
new file mode 100644
index 0000000000..146ab82628
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_opendir.js
@@ -0,0 +1,175 @@
+/* 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/. */
+
+// This test makes sure that the last open directory used inside the private
+// browsing mode is not remembered after leaving that mode.
+
+var windowsToClose = [];
+function testOnWindow(options, callback) {
+ var win = OpenBrowserWindow(options);
+ win.addEventListener(
+ "load",
+ function () {
+ windowsToClose.push(win);
+ callback(win);
+ },
+ { once: true }
+ );
+}
+
+registerCleanupFunction(function () {
+ windowsToClose.forEach(function (win) {
+ win.close();
+ });
+});
+
+function test() {
+ // initialization
+ waitForExplicitFinish();
+ let dir1 = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ let dir2 = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ let file = dir2.clone();
+ file.append("pbtest.file");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ const kPrefName = "browser.open.lastDir";
+
+ function setupCleanSlate(win) {
+ win.gLastOpenDirectory.reset();
+ Services.prefs.clearUserPref(kPrefName);
+ }
+
+ setupCleanSlate(window);
+
+ // open one regular and one private window
+ testOnWindow(undefined, function (nonPrivateWindow) {
+ setupCleanSlate(nonPrivateWindow);
+ testOnWindow({ private: true }, function (privateWindow) {
+ setupCleanSlate(privateWindow);
+
+ // Test 1: general workflow test
+
+ // initial checks
+ ok(
+ !nonPrivateWindow.gLastOpenDirectory.path,
+ "Last open directory path should be initially empty"
+ );
+ nonPrivateWindow.gLastOpenDirectory.path = dir2;
+ is(
+ nonPrivateWindow.gLastOpenDirectory.path.path,
+ dir2.path,
+ "The path should be successfully set"
+ );
+ nonPrivateWindow.gLastOpenDirectory.path = null;
+ is(
+ nonPrivateWindow.gLastOpenDirectory.path.path,
+ dir2.path,
+ "The path should be not change when assigning it to null"
+ );
+ nonPrivateWindow.gLastOpenDirectory.path = dir1;
+ is(
+ nonPrivateWindow.gLastOpenDirectory.path.path,
+ dir1.path,
+ "The path should be successfully outside of the private browsing mode"
+ );
+
+ // test the private window
+ is(
+ privateWindow.gLastOpenDirectory.path.path,
+ dir1.path,
+ "The path should not change when entering the private browsing mode"
+ );
+ privateWindow.gLastOpenDirectory.path = dir2;
+ is(
+ privateWindow.gLastOpenDirectory.path.path,
+ dir2.path,
+ "The path should successfully change inside the private browsing mode"
+ );
+
+ // test the non-private window
+ is(
+ nonPrivateWindow.gLastOpenDirectory.path.path,
+ dir1.path,
+ "The path should be reset to the same path as before entering the private browsing mode"
+ );
+
+ setupCleanSlate(nonPrivateWindow);
+ setupCleanSlate(privateWindow);
+
+ // Test 2: the user first tries to open a file inside the private browsing mode
+
+ // test the private window
+ ok(
+ !privateWindow.gLastOpenDirectory.path,
+ "No original path should exist inside the private browsing mode"
+ );
+ privateWindow.gLastOpenDirectory.path = dir1;
+ is(
+ privateWindow.gLastOpenDirectory.path.path,
+ dir1.path,
+ "The path should be successfully set inside the private browsing mode"
+ );
+ // test the non-private window
+ ok(
+ !nonPrivateWindow.gLastOpenDirectory.path,
+ "The path set inside the private browsing mode should not leak when leaving that mode"
+ );
+
+ setupCleanSlate(nonPrivateWindow);
+ setupCleanSlate(privateWindow);
+
+ // Test 3: the last open directory is set from a previous session, it should be used
+ // in normal mode
+
+ Services.prefs.setComplexValue(kPrefName, Ci.nsIFile, dir1);
+ is(
+ nonPrivateWindow.gLastOpenDirectory.path.path,
+ dir1.path,
+ "The pref set from last session should take effect outside the private browsing mode"
+ );
+
+ setupCleanSlate(nonPrivateWindow);
+ setupCleanSlate(privateWindow);
+
+ // Test 4: the last open directory is set from a previous session, it should be used
+ // in private browsing mode mode
+
+ Services.prefs.setComplexValue(kPrefName, Ci.nsIFile, dir1);
+ // test the private window
+ is(
+ privateWindow.gLastOpenDirectory.path.path,
+ dir1.path,
+ "The pref set from last session should take effect inside the private browsing mode"
+ );
+ // test the non-private window
+ is(
+ nonPrivateWindow.gLastOpenDirectory.path.path,
+ dir1.path,
+ "The pref set from last session should remain in effect after leaving the private browsing mode"
+ );
+
+ setupCleanSlate(nonPrivateWindow);
+ setupCleanSlate(privateWindow);
+
+ // Test 5: setting the path to a file shouldn't work
+
+ nonPrivateWindow.gLastOpenDirectory.path = file;
+ ok(
+ !nonPrivateWindow.gLastOpenDirectory.path,
+ "Setting the path to a file shouldn't work when it's originally null"
+ );
+ nonPrivateWindow.gLastOpenDirectory.path = dir1;
+ nonPrivateWindow.gLastOpenDirectory.path = file;
+ is(
+ nonPrivateWindow.gLastOpenDirectory.path.path,
+ dir1.path,
+ "Setting the path to a file shouldn't work when it's not originally null"
+ );
+
+ // cleanup
+ file.remove(false);
+ finish();
+ });
+ });
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.html
new file mode 100644
index 0000000000..f5bb3212f8
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <title>Title 1</title>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js
new file mode 100644
index 0000000000..855bfe4c41
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js
@@ -0,0 +1,59 @@
+/* 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/. */
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+// Test to make sure that the visited page titles do not get updated inside the
+// private browsing mode.
+"use strict";
+
+add_task(async function test() {
+ const TEST_URL =
+ "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.html";
+ const TITLE_1 = "Title 1";
+ const TITLE_2 = "Title 2";
+
+ await PlacesUtils.history.clear();
+
+ let promiseTitleChanged = PlacesTestUtils.waitForNotification(
+ "page-title-changed",
+ events => events[0].url == TEST_URL
+ );
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+ registerCleanupFunction(async () => {
+ BrowserTestUtils.removeTab(tab);
+ });
+ info("Wait for a title change notification.");
+ await promiseTitleChanged;
+ await BrowserTestUtils.waitForCondition(async function () {
+ let entry = await PlacesUtils.history.fetch(TEST_URL);
+ return entry && entry.title == TITLE_1;
+ }, "The title matches the original title after first visit");
+
+ promiseTitleChanged = PlacesTestUtils.waitForNotification(
+ "page-title-changed",
+ events => events[0].url == TEST_URL
+ );
+ await PlacesTestUtils.addVisits({ uri: TEST_URL, title: TITLE_2 });
+ info("Wait for a title change notification.");
+ await promiseTitleChanged;
+ await BrowserTestUtils.waitForCondition(async function () {
+ let entry = await PlacesUtils.history.fetch(TEST_URL);
+ return entry && entry.title == TITLE_2;
+ }, "The title matches the original title after updating visit");
+
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ registerCleanupFunction(async () => {
+ await BrowserTestUtils.closeWindow(privateWin);
+ });
+ await BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser, TEST_URL);
+ // Wait long enough to be sure history didn't set a title.
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ is(
+ (await PlacesUtils.history.fetch(TEST_URL)).title,
+ TITLE_2,
+ "The title remains the same after visiting in private window"
+ );
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js
new file mode 100644
index 0000000000..af2c6aeb65
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js
@@ -0,0 +1,82 @@
+/* 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/. */
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+// This test makes sure that the title of existing history entries does not
+// change inside a private window.
+
+add_task(async function test() {
+ const TEST_URL =
+ "http://mochi.test:8888/browser/browser/components/" +
+ "privatebrowsing/test/browser/title.sjs";
+ let cm = Services.cookies;
+
+ function cleanup() {
+ // delete all cookies
+ cm.removeAll();
+ // delete all history items
+ return PlacesUtils.history.clear();
+ }
+
+ await cleanup();
+ registerCleanupFunction(cleanup);
+
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ registerCleanupFunction(async () => {
+ await BrowserTestUtils.closeWindow(win);
+ });
+
+ let promiseTitleChanged = PlacesTestUtils.waitForNotification(
+ "page-title-changed",
+ events => events[0].url == TEST_URL
+ );
+ await BrowserTestUtils.openNewForegroundTab(win.gBrowser, TEST_URL);
+ await promiseTitleChanged;
+ await BrowserTestUtils.waitForCondition(async function () {
+ let entry = await PlacesUtils.history.fetch(TEST_URL);
+ return entry && entry.title == "No Cookie";
+ }, "The page should be loaded without any cookie for the first time");
+
+ promiseTitleChanged = PlacesTestUtils.waitForNotification(
+ "page-title-changed",
+ events => events[0].url == TEST_URL
+ );
+ await BrowserTestUtils.openNewForegroundTab(win.gBrowser, TEST_URL);
+ await promiseTitleChanged;
+ await BrowserTestUtils.waitForCondition(async function () {
+ let entry = await PlacesUtils.history.fetch(TEST_URL);
+ return entry && entry.title == "Cookie";
+ }, "The page should be loaded with a cookie for the second time");
+
+ await cleanup();
+
+ promiseTitleChanged = PlacesTestUtils.waitForNotification(
+ "page-title-changed",
+ events => events[0].url == TEST_URL
+ );
+ await BrowserTestUtils.openNewForegroundTab(win.gBrowser, TEST_URL);
+ await promiseTitleChanged;
+ await BrowserTestUtils.waitForCondition(async function () {
+ let entry = await PlacesUtils.history.fetch(TEST_URL);
+ return entry && entry.title == "No Cookie";
+ }, "The page should be loaded without any cookie again");
+
+ // Reopen the page in a private browser window, it should not notify a title
+ // change.
+ let win2 = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ registerCleanupFunction(async () => {
+ let promisePBExit = TestUtils.topicObserved("last-pb-context-exited");
+ await BrowserTestUtils.closeWindow(win2);
+ await promisePBExit;
+ });
+
+ await BrowserTestUtils.openNewForegroundTab(win2.gBrowser, TEST_URL);
+ // Wait long enough to be sure history didn't set a title.
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ is(
+ (await PlacesUtils.history.fetch(TEST_URL)).title,
+ "No Cookie",
+ "The title remains the same after visiting in private window"
+ );
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler.js
new file mode 100644
index 0000000000..4ab90e2079
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler.js
@@ -0,0 +1,71 @@
+/* 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/. */
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+// This test makes sure that the web pages can't register protocol handlers
+// inside the private browsing mode.
+
+add_task(async function test() {
+ let notificationValue = "Protocol Registration: web+testprotocol";
+ let testURI =
+ "https://example.com/browser/" +
+ "browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler_page.html";
+
+ let doTest = async function (aIsPrivateMode, aWindow) {
+ let tab = (aWindow.gBrowser.selectedTab = BrowserTestUtils.addTab(
+ aWindow.gBrowser,
+ testURI
+ ));
+ let notificationBox = aWindow.gBrowser.getNotificationBox();
+ let notificationShownPromise;
+ if (!aIsPrivateMode) {
+ notificationShownPromise = BrowserTestUtils.waitForEvent(
+ notificationBox.stack,
+ "AlertActive",
+ false,
+ event => event.target.getAttribute("value") == notificationValue
+ );
+ }
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await notificationShownPromise;
+
+ let promiseFinished = Promise.withResolvers();
+ setTimeout(function () {
+ let notification =
+ notificationBox.getNotificationWithValue(notificationValue);
+
+ if (aIsPrivateMode) {
+ // Make sure the notification is correctly displayed without a remember control
+ ok(
+ !notification,
+ "Notification box should not be displayed inside of private browsing mode"
+ );
+ } else {
+ // Make sure the notification is correctly displayed with a remember control
+ ok(
+ notification,
+ "Notification box should be displaying outside of private browsing mode"
+ );
+ }
+
+ promiseFinished.resolve();
+ }, 100); // remember control is added in a setTimeout(0) call
+
+ await promiseFinished.promise;
+ };
+
+ // test first when not on private mode
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await doTest(false, win);
+
+ // then test when on private mode
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ await doTest(true, privateWin);
+
+ // Cleanup
+ await BrowserTestUtils.closeWindow(win);
+ await BrowserTestUtils.closeWindow(privateWin);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler_page.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler_page.html
new file mode 100644
index 0000000000..200fda0d42
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler_page.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <title>Protocol registrar page</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ navigator.registerProtocolHandler("web+testprotocol",
+ "https://example.com/foobar?uri=%s",
+ "Test Protocol");
+ </script>
+ </body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_rememberprompt.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_rememberprompt.js
new file mode 100644
index 0000000000..2090742c91
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_rememberprompt.js
@@ -0,0 +1,90 @@
+/* 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/. */
+
+// This test makes sure that the geolocation prompt does not show a remember
+// control inside the private browsing mode.
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.vr.always_support_vr", true]],
+ });
+});
+
+add_task(async function test() {
+ function checkPrompt(aURL, aName, aPrivateMode, aWindow) {
+ return (async function () {
+ aWindow.gBrowser.selectedTab = BrowserTestUtils.addTab(
+ aWindow.gBrowser,
+ aURL
+ );
+ await BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser);
+
+ let notification = aWindow.PopupNotifications.getNotification(aName);
+
+ // Wait until the notification is available.
+ while (!notification) {
+ await new Promise(resolve => {
+ executeSoon(resolve);
+ });
+ notification = aWindow.PopupNotifications.getNotification(aName);
+ }
+
+ if (aPrivateMode) {
+ // Make sure the notification is correctly displayed without a remember control
+ ok(
+ !notification.options.checkbox.show,
+ "Secondary actions should not exist (always/never remember)"
+ );
+ } else {
+ ok(
+ notification.options.checkbox.show,
+ "Secondary actions should exist (always/never remember)"
+ );
+ }
+ notification.remove();
+
+ aWindow.gBrowser.removeCurrentTab();
+ })();
+ }
+
+ function checkPrivateBrowsingRememberPrompt(aURL, aName) {
+ return (async function () {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ let browser = win.gBrowser.selectedBrowser;
+ BrowserTestUtils.startLoadingURIString(browser, aURL);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await checkPrompt(aURL, aName, false, win);
+
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let privateBrowser = privateWin.gBrowser.selectedBrowser;
+ BrowserTestUtils.startLoadingURIString(privateBrowser, aURL);
+ await BrowserTestUtils.browserLoaded(privateBrowser);
+
+ await checkPrompt(aURL, aName, true, privateWin);
+
+ // Cleanup
+ await BrowserTestUtils.closeWindow(win);
+ await BrowserTestUtils.closeWindow(privateWin);
+ })();
+ }
+
+ const geoTestPageURL =
+ "https://example.com/browser/" +
+ "browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html";
+
+ await checkPrivateBrowsingRememberPrompt(geoTestPageURL, "geolocation");
+
+ const vrEnabled = Services.prefs.getBoolPref("dom.vr.enabled");
+
+ if (vrEnabled) {
+ const xrTestPageURL =
+ "https://example.com/browser/" +
+ "browser/components/privatebrowsing/test/browser/browser_privatebrowsing_xrprompt_page.html";
+
+ await checkPrivateBrowsingRememberPrompt(xrTestPageURL, "xr");
+ }
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_resetPBM.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_resetPBM.js
new file mode 100644
index 0000000000..a80d818c87
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_resetPBM.js
@@ -0,0 +1,824 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SessionStoreTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SessionStoreTestUtils.sys.mjs"
+);
+
+const PREF_ID_ALWAYS_ASK =
+ "browser.privatebrowsing.resetPBM.showConfirmationDialog";
+
+const SELECTOR_TOOLBAR_BUTTON = "#reset-pbm-toolbar-button";
+
+const SELECTOR_PANELVIEW = "panel #reset-pbm-panel";
+const SELECTOR_CONTAINER = "#reset-pbm-panel-container";
+const SELECTOR_PANEL_HEADING = "#reset-pbm-panel-header > description";
+const SELECTOR_PANEL_DESCRIPTION = "#reset-pbm-panel-description";
+const SELECTOR_PANEL_CHECKBOX = "#reset-pbm-panel-checkbox";
+const SELECTOR_PANEL_CONFIRM_BTN = "#reset-pbm-panel-confirm-button";
+const SELECTOR_PANEL_CANCEL_BTN = "#reset-pbm-panel-cancel-button";
+
+const SELECTOR_PANEL_COMPLETION_TOAST = "#confirmation-hint";
+
+/**
+ * Wait for the reset pbm confirmation panel to open. May also be called if the
+ * panel is already open.
+ * @param {ChromeWindow} win - Chrome window in which the panel is embedded.
+ * @returns {Promise} - Promise which resolves once the panel has been shown.
+ * Resolves directly if the panel is already visible.
+ */
+async function waitForConfirmPanelShow(win) {
+ // Check for the panel, if it's not present yet wait for it to be inserted.
+ let panelview = win.document.querySelector(SELECTOR_PANELVIEW);
+ if (!panelview) {
+ let navToolbox = win.document.getElementById("navigator-toolbox");
+ await BrowserTestUtils.waitForMutationCondition(
+ navToolbox,
+ { childList: true, subtree: true },
+ () => {
+ panelview = win.document.querySelector(SELECTOR_PANELVIEW);
+ return !!panelview;
+ }
+ );
+ }
+
+ // Panel already visible, we can exit early.
+ if (BrowserTestUtils.isVisible(panelview)) {
+ return;
+ }
+
+ // Wait for panel shown event.
+ await BrowserTestUtils.waitForEvent(panelview.closest("panel"), "popupshown");
+}
+
+/**
+ * Hides the completion toast which is shown after the reset action has been
+ * completed.
+ * @param {ChromeWindow} win - Chrome window the toast is shown in.
+ */
+async function hideCompletionToast(win) {
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ win.ConfirmationHint._panel,
+ "popuphidden"
+ );
+
+ win.ConfirmationHint._panel.hidePopup();
+
+ await promiseHidden;
+}
+
+/**
+ * Trigger the reset pbm toolbar button which may open the confirm panel in the
+ * given window.
+ * @param {nsIDOMWindow} win - PBM window to trigger the button in.
+ * @param {boolean} [expectPanelOpen] - After the button action: whether the
+ * panel is expected to open (true) or remain closed (false).
+ * @returns {Promise} - Promise which resolves once the button has been
+ * triggered and, if applicable, the panel has been opened.
+ */
+async function triggerResetBtn(win, expectPanelOpen = true) {
+ Assert.ok(
+ PrivateBrowsingUtils.isWindowPrivate(win),
+ "Window to open panel is in PBM."
+ );
+
+ let shownPromise;
+ if (expectPanelOpen) {
+ shownPromise = waitForConfirmPanelShow(win);
+ }
+
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ SELECTOR_TOOLBAR_BUTTON,
+ {},
+ win.browsingContext
+ );
+
+ // Promise may not be defined at this point in which case this is a no-op.
+ await shownPromise;
+}
+
+/**
+ * Provides a promise that resolves once the reset confirmation panel has been hidden.
+ * @param nsIDOMWindow win - Chrome window that has the panel.
+ * @returns {Promise}
+ */
+function waitForConfirmPanelHidden(win) {
+ return BrowserTestUtils.waitForEvent(
+ win.document.querySelector(SELECTOR_PANELVIEW).closest("panel"),
+ "popuphidden"
+ );
+}
+
+/**
+ * Provides a promise that resolves once the completion toast has been shown.
+ * @param nsIDOMWindow win - Chrome window that has the panel.
+ * @returns {Promise}
+ */
+function waitForCompletionToastShown(win) {
+ // Init the confirmation hint panel so we can listen for show events.
+ win.ConfirmationHint._ensurePanel();
+ return BrowserTestUtils.waitForEvent(
+ win.document.querySelector(SELECTOR_PANEL_COMPLETION_TOAST),
+ "popupshown"
+ );
+}
+
+/**
+ * Wait for private browsing data clearing to be triggered.
+ * Clearing is not guaranteed to be done at this point. Bug 1846494 will add a
+ * promise based mechanism and potentially a new triggering method for clearing,
+ * at which point this helper should be updated.
+ * @returns {Promise} Promise which resolves when the last-pb-context-exited
+ * message has been dispatched.
+ */
+function waitForPBMDataClear() {
+ return TestUtils.topicObserved("last-pb-context-exited");
+}
+
+/**
+ * Test panel visibility.
+ * @param {nsIDOMWindow} win - Chrome window which is the parent of the panel.
+ * @param {string} selector - Query selector for the panel.
+ * @param {boolean} expectVisible - Whether the panel should be visible (true) or invisible or not present (false).
+ */
+function assertPanelVisibility(win, selector, expectVisible) {
+ let panelview = win.document.querySelector(selector);
+
+ if (expectVisible) {
+ Assert.ok(panelview, `Panelview element ${selector} should exist.`);
+ Assert.ok(
+ BrowserTestUtils.isVisible(panelview),
+ `Panelview ${selector} should be visible.`
+ );
+ return;
+ }
+
+ Assert.ok(
+ !panelview || !BrowserTestUtils.isVisible(panelview),
+ `Panelview ${selector} should be invisible or non-existent.`
+ );
+}
+
+function transformGleanEvents(events) {
+ if (!events) {
+ return [];
+ }
+ return events.map(e => ({ ...e.extra }));
+}
+
+function assertTelemetry(expectedConfirmPanel, expectedResetAction, message) {
+ info(message);
+
+ let confirmPanelEvents = transformGleanEvents(
+ Glean.privateBrowsingResetPbm.confirmPanel.testGetValue()
+ );
+ Assert.deepEqual(
+ confirmPanelEvents,
+ expectedConfirmPanel,
+ "confirmPanel events should match."
+ );
+
+ let resetActionEvents = transformGleanEvents(
+ Glean.privateBrowsingResetPbm.resetAction.testGetValue()
+ );
+ Assert.deepEqual(
+ resetActionEvents,
+ expectedResetAction,
+ "resetAction events should match."
+ );
+}
+
+/**
+ * Tests that the reset button is only visible in private browsing windows and
+ * when the feature is enabled.
+ */
+add_task(async function test_toolbar_button_visibility() {
+ assertTelemetry([], [], "No glean events initially.");
+
+ for (let isEnabled of [false, true]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.privatebrowsing.resetPBM.enabled", isEnabled]],
+ });
+
+ info(
+ "Test that the toolbar button is never visible in a normal browsing window."
+ );
+ let toolbarButtonNormalBrowsing = document.querySelector(
+ SELECTOR_TOOLBAR_BUTTON
+ );
+ Assert.equal(
+ !!toolbarButtonNormalBrowsing,
+ isEnabled,
+ "Normal browsing toolbar button element exists, depending on enabled pref state."
+ );
+ if (toolbarButtonNormalBrowsing) {
+ Assert.ok(
+ !BrowserTestUtils.isVisible(toolbarButtonNormalBrowsing),
+ "Toolbar button is not visible in normal browsing"
+ );
+ }
+
+ info(
+ "Test that the toolbar button is visible in a private browsing window, depending on enabled pref state."
+ );
+ let privateWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ let toolbarButtonPrivateBrowsing = privateWindow.document.querySelector(
+ SELECTOR_TOOLBAR_BUTTON
+ );
+ Assert.equal(
+ !!toolbarButtonPrivateBrowsing,
+ isEnabled,
+ "Private browsing toolbar button element exists, depending on enabled pref state."
+ );
+ if (toolbarButtonPrivateBrowsing) {
+ Assert.equal(
+ BrowserTestUtils.isVisible(toolbarButtonPrivateBrowsing),
+ isEnabled,
+ "Toolbar button is visible in private browsing if enabled."
+ );
+ }
+
+ await BrowserTestUtils.closeWindow(privateWindow);
+
+ await SpecialPowers.popPrefEnv();
+ }
+
+ assertTelemetry([], [], "No glean events after test.");
+});
+
+/**
+ * Tests the panel UI, the 'always ask' checkbox and the confirm and cancel
+ * actions.
+ */
+add_task(async function test_panel() {
+ assertTelemetry([], [], "No glean events initially.");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.privatebrowsing.resetPBM.enabled", true]],
+ });
+
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ // Panel does either not exist (because it's lazy and hasn't been opened yet),
+ // or it is invisible.
+ assertPanelVisibility(privateWin, SELECTOR_PANELVIEW, false);
+ assertPanelVisibility(privateWin, SELECTOR_PANEL_COMPLETION_TOAST, false);
+
+ info("Open the panel.");
+ await triggerResetBtn(privateWin);
+
+ assertTelemetry(
+ [{ action: "show", reason: "toolbar-btn" }],
+ [],
+ "There should be a panel show event."
+ );
+
+ info("Check that all expected elements are present and visible.");
+ [
+ SELECTOR_PANEL_HEADING,
+ SELECTOR_PANEL_DESCRIPTION,
+ SELECTOR_PANEL_CHECKBOX,
+ SELECTOR_PANEL_CONFIRM_BTN,
+ SELECTOR_PANEL_CANCEL_BTN,
+ ].forEach(elSelector => {
+ let el = privateWin.document.querySelector(elSelector);
+ Assert.ok(el, `Panel element ${elSelector} exists.`);
+ if (el) {
+ Assert.ok(
+ BrowserTestUtils.isVisible(el),
+ `Panel element ${elSelector} is visible.`
+ );
+ }
+ });
+
+ info("Inspect checkbox and pref state.");
+ let checkbox = privateWin.document.querySelector(SELECTOR_PANEL_CHECKBOX);
+ Assert.ok(
+ checkbox.checked,
+ "The 'always ask' checkbox should be checked initially."
+ );
+ Assert.ok(
+ Services.prefs.getBoolPref(
+ "browser.privatebrowsing.resetPBM.showConfirmationDialog"
+ ),
+ "The always ask pref should be true."
+ );
+
+ info("Accessibility checks");
+ let panel = privateWin.document.querySelector(SELECTOR_PANELVIEW);
+ Assert.equal(
+ panel.getAttribute("role"),
+ "document",
+ "Panel should have role document."
+ );
+
+ let container = panel.querySelector(SELECTOR_CONTAINER);
+ Assert.equal(
+ container.getAttribute("role"),
+ "alertdialog",
+ "Panel container should have role alertdialog."
+ );
+ Assert.equal(
+ container.getAttribute("aria-labelledby"),
+ "reset-pbm-panel-header",
+ "aria-labelledby should point to heading."
+ );
+
+ let heading = panel.querySelector(SELECTOR_PANEL_HEADING);
+ Assert.equal(
+ heading.getAttribute("role"),
+ "heading",
+ "Heading should have role heading."
+ );
+ Assert.equal(
+ heading.getAttribute("aria-level"),
+ "2",
+ "heading should have aria-level 2"
+ );
+
+ info("Click the checkbox to uncheck it.");
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ SELECTOR_PANEL_CHECKBOX,
+ {},
+ privateWin.browsingContext
+ );
+ Assert.ok(
+ !checkbox.checked,
+ "The 'always ask' checkbox should no longer be checked."
+ );
+ info(
+ "The pref shouldn't update after clicking the checkbox. It only updates on panel confirm."
+ );
+ Assert.ok(
+ Services.prefs.getBoolPref(
+ "browser.privatebrowsing.resetPBM.showConfirmationDialog"
+ ),
+ "The always ask pref should still be true."
+ );
+
+ info("Close the panel via cancel.");
+ let promisePanelHidden = waitForConfirmPanelHidden(privateWin);
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ SELECTOR_PANEL_CANCEL_BTN,
+ {},
+ privateWin.browsingContext
+ );
+ await promisePanelHidden;
+
+ assertTelemetry(
+ [
+ { action: "show", reason: "toolbar-btn" },
+ { action: "hide", reason: "cancel-btn" },
+ ],
+ [],
+ "There should be a panel show and a hide event."
+ );
+
+ assertPanelVisibility(privateWin, SELECTOR_PANELVIEW, false);
+ assertPanelVisibility(privateWin, SELECTOR_PANEL_COMPLETION_TOAST, false);
+
+ info("Reopen the panel.");
+ await triggerResetBtn(privateWin);
+
+ assertTelemetry(
+ [
+ { action: "show", reason: "toolbar-btn" },
+ { action: "hide", reason: "cancel-btn" },
+ { action: "show", reason: "toolbar-btn" },
+ ],
+ [],
+ "Should have added a show event."
+ );
+
+ assertPanelVisibility(privateWin, SELECTOR_PANELVIEW, true);
+ assertPanelVisibility(privateWin, SELECTOR_PANEL_COMPLETION_TOAST, false);
+ Assert.ok(
+ checkbox.checked,
+ "The 'always ask' checkbox should be checked again."
+ );
+ Assert.ok(
+ Services.prefs.getBoolPref(PREF_ID_ALWAYS_ASK),
+ "The always ask pref should be true."
+ );
+
+ info("Test the checkbox on confirm.");
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ SELECTOR_PANEL_CHECKBOX,
+ {},
+ privateWin.browsingContext
+ );
+ Assert.ok(
+ !checkbox.checked,
+ "The 'always ask' checkbox should no longer be checked."
+ );
+
+ info("Close the panel via confirm.");
+ let promiseDataCleared = waitForPBMDataClear();
+ promisePanelHidden = waitForConfirmPanelHidden(privateWin);
+ let promiseCompletionToastShown = waitForCompletionToastShown(privateWin);
+
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ SELECTOR_PANEL_CONFIRM_BTN,
+ {},
+ privateWin.browsingContext
+ );
+ await promisePanelHidden;
+
+ assertTelemetry(
+ [
+ { action: "show", reason: "toolbar-btn" },
+ { action: "hide", reason: "cancel-btn" },
+ { action: "show", reason: "toolbar-btn" },
+ { action: "hide", reason: "confirm-btn" },
+ ],
+ [{ did_confirm: "true" }],
+ "Should have added a hide and a reset event."
+ );
+ await promiseCompletionToastShown;
+ assertPanelVisibility(privateWin, SELECTOR_PANELVIEW, false);
+ assertPanelVisibility(privateWin, SELECTOR_PANEL_COMPLETION_TOAST, true);
+
+ await promiseDataCleared;
+
+ Assert.ok(
+ !Services.prefs.getBoolPref(PREF_ID_ALWAYS_ASK),
+ "The always ask pref should now be false."
+ );
+
+ // Need to hide the completion toast, otherwise the next click on the toolbar
+ // button won't work.
+ info("Hide the completion toast.");
+ await hideCompletionToast(privateWin);
+
+ info(
+ "Simulate a click on the toolbar button. This time the panel should not open - we have unchecked 'always ask'."
+ );
+ promiseDataCleared = waitForPBMDataClear();
+ promiseCompletionToastShown = waitForCompletionToastShown(privateWin);
+
+ await triggerResetBtn(privateWin, false);
+
+ info("Waiting for PBM session to end.");
+ await promiseDataCleared;
+ info("Data has been cleared.");
+
+ assertPanelVisibility(privateWin, SELECTOR_PANELVIEW, false);
+
+ info("Waiting for the completion toast to show.");
+ await promiseCompletionToastShown;
+
+ assertPanelVisibility(privateWin, SELECTOR_PANELVIEW, false);
+ assertPanelVisibility(privateWin, SELECTOR_PANEL_COMPLETION_TOAST, true);
+
+ assertTelemetry(
+ [
+ { action: "show", reason: "toolbar-btn" },
+ { action: "hide", reason: "cancel-btn" },
+ { action: "show", reason: "toolbar-btn" },
+ { action: "hide", reason: "confirm-btn" },
+ ],
+ [{ did_confirm: "true" }, { did_confirm: "false" }],
+ "Should have added a reset event."
+ );
+
+ await BrowserTestUtils.closeWindow(privateWin);
+ Services.prefs.clearUserPref(PREF_ID_ALWAYS_ASK);
+
+ // Clean up telemetry
+ Services.fog.testResetFOG();
+});
+
+/**
+ * Tests that the reset action closes all other private browsing windows and
+ * tabs and triggers private browsing data clearing.
+ */
+add_task(async function test_reset_action() {
+ assertTelemetry([], [], "No glean events initially.");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.privatebrowsing.resetPBM.enabled", true]],
+ });
+
+ info("Open a few private browsing windows.");
+ let privateBrowsingWindows = [];
+ for (let i = 0; i < 3; i += 1) {
+ privateBrowsingWindows.push(
+ await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ })
+ );
+ }
+
+ info(
+ "Open an additional normal browsing window. It should remain open on reset PBM action."
+ );
+ let additionalNormalWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: false,
+ });
+
+ info("Use one of the PBM windows to trigger the PBM restart action.");
+ let [win] = privateBrowsingWindows;
+ win.focus();
+
+ Assert.ok(
+ PrivateBrowsingUtils.isWindowPrivate(win),
+ "Window for PBM reset trigger is private window."
+ );
+
+ info("Load a bunch of tabs in the private window.");
+ let loadPromises = [
+ "https://example.com",
+ "https://example.org",
+ "https://example.net",
+ ].map(async url => {
+ let tab = BrowserTestUtils.addTab(win.gBrowser, url);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ });
+ await Promise.all(loadPromises);
+
+ info("Switch to the last tab.");
+ win.gBrowser.selectedTab = win.gBrowser.tabs[win.gBrowser.tabs.length - 1];
+ Assert.ok(
+ win.gBrowser.selectedBrowser.currentURI.spec != "about:privatebrowsing",
+ "The selected tab should not show about:privatebrowsing."
+ );
+
+ let windowClosePromises = [
+ ...privateBrowsingWindows.filter(w => win != w),
+ ].map(w => BrowserTestUtils.windowClosed(w));
+
+ // Create promises for tab close for all tabs in the triggering private browsing window.
+ let promisesTabsClosed = win.gBrowser.tabs.map(tab =>
+ BrowserTestUtils.waitForTabClosing(tab)
+ );
+
+ info("Trigger the restart PBM action");
+
+ let promiseDataClear = waitForPBMDataClear();
+ await ResetPBMPanel._restartPBM(win);
+
+ info(
+ "Wait for all the windows but the default normal window and the private window which triggered the reset action to be closed."
+ );
+ await Promise.all(windowClosePromises);
+
+ info("Wait for tabs in the trigger private window to close.");
+ await Promise.all(promisesTabsClosed);
+
+ info("Wait for data to be cleared.");
+ await promiseDataClear;
+
+ Assert.equal(
+ win.gBrowser.tabs.length,
+ 1,
+ "Should only have 1 tab remaining."
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ win.gBrowser.selectedBrowser.currentURI.spec == "about:privatebrowsing"
+ );
+ Assert.equal(
+ win.gBrowser.selectedBrowser.currentURI.spec,
+ "about:privatebrowsing",
+ "The remaining tab should point to about:privatebrowsing."
+ );
+
+ // Close the private window that remained open.
+ await BrowserTestUtils.closeWindow(win);
+ await BrowserTestUtils.closeWindow(additionalNormalWindow);
+
+ // We bypass telemetry by calling ResetPBMPanel._restartPBM directly.
+ assertTelemetry([], [], "No glean events after the test.");
+});
+
+/**
+ * Ensure that we don't show the tab close warning when closing multiple tabs
+ * with the reset PBM action.
+ */
+add_task(async function test_tab_close_warning_suppressed() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.privatebrowsing.resetPBM.enabled", true],
+ ["browser.tabs.warnOnClose", true],
+ ["browser.tabs.warnOnCloseOtherTabs", true],
+ ],
+ });
+
+ info("Open a private browsing window.");
+ let win = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ info("Open enough tabs so the tab close warning would show.");
+ let loadPromises = [];
+ // warnAboutClosingTabs uses this number to determine whether to show the tab
+ // close warning.
+ let maxTabsUndo = Services.prefs.getIntPref(
+ "browser.sessionstore.max_tabs_undo"
+ );
+ for (let i = 0; i < maxTabsUndo + 2; i++) {
+ let tab = BrowserTestUtils.addTab(win.gBrowser, "about:blank");
+ loadPromises.push(BrowserTestUtils.browserLoaded(tab.linkedBrowser));
+ }
+ await Promise.all(loadPromises);
+
+ // Create promises for tab close for all tabs in the triggering private browsing window.
+ let promisesTabsClosed = win.gBrowser.tabs.map(tab =>
+ BrowserTestUtils.waitForTabClosing(tab)
+ );
+
+ info("Trigger the restart PBM action");
+
+ let promiseDataClear = waitForPBMDataClear();
+ await ResetPBMPanel._restartPBM(win);
+
+ info("Wait for tabs in the trigger private window to close.");
+ await Promise.all(promisesTabsClosed);
+
+ info("Wait for data to be cleared.");
+ await promiseDataClear;
+
+ Assert.equal(
+ win.gBrowser.tabs.length,
+ 1,
+ "Should only have 1 tab remaining."
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ win.gBrowser.selectedBrowser.currentURI.spec == "about:privatebrowsing"
+ );
+ Assert.equal(
+ win.gBrowser.selectedBrowser.currentURI.spec,
+ "about:privatebrowsing",
+ "The remaining tab should point to about:privatebrowsing."
+ );
+
+ // Close the private window that remained open.
+ await BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * Test that if the browser sidebar is open the reset action closes it.
+ */
+add_task(async function test_reset_action_closes_sidebar() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.privatebrowsing.resetPBM.enabled", true]],
+ });
+
+ info("Open a private browsing window.");
+ let win = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ info(
+ "Open the sidebar of both the private browsing window and the normal browsing window."
+ );
+ await SidebarUI.show("viewBookmarksSidebar");
+ await win.SidebarUI.show("viewBookmarksSidebar");
+
+ info("Trigger the restart PBM action");
+ await ResetPBMPanel._restartPBM(win);
+
+ Assert.ok(
+ SidebarUI.isOpen,
+ "Normal browsing window sidebar should still be open."
+ );
+ Assert.ok(
+ !win.SidebarUI.isOpen,
+ "Private browsing sidebar should be closed."
+ );
+
+ // Cleanup: Close the sidebar of the normal browsing window.
+ SidebarUI.hide();
+
+ // Cleanup: Close the private window that remained open.
+ await BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * Test that the session store history gets purged by the reset action.
+ */
+add_task(async function test_reset_action_purges_session_store() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.privatebrowsing.resetPBM.enabled", true]],
+ });
+
+ info("Open a private browsing window.");
+ let win = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ Assert.equal(
+ SessionStore.getClosedTabCountForWindow(win),
+ 0,
+ "Initially there should be no closed tabs recorded for the PBM window in SessionStore."
+ );
+
+ info("Load a bunch of tabs in the private window.");
+
+ let tab;
+ let loadPromises = [
+ "https://example.com",
+ "https://example.org",
+ "https://example.net",
+ ].map(async url => {
+ tab = BrowserTestUtils.addTab(win.gBrowser, url);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ });
+ await Promise.all(loadPromises);
+
+ info("Manually close a tab");
+ await SessionStoreTestUtils.closeTab(tab);
+
+ Assert.equal(
+ SessionStore.getClosedTabCountForWindow(win),
+ 1,
+ "The manually closed tab should be recorded in SessionStore."
+ );
+
+ info("Trigger the restart PBM action");
+ await ResetPBMPanel._restartPBM(win);
+
+ Assert.equal(
+ SessionStore.getClosedTabCountForWindow(win),
+ 0,
+ "After triggering the PBM reset action there should be no closed tabs recorded for the PBM window in SessionStore."
+ );
+
+ // Cleanup: Close the private window that remained open.
+ await BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * Test that the the reset action closes all tabs, including pinned and (multi-)selected
+ * tabs.
+ */
+add_task(async function test_reset_action_closes_pinned_and_selected_tabs() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.privatebrowsing.resetPBM.enabled", true]],
+ });
+
+ info("Open a private browsing window.");
+ let win = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ info("Load a list of tabs.");
+ let loadPromises = [
+ "https://example.com",
+ "https://example.org",
+ "https://example.net",
+ "about:blank",
+ ].map(async url => {
+ let tab = BrowserTestUtils.addTab(win.gBrowser, url);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ return tab;
+ });
+ let tabs = await Promise.all(loadPromises);
+
+ info("Pin a tab");
+ win.gBrowser.pinTab(tabs[0]);
+
+ info("Multi-select tabs.");
+ await BrowserTestUtils.switchTab(win.gBrowser, tabs[2]);
+ win.gBrowser.addToMultiSelectedTabs(tabs[3]);
+
+ // Create promises for tab close for all tabs in the triggering private browsing window.
+ let promisesTabsClosed = win.gBrowser.tabs.map(tab =>
+ BrowserTestUtils.waitForTabClosing(tab)
+ );
+
+ info("Trigger the restart PBM action");
+ await ResetPBMPanel._restartPBM(win);
+
+ info("Wait for all tabs to be closed.");
+ await promisesTabsClosed;
+
+ Assert.equal(
+ win.gBrowser.tabs.length,
+ 1,
+ "Should only have 1 tab remaining."
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ win.gBrowser.selectedBrowser.currentURI.spec == "about:privatebrowsing"
+ );
+ Assert.equal(
+ win.gBrowser.selectedBrowser.currentURI.spec,
+ "about:privatebrowsing",
+ "The remaining tab should point to about:privatebrowsing."
+ );
+
+ // Cleanup: Close the private window/
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_sidebar.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_sidebar.js
new file mode 100644
index 0000000000..58a333bfdb
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_sidebar.js
@@ -0,0 +1,88 @@
+/* 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/. */
+
+// This test makes sure that Sidebars do not migrate across windows with
+// different privacy states
+
+// See Bug 885054: https://bugzilla.mozilla.org/show_bug.cgi?id=885054
+
+function test() {
+ waitForExplicitFinish();
+
+ // opens a sidebar
+ function openSidebar(win) {
+ return win.SidebarUI.show("viewBookmarksSidebar").then(() => win);
+ }
+
+ let windowCache = [];
+ function cacheWindow(w) {
+ windowCache.push(w);
+ return w;
+ }
+ function closeCachedWindows() {
+ windowCache.forEach(w => w.close());
+ }
+
+ // Part 1: NON PRIVATE WINDOW -> PRIVATE WINDOW
+ openWindow(window, {}, 1)
+ .then(cacheWindow)
+ .then(openSidebar)
+ .then(win => openWindow(win, { private: true }))
+ .then(cacheWindow)
+ .then(function ({ document }) {
+ let sidebarBox = document.getElementById("sidebar-box");
+ is(
+ sidebarBox.hidden,
+ true,
+ "Opening a private window from reg window does not open the sidebar"
+ );
+ })
+ .then(closeCachedWindows)
+ // Part 2: NON PRIVATE WINDOW -> NON PRIVATE WINDOW
+ .then(() => openWindow(window))
+ .then(cacheWindow)
+ .then(openSidebar)
+ .then(win => openWindow(win))
+ .then(cacheWindow)
+ .then(function ({ document }) {
+ let sidebarBox = document.getElementById("sidebar-box");
+ is(
+ sidebarBox.hidden,
+ false,
+ "Opening a reg window from reg window does open the sidebar"
+ );
+ })
+ .then(closeCachedWindows)
+ // Part 3: PRIVATE WINDOW -> NON PRIVATE WINDOW
+ .then(() => openWindow(window, { private: true }))
+ .then(cacheWindow)
+ .then(openSidebar)
+ .then(win => openWindow(win))
+ .then(cacheWindow)
+ .then(function ({ document }) {
+ let sidebarBox = document.getElementById("sidebar-box");
+ is(
+ sidebarBox.hidden,
+ true,
+ "Opening a reg window from a private window does not open the sidebar"
+ );
+ })
+ .then(closeCachedWindows)
+ // Part 4: PRIVATE WINDOW -> PRIVATE WINDOW
+ .then(() => openWindow(window, { private: true }))
+ .then(cacheWindow)
+ .then(openSidebar)
+ .then(win => openWindow(win, { private: true }))
+ .then(cacheWindow)
+ .then(function ({ document }) {
+ let sidebarBox = document.getElementById("sidebar-box");
+ is(
+ sidebarBox.hidden,
+ false,
+ "Opening a private window from private window does open the sidebar"
+ );
+ })
+ .then(closeCachedWindows)
+ .then(finish);
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_theming.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_theming.js
new file mode 100644
index 0000000000..18b398e961
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_theming.js
@@ -0,0 +1,46 @@
+/* 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/. */
+
+// This test makes sure that privatebrowsingmode attribute of the window is correctly
+// adjusted based on whether the window is a private window.
+
+var windowsToClose = [];
+function testOnWindow(options, callback) {
+ var win = OpenBrowserWindow(options);
+ win.addEventListener(
+ "load",
+ function () {
+ windowsToClose.push(win);
+ executeSoon(() => callback(win));
+ },
+ { once: true }
+ );
+}
+
+registerCleanupFunction(function () {
+ windowsToClose.forEach(function (win) {
+ win.close();
+ });
+});
+
+function test() {
+ // initialization
+ waitForExplicitFinish();
+
+ ok(
+ !document.documentElement.hasAttribute("privatebrowsingmode"),
+ "privatebrowsingmode should not be present in normal mode"
+ );
+
+ // open a private window
+ testOnWindow({ private: true }, function (win) {
+ is(
+ win.document.documentElement.getAttribute("privatebrowsingmode"),
+ "temporary",
+ 'privatebrowsingmode should be "temporary" inside the private browsing mode'
+ );
+
+ finish();
+ });
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js
new file mode 100644
index 0000000000..ab74caeb5e
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js
@@ -0,0 +1,102 @@
+/* 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/. */
+
+// This test makes sure that the gPrivateBrowsingUI object, the Private Browsing
+// menu item and its XUL <command> element work correctly.
+
+function test() {
+ // initialization
+ waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({
+ set: [["security.allow_eval_with_system_principal", true]],
+ });
+ let windowsToClose = [];
+ let testURI = "about:blank";
+ let pbMenuItem;
+ let cmd;
+
+ function doTest(aIsPrivateMode, aWindow, aCallback) {
+ BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser).then(
+ function () {
+ ok(aWindow.gPrivateBrowsingUI, "The gPrivateBrowsingUI object exists");
+
+ pbMenuItem = aWindow.document.getElementById("menu_newPrivateWindow");
+ ok(pbMenuItem, "The Private Browsing menu item exists");
+
+ cmd = aWindow.document.getElementById("Tools:PrivateBrowsing");
+ isnot(
+ cmd,
+ null,
+ "XUL command object for the private browsing service exists"
+ );
+
+ is(
+ pbMenuItem.getAttribute("label"),
+ "New Private Window",
+ 'The Private Browsing menu item should read "New Private Window"'
+ );
+ is(
+ PrivateBrowsingUtils.isWindowPrivate(aWindow),
+ aIsPrivateMode,
+ "PrivateBrowsingUtils should report the correct per-window private browsing status (privateBrowsing should be " +
+ aIsPrivateMode +
+ ")"
+ );
+
+ aCallback();
+ }
+ );
+
+ BrowserTestUtils.startLoadingURIString(
+ aWindow.gBrowser.selectedBrowser,
+ testURI
+ );
+ }
+
+ function openPrivateBrowsingModeByUI(aWindow, aCallback) {
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ aSubject.addEventListener(
+ "load",
+ function () {
+ Services.obs.removeObserver(observer, "domwindowopened");
+ windowsToClose.push(aSubject);
+ aCallback(aSubject);
+ },
+ { once: true }
+ );
+ }, "domwindowopened");
+
+ cmd = aWindow.document.getElementById("Tools:PrivateBrowsing");
+ var func = new Function("", cmd.getAttribute("oncommand"));
+ func.call(cmd);
+ }
+
+ function testOnWindow(aOptions, aCallback) {
+ whenNewWindowLoaded(aOptions, function (aWin) {
+ windowsToClose.push(aWin);
+ // execute should only be called when need, like when you are opening
+ // web pages on the test. If calling executeSoon() is not necesary, then
+ // call whenNewWindowLoaded() instead of testOnWindow() on your test.
+ executeSoon(() => aCallback(aWin));
+ });
+ }
+
+ // this function is called after calling finish() on the test.
+ registerCleanupFunction(function () {
+ windowsToClose.forEach(function (aWin) {
+ aWin.close();
+ });
+ });
+
+ // test first when not on private mode
+ testOnWindow({}, function (aWin) {
+ doTest(false, aWin, function () {
+ // then test when on private mode, opening a new private window from the
+ // user interface.
+ openPrivateBrowsingModeByUI(aWin, function (aPrivateWin) {
+ doTest(true, aPrivateWin, finish);
+ });
+ });
+ });
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_urlbarfocus.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_urlbarfocus.js
new file mode 100644
index 0000000000..cc4c0585de
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_urlbarfocus.js
@@ -0,0 +1,44 @@
+/* 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/. */
+
+// This test makes sure that the URL bar is focused when entering the private window.
+
+"use strict";
+
+function checkUrlbarFocus(win) {
+ let urlbar = win.gURLBar;
+ is(
+ win.document.activeElement,
+ urlbar.inputField,
+ "URL Bar should be focused"
+ );
+ is(urlbar.value, "", "URL Bar should be empty");
+}
+
+function openNewPrivateWindow() {
+ return new Promise(resolve => {
+ whenNewWindowLoaded({ private: true }, win => {
+ executeSoon(() => resolve(win));
+ });
+ });
+}
+
+add_task(async function () {
+ let win = await openNewPrivateWindow();
+ checkUrlbarFocus(win);
+ win.close();
+});
+
+add_task(async function () {
+ AboutNewTab.newTabURL = "about:blank";
+ registerCleanupFunction(() => {
+ AboutNewTab.resetNewTabURL();
+ });
+
+ let win = await openNewPrivateWindow();
+ checkUrlbarFocus(win);
+ win.close();
+
+ AboutNewTab.resetNewTabURL();
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle.js
new file mode 100644
index 0000000000..de99b19a44
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle.js
@@ -0,0 +1,140 @@
+/* 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/. */
+
+// This test makes sure that the window title changes correctly while switching
+// from and to private browsing mode.
+
+"use strict";
+
+add_task(async function test() {
+ const testPageURL =
+ "http://mochi.test:8888/browser/" +
+ "browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle_page.html";
+ requestLongerTimeout(2);
+
+ // initialization of expected titles
+ let test_title = "Test title";
+ let app_name = document.title;
+
+ // XXX: Bug 1597849 - Dehardcode titles by fetching them from Fluent
+ // to compare with the actual values.
+ const isMacOS = AppConstants.platform == "macosx";
+
+ let pb_postfix = isMacOS ? ` — Private Browsing` : ` Private Browsing`;
+ let page_with_title = isMacOS ? test_title : `${test_title} — ${app_name}`;
+ let page_without_title = app_name;
+ let about_pb_title = app_name;
+ let pb_page_with_title = isMacOS
+ ? `${test_title}${pb_postfix}`
+ : `${test_title} — ${app_name}${pb_postfix}`;
+ let pb_page_without_title = `${app_name}${pb_postfix}`;
+ let pb_about_pb_title = `${app_name}${pb_postfix}`;
+
+ async function testTabTitle(aWindow, url, insidePB, expected_title) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(aWindow.gBrowser);
+ BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, url);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ await BrowserTestUtils.waitForCondition(() => {
+ return aWindow.document.title === expected_title;
+ }, `Window title should be ${expected_title}, got ${aWindow.document.title}`);
+
+ is(
+ aWindow.document.title,
+ expected_title,
+ "The window title for " +
+ url +
+ " is correct (" +
+ (insidePB ? "inside" : "outside") +
+ " private browsing mode)"
+ );
+
+ let win = aWindow.gBrowser.replaceTabWithWindow(tab);
+ await BrowserTestUtils.waitForEvent(win, "load", false);
+
+ await BrowserTestUtils.waitForCondition(() => {
+ return win.document.title === expected_title;
+ }, `Window title should be ${expected_title}, got ${win.document.title}`);
+
+ is(
+ win.document.title,
+ expected_title,
+ "The window title for " +
+ url +
+ " detached tab is correct (" +
+ (insidePB ? "inside" : "outside") +
+ " private browsing mode)"
+ );
+
+ await Promise.all([
+ BrowserTestUtils.closeWindow(win),
+ BrowserTestUtils.closeWindow(aWindow),
+ ]);
+ }
+
+ function openWin(isPrivate) {
+ return BrowserTestUtils.openNewBrowserWindow({ private: isPrivate });
+ }
+ await testTabTitle(
+ await openWin(false),
+ "about:blank",
+ false,
+ page_without_title
+ );
+ await testTabTitle(await openWin(false), testPageURL, false, page_with_title);
+ await testTabTitle(
+ await openWin(false),
+ "about:privatebrowsing",
+ false,
+ about_pb_title
+ );
+ await testTabTitle(
+ await openWin(true),
+ "about:blank",
+ true,
+ pb_page_without_title
+ );
+ await testTabTitle(
+ await openWin(true),
+ testPageURL,
+ true,
+ pb_page_with_title
+ );
+ await testTabTitle(
+ await openWin(true),
+ "about:privatebrowsing",
+ true,
+ pb_about_pb_title
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.exposeContentTitleInWindow.pbm", false]],
+ });
+ await testTabTitle(await openWin(false), testPageURL, false, page_with_title);
+ await testTabTitle(
+ await openWin(true),
+ testPageURL,
+ true,
+ pb_page_without_title
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.exposeContentTitleInWindow", false],
+ ["privacy.exposeContentTitleInWindow.pbm", true],
+ ],
+ });
+ await testTabTitle(
+ await openWin(false),
+ testPageURL,
+ false,
+ page_without_title
+ );
+ // The generic preference set to false is intended to override the PBM one
+ await testTabTitle(
+ await openWin(true),
+ testPageURL,
+ true,
+ pb_page_without_title
+ );
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle_page.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle_page.html
new file mode 100644
index 0000000000..760bde7d14
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle_page.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <title>Test title</title>
+ </head>
+ <body>
+ Test page for the window title test
+ </body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_xrprompt_page.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_xrprompt_page.html
new file mode 100644
index 0000000000..4330785df2
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_xrprompt_page.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <title>XR invoker</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ navigator.getVRDisplays();
+ </script>
+ </body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoom.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoom.js
new file mode 100644
index 0000000000..048796e7d2
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoom.js
@@ -0,0 +1,46 @@
+/* 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/. */
+
+// This test makes sure that private browsing turns off doesn't cause zoom
+// settings to be reset on tab switch (bug 464962)
+
+add_task(async function test() {
+ let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ let tabAbout = await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ "about:mozilla"
+ );
+ let tabMozilla = await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ "about:mozilla"
+ );
+
+ let mozillaZoom = win.ZoomManager.zoom;
+
+ // change the zoom on the mozilla page
+ win.FullZoom.enlarge();
+ // make sure the zoom level has been changed
+ isnot(win.ZoomManager.zoom, mozillaZoom, "Zoom level can be changed");
+ mozillaZoom = win.ZoomManager.zoom;
+
+ // switch to about: tab
+ await BrowserTestUtils.switchTab(win.gBrowser, tabAbout);
+
+ // switch back to mozilla tab
+ await BrowserTestUtils.switchTab(win.gBrowser, tabMozilla);
+
+ // make sure the zoom level has not changed
+ is(
+ win.ZoomManager.zoom,
+ mozillaZoom,
+ "Entering private browsing should not reset the zoom on a tab"
+ );
+
+ // cleanup
+ win.FullZoom.reset();
+ BrowserTestUtils.removeTab(tabMozilla);
+ BrowserTestUtils.removeTab(tabAbout);
+
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoomrestore.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoomrestore.js
new file mode 100644
index 0000000000..dd0e2e1b64
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoomrestore.js
@@ -0,0 +1,80 @@
+/* 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/. */
+
+// This test makes sure that about:privatebrowsing does not appear zoomed in
+// if there is already a zoom site pref for about:blank (bug 487656).
+
+add_task(async function test() {
+ // initialization
+ let windowsToClose = [];
+ let windowsToReset = [];
+
+ function promiseLocationChange() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function onLocationChange(subj, topic, data) {
+ Services.obs.removeObserver(onLocationChange, topic);
+ resolve();
+ }, "browser-fullZoom:location-change");
+ });
+ }
+
+ async function promiseTestReady(aIsZoomedWindow, aWindow) {
+ // Need to wait on two things, the ordering of which is not guaranteed:
+ // (1) the page load, and (2) FullZoom's update to the new page's zoom
+ // level. FullZoom broadcasts "browser-fullZoom:location-change" when its
+ // update is done. (See bug 856366 for details.)
+
+ let browser = aWindow.gBrowser.selectedBrowser;
+ BrowserTestUtils.startLoadingURIString(browser, "about:blank");
+ await Promise.all([
+ BrowserTestUtils.browserLoaded(browser),
+ promiseLocationChange(),
+ ]);
+ doTest(aIsZoomedWindow, aWindow);
+ }
+
+ function doTest(aIsZoomedWindow, aWindow) {
+ if (aIsZoomedWindow) {
+ is(
+ aWindow.ZoomManager.zoom,
+ 1,
+ "Zoom level for freshly loaded about:blank should be 1"
+ );
+ // change the zoom on the blank page
+ aWindow.FullZoom.enlarge();
+ isnot(
+ aWindow.ZoomManager.zoom,
+ 1,
+ "Zoom level for about:blank should be changed"
+ );
+ return;
+ }
+
+ // make sure the zoom level is set to 1
+ is(
+ aWindow.ZoomManager.zoom,
+ 1,
+ "Zoom level for about:privatebrowsing should be reset"
+ );
+ }
+
+ function testOnWindow(options, callback) {
+ return BrowserTestUtils.openNewBrowserWindow(options).then(win => {
+ windowsToClose.push(win);
+ windowsToReset.push(win);
+ return win;
+ });
+ }
+
+ await testOnWindow({}).then(win => promiseTestReady(true, win));
+ await testOnWindow({ private: true }).then(win =>
+ promiseTestReady(false, win)
+ );
+
+ // cleanup
+ windowsToReset.forEach(win => win.FullZoom.reset());
+ await Promise.all(
+ windowsToClose.map(win => BrowserTestUtils.closeWindow(win))
+ );
+});
diff --git a/browser/components/privatebrowsing/test/browser/empty_file.html b/browser/components/privatebrowsing/test/browser/empty_file.html
new file mode 100644
index 0000000000..0dc101b533
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/empty_file.html
@@ -0,0 +1 @@
+<html><body></body></html>
diff --git a/browser/components/privatebrowsing/test/browser/file_favicon.html b/browser/components/privatebrowsing/test/browser/file_favicon.html
new file mode 100644
index 0000000000..f294b47758
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/file_favicon.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Favicon Test for originAttributes</title>
+ <link rel="icon" type="image/png" href="file_favicon.png" />
+ </head>
+ <body>
+ Favicon!!
+ </body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/file_favicon.png b/browser/components/privatebrowsing/test/browser/file_favicon.png
new file mode 100644
index 0000000000..5535363c94
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/file_favicon.png
Binary files differ
diff --git a/browser/components/privatebrowsing/test/browser/file_favicon.png^headers^ b/browser/components/privatebrowsing/test/browser/file_favicon.png^headers^
new file mode 100644
index 0000000000..9e23c73b7f
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/file_favicon.png^headers^
@@ -0,0 +1 @@
+Cache-Control: no-cache
diff --git a/browser/components/privatebrowsing/test/browser/file_triggeringprincipal_oa.html b/browser/components/privatebrowsing/test/browser/file_triggeringprincipal_oa.html
new file mode 100644
index 0000000000..cd05e833f3
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/file_triggeringprincipal_oa.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1348801</title>
+</head>
+<body>
+ <a href="empty_file.html" id="checkPrincipalOA">checkPrincipalOA</a>
+</body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/head.js b/browser/components/privatebrowsing/test/browser/head.js
new file mode 100644
index 0000000000..f880c10a91
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/head.js
@@ -0,0 +1,163 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+ChromeUtils.defineESModuleGetters(this, {
+ ASRouter: "resource:///modules/asrouter/ASRouter.sys.mjs",
+ ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
+ ExperimentFakes: "resource://testing-common/NimbusTestUtils.sys.mjs",
+ FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
+ PanelTestProvider: "resource:///modules/asrouter/PanelTestProvider.sys.mjs",
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
+ sinon: "resource://testing-common/Sinon.sys.mjs",
+});
+
+function whenNewWindowLoaded(aOptions, aCallback) {
+ let win = OpenBrowserWindow(aOptions);
+ let focused = SimpleTest.promiseFocus(win);
+ let startupFinished = TestUtils.topicObserved(
+ "browser-delayed-startup-finished",
+ subject => subject == win
+ ).then(() => win);
+ Promise.all([focused, startupFinished]).then(results =>
+ executeSoon(() => aCallback(results[1]))
+ );
+
+ return win;
+}
+
+function openWindow(aParent, aOptions) {
+ return BrowserWindowTracker.promiseOpenWindow({
+ openerWindow: aParent,
+ ...aOptions,
+ });
+}
+
+/**
+ * Opens a new private window and loads "about:privatebrowsing" there.
+ */
+async function openAboutPrivateBrowsing() {
+ let win = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ waitForTabURL: "about:privatebrowsing",
+ });
+ let tab = win.gBrowser.selectedBrowser;
+ return { win, tab };
+}
+
+/**
+ * Wrapper for openAboutPrivateBrowsing that returns after render is complete
+ */
+async function openTabAndWaitForRender() {
+ let { win, tab } = await openAboutPrivateBrowsing();
+ await SpecialPowers.spawn(tab, [], async function () {
+ // Wait for render to complete
+ await ContentTaskUtils.waitForCondition(() =>
+ content.document.documentElement.hasAttribute(
+ "PrivateBrowsingRenderComplete"
+ )
+ );
+ });
+ return { win, tab };
+}
+
+function newDirectory() {
+ let dir = FileUtils.getDir("TmpD", ["testdir"]);
+ dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ return dir;
+}
+
+function newFileInDirectory(aDir) {
+ let file = aDir.clone();
+ file.append("testfile");
+ file.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_FILE);
+ return file;
+}
+
+function clearHistory() {
+ // simulate clearing the private data
+ Services.obs.notifyObservers(null, "browser:purge-session-history");
+}
+
+function _initTest() {
+ // Don't use about:home as the homepage for new windows
+ Services.prefs.setIntPref("browser.startup.page", 0);
+ registerCleanupFunction(() =>
+ Services.prefs.clearUserPref("browser.startup.page")
+ );
+}
+
+function waitForTelemetryEvent(category, value) {
+ info("waiting for telemetry event");
+ return TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ false
+ ).content;
+
+ if (!events) {
+ return null;
+ }
+ events = events.filter(e => e[1] == category);
+ info(JSON.stringify(events));
+
+ // Check for experimentId passed as value
+ // if exists return events only for specific experimentId
+ if (value) {
+ events = events.filter(e => e[4].includes(value));
+ }
+ if (events.length) {
+ return events[0];
+ }
+ return null;
+ }, "wait and retrieve telemetry event");
+}
+
+async function setupMSExperimentWithMessage(message) {
+ Services.telemetry.clearEvents();
+ Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ );
+ let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
+ featureId: "pbNewtab",
+ value: message,
+ });
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "browser.newtabpage.activity-stream.asrouter.providers.messaging-experiments",
+ '{"id":"messaging-experiments","enabled":true,"type":"remote-experiments","updateCycleInMs":0}',
+ ],
+ ],
+ });
+ // Reload the provider
+ await ASRouter._updateMessageProviders();
+ // Wait to load the messages from the messaging-experiments provider
+ await ASRouter.loadMessagesFromAllProviders();
+
+ // XXX this only runs at the end of the file, so some of this stuff (eg unblockAll) should be run
+ // at the bottom of various test functions too. Quite possibly other stuff beside unblockAll too.
+ registerCleanupFunction(async () => {
+ // Clear telemetry side effects
+ Services.telemetry.clearEvents();
+ // Make sure the side-effects from dismisses are cleared.
+ ASRouter.unblockAll();
+ // put the disabled providers back
+ SpecialPowers.popPrefEnv();
+ // Reload the provider again at cleanup to remove the experiment message
+ await ASRouter._updateMessageProviders();
+ // Wait to load the messages from the messaging-experiments provider
+ await ASRouter.loadMessagesFromAllProviders();
+ });
+
+ Assert.ok(
+ ASRouter.state.messages.find(m => m.id.includes(message.id)),
+ "Experiment message found in ASRouter state"
+ );
+
+ return doExperimentCleanup;
+}
+
+_initTest();
diff --git a/browser/components/privatebrowsing/test/browser/title.sjs b/browser/components/privatebrowsing/test/browser/title.sjs
new file mode 100644
index 0000000000..817f124888
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/title.sjs
@@ -0,0 +1,23 @@
+/* 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/. */
+
+// This provides the tests with a page with different titles based on whether
+// a cookie is present or not.
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var cookie = "name=value";
+ var title = "No Cookie";
+ if (request.hasHeader("Cookie") && request.getHeader("Cookie") == cookie) {
+ title = "Cookie";
+ } else {
+ response.setHeader("Set-Cookie", cookie, false);
+ }
+
+ response.write("<html><head><title>");
+ response.write(title);
+ response.write("</title><body>test page</body></html>");
+}