summaryrefslogtreecommitdiffstats
path: root/browser/components/privatebrowsing
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /browser/components/privatebrowsing
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--browser/components/privatebrowsing/content/aboutPrivateBrowsing.css8
-rw-r--r--browser/components/privatebrowsing/content/aboutPrivateBrowsing.html123
-rw-r--r--browser/components/privatebrowsing/content/aboutPrivateBrowsing.js425
-rw-r--r--browser/components/privatebrowsing/content/assets/cookie-banners-begone.svg34
-rw-r--r--browser/components/privatebrowsing/content/assets/focus-logo.svg112
-rw-r--r--browser/components/privatebrowsing/content/assets/focus-promo.pngbin0 -> 49712 bytes
-rw-r--r--browser/components/privatebrowsing/content/assets/focus-qr-code.svg114
-rw-r--r--browser/components/privatebrowsing/content/assets/klar-qr-code.svg114
-rw-r--r--browser/components/privatebrowsing/content/assets/moz-vpn.svg4
-rw-r--r--browser/components/privatebrowsing/content/assets/private-promo-asset.svg144
-rw-r--r--browser/components/privatebrowsing/content/assets/vpn-logo.svg6
-rw-r--r--browser/components/privatebrowsing/jar.mn9
-rw-r--r--browser/components/privatebrowsing/moz.build14
-rw-r--r--browser/components/privatebrowsing/test/browser/browser.ini75
-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.js82
-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.js148
-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.js461
-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.js93
-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.js61
-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_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.js99
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_urlbarfocus.js44
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle.js110
-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.js168
-rw-r--r--browser/components/privatebrowsing/test/browser/title.sjs23
76 files changed, 6672 insertions, 0 deletions
diff --git a/browser/components/privatebrowsing/content/aboutPrivateBrowsing.css b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.css
new file mode 100644
index 0000000000..26d143a7b0
--- /dev/null
+++ b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.css
@@ -0,0 +1,8 @@
+/* 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/. */
+
+html.private .showNormal,
+html.normal .showPrivate {
+ display: none !important;
+}
diff --git a/browser/components/privatebrowsing/content/aboutPrivateBrowsing.html b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.html
new file mode 100644
index 0000000000..c9be4a9090
--- /dev/null
+++ b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.html
@@ -0,0 +1,123 @@
+<!--
+# 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/.
+-->
+<!DOCTYPE html>
+
+<html xmlns="http://www.w3.org/1999/xhtml" class="private">
+ <head>
+ <meta charset="utf-8" />
+ <meta
+ http-equiv="Content-Security-Policy"
+ content="default-src chrome: blob:; object-src 'none'"
+ />
+ <meta name="color-scheme" content="light dark" />
+ <link rel="icon" href="chrome://browser/skin/privatebrowsing/favicon.svg" />
+ <link
+ rel="stylesheet"
+ href="chrome://browser/content/aboutPrivateBrowsing.css"
+ media="all"
+ />
+ <link
+ rel="stylesheet"
+ href="chrome://browser/skin/privatebrowsing/aboutPrivateBrowsing.css"
+ media="all"
+ />
+ <link rel="localization" href="branding/brand.ftl" />
+ <link rel="localization" href="toolkit/branding/brandings.ftl" />
+ <link rel="localization" href="browser/aboutPrivateBrowsing.ftl" />
+ <script src="chrome://browser/content/aboutPrivateBrowsing.js"></script>
+ <script src="chrome://browser/content/contentSearchHandoffUI.js"></script>
+ </head>
+
+ <body>
+ <p class="showNormal" data-l10n-id="about-private-browsing-not-private"></p>
+ <button
+ id="startPrivateBrowsing"
+ class="showNormal"
+ data-l10n-id="privatebrowsingpage-open-private-window-label"
+ ></button>
+ <div id="search-banner" class="search-banner" hidden="true">
+ <button
+ id="search-banner-close-button"
+ class="search-banner-close-button"
+ data-l10n-id="about-private-browsing-search-banner-close-button"
+ >
+ <img
+ class="search-banner-close-image"
+ src="chrome://global/skin/icons/close.svg"
+ />
+ </button>
+ <div class="banner-body">
+ <h1
+ id="about-private-browsing-search-banner-title"
+ data-l10n-id="about-private-browsing-search-banner-title"
+ data-l10n-args='{"engineName": ""}'
+ ></h1>
+ <p
+ id="about-private-browsing-search-banner-description"
+ data-l10n-id="about-private-browsing-search-banner-description"
+ >
+ <a
+ href=""
+ id="open-search-options-link"
+ data-l10n-name="link-options"
+ ></a>
+ </p>
+ </div>
+ </div>
+ <div class="showPrivate showSearch container">
+ <div class="logo-and-wordmark">
+ <div id="about-private-browsing-logo" class="logo"></div>
+ <div class="wordmark"></div>
+ </div>
+ <div class="search-inner-wrapper">
+ <button
+ id="search-handoff-button"
+ class="search-handoff-button"
+ tabindex="-1"
+ aria-hidden="true"
+ >
+ <div class="fake-textbox"></div>
+ <input
+ id="fake-editable"
+ class="fake-editable"
+ tabindex="-1"
+ aria-hidden="true"
+ />
+ <div class="fake-caret"></div>
+ </button>
+ </div>
+ <div class="info">
+ <h1 id="info-title"></h1>
+ <p id="info-body"></p>
+ <a id="private-browsing-myths"></a>
+ </div>
+ </div>
+
+ <div class="promo" hidden>
+ <div class="promo-image-large">
+ <img src="" alt="" role="presentation" />
+ </div>
+ <div class="promo-content">
+ <h1 id="promo-header"></h1>
+ <p id="private-browsing-promo-text" class="vpn-promo"></p>
+ <div class="promo-cta">
+ <button
+ id="private-browsing-promo-link"
+ class="vpn-promo primary"
+ ></button>
+ <div class="promo-image-small">
+ <img src="" alt="" />
+ </div>
+ </div>
+ </div>
+ <button
+ data-l10n-id="about-private-browsing-promo-close-button"
+ id="dismiss-btn"
+ class="promo-dismiss"
+ ></button>
+ </div>
+ </body>
+</html>
diff --git a/browser/components/privatebrowsing/content/aboutPrivateBrowsing.js b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.js
new file mode 100644
index 0000000000..2a64c064a5
--- /dev/null
+++ b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.js
@@ -0,0 +1,425 @@
+/* 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-env mozilla/remote-page */
+
+/**
+ * Determines whether a given value is a fluent id or plain text and adds it to an element
+ * @param {Array<[HTMLElement, string]>} items An array of [element, value] where value is
+ * a fluent id starting with "fluent:" or plain text
+ */
+function translateElements(items) {
+ items.forEach(([element, value]) => {
+ // Skip empty text or elements
+ if (!element || !value) {
+ return;
+ }
+ const fluentId = value.replace(/^fluent:/, "");
+ if (fluentId !== value) {
+ document.l10n.setAttributes(element, fluentId);
+ } else {
+ element.textContent = value;
+ element.removeAttribute("data-l10n-id");
+ }
+ });
+}
+
+function renderInfo({
+ infoEnabled = false,
+ infoTitle,
+ infoTitleEnabled,
+ infoBody,
+ infoLinkText,
+ infoLinkUrl,
+ infoIcon,
+} = {}) {
+ const container = document.querySelector(".info");
+ if (!infoEnabled) {
+ container.remove();
+ return;
+ }
+
+ const titleEl = document.getElementById("info-title");
+ const bodyEl = document.getElementById("info-body");
+ const linkEl = document.getElementById("private-browsing-myths");
+
+ if (infoIcon) {
+ container.style.backgroundImage = `url(${infoIcon})`;
+ }
+
+ if (!infoTitleEnabled) {
+ titleEl.remove();
+ }
+
+ translateElements([
+ [titleEl, infoTitle],
+ [bodyEl, infoBody],
+ [linkEl, infoLinkText],
+ ]);
+
+ linkEl.setAttribute(
+ "href",
+ infoLinkUrl ||
+ RPMGetFormatURLPref("app.support.baseURL") + "private-browsing-myths"
+ );
+ linkEl.setAttribute("target", "_blank");
+
+ linkEl.addEventListener("click", () => {
+ window.PrivateBrowsingRecordClick("info_link");
+ });
+}
+
+async function renderPromo({
+ messageId = null,
+ promoEnabled = false,
+ promoType = "VPN",
+ promoTitle,
+ promoTitleEnabled,
+ promoLinkText,
+ promoLinkType,
+ promoSectionStyle,
+ promoHeader,
+ promoImageLarge,
+ promoImageSmall,
+ promoButton = null,
+} = {}) {
+ const shouldShow = await RPMSendQuery("ShouldShowPromo", { type: promoType });
+ const container = document.querySelector(".promo");
+
+ if (!promoEnabled || !shouldShow) {
+ container.remove();
+ return false;
+ }
+
+ const titleEl = document.getElementById("private-browsing-promo-text");
+ const linkEl = document.getElementById("private-browsing-promo-link");
+ const promoHeaderEl = document.getElementById("promo-header");
+ const infoContainerEl = document.querySelector(".info");
+ const promoImageLargeEl = document.querySelector(".promo-image-large img");
+ const promoImageSmallEl = document.querySelector(".promo-image-small img");
+ const dismissBtn = document.querySelector("#dismiss-btn");
+
+ if (promoLinkType === "link") {
+ linkEl.classList.remove("primary");
+ linkEl.classList.add("text-link", "promo-link");
+ }
+
+ if (promoButton?.action) {
+ linkEl.addEventListener("click", async event => {
+ event.preventDefault();
+
+ // Record promo click telemetry and set metrics as allow for spotlight
+ // modal opened on promo click if user is enrolled in an experiment
+ let isExperiment = window.PrivateBrowsingRecordClick("promo_link");
+ const promoButtonData = promoButton?.action?.data;
+ if (
+ promoButton?.action?.type === "SHOW_SPOTLIGHT" &&
+ promoButtonData?.content
+ ) {
+ promoButtonData.content.metrics = isExperiment ? "allow" : "block";
+ }
+
+ await RPMSendQuery("SpecialMessageActionDispatch", promoButton.action);
+ });
+ } else {
+ // If the action doesn't exist, remove the promo completely
+ container.remove();
+ return false;
+ }
+
+ const onDismissBtnClick = () => {
+ window.ASRouterMessage({
+ type: "BLOCK_MESSAGE_BY_ID",
+ data: { id: messageId },
+ });
+ window.PrivateBrowsingRecordClick("dismiss_button");
+ container.remove();
+ };
+
+ if (dismissBtn && messageId) {
+ dismissBtn.addEventListener("click", onDismissBtnClick, { once: true });
+ }
+
+ if (promoSectionStyle) {
+ container.classList.add(promoSectionStyle);
+
+ switch (promoSectionStyle) {
+ case "below-search":
+ container.remove();
+ infoContainerEl?.insertAdjacentElement("beforebegin", container);
+ break;
+ case "top":
+ container.remove();
+ document.body.insertAdjacentElement("afterbegin", container);
+ }
+ }
+
+ if (promoImageLarge) {
+ promoImageLargeEl.src = promoImageLarge;
+ } else {
+ promoImageLargeEl.parentNode.remove();
+ }
+
+ if (promoImageSmall) {
+ promoImageSmallEl.src = promoImageSmall;
+ } else {
+ promoImageSmallEl.parentNode.remove();
+ }
+
+ if (!promoTitleEnabled) {
+ titleEl.remove();
+ }
+
+ if (!promoHeader) {
+ promoHeaderEl.remove();
+ }
+
+ translateElements([
+ [titleEl, promoTitle],
+ [linkEl, promoLinkText],
+ [promoHeaderEl, promoHeader],
+ ]);
+
+ // Only make promo section visible after adding content
+ // and translations to prevent layout shifting in page
+ container.classList.add("promo-visible");
+ return true;
+}
+
+/**
+ * For every PB newtab loaded, a second is pre-rendered in the background.
+ * We need to guard against invalid impressions by checking visibility state.
+ * If visible, record. Otherwise, listen for visibility change and record later.
+ */
+function recordOnceVisible(message) {
+ const recordImpression = () => {
+ if (document.visibilityState === "visible") {
+ window.ASRouterMessage({
+ type: "IMPRESSION",
+ data: message,
+ });
+ // Similar telemetry, but for Nimbus experiments
+ window.PrivateBrowsingExposureTelemetry();
+ document.removeEventListener("visibilitychange", recordImpression);
+ }
+ };
+
+ if (document.visibilityState === "visible") {
+ window.ASRouterMessage({
+ type: "IMPRESSION",
+ data: message,
+ });
+ // Similar telemetry, but for Nimbus experiments
+ window.PrivateBrowsingExposureTelemetry();
+ } else {
+ document.addEventListener("visibilitychange", recordImpression);
+ }
+}
+
+// The PB newtab may be pre-rendered. Once the tab is visible, check to make sure the message wasn't blocked after the initial render. If it was, remove the promo.
+function handlePromoOnPreload(message) {
+ async function removePromoIfBlocked() {
+ if (document.visibilityState === "visible") {
+ let blocked = await RPMSendQuery("IsPromoBlocked", message);
+ if (blocked) {
+ const container = document.querySelector(".promo");
+ container.remove();
+ }
+ }
+ document.removeEventListener("visibilitychange", removePromoIfBlocked);
+ }
+ // Only add the listener to pre-rendered tabs that aren't visible
+ if (document.visibilityState !== "visible") {
+ document.addEventListener("visibilitychange", removePromoIfBlocked);
+ }
+}
+
+async function setupMessageConfig(config = null) {
+ let message = null;
+
+ if (!config) {
+ let hideDefault = window.PrivateBrowsingShouldHideDefault();
+ try {
+ let response = await window.ASRouterMessage({
+ type: "PBNEWTAB_MESSAGE_REQUEST",
+ data: { hideDefault: !!hideDefault },
+ });
+ message = response?.message;
+ config = message?.content;
+ config.messageId = message?.id;
+ } catch (e) {}
+ }
+
+ renderInfo(config);
+ let hasRendered = await renderPromo(config);
+ if (hasRendered && message) {
+ recordOnceVisible(message);
+ handlePromoOnPreload(message);
+ }
+ // For tests
+ document.documentElement.setAttribute("PrivateBrowsingRenderComplete", true);
+}
+
+let SHOW_DEVTOOLS_MESSAGE = "ShowDevToolsMessage";
+
+function showDevToolsMessage(msg) {
+ msg.data.content.messageId = "DEVTOOLS_MESSAGE";
+ setupMessageConfig(msg?.data?.content);
+ RPMRemoveMessageListener(SHOW_DEVTOOLS_MESSAGE, showDevToolsMessage);
+}
+
+document.addEventListener("DOMContentLoaded", function () {
+ // check the url to see if we're rendering a devtools message
+ if (document.location.toString().includes("debug")) {
+ RPMAddMessageListener(SHOW_DEVTOOLS_MESSAGE, showDevToolsMessage);
+ return;
+ }
+ if (!RPMIsWindowPrivate()) {
+ document.documentElement.classList.remove("private");
+ document.documentElement.classList.add("normal");
+ document
+ .getElementById("startPrivateBrowsing")
+ .addEventListener("click", function () {
+ RPMSendAsyncMessage("OpenPrivateWindow");
+ });
+ return;
+ }
+
+ let newLogoEnabled = window.PrivateBrowsingEnableNewLogo();
+ document
+ .getElementById("about-private-browsing-logo")
+ .toggleAttribute("legacy", !newLogoEnabled);
+
+ // We don't do this setup until now, because we don't want to record any impressions until we're
+ // sure we're actually running a private window, not just about:privatebrowsing in a normal window.
+ setupMessageConfig();
+
+ // Set up the private search banner.
+ const privateSearchBanner = document.getElementById("search-banner");
+
+ RPMSendQuery("ShouldShowSearchBanner", {}).then(engineName => {
+ if (engineName) {
+ document.l10n.setAttributes(
+ document.getElementById("about-private-browsing-search-banner-title"),
+ "about-private-browsing-search-banner-title",
+ { engineName }
+ );
+ privateSearchBanner.removeAttribute("hidden");
+ document.body.classList.add("showBanner");
+ }
+
+ // We set this attribute so that tests know when we are done.
+ document.documentElement.setAttribute("SearchBannerInitialized", true);
+ });
+
+ function hideSearchBanner() {
+ privateSearchBanner.hidden = true;
+ document.body.classList.remove("showBanner");
+ RPMSendAsyncMessage("SearchBannerDismissed");
+ }
+
+ document
+ .getElementById("search-banner-close-button")
+ .addEventListener("click", () => {
+ hideSearchBanner();
+ });
+
+ let openSearchOptions = document.getElementById(
+ "about-private-browsing-search-banner-description"
+ );
+ let openSearchOptionsEvtHandler = evt => {
+ if (
+ evt.target.id == "open-search-options-link" &&
+ (evt.keyCode == evt.DOM_VK_RETURN || evt.type == "click")
+ ) {
+ RPMSendAsyncMessage("OpenSearchPreferences");
+ hideSearchBanner();
+ }
+ };
+ openSearchOptions.addEventListener("click", openSearchOptionsEvtHandler);
+ openSearchOptions.addEventListener("keypress", openSearchOptionsEvtHandler);
+
+ // Setup the search hand-off box.
+ let btn = document.getElementById("search-handoff-button");
+ RPMSendQuery("ShouldShowSearch", {}).then(
+ ([engineName, shouldHandOffToSearchMode]) => {
+ let input = document.querySelector(".fake-textbox");
+ if (shouldHandOffToSearchMode) {
+ document.l10n.setAttributes(btn, "about-private-browsing-search-btn");
+ document.l10n.setAttributes(
+ input,
+ "about-private-browsing-search-placeholder"
+ );
+ } else if (engineName) {
+ document.l10n.setAttributes(btn, "about-private-browsing-handoff", {
+ engine: engineName,
+ });
+ document.l10n.setAttributes(
+ input,
+ "about-private-browsing-handoff-text",
+ {
+ engine: engineName,
+ }
+ );
+ } else {
+ document.l10n.setAttributes(
+ btn,
+ "about-private-browsing-handoff-no-engine"
+ );
+ document.l10n.setAttributes(
+ input,
+ "about-private-browsing-handoff-text-no-engine"
+ );
+ }
+ }
+ );
+
+ let editable = document.getElementById("fake-editable");
+ let DISABLE_SEARCH_TOPIC = "DisableSearch";
+ let SHOW_SEARCH_TOPIC = "ShowSearch";
+ let SEARCH_HANDOFF_TOPIC = "SearchHandoff";
+
+ function showSearch() {
+ btn.classList.remove("focused");
+ btn.classList.remove("disabled");
+ RPMRemoveMessageListener(SHOW_SEARCH_TOPIC, showSearch);
+ }
+
+ function disableSearch() {
+ btn.classList.add("disabled");
+ }
+
+ function handoffSearch(text) {
+ RPMSendAsyncMessage(SEARCH_HANDOFF_TOPIC, { text });
+ RPMAddMessageListener(SHOW_SEARCH_TOPIC, showSearch);
+ if (text) {
+ disableSearch();
+ } else {
+ btn.classList.add("focused");
+ RPMAddMessageListener(DISABLE_SEARCH_TOPIC, disableSearch);
+ }
+ }
+ btn.addEventListener("focus", function () {
+ handoffSearch();
+ });
+ btn.addEventListener("click", function () {
+ handoffSearch();
+ });
+
+ // Hand-off any text that gets dropped or pasted
+ editable.addEventListener("drop", function (ev) {
+ ev.preventDefault();
+ let text = ev.dataTransfer.getData("text");
+ if (text) {
+ handoffSearch(text);
+ }
+ });
+ editable.addEventListener("paste", function (ev) {
+ ev.preventDefault();
+ handoffSearch(ev.clipboardData.getData("Text"));
+ });
+
+ // Load contentSearchUI so it sets the search engine icon and name for us.
+ new window.ContentSearchHandoffUIController();
+});
diff --git a/browser/components/privatebrowsing/content/assets/cookie-banners-begone.svg b/browser/components/privatebrowsing/content/assets/cookie-banners-begone.svg
new file mode 100644
index 0000000000..66e47020cd
--- /dev/null
+++ b/browser/components/privatebrowsing/content/assets/cookie-banners-begone.svg
@@ -0,0 +1,34 @@
+<!-- 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/. -->
+<svg width="204" height="157" viewBox="0 0 204 157" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.17" d="M115.519 43.5388C134.333 55.7818 140.573 61.0189 172.319 67.711C204.064 74.403 213.487 114.04 156.229 115.501C127.032 112.793 117.144 125.82 85.1204 140.74C53.0965 155.66 32.2804 140.74 32.2804 124.94C32.2804 112.233 19.2493 106.73 14.1714 98.0791C9.09353 89.4279 12.9554 83.4625 49.0529 68.5243C66.797 61.1813 48.6934 46.2048 58.7293 32.4458C68.7652 18.6868 92.0002 28.235 115.519 43.5388Z" fill="#EAE1FE"/>
+<g clip-path="url(#clip0_4123_37239)">
+<rect x="39.1201" y="48.7402" width="113.695" height="74.9052" rx="6.68797" fill="#42414D"/>
+<path d="M45.7534 88.1315L46.4509 88.0725C48.7392 87.8789 49.88 86.9939 50.4607 86.2398C50.7593 85.8521 50.9264 85.4772 51.0182 85.2039C51.064 85.0674 51.0906 84.9577 51.1052 84.8865C51.1125 84.8509 51.1168 84.8252 51.119 84.8109L51.1207 84.7988L51.1207 84.7985L51.1208 84.7981L45.7534 88.1315ZM45.7534 88.1315L45.8124 88.829C47.0785 103.798 60.2395 114.906 75.2082 113.64C90.1768 112.374 101.285 99.2126 100.019 84.2439C98.7527 69.2752 85.5918 58.1671 70.6231 59.4332L69.9705 59.4884L69.9822 60.1432C69.989 60.5232 69.998 60.8919 70.0068 61.2502C70.0662 63.6767 70.1139 65.6249 69.37 67.3752C68.548 69.3091 66.6872 71.1072 62.2759 72.7945L61.7451 72.9975L61.8347 73.5586C62.2738 76.3096 61.8453 78.2966 61.0198 79.7457C60.1903 81.2019 58.9192 82.1859 57.5536 82.8519C56.1854 83.5192 54.7496 83.8531 53.6445 84.0185C53.0945 84.1009 52.6326 84.1406 52.3105 84.1597C52.1495 84.1692 52.0239 84.1736 51.9401 84.1756C51.8982 84.1766 51.8668 84.177 51.8467 84.1771L51.8251 84.1772L51.8211 84.1772L45.7534 88.1315Z" fill="#42414D" stroke="white" stroke-width="1.4"/>
+<circle cx="81.0152" cy="94.0785" r="5.30683" transform="rotate(-4.83489 81.0152 94.0785)" fill="#EAE1FE" stroke="white" stroke-width="0.7"/>
+<circle cx="60.0222" cy="94.7062" r="3.40036" transform="rotate(-4.83489 60.0222 94.7062)" fill="#EAE1FE" stroke="white" stroke-width="0.7"/>
+<circle cx="82.2873" cy="72.925" r="3.40036" transform="rotate(-4.83489 82.2873 72.925)" fill="#EAE1FE" stroke="white" stroke-width="0.7"/>
+<circle cx="85.8787" cy="83.718" r="1.90647" transform="rotate(-4.83489 85.8787 83.718)" fill="#8A52FF" stroke="white" stroke-width="0.7"/>
+<circle cx="67.6419" cy="85.261" r="1.90647" transform="rotate(-4.83489 67.6419 85.261)" fill="#8A52FF" stroke="white" stroke-width="0.7"/>
+<circle cx="70.827" cy="100.297" r="1.90647" transform="rotate(-4.83489 70.827 100.297)" fill="#8A52FF" stroke="white" stroke-width="0.7"/>
+<circle cx="91.1733" cy="76.1911" r="0.571942" transform="rotate(-4.83489 91.1733 76.1911)" fill="white" stroke="white" stroke-width="0.904114"/>
+<circle cx="69.9321" cy="75.7375" r="0.571942" transform="rotate(-4.83489 69.9321 75.7375)" fill="white" stroke="white" stroke-width="0.904114"/>
+<circle cx="72.6982" cy="108.436" r="0.571942" transform="rotate(-4.83489 72.6982 108.436)" fill="white" stroke="white" stroke-width="0.904114"/>
+<circle cx="60.3051" cy="103.496" r="0.571942" transform="rotate(-4.83489 60.3051 103.496)" fill="white" stroke="white" stroke-width="0.904114"/>
+<circle cx="50.3813" cy="92.3606" r="0.571942" transform="rotate(-4.83489 50.3813 92.3606)" fill="white" stroke="white" stroke-width="0.904114"/>
+<circle cx="93.7353" cy="91.6872" r="0.571942" transform="rotate(-4.83489 93.7353 91.6872)" fill="white" stroke="white" stroke-width="0.904114"/>
+<rect x="129.12" y="104.74" width="19" height="6" rx="0.697128" fill="#8A52FF" stroke="white" stroke-width="0.701824"/>
+<rect x="107.12" y="104.74" width="19" height="6" rx="0.697128" fill="#E7DEFD" stroke="white" stroke-width="0.701824"/>
+</g>
+<rect x="38.4183" y="48.0384" width="115.099" height="76.3089" rx="7.38979" stroke="white" stroke-width="1.40365"/>
+<path d="M170 56C170 68.7025 159.703 79 147 79C134.297 79 124 68.7025 124 56C124 43.2975 134.297 33 147 33C159.703 33 170 43.2975 170 56Z" fill="#E7DEFD" fill-opacity="0.8"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M130.203 40.2878C131.346 39.0672 132.619 37.9711 134.002 37.0223C137.7 34.4848 142.176 33 147 33C159.703 33 170 43.2975 170 56C170 61.0584 168.367 65.7354 165.6 69.5326C164.612 70.887 163.481 72.1294 162.228 73.2373C158.172 76.8235 152.84 79 147 79C134.297 79 124 68.7026 124 56C124 49.9246 126.356 44.3993 130.203 40.2878ZM158.738 69.6467C155.586 72.3599 151.485 74 147 74C137.059 74 129 65.9411 129 56C129 51.331 130.778 47.0772 133.693 43.8784L158.738 69.6467ZM162.049 65.8794L137.553 40.6756C140.299 38.979 143.535 38 147 38C156.941 38 165 46.0589 165 56C165 59.6484 163.915 63.0433 162.049 65.8794Z" fill="#8A52FF"/>
+<path d="M128.12 79.7402H107.12" stroke="white" stroke-width="1.54777" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M166.12 92.7402L107.12 92.7402" stroke="white" stroke-width="1.54777" stroke-linecap="round" stroke-linejoin="round"/>
+<defs>
+<clipPath id="clip0_4123_37239">
+<rect x="39.1201" y="48.7402" width="113.695" height="74.9052" rx="6.68797" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/browser/components/privatebrowsing/content/assets/focus-logo.svg b/browser/components/privatebrowsing/content/assets/focus-logo.svg
new file mode 100644
index 0000000000..30fa505b2a
--- /dev/null
+++ b/browser/components/privatebrowsing/content/assets/focus-logo.svg
@@ -0,0 +1,112 @@
+<!-- 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/. -->
+<svg width="40" height="42" viewBox="0 0 40 42" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M38.5557 14.0864C37.6848 11.961 35.9222 9.66165 34.538 8.9361C35.6646 11.1804 36.3167 13.4314 36.5658 15.1114C36.5658 15.1114 36.5658 15.123 36.5658 15.1452C34.3012 9.41046 30.4613 7.09761 27.3259 2.06228C27.1671 1.80723 27.0093 1.55218 26.8505 1.28264C26.7621 1.12903 26.6918 0.99281 26.63 0.86142C26.5 0.605869 26.3998 0.335783 26.3314 0.0566488C26.3318 0.0434884 26.3273 0.0306749 26.3189 0.0206794C26.3104 0.0106838 26.2986 0.00422416 26.2858 0.00255317C26.2733 -0.000851057 26.2602 -0.000851057 26.2478 0.00255317L26.2383 0.00834366C26.2332 0.0103302 26.2284 0.0129327 26.224 0.01608L26.2316 0.00448726C21.2014 2.99941 19.4949 8.53711 19.338 11.3079C17.3282 11.4474 15.4065 12.1996 13.8239 13.4662C13.6581 13.324 13.4848 13.1911 13.3048 13.0681C12.8486 11.4453 12.8292 9.7277 13.2487 8.09463C11.1914 9.04624 9.59135 10.5514 8.42863 11.8798H8.43243C7.63859 10.8586 7.69468 7.48791 7.73936 6.78362C7.72986 6.74015 7.14707 7.09084 7.07387 7.14398C6.3732 7.65229 5.71814 8.22258 5.11636 8.84818C4.43175 9.55372 3.80624 10.316 3.24631 11.1272C1.95709 12.9817 1.04202 15.0772 0.553904 17.2929C0.544397 17.3364 0.535841 17.3827 0.527285 17.4272C0.489256 17.6059 0.353303 18.5025 0.329535 18.6976C0.329535 18.7121 0.329535 18.7266 0.329535 18.742C0.153917 19.6686 0.0452477 20.6069 0.00439453 21.5495C0.00439453 21.5843 0.00439453 21.6181 0.00439453 21.6529C0.00439453 32.8906 8.97053 42 20.0301 42C29.9346 42 38.1592 34.6934 39.7688 25.0932C39.803 24.8333 39.8296 24.5715 39.8638 24.3087C40.2527 20.8211 39.8058 17.1528 38.5557 14.0864ZM15.4743 30.0136C15.5694 30.059 15.6559 30.1102 15.7519 30.1517L15.7653 30.1604C15.6683 30.1131 15.5713 30.0638 15.4743 30.0136ZM36.5734 15.151V15.1308C36.573 15.1382 36.573 15.1456 36.5734 15.153V15.151Z" fill="url(#paint0_linear_1208_5805)"/>
+<path d="M38.557 14.0866C37.6861 11.9612 35.9235 9.66182 34.5393 8.93628C35.6659 11.1805 36.318 13.4316 36.5671 15.1116C36.5671 15.1068 36.5671 15.1116 36.5671 15.1309C36.5667 15.1383 36.5667 15.1457 36.5671 15.1531C38.4562 20.3585 37.4228 25.6508 35.9435 28.8853C33.6485 33.8898 28.0925 39.0188 19.4011 38.7686C10.0053 38.4981 1.72839 31.4136 0.183486 22.1352C-0.101727 20.6725 0.183485 19.9286 0.325141 18.7413C0.152112 19.6571 0.0865145 19.9218 0 21.5488C0 21.5835 0 21.6173 0 21.6521C0 32.8898 8.96614 41.9992 20.0257 41.9992C29.9302 41.9992 38.1548 34.6926 39.7644 25.0924C39.7986 24.8325 39.8252 24.5707 39.8594 24.3079C40.254 20.8213 39.8071 17.153 38.557 14.0866Z" fill="url(#paint1_radial_1208_5805)"/>
+<path d="M38.557 14.0866C37.6861 11.9612 35.9235 9.66182 34.5393 8.93628C35.6659 11.1805 36.318 13.4316 36.5671 15.1116C36.5671 15.1068 36.5671 15.1116 36.5671 15.1309C36.5667 15.1383 36.5667 15.1457 36.5671 15.1531C38.4562 20.3585 37.4228 25.6508 35.9435 28.8853C33.6485 33.8898 28.0925 39.0188 19.4011 38.7686C10.0053 38.4981 1.72839 31.4136 0.183486 22.1352C-0.101727 20.6725 0.183485 19.9286 0.325141 18.7413C0.152112 19.6571 0.0865145 19.9218 0 21.5488C0 21.5835 0 21.6173 0 21.6521C0 32.8898 8.96614 41.9992 20.0257 41.9992C29.9302 41.9992 38.1548 34.6926 39.7644 25.0924C39.7986 24.8325 39.8252 24.5707 39.8594 24.3079C40.254 20.8213 39.8071 17.153 38.557 14.0866Z" fill="url(#paint2_radial_1208_5805)"/>
+<path d="M28.8253 16.4772C28.8691 16.5081 28.909 16.539 28.9499 16.5738C28.4477 15.6659 27.8219 14.8346 27.0912 14.1044C20.866 7.77933 25.4589 0.38575 26.2356 0.0147666L26.2432 0.00317383C21.213 2.99809 19.5065 8.53579 19.3496 11.3066C19.5825 11.2902 19.8155 11.2699 20.0531 11.2699C23.8046 11.2708 27.0751 13.3682 28.8253 16.4772Z" fill="url(#paint3_radial_1208_5805)"/>
+<path d="M20.1041 17.7395C20.0718 18.2457 18.312 19.9915 17.696 19.9915C12.0012 19.9915 11.0771 23.4917 11.0771 23.4917C11.3291 26.4402 13.3484 28.8671 15.7946 30.152C15.9058 30.2109 16.0189 30.2641 16.1321 30.3162C16.3279 30.4042 16.5238 30.4863 16.7206 30.5607C17.5588 30.8623 18.437 31.0342 19.3255 31.0708C29.308 31.547 31.2407 18.9442 24.0372 15.2846C25.8825 14.9581 27.7973 15.7126 28.8659 16.4759C27.1156 13.3669 23.8461 11.2686 20.0918 11.2686C19.8541 11.2686 19.6164 11.2888 19.3882 11.3053C17.3784 11.4448 15.4568 12.197 13.8741 13.4635C14.1793 13.7263 14.5235 14.077 15.2498 14.8045C16.6103 16.1648 20.0965 17.5733 20.1041 17.7395Z" fill="url(#paint4_radial_1208_5805)"/>
+<path d="M20.1041 17.7395C20.0718 18.2457 18.312 19.9915 17.696 19.9915C12.0012 19.9915 11.0771 23.4917 11.0771 23.4917C11.3291 26.4402 13.3484 28.8671 15.7946 30.152C15.9058 30.2109 16.0189 30.2641 16.1321 30.3162C16.3279 30.4042 16.5238 30.4863 16.7206 30.5607C17.5588 30.8623 18.437 31.0342 19.3255 31.0708C29.308 31.547 31.2407 18.9442 24.0372 15.2846C25.8825 14.9581 27.7973 15.7126 28.8659 16.4759C27.1156 13.3669 23.8461 11.2686 20.0918 11.2686C19.8541 11.2686 19.6164 11.2888 19.3882 11.3053C17.3784 11.4448 15.4568 12.197 13.8741 13.4635C14.1793 13.7263 14.5235 14.077 15.2498 14.8045C16.6103 16.1648 20.0965 17.5733 20.1041 17.7395Z" fill="url(#paint5_radial_1208_5805)"/>
+<path d="M12.9021 12.7888C13.0647 12.896 13.1988 12.982 13.3176 13.068C12.8613 11.4451 12.842 9.72755 13.2615 8.09448C11.2042 9.04609 9.60412 10.5513 8.44141 11.8797C8.53648 11.8826 11.4409 11.8236 12.9021 12.7888Z" fill="url(#paint6_radial_1208_5805)"/>
+<path d="M0.185822 22.1349C1.73073 31.4095 10.0076 38.4978 19.4035 38.7683C28.0996 39.0185 33.6555 33.8895 35.9458 28.8851C37.4289 25.6505 38.4585 20.3582 36.5695 15.1529V15.1326C36.5695 15.1181 36.5695 15.1084 36.5695 15.1133C36.5695 15.1181 36.5695 15.1249 36.5695 15.1471C37.2797 19.8607 34.92 24.4217 31.2322 27.5132L31.2208 27.5393C24.0344 33.4856 17.1579 31.1274 15.7651 30.1613C15.67 30.1139 15.575 30.0647 15.4742 30.0144C11.2835 27.9789 9.55316 24.0999 9.92394 20.7737C6.38635 20.7737 5.1799 17.7411 5.1799 17.7411C5.1799 17.7411 8.35622 15.4398 12.5422 17.4416C16.4201 19.2955 20.0614 17.742 20.0614 17.742C20.0537 17.5759 16.5684 16.1673 15.2127 14.807C14.4864 14.0805 14.1422 13.7298 13.8371 13.467C13.6713 13.3248 13.498 13.192 13.318 13.069C13.1991 12.9859 13.0651 12.897 12.9025 12.7898C11.4422 11.8237 8.5378 11.8787 8.44178 11.8807H8.43418C7.64034 10.8595 7.69643 7.48877 7.74111 6.78448C7.7316 6.74101 7.14882 7.0917 7.07561 7.14484C6.37494 7.65315 5.71989 8.22344 5.1181 8.84904C4.4335 9.55458 3.80798 10.3169 3.24806 11.1281V11.1281C1.95895 12.9822 1.04389 15.0774 0.55565 17.2928C0.54329 17.3353 -0.164989 20.4954 0.185822 22.1349Z" fill="url(#paint7_radial_1208_5805)"/>
+<path d="M27.0926 14.1048C27.8231 14.8338 28.4488 15.6638 28.9513 16.5702C29.0616 16.6543 29.1642 16.7383 29.2517 16.8195C33.7923 21.0704 31.4136 27.0834 31.2358 27.5114C34.9236 24.4198 37.2833 19.8579 36.5731 15.1452C34.3085 9.41046 30.4686 7.09761 27.3332 2.06228C27.1744 1.80723 27.0166 1.55218 26.8578 1.28264C26.7694 1.12903 26.699 0.99281 26.6372 0.86142C26.5073 0.605869 26.4071 0.335783 26.3387 0.0566488C26.3391 0.0434884 26.3346 0.0306749 26.3262 0.0206794C26.3177 0.0106838 26.3059 0.00422416 26.2931 0.00255317C26.2806 -0.000851057 26.2675 -0.000851057 26.2551 0.00255317L26.2456 0.00834366C26.2405 0.0103302 26.2357 0.0129327 26.2313 0.01608C25.4603 0.390928 20.8674 7.78064 27.0926 14.1048Z" fill="url(#paint8_radial_1208_5805)"/>
+<path d="M29.2531 16.8202C29.1656 16.7391 29.063 16.655 28.9527 16.571C28.9118 16.5391 28.8719 16.5082 28.8281 16.4744C27.7595 15.7111 25.8448 14.9576 23.9995 15.2831C31.203 18.9427 29.2693 31.5436 19.2878 31.0673C18.3989 31.0312 17.5204 30.8592 16.6819 30.5572C16.486 30.4829 16.2902 30.4007 16.0943 30.3128C15.9812 30.2606 15.8681 30.2075 15.7568 30.1486L15.7701 30.1573C17.1629 31.1234 24.0413 33.4816 31.2258 27.5353L31.2372 27.5092C31.4103 27.0841 33.7889 21.0711 29.2531 16.8202Z" fill="url(#paint9_radial_1208_5805)"/>
+<path d="M11.0364 23.4921C11.0364 23.4921 11.9604 19.9919 17.6552 19.9919C18.2712 19.9919 20.032 18.2472 20.0633 17.7409C20.0633 17.7409 16.4221 19.2944 12.5442 17.4405C8.36106 15.4387 5.18188 17.74 5.18188 17.74C5.18188 17.74 6.38833 20.7726 9.92592 20.7726C9.55515 24.0988 11.2854 27.9778 15.4762 30.0133C15.5712 30.0587 15.6577 30.1099 15.7538 30.1515C13.3076 28.8675 11.2864 26.4397 11.0364 23.4921Z" fill="url(#paint10_radial_1208_5805)"/>
+<path d="M38.556 14.0864C37.6852 11.961 35.9226 9.66165 34.5383 8.9361C35.6649 11.1804 36.3171 13.4314 36.5662 15.1114C36.5662 15.1114 36.5662 15.123 36.5662 15.1452C34.3016 9.41046 30.4617 7.09761 27.3263 2.06228C27.1675 1.80723 27.0097 1.55218 26.8509 1.28264C26.7625 1.12903 26.6921 0.99281 26.6303 0.86142C26.5004 0.605869 26.4002 0.335783 26.3318 0.0566488C26.3322 0.0434884 26.3277 0.0306749 26.3192 0.0206794C26.3108 0.0106838 26.299 0.00422416 26.2862 0.00255317C26.2737 -0.000851057 26.2606 -0.000851057 26.2482 0.00255317L26.2386 0.00834366C26.2336 0.0103302 26.2288 0.0129327 26.2244 0.01608L26.232 0.00448726C21.2018 2.99941 19.4953 8.53711 19.3384 11.3079C19.5713 11.2915 19.8042 11.2712 20.0419 11.2712C23.7953 11.2712 27.0658 13.3686 28.816 16.4775C27.7474 15.7143 25.8327 14.9607 23.9874 15.2863C31.1909 18.9459 29.2571 31.5468 19.2757 31.0705C18.3868 31.0343 17.5083 30.8624 16.6698 30.5604C16.4739 30.486 16.2781 30.4039 16.0822 30.316C15.9691 30.2638 15.856 30.2106 15.7447 30.1517L15.758 30.1604C15.663 30.1131 15.5679 30.0638 15.4671 30.0136C15.5622 30.059 15.6487 30.1102 15.7447 30.1517C13.2985 28.8678 11.2764 26.44 11.0273 23.4924C11.0273 23.4924 11.9514 19.9922 17.6461 19.9922C18.2622 19.9922 20.0229 18.2474 20.0543 17.7412C20.0467 17.575 16.5614 16.1664 15.2057 14.8061C14.4793 14.0796 14.1352 13.7289 13.83 13.4662C13.6642 13.324 13.4909 13.1911 13.3109 13.0681C12.8546 11.4453 12.8353 9.7277 13.2548 8.09463C11.1975 9.04624 9.59743 10.5514 8.43472 11.8798H8.43282C7.63897 10.8586 7.69507 7.48791 7.73975 6.78362C7.73024 6.74015 7.14746 7.09084 7.07425 7.14398C6.37358 7.65229 5.71853 8.22258 5.11674 8.84818C4.43214 9.55372 3.80662 10.316 3.2467 11.1272C1.95748 12.9817 1.04241 15.0772 0.55429 17.2929C0.544783 17.3364 0.536227 17.3827 0.52767 17.4272C0.489642 17.6059 0.320416 18.515 0.296649 18.7102C0.141955 19.6497 0.0432169 20.5979 0.000976562 21.5495C0.000976563 21.5843 0.000976562 21.6181 0.000976562 21.6529C0.000976562 32.8906 8.96712 42 20.0267 42C29.9312 42 38.1558 34.6934 39.7653 25.0932C39.7996 24.8333 39.8262 24.5715 39.8604 24.3087C40.2531 20.8211 39.8062 17.1528 38.556 14.0864ZM36.5738 15.1308C36.5734 15.1382 36.5734 15.1456 36.5738 15.153V15.153V15.1308Z" fill="url(#paint11_linear_1208_5805)"/>
+<defs>
+<linearGradient id="paint0_linear_1208_5805" x1="37.9795" y1="10.7833" x2="3.01599" y2="32.4565" gradientUnits="userSpaceOnUse">
+<stop stop-color="#9658F9"/>
+<stop offset="0.13" stop-color="#9356F4"/>
+<stop offset="0.29" stop-color="#8951E5"/>
+<stop offset="0.46" stop-color="#7A49CD"/>
+<stop offset="0.64" stop-color="#643DAB"/>
+<stop offset="0.82" stop-color="#492E81"/>
+<stop offset="1" stop-color="#291D4F"/>
+</linearGradient>
+<radialGradient id="paint1_radial_1208_5805" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(33.0035 6.68683) rotate(112.511) scale(42.3344 44.2048)">
+<stop stop-color="#9658F9"/>
+<stop offset="0.65" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint2_radial_1208_5805" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(23.4226 26.0616) scale(41.7118 42.3872)">
+<stop offset="0.25" stop-color="#AB71FF" stop-opacity="0"/>
+<stop offset="0.42" stop-color="#9462E0" stop-opacity="0.18"/>
+<stop offset="0.73" stop-color="#573B8D" stop-opacity="0.65"/>
+<stop offset="0.94" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint3_radial_1208_5805" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(28.4515 -0.882057) scale(30.1533 30.6415)">
+<stop offset="0.18" stop-color="#9658F9"/>
+<stop offset="0.33" stop-color="#7E48EA"/>
+<stop offset="0.37" stop-color="#7542E5"/>
+<stop offset="0.41" stop-color="#6A3DD0"/>
+<stop offset="0.48" stop-color="#5332A2"/>
+<stop offset="0.56" stop-color="#41297E"/>
+<stop offset="0.63" stop-color="#342264"/>
+<stop offset="0.69" stop-color="#2C1E54"/>
+<stop offset="0.74" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint4_radial_1208_5805" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(14.2687 31.0708) scale(19.8784 20.2002)">
+<stop offset="0.07" stop-color="#9358FC"/>
+<stop offset="0.13" stop-color="#9857F8"/>
+<stop offset="0.21" stop-color="#A755EB"/>
+<stop offset="0.3" stop-color="#BF52D7"/>
+<stop offset="0.39" stop-color="#E14EBA"/>
+<stop offset="0.42" stop-color="#EF4CAF"/>
+<stop offset="0.74" stop-color="#FF7583"/>
+<stop offset="0.97" stop-color="#FFB753"/>
+</radialGradient>
+<radialGradient id="paint5_radial_1208_5805" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(19.0521 16.5915) rotate(-14.1133) scale(10.5406 12.5596)">
+<stop offset="0.22" stop-color="#FFB653" stop-opacity="0.3"/>
+<stop offset="0.34" stop-color="#FF807A" stop-opacity="0.5"/>
+<stop offset="0.44" stop-color="#FF7781" stop-opacity="0.48"/>
+<stop offset="0.57" stop-color="#FF5C94" stop-opacity="0.43"/>
+<stop offset="0.64" stop-color="#FF4AA2" stop-opacity="0.4"/>
+<stop offset="0.86" stop-color="#9658F9" stop-opacity="0.3"/>
+</radialGradient>
+<radialGradient id="paint6_radial_1208_5805" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(22.8961 7.00279) scale(14.2894 14.5208)">
+<stop offset="0.03" stop-color="#9658F9"/>
+<stop offset="0.62" stop-color="#7542E5"/>
+<stop offset="0.72" stop-color="#6339C2"/>
+<stop offset="0.93" stop-color="#37246B"/>
+<stop offset="1" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint7_radial_1208_5805" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(25.1644 -10.6352) rotate(102.662) scale(59.4963 46.0276)">
+<stop offset="0.2" stop-color="#AB71FF"/>
+<stop offset="0.29" stop-color="#A46BFC"/>
+<stop offset="0.41" stop-color="#9059F2"/>
+<stop offset="0.54" stop-color="#7542E5"/>
+<stop offset="0.64" stop-color="#6239BF"/>
+<stop offset="0.94" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint8_radial_1208_5805" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(26.3744 7.62676) rotate(84.0697) scale(45.3695 29.3571)">
+<stop offset="0.09" stop-color="#9658F9"/>
+<stop offset="0.14" stop-color="#8C51F3"/>
+<stop offset="0.26" stop-color="#7542E5"/>
+<stop offset="0.29" stop-color="#6E3ED6"/>
+<stop offset="0.4" stop-color="#5533A6"/>
+<stop offset="0.5" stop-color="#422980"/>
+<stop offset="0.59" stop-color="#342365"/>
+<stop offset="0.67" stop-color="#2C1E55"/>
+<stop offset="0.74" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint9_radial_1208_5805" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(22.5593 12.3798) scale(38.0569 38.6731)">
+<stop stop-color="#9658F9"/>
+<stop offset="0.12" stop-color="#9155F6"/>
+<stop offset="0.25" stop-color="#834CEE"/>
+<stop offset="0.34" stop-color="#7542E5"/>
+<stop offset="0.78" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint10_radial_1208_5805" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(32.607 14.6408) scale(41.5672 42.2403)">
+<stop offset="0.19" stop-color="#9658F9"/>
+<stop offset="0.49" stop-color="#834BED"/>
+<stop offset="0.66" stop-color="#7542E5"/>
+<stop offset="0.7" stop-color="#6B3DD2"/>
+<stop offset="0.79" stop-color="#52319F"/>
+<stop offset="0.91" stop-color="#291D4F"/>
+</radialGradient>
+<linearGradient id="paint11_linear_1208_5805" x1="35.5033" y1="6.33149" x2="6.7645" y2="34.6124" gradientUnits="userSpaceOnUse">
+<stop stop-color="#9059FF" stop-opacity="0.9"/>
+<stop offset="0.61" stop-color="#291D4F" stop-opacity="0"/>
+</linearGradient>
+</defs>
+</svg>
diff --git a/browser/components/privatebrowsing/content/assets/focus-promo.png b/browser/components/privatebrowsing/content/assets/focus-promo.png
new file mode 100644
index 0000000000..b7badd3cdd
--- /dev/null
+++ b/browser/components/privatebrowsing/content/assets/focus-promo.png
Binary files differ
diff --git a/browser/components/privatebrowsing/content/assets/focus-qr-code.svg b/browser/components/privatebrowsing/content/assets/focus-qr-code.svg
new file mode 100644
index 0000000000..f182567314
--- /dev/null
+++ b/browser/components/privatebrowsing/content/assets/focus-qr-code.svg
@@ -0,0 +1,114 @@
+<!-- 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/. -->
+<svg width="113" height="113" viewBox="0 0 113 113" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="113" height="113" rx="4" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M4 2H109C110.105 2 111 2.89543 111 4V109C111 110.105 110.105 111 109 111H4C2.89543 111 2 110.105 2 109V4C2 2.89543 2.89543 2 4 2ZM0 4C0 1.79086 1.79086 0 4 0H109C111.209 0 113 1.79086 113 4V109C113 111.209 111.209 113 109 113H4C1.79086 113 0 111.209 0 109V4ZM103 10H99.7931H96.5862H93.3793H90.1724H86.9655H83.7586H80.5517V13.2069V16.4138V19.6207V22.8276V26.0345V29.2414V32.4483H83.7586H86.9655H90.1724H93.3793H96.5862H99.7931H103V29.2414V26.0345V22.8276V19.6207V16.4138V13.2069V10ZM96.5862 13.2069H99.7931V16.4138V19.6207V22.8276V26.0345V29.2414H96.5862H93.3793H90.1724H86.9655H83.7586V26.0345V22.8276V19.6207V16.4138V13.2069H86.9655H90.1724H93.3793H96.5862ZM77.3448 10H74.1379H70.931H67.7241V13.2069H64.5172H61.3103V16.4138V19.6207H58.1034H54.8966V16.4138V13.2069V10H51.6897V13.2069H48.4828V10H45.2759H42.069V13.2069H38.8621V10H35.6552V13.2069H38.8621V16.4138H35.6552V19.6207H38.8621V22.8276H35.6552V26.0345H38.8621H42.069V29.2414V32.4483H38.8621V29.2414H35.6552V32.4483H38.8621V35.6552V38.8621H42.069H45.2759V35.6552H42.069V32.4483H45.2759V29.2414V26.0345V22.8276H48.4828V26.0345H51.6897V22.8276H54.8966H58.1034H61.3103V26.0345H58.1034V29.2414H54.8966V32.4483H51.6897V29.2414H48.4828V32.4483V35.6552V38.8621H51.6897V42.069H54.8966V38.8621H58.1034H61.3103V35.6552H64.5172V38.8621V42.069H67.7241V45.2759H70.931H74.1379V42.069H77.3448H80.5517V45.2759H83.7586V48.4828H80.5517V51.6897H83.7586V54.8966H80.5517H77.3448H74.1379V51.6897V48.4828H70.931V51.6897V54.8966V58.1034H74.1379V61.3103H77.3448V64.5172V67.7241H74.1379V70.931H70.931V67.7241H67.7241V70.931H64.5172V74.1379H67.7241V70.931H70.931V74.1379H74.1379V77.3448H70.931H67.7241V80.5517H64.5172V83.7586H61.3103V86.9655H58.1034V83.7586H54.8966V86.9655H58.1034V90.1724H54.8966V93.3793H51.6897V90.1724V86.9655V83.7586V80.5517V77.3448H48.4828V74.1379H51.6897V70.931H48.4828V74.1379H45.2759V70.931V67.7241H42.069V64.5172V61.3103H38.8621V58.1034V54.8966H42.069V51.6897H38.8621V48.4828H35.6552V45.2759H38.8621V42.069H35.6552V38.8621V35.6552H32.4483H29.2414H26.0345V38.8621H22.8276V42.069H19.6207V45.2759H22.8276V48.4828H26.0345V51.6897H22.8276H19.6207V48.4828H16.4138V45.2759H13.2069V48.4828H10V51.6897V54.8966H13.2069H16.4138V51.6897H19.6207V54.8966H22.8276H26.0345V58.1034H22.8276H19.6207V61.3103H16.4138V58.1034H13.2069V61.3103H10V64.5172V67.7241H13.2069H16.4138V70.931H13.2069H10V74.1379H13.2069H16.4138V77.3448H13.2069H10V80.5517H13.2069H16.4138V77.3448H19.6207V80.5517H22.8276V83.7586H19.6207V86.9655H16.4138V83.7586H13.2069H10V86.9655V90.1724H13.2069V86.9655H16.4138V90.1724H19.6207H22.8276H26.0345V93.3793H22.8276H19.6207H16.4138H13.2069V96.5862V99.7931H16.4138V96.5862H19.6207V99.7931H22.8276V103H26.0345V99.7931V96.5862H29.2414H32.4483V99.7931H29.2414V103H32.4483V99.7931H35.6552H38.8621V103H42.069H45.2759V99.7931H48.4828V103H51.6897V99.7931H54.8966H58.1034V103H61.3103H64.5172V99.7931H67.7241V103H70.931V99.7931H74.1379V96.5862H77.3448V93.3793V90.1724V86.9655H74.1379V83.7586V80.5517H77.3448V77.3448V74.1379H80.5517V77.3448H83.7586H86.9655H90.1724H93.3793V74.1379H96.5862V70.931V67.7241H93.3793V64.5172H90.1724V61.3103H93.3793H96.5862V64.5172H99.7931V67.7241V70.931V74.1379V77.3448H103V74.1379V70.931V67.7241V64.5172H99.7931V61.3103H96.5862V58.1034H99.7931V54.8966H103V51.6897H99.7931V54.8966H96.5862V58.1034H93.3793V54.8966H90.1724V58.1034V61.3103H86.9655V58.1034H83.7586V54.8966H86.9655V51.6897H90.1724H93.3793H96.5862V48.4828H93.3793V45.2759V42.069H96.5862V45.2759H99.7931V42.069H103V38.8621H99.7931V42.069H96.5862V38.8621V35.6552H93.3793H90.1724H86.9655V38.8621H90.1724V42.069V45.2759V48.4828H86.9655V45.2759V42.069H83.7586H80.5517V38.8621H83.7586V35.6552H80.5517V38.8621H77.3448V35.6552V32.4483V29.2414V26.0345H74.1379V22.8276H70.931V26.0345H74.1379V29.2414V32.4483V35.6552V38.8621H70.931H67.7241V35.6552H70.931V32.4483V29.2414H67.7241V26.0345V22.8276H64.5172V19.6207V16.4138H67.7241H70.931V19.6207H74.1379H77.3448V16.4138V13.2069V10ZM64.5172 29.2414H67.7241V32.4483H64.5172V29.2414ZM61.3103 29.2414V32.4483H58.1034V29.2414H61.3103ZM61.3103 29.2414H64.5172V26.0345H61.3103V29.2414ZM83.7586 58.1034V61.3103H80.5517V58.1034H83.7586ZM90.1724 64.5172V67.7241V70.931H93.3793V74.1379H90.1724H86.9655V70.931V67.7241H83.7586H80.5517V64.5172H83.7586H86.9655H90.1724ZM80.5517 74.1379V70.931H83.7586V74.1379H80.5517ZM70.931 90.1724V93.3793H74.1379V96.5862H70.931H67.7241V93.3793V90.1724H70.931ZM64.5172 90.1724V86.9655H67.7241V90.1724H64.5172ZM61.3103 93.3793V90.1724H64.5172V93.3793H61.3103ZM61.3103 93.3793V96.5862H64.5172V99.7931H61.3103H58.1034V96.5862V93.3793H61.3103ZM70.931 90.1724V86.9655H74.1379V90.1724H70.931ZM45.2759 99.7931H42.069H38.8621V96.5862H42.069V93.3793H45.2759H48.4828V96.5862H45.2759V99.7931ZM16.4138 74.1379V70.931H19.6207H22.8276H26.0345V74.1379H22.8276H19.6207H16.4138ZM13.2069 61.3103H16.4138V64.5172H13.2069V61.3103ZM29.2414 51.6897V54.8966H26.0345V51.6897H29.2414ZM29.2414 51.6897V48.4828H32.4483V51.6897H29.2414ZM22.8276 45.2759V42.069H26.0345V45.2759H22.8276ZM26.0345 58.1034H29.2414V61.3103H32.4483V58.1034H35.6552V61.3103H38.8621V64.5172H35.6552H32.4483H29.2414H26.0345H22.8276V61.3103H26.0345V58.1034ZM42.069 80.5517H38.8621V77.3448V74.1379V70.931H35.6552V74.1379H32.4483V70.931V67.7241H35.6552H38.8621H42.069V70.931V74.1379V77.3448V80.5517ZM42.069 86.9655V83.7586V80.5517H45.2759V77.3448H48.4828V80.5517V83.7586V86.9655V90.1724H45.2759V86.9655H42.069ZM42.069 86.9655V90.1724H38.8621V86.9655H42.069ZM54.8966 38.8621H51.6897V35.6552H54.8966V38.8621ZM42.069 13.2069V16.4138H45.2759V19.6207H48.4828V16.4138V13.2069H45.2759H42.069ZM58.1034 74.1379V70.931H61.3103V74.1379H58.1034ZM32.4483 10H29.2414H26.0345H22.8276H19.6207H16.4138H13.2069H10V13.2069V16.4138V19.6207V22.8276V26.0345V29.2414V32.4483H13.2069H16.4138H19.6207H22.8276H26.0345H29.2414H32.4483V29.2414V26.0345V22.8276V19.6207V16.4138V13.2069V10ZM26.0345 13.2069H29.2414V16.4138V19.6207V22.8276V26.0345V29.2414H26.0345H22.8276H19.6207H16.4138H13.2069V26.0345V22.8276V19.6207V16.4138V13.2069H16.4138H19.6207H22.8276H26.0345ZM96.5862 16.4138H93.3793H90.1724H86.9655V19.6207V22.8276V26.0345H90.1724H93.3793H96.5862V22.8276V19.6207V16.4138ZM26.0345 16.4138H22.8276H19.6207H16.4138V19.6207V22.8276V26.0345H19.6207H22.8276H26.0345V22.8276V19.6207V16.4138ZM19.6207 35.6552H16.4138H13.2069H10V38.8621V42.069H13.2069V38.8621H16.4138H19.6207V35.6552ZM32.4483 77.3448H35.6552V80.5517V83.7586V86.9655H32.4483H29.2414H26.0345V83.7586V80.5517V77.3448H29.2414H32.4483ZM61.3103 77.3448H58.1034V80.5517H61.3103V77.3448ZM103 80.5517H99.7931H96.5862H93.3793H90.1724H86.9655H83.7586H80.5517V83.7586V86.9655V90.1724V93.3793V96.5862V99.7931V103H83.7586H86.9655H90.1724H93.3793H96.5862H99.7931H103V99.7931V96.5862V93.3793V90.1724V86.9655V83.7586V80.5517ZM96.5862 83.7586H99.7931V86.9655V90.1724V93.3793V96.5862V99.7931H96.5862H93.3793H90.1724H86.9655H83.7586V96.5862V93.3793V90.1724V86.9655V83.7586H86.9655H90.1724H93.3793H96.5862ZM32.4483 80.5517H29.2414V83.7586H32.4483V80.5517ZM96.5862 86.9655H93.3793H90.1724H86.9655V90.1724V93.3793V96.5862H90.1724H93.3793H96.5862V93.3793V90.1724V86.9655ZM29.2414 90.1724H32.4483V93.3793H29.2414V90.1724Z" fill="#1C1B22"/>
+<path d="M66.2056 52.7475C65.7266 51.5785 64.7572 50.3139 63.9959 49.9148C64.6155 51.1492 64.9742 52.3872 65.1112 53.3113C65.1112 53.3113 65.1112 53.3177 65.1112 53.3299C63.8657 50.1757 61.7537 48.9037 60.0292 46.1343C59.9419 45.994 59.8551 45.8537 59.7678 45.7055C59.7192 45.621 59.6805 45.546 59.6465 45.4738C59.575 45.3332 59.5199 45.1847 59.4823 45.0312C59.4825 45.0239 59.48 45.0169 59.4754 45.0114C59.4707 45.0059 59.4643 45.0023 59.4572 45.0014C59.4503 44.9995 59.4431 44.9995 59.4363 45.0014L59.431 45.0046C59.4283 45.0057 59.4256 45.0071 59.4232 45.0088L59.4274 45.0025C56.6608 46.6497 55.7222 49.6954 55.6359 51.2193C54.5305 51.2961 53.4736 51.7098 52.6032 52.4064C52.512 52.3282 52.4167 52.2551 52.3177 52.1875C52.0667 51.2949 52.0561 50.3502 52.2868 49.452C51.1553 49.9754 50.2753 50.8033 49.6358 51.5339H49.6379C49.2012 50.9722 49.2321 49.1183 49.2567 48.731C49.2514 48.7071 48.9309 48.9 48.8906 48.9292C48.5053 49.2088 48.145 49.5224 47.814 49.8665C47.4375 50.2545 47.0935 50.6738 46.7855 51.12C46.0764 52.1399 45.5731 53.2924 45.3047 54.5111C45.2994 54.535 45.2947 54.5605 45.29 54.5849C45.2691 54.6832 45.1943 55.1763 45.1813 55.2837C45.1813 55.2916 45.1813 55.2996 45.1813 55.3081C45.0847 55.8177 45.0249 56.3338 45.0024 56.8522C45.0024 56.8714 45.0024 56.89 45.0024 56.9091C45.0024 63.0898 49.9338 68.1 56.0166 68.1C61.464 68.1 65.9876 64.0813 66.8728 58.8012C66.8916 58.6583 66.9063 58.5143 66.9251 58.3698C67.139 56.4516 66.8932 54.434 66.2056 52.7475ZM53.5109 61.5074C53.5632 61.5324 53.6108 61.5606 53.6636 61.5834L53.6709 61.5882C53.6176 61.5622 53.5642 61.5351 53.5109 61.5074ZM65.1154 53.3331V53.3219C65.1151 53.326 65.1151 53.3301 65.1154 53.3341V53.3331Z" fill="url(#paint0_linear_1186_7637)"/>
+<path d="M66.2063 52.7477C65.7273 51.5787 64.7579 50.3141 63.9966 49.915C64.6162 51.1494 64.9749 52.3874 65.1119 53.3115C65.1119 53.3088 65.1119 53.3115 65.1119 53.3221C65.1116 53.3262 65.1116 53.3302 65.1119 53.3343C66.1509 56.1973 65.5825 59.108 64.7689 60.887C63.5066 63.6394 60.4509 66.4604 55.6706 66.3228C50.5029 66.174 45.9506 62.2776 45.1009 57.1744C44.9441 56.3699 45.1009 55.9608 45.1788 55.3078C45.0837 55.8115 45.0476 55.9571 45 56.8519C45 56.871 45 56.8896 45 56.9087C45 63.0895 49.9314 68.0996 56.0141 68.0996C61.4616 68.0996 65.9851 64.081 66.8704 58.8009C66.8892 58.658 66.9038 58.514 66.9227 58.3694C67.1397 56.4518 66.8939 54.4342 66.2063 52.7477Z" fill="url(#paint1_radial_1186_7637)"/>
+<path d="M66.2063 52.7477C65.7273 51.5787 64.7579 50.3141 63.9966 49.915C64.6162 51.1494 64.9749 52.3874 65.1119 53.3115C65.1119 53.3088 65.1119 53.3115 65.1119 53.3221C65.1116 53.3262 65.1116 53.3302 65.1119 53.3343C66.1509 56.1973 65.5825 59.108 64.7689 60.887C63.5066 63.6394 60.4509 66.4604 55.6706 66.3228C50.5029 66.174 45.9506 62.2776 45.1009 57.1744C44.9441 56.3699 45.1009 55.9608 45.1788 55.3078C45.0837 55.8115 45.0476 55.9571 45 56.8519C45 56.871 45 56.8896 45 56.9087C45 63.0895 49.9314 68.0996 56.0141 68.0996C61.4616 68.0996 65.9851 64.081 66.8704 58.8009C66.8892 58.658 66.9038 58.514 66.9227 58.3694C67.1397 56.4518 66.8939 54.4342 66.2063 52.7477Z" fill="url(#paint2_radial_1186_7637)"/>
+<path d="M60.854 54.0624C60.878 54.0794 60.9 54.0964 60.9225 54.1155C60.6463 53.6162 60.3021 53.159 59.9002 52.7574C56.4763 49.2786 59.0024 45.2121 59.4296 45.0081L59.4338 45.0017C56.6672 46.6489 55.7286 49.6946 55.6423 51.2186C55.7704 51.2095 55.8986 51.1984 56.0293 51.1984C58.0926 51.1989 59.8913 52.3525 60.854 54.0624Z" fill="url(#paint3_radial_1186_7637)"/>
+<path d="M56.0574 54.7568C56.0396 55.0352 55.0717 55.9954 54.7329 55.9954C51.6008 55.9954 51.0925 57.9205 51.0925 57.9205C51.2311 59.5422 52.3417 60.8769 53.6871 61.5836C53.7483 61.616 53.8105 61.6453 53.8727 61.674C53.9804 61.7223 54.0882 61.7675 54.1964 61.8084C54.6575 61.9743 55.1404 62.0688 55.6291 62.089C61.1195 62.3509 62.1825 55.4194 58.2206 53.4066C59.2355 53.227 60.2886 53.642 60.8763 54.0618C59.9137 52.3519 58.1155 51.1978 56.0506 51.1978C55.9198 51.1978 55.7891 51.2089 55.6636 51.2179C54.5582 51.2947 53.5013 51.7084 52.6309 52.405C52.7987 52.5495 52.988 52.7424 53.3875 53.1425C54.1357 53.8907 56.0532 54.6654 56.0574 54.7568Z" fill="url(#paint4_radial_1186_7637)"/>
+<path d="M56.0574 54.7568C56.0396 55.0352 55.0717 55.9954 54.7329 55.9954C51.6008 55.9954 51.0925 57.9205 51.0925 57.9205C51.2311 59.5422 52.3417 60.8769 53.6871 61.5836C53.7483 61.616 53.8105 61.6453 53.8727 61.674C53.9804 61.7223 54.0882 61.7675 54.1964 61.8084C54.6575 61.9743 55.1404 62.0688 55.6291 62.089C61.1195 62.3509 62.1825 55.4194 58.2206 53.4066C59.2355 53.227 60.2886 53.642 60.8763 54.0618C59.9137 52.3519 58.1155 51.1978 56.0506 51.1978C55.9198 51.1978 55.7891 51.2089 55.6636 51.2179C54.5582 51.2947 53.5013 51.7084 52.6309 52.405C52.7987 52.5495 52.988 52.7424 53.3875 53.1425C54.1357 53.8907 56.0532 54.6654 56.0574 54.7568Z" fill="url(#paint5_radial_1186_7637)"/>
+<path d="M52.0962 52.0338C52.1856 52.0927 52.2594 52.14 52.3247 52.1873C52.0738 51.2947 52.0631 50.3501 52.2939 49.4519C51.1623 49.9753 50.2823 50.8031 49.6428 51.5338C49.6951 51.5353 51.2925 51.5029 52.0962 52.0338Z" fill="url(#paint6_radial_1186_7637)"/>
+<path d="M45.1022 57.1743C45.9519 62.2753 50.5041 66.1738 55.6719 66.3226C60.4547 66.4602 63.5105 63.6393 64.7701 60.8868C65.5858 59.1079 66.1521 56.1971 65.1132 53.3341V53.323C65.1132 53.315 65.1132 53.3097 65.1132 53.3124C65.1132 53.315 65.1132 53.3187 65.1132 53.331C65.5038 55.9234 64.2059 58.432 62.1777 60.1323L62.1714 60.1467C58.2189 63.4172 54.4368 62.1201 53.6708 61.5888C53.6185 61.5627 53.5662 61.5356 53.5108 61.508C51.2059 60.3884 50.2542 58.255 50.4581 56.4256C48.5125 56.4256 47.8489 54.7576 47.8489 54.7576C47.8489 54.7576 49.5959 53.492 51.8982 54.5929C54.031 55.6126 56.0337 54.7582 56.0337 54.7582C56.0295 54.6668 54.1126 53.8921 53.367 53.1439C52.9675 52.7443 52.7782 52.5515 52.6103 52.4069C52.5192 52.3287 52.4239 52.2557 52.3248 52.188C52.2595 52.1423 52.1858 52.0934 52.0963 52.0344C51.2932 51.5031 49.6958 51.5334 49.6429 51.5344H49.6388C49.2022 50.9728 49.233 49.1189 49.2576 48.7315C49.2524 48.7076 48.9318 48.9005 48.8916 48.9297C48.5062 49.2093 48.1459 49.523 47.8149 49.867C47.4384 50.2551 47.0944 50.6744 46.7864 51.1205C46.0774 52.1403 45.5741 53.2927 45.3056 54.5111C45.2988 54.5345 44.9092 56.2725 45.1022 57.1743Z" fill="url(#paint7_radial_1186_7637)"/>
+<path d="M59.9009 52.7576C60.3027 53.1586 60.6468 53.6151 60.9232 54.1136C60.9839 54.1599 61.0403 54.2061 61.0884 54.2507C63.5858 56.5887 62.2775 59.8958 62.1797 60.1312C64.208 58.4309 65.5058 55.9218 65.1152 53.3299C63.8697 50.1757 61.7577 48.9037 60.0332 46.1343C59.9459 45.994 59.8591 45.8537 59.7718 45.7055C59.7232 45.621 59.6845 45.546 59.6505 45.4738C59.579 45.3332 59.5239 45.1847 59.4863 45.0312C59.4865 45.0239 59.484 45.0169 59.4794 45.0114C59.4747 45.0059 59.4683 45.0023 59.4612 45.0014C59.4543 44.9995 59.4471 44.9995 59.4403 45.0014L59.4351 45.0046C59.4323 45.0057 59.4296 45.0071 59.4272 45.0088C59.0031 45.215 56.4771 49.2793 59.9009 52.7576Z" fill="url(#paint8_radial_1186_7637)"/>
+<path d="M61.0892 54.251C61.0411 54.2064 60.9846 54.1601 60.924 54.1139C60.9015 54.0964 60.8795 54.0794 60.8555 54.0608C60.2677 53.641 59.2146 53.2265 58.1997 53.4056C62.1616 55.4184 61.0981 62.3488 55.6083 62.0869C55.1194 62.067 54.6362 61.9724 54.175 61.8063C54.0673 61.7654 53.9596 61.7203 53.8519 61.6719C53.7897 61.6432 53.7274 61.614 53.6663 61.5816L53.6736 61.5864C54.4396 62.1177 58.2227 63.4148 62.1742 60.1443L62.1805 60.1299C62.2756 59.8961 63.5839 56.589 61.0892 54.251Z" fill="url(#paint9_radial_1186_7637)"/>
+<path d="M51.07 57.9207C51.07 57.9207 51.5783 55.9956 54.7104 55.9956C55.0492 55.9956 56.0176 55.036 56.0349 54.7576C56.0349 54.7576 54.0322 55.612 51.8994 54.5923C49.5986 53.4913 47.8501 54.757 47.8501 54.757C47.8501 54.757 48.5136 56.425 50.4593 56.425C50.2554 58.2544 51.207 60.3878 53.5119 61.5074C53.5642 61.5324 53.6118 61.5605 53.6646 61.5834C52.3192 60.8772 51.2076 59.5419 51.07 57.9207Z" fill="url(#paint10_radial_1186_7637)"/>
+<path d="M66.2057 52.7475C65.7268 51.5785 64.7573 50.3139 63.996 49.9148C64.6156 51.1492 64.9743 52.3872 65.1113 53.3113C65.1113 53.3113 65.1113 53.3177 65.1113 53.3299C63.8658 50.1757 61.7539 48.9037 60.0294 46.1343C59.942 45.994 59.8552 45.8537 59.7679 45.7055C59.7193 45.621 59.6806 45.546 59.6466 45.4738C59.5751 45.3332 59.52 45.1847 59.4824 45.0312C59.4826 45.0239 59.4802 45.0169 59.4755 45.0114C59.4709 45.0059 59.4644 45.0023 59.4573 45.0014C59.4505 44.9995 59.4433 44.9995 59.4364 45.0014L59.4312 45.0046C59.4284 45.0057 59.4258 45.0071 59.4233 45.0088L59.4275 45.0025C56.6609 46.6497 55.7223 49.6954 55.6361 51.2193C55.7642 51.2103 55.8923 51.1991 56.023 51.1991C58.0874 51.1991 59.8861 52.3527 60.8487 54.0626C60.261 53.6428 59.2079 53.2284 58.193 53.4074C62.1549 55.4202 61.0914 62.3507 55.6015 62.0887C55.1126 62.0688 54.6295 61.9743 54.1683 61.8082C54.0606 61.7673 53.9529 61.7221 53.8452 61.6738C53.7829 61.6451 53.7207 61.6158 53.6595 61.5834L53.6669 61.5882C53.6146 61.5622 53.5623 61.5351 53.5069 61.5074C53.5591 61.5324 53.6067 61.5606 53.6595 61.5834C52.3141 60.8772 51.202 59.542 51.065 57.9208C51.065 57.9208 51.5732 55.9957 54.7053 55.9957C55.0441 55.9957 56.0125 55.0361 56.0298 54.7576C56.0256 54.6662 54.1087 53.8915 53.3631 53.1434C52.9636 52.7438 52.7743 52.5509 52.6064 52.4064C52.5153 52.3282 52.42 52.2551 52.3209 52.1875C52.07 51.2949 52.0593 50.3502 52.2901 49.452C51.1586 49.9754 50.2785 50.8033 49.639 51.5339H49.638C49.2014 50.9722 49.2322 49.1183 49.2568 48.731C49.2516 48.7071 48.931 48.9 48.8908 48.9292C48.5054 49.2088 48.1451 49.5224 47.8142 49.8665C47.4376 50.2545 47.0936 50.6738 46.7856 51.12C46.0766 52.1399 45.5733 53.2924 45.3048 54.5111C45.2996 54.535 45.2949 54.5605 45.2902 54.5849C45.2693 54.6832 45.1762 55.1832 45.1631 55.2906C45.078 55.8073 45.0237 56.3288 45.0005 56.8522C45.0005 56.8714 45.0005 56.89 45.0005 56.9091C45.0005 63.0898 49.9319 68.1 56.0146 68.1C61.4621 68.1 65.9856 64.0813 66.8709 58.8012C66.8897 58.6583 66.9043 58.5143 66.9231 58.3698C67.1391 56.4516 66.8933 54.434 66.2057 52.7475ZM65.1155 53.3219C65.1153 53.326 65.1153 53.3301 65.1155 53.3341V53.3219Z" fill="url(#paint11_linear_1186_7637)"/>
+<defs>
+<linearGradient id="paint0_linear_1186_7637" x1="65.8887" y1="50.9308" x2="46.6588" y2="62.8511" gradientUnits="userSpaceOnUse">
+<stop stop-color="#9658F9"/>
+<stop offset="0.13" stop-color="#9356F4"/>
+<stop offset="0.29" stop-color="#8951E5"/>
+<stop offset="0.46" stop-color="#7A49CD"/>
+<stop offset="0.64" stop-color="#643DAB"/>
+<stop offset="0.82" stop-color="#492E81"/>
+<stop offset="1" stop-color="#291D4F"/>
+</linearGradient>
+<radialGradient id="paint1_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(63.1519 48.6778) rotate(112.511) scale(23.2839 24.3126)">
+<stop stop-color="#9658F9"/>
+<stop offset="0.65" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint2_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(57.8824 59.334) scale(22.9415 23.3129)">
+<stop offset="0.25" stop-color="#AB71FF" stop-opacity="0"/>
+<stop offset="0.42" stop-color="#9462E0" stop-opacity="0.18"/>
+<stop offset="0.73" stop-color="#573B8D" stop-opacity="0.65"/>
+<stop offset="0.94" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint3_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(60.6484 44.5148) scale(16.5843 16.8528)">
+<stop offset="0.18" stop-color="#9658F9"/>
+<stop offset="0.33" stop-color="#7E48EA"/>
+<stop offset="0.37" stop-color="#7542E5"/>
+<stop offset="0.41" stop-color="#6A3DD0"/>
+<stop offset="0.48" stop-color="#5332A2"/>
+<stop offset="0.56" stop-color="#41297E"/>
+<stop offset="0.63" stop-color="#342264"/>
+<stop offset="0.69" stop-color="#2C1E54"/>
+<stop offset="0.74" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint4_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(52.8479 62.089) scale(10.9331 11.1101)">
+<stop offset="0.07" stop-color="#9358FC"/>
+<stop offset="0.13" stop-color="#9857F8"/>
+<stop offset="0.21" stop-color="#A755EB"/>
+<stop offset="0.3" stop-color="#BF52D7"/>
+<stop offset="0.39" stop-color="#E14EBA"/>
+<stop offset="0.42" stop-color="#EF4CAF"/>
+<stop offset="0.74" stop-color="#FF7583"/>
+<stop offset="0.97" stop-color="#FFB753"/>
+</radialGradient>
+<radialGradient id="paint5_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(55.4787 54.1254) rotate(-14.1133) scale(5.79733 6.90775)">
+<stop offset="0.22" stop-color="#FFB653" stop-opacity="0.3"/>
+<stop offset="0.34" stop-color="#FF807A" stop-opacity="0.5"/>
+<stop offset="0.44" stop-color="#FF7781" stop-opacity="0.48"/>
+<stop offset="0.57" stop-color="#FF5C94" stop-opacity="0.43"/>
+<stop offset="0.64" stop-color="#FF4AA2" stop-opacity="0.4"/>
+<stop offset="0.86" stop-color="#9658F9" stop-opacity="0.3"/>
+</radialGradient>
+<radialGradient id="paint6_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(57.5929 48.8515) scale(7.85915 7.9864)">
+<stop offset="0.03" stop-color="#9658F9"/>
+<stop offset="0.62" stop-color="#7542E5"/>
+<stop offset="0.72" stop-color="#6339C2"/>
+<stop offset="0.93" stop-color="#37246B"/>
+<stop offset="1" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint7_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(58.8404 39.1507) rotate(102.662) scale(32.7229 25.3152)">
+<stop offset="0.2" stop-color="#AB71FF"/>
+<stop offset="0.29" stop-color="#A46BFC"/>
+<stop offset="0.41" stop-color="#9059F2"/>
+<stop offset="0.54" stop-color="#7542E5"/>
+<stop offset="0.64" stop-color="#6239BF"/>
+<stop offset="0.94" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint8_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(59.5059 49.1947) rotate(84.0697) scale(24.9532 16.1464)">
+<stop offset="0.09" stop-color="#9658F9"/>
+<stop offset="0.14" stop-color="#8C51F3"/>
+<stop offset="0.26" stop-color="#7542E5"/>
+<stop offset="0.29" stop-color="#6E3ED6"/>
+<stop offset="0.4" stop-color="#5533A6"/>
+<stop offset="0.5" stop-color="#422980"/>
+<stop offset="0.59" stop-color="#342365"/>
+<stop offset="0.67" stop-color="#2C1E55"/>
+<stop offset="0.74" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint9_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(57.4076 51.8087) scale(20.9312 21.2702)">
+<stop stop-color="#9658F9"/>
+<stop offset="0.12" stop-color="#9155F6"/>
+<stop offset="0.25" stop-color="#834CEE"/>
+<stop offset="0.34" stop-color="#7542E5"/>
+<stop offset="0.78" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint10_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(62.9339 53.0525) scale(22.862 23.2321)">
+<stop offset="0.19" stop-color="#9658F9"/>
+<stop offset="0.49" stop-color="#834BED"/>
+<stop offset="0.66" stop-color="#7542E5"/>
+<stop offset="0.7" stop-color="#6B3DD2"/>
+<stop offset="0.79" stop-color="#52319F"/>
+<stop offset="0.91" stop-color="#291D4F"/>
+</radialGradient>
+<linearGradient id="paint11_linear_1186_7637" x1="64.5267" y1="48.4823" x2="48.7204" y2="64.0368" gradientUnits="userSpaceOnUse">
+<stop stop-color="#9059FF" stop-opacity="0.9"/>
+<stop offset="0.61" stop-color="#291D4F" stop-opacity="0"/>
+</linearGradient>
+</defs>
+</svg>
diff --git a/browser/components/privatebrowsing/content/assets/klar-qr-code.svg b/browser/components/privatebrowsing/content/assets/klar-qr-code.svg
new file mode 100644
index 0000000000..2217ca055c
--- /dev/null
+++ b/browser/components/privatebrowsing/content/assets/klar-qr-code.svg
@@ -0,0 +1,114 @@
+<!-- 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/. -->
+<svg width="113" height="113" viewBox="0 0 113 113" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="113" height="113" rx="4" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M4 2H109C110.105 2 111 2.89543 111 4V109C111 110.105 110.105 111 109 111H4C2.89543 111 2 110.105 2 109V4C2 2.89543 2.89543 2 4 2ZM0 4C0 1.79086 1.79086 0 4 0H109C111.209 0 113 1.79086 113 4V109C113 111.209 111.209 113 109 113H4C1.79086 113 0 111.209 0 109V4ZM103 10H99.7931H96.5862H93.3793H90.1724H86.9655H83.7586H80.5517V13.2069V16.4138V19.6207V22.8276V26.0345V29.2414V32.4483H83.7586H86.9655H90.1724H93.3793H96.5862H99.7931H103V29.2414V26.0345V22.8276V19.6207V16.4138V13.2069V10ZM96.5862 13.2069H99.7931V16.4138V19.6207V22.8276V26.0345V29.2414H96.5862H93.3793H90.1724H86.9655H83.7586V26.0345V22.8276V19.6207V16.4138V13.2069H86.9655H90.1724H93.3793H96.5862ZM70.931 10H67.7241V13.2069H64.5172V10H61.3103H58.1034V13.2069H54.8966V10H51.6897H48.4828H45.2759H42.069H38.8621V13.2069H35.6552V16.4138V19.6207H38.8621V22.8276H42.069V26.0345H38.8621H35.6552V29.2414V32.4483H38.8621V35.6552H35.6552V38.8621V42.069H32.4483V38.8621V35.6552H29.2414H26.0345V38.8621H22.8276H19.6207H16.4138V35.6552H13.2069V38.8621H10V42.069V45.2759V48.4828V51.6897V54.8966V58.1034V61.3103V64.5172V67.7241V70.931H13.2069V74.1379V77.3448H10V80.5517V83.7586H13.2069V86.9655H10V90.1724H13.2069V93.3793H10V96.5862V99.7931H13.2069V103H16.4138V99.7931V96.5862H19.6207V99.7931H22.8276V103H26.0345V99.7931H29.2414H32.4483V103H35.6552H38.8621H42.069H45.2759V99.7931V96.5862H48.4828H51.6897V99.7931H48.4828V103H51.6897H54.8966H58.1034V99.7931V96.5862V93.3793V90.1724H54.8966V86.9655H51.6897V90.1724H48.4828H45.2759V86.9655H48.4828V83.7586H45.2759V80.5517H48.4828H51.6897H54.8966V83.7586H58.1034V80.5517H61.3103V83.7586H64.5172V80.5517H61.3103V77.3448V74.1379H64.5172H67.7241H70.931V77.3448H67.7241V80.5517V83.7586V86.9655V90.1724V93.3793V96.5862H70.931V99.7931H67.7241V103H70.931H74.1379V99.7931V96.5862H70.931V93.3793V90.1724V86.9655H74.1379V90.1724H77.3448V86.9655V83.7586V80.5517V77.3448H80.5517H83.7586H86.9655H90.1724V74.1379V70.931V67.7241H86.9655V70.931V74.1379H83.7586H80.5517V70.931H83.7586V67.7241H80.5517V64.5172H83.7586H86.9655V61.3103H90.1724V64.5172H93.3793H96.5862V67.7241H99.7931H103V64.5172H99.7931H96.5862V61.3103V58.1034H99.7931V61.3103H103V58.1034H99.7931V54.8966H103V51.6897H99.7931V48.4828H103V45.2759V42.069V38.8621H99.7931H96.5862H93.3793V42.069H90.1724H86.9655V45.2759V48.4828H83.7586H80.5517V51.6897H77.3448V48.4828H74.1379V51.6897H70.931V54.8966V58.1034H74.1379V61.3103H70.931V64.5172V67.7241H74.1379V64.5172H77.3448V67.7241V70.931H74.1379H70.931H67.7241H64.5172H61.3103H58.1034V74.1379H54.8966V70.931H51.6897V74.1379H54.8966V77.3448H51.6897H48.4828V74.1379V70.931H45.2759V74.1379H42.069H38.8621H35.6552H32.4483H29.2414V70.931V67.7241V64.5172H26.0345H22.8276V67.7241H19.6207H16.4138V70.931H13.2069V67.7241V64.5172H16.4138V61.3103V58.1034H13.2069V54.8966V51.6897V48.4828H16.4138V45.2759H13.2069V42.069H16.4138H19.6207V45.2759H22.8276V42.069H26.0345H29.2414H32.4483V45.2759V48.4828H35.6552H38.8621V51.6897H42.069V48.4828V45.2759H45.2759V42.069H42.069V38.8621H45.2759V35.6552H48.4828V32.4483H51.6897V35.6552V38.8621V42.069H54.8966H58.1034H61.3103V38.8621H58.1034V35.6552H61.3103V32.4483H64.5172V29.2414H67.7241V32.4483H70.931V35.6552H74.1379H77.3448V38.8621H80.5517H83.7586H86.9655H90.1724V35.6552H86.9655H83.7586H80.5517H77.3448V32.4483V29.2414V26.0345H74.1379V22.8276H70.931V19.6207H74.1379V16.4138H77.3448V13.2069H74.1379V16.4138H70.931V13.2069V10ZM70.931 26.0345V22.8276H67.7241V19.6207H64.5172V16.4138H61.3103H58.1034H54.8966V19.6207H58.1034H61.3103V22.8276H64.5172H67.7241V26.0345H70.931ZM70.931 26.0345V29.2414V32.4483H74.1379V29.2414V26.0345H70.931ZM64.5172 29.2414V26.0345H61.3103H58.1034V29.2414V32.4483H61.3103V29.2414H64.5172ZM51.6897 29.2414H54.8966V32.4483H51.6897V29.2414ZM51.6897 29.2414V26.0345H48.4828V22.8276H51.6897V19.6207H48.4828H45.2759V16.4138H48.4828V13.2069H45.2759H42.069H38.8621V16.4138H42.069V19.6207V22.8276H45.2759V26.0345V29.2414V32.4483H48.4828V29.2414H51.6897ZM42.069 42.069H38.8621V45.2759H42.069V42.069ZM22.8276 90.1724H19.6207H16.4138V86.9655V83.7586H13.2069V80.5517V77.3448H16.4138V80.5517H19.6207V77.3448H16.4138V74.1379V70.931H19.6207H22.8276H26.0345V74.1379H22.8276V77.3448V80.5517V83.7586H19.6207V86.9655H22.8276V90.1724ZM80.5517 61.3103V64.5172H77.3448V61.3103H80.5517ZM86.9655 58.1034V61.3103H83.7586H80.5517V58.1034H83.7586H86.9655ZM86.9655 58.1034V54.8966H90.1724V58.1034H86.9655ZM80.5517 51.6897V54.8966H83.7586V51.6897H80.5517ZM96.5862 45.2759H99.7931V48.4828H96.5862V45.2759ZM93.3793 45.2759V42.069H96.5862V45.2759H93.3793ZM93.3793 45.2759V48.4828V51.6897H90.1724V48.4828V45.2759H93.3793ZM96.5862 58.1034V54.8966H93.3793V58.1034H96.5862ZM77.3448 77.3448V74.1379H74.1379V77.3448H77.3448ZM45.2759 83.7586V86.9655H42.069V83.7586H45.2759ZM32.4483 96.5862H29.2414H26.0345V99.7931H22.8276V96.5862V93.3793V90.1724H26.0345V93.3793H29.2414V90.1724H32.4483H35.6552H38.8621H42.069H45.2759V93.3793H42.069H38.8621H35.6552H32.4483V96.5862ZM51.6897 90.1724V93.3793V96.5862H54.8966V93.3793V90.1724H51.6897ZM32.4483 96.5862V99.7931H35.6552V96.5862H32.4483ZM42.069 38.8621H38.8621V35.6552H42.069V38.8621ZM38.8621 32.4483V29.2414H42.069V32.4483H38.8621ZM64.5172 38.8621V42.069H67.7241V38.8621V35.6552H64.5172V38.8621ZM32.4483 10H29.2414H26.0345H22.8276H19.6207H16.4138H13.2069H10V13.2069V16.4138V19.6207V22.8276V26.0345V29.2414V32.4483H13.2069H16.4138H19.6207H22.8276H26.0345H29.2414H32.4483V29.2414V26.0345V22.8276V19.6207V16.4138V13.2069V10ZM26.0345 13.2069H29.2414V16.4138V19.6207V22.8276V26.0345V29.2414H26.0345H22.8276H19.6207H16.4138H13.2069V26.0345V22.8276V19.6207V16.4138V13.2069H16.4138H19.6207H22.8276H26.0345ZM96.5862 16.4138H93.3793H90.1724H86.9655V19.6207V22.8276V26.0345H90.1724H93.3793H96.5862V22.8276V19.6207V16.4138ZM26.0345 16.4138H22.8276H19.6207H16.4138V19.6207V22.8276V26.0345H19.6207H22.8276H26.0345V22.8276V19.6207V16.4138ZM83.7586 42.069H80.5517V45.2759H83.7586V42.069ZM74.1379 42.069H70.931V45.2759H74.1379V42.069ZM29.2414 45.2759H26.0345V48.4828V51.6897H22.8276V48.4828H19.6207V51.6897H16.4138V54.8966H19.6207V51.6897H22.8276V54.8966V58.1034H19.6207V61.3103H22.8276V58.1034H26.0345V61.3103H29.2414V58.1034H32.4483H35.6552V61.3103H32.4483V64.5172V67.7241H35.6552V70.931H38.8621V67.7241V64.5172H35.6552V61.3103H38.8621V58.1034V54.8966H35.6552H32.4483V51.6897H29.2414V48.4828V45.2759ZM29.2414 51.6897V54.8966V58.1034H26.0345V54.8966V51.6897H29.2414ZM96.5862 70.931H93.3793V74.1379H96.5862V77.3448H99.7931H103V74.1379H99.7931H96.5862V70.931ZM32.4483 77.3448H35.6552V80.5517V83.7586V86.9655H32.4483H29.2414H26.0345V83.7586V80.5517V77.3448H29.2414H32.4483ZM70.931 80.5517H74.1379V83.7586H70.931V80.5517ZM103 80.5517H99.7931H96.5862H93.3793H90.1724H86.9655H83.7586H80.5517V83.7586V86.9655V90.1724V93.3793V96.5862V99.7931V103H83.7586H86.9655H90.1724H93.3793H96.5862H99.7931H103V99.7931V96.5862V93.3793V90.1724V86.9655V83.7586V80.5517ZM96.5862 83.7586H99.7931V86.9655V90.1724V93.3793V96.5862V99.7931H96.5862H93.3793H90.1724H86.9655H83.7586V96.5862V93.3793V90.1724V86.9655V83.7586H86.9655H90.1724H93.3793H96.5862ZM32.4483 80.5517H29.2414V83.7586H32.4483V80.5517ZM96.5862 86.9655H93.3793H90.1724H86.9655V90.1724V93.3793V96.5862H90.1724H93.3793H96.5862V93.3793V90.1724V86.9655ZM64.5172 90.1724H61.3103V93.3793V96.5862V99.7931V103H64.5172V99.7931V96.5862V93.3793V90.1724Z" fill="#1C1B22"/>
+<path d="M66.2056 52.7475C65.7266 51.5785 64.7572 50.3139 63.9959 49.9148C64.6155 51.1492 64.9742 52.3872 65.1112 53.3113C65.1112 53.3113 65.1112 53.3177 65.1112 53.3299C63.8657 50.1757 61.7537 48.9037 60.0292 46.1343C59.9419 45.994 59.8551 45.8537 59.7678 45.7055C59.7192 45.621 59.6805 45.546 59.6465 45.4738C59.575 45.3332 59.5199 45.1847 59.4823 45.0312C59.4825 45.0239 59.48 45.0169 59.4754 45.0114C59.4707 45.0059 59.4643 45.0023 59.4572 45.0014C59.4503 44.9995 59.4431 44.9995 59.4363 45.0014L59.431 45.0046C59.4283 45.0057 59.4256 45.0071 59.4232 45.0088L59.4274 45.0025C56.6608 46.6497 55.7222 49.6954 55.6359 51.2193C54.5305 51.2961 53.4736 51.7098 52.6032 52.4064C52.512 52.3282 52.4167 52.2551 52.3177 52.1875C52.0667 51.2949 52.0561 50.3502 52.2868 49.452C51.1553 49.9754 50.2753 50.8033 49.6358 51.5339H49.6379C49.2012 50.9722 49.2321 49.1183 49.2567 48.731C49.2514 48.7071 48.9309 48.9 48.8906 48.9292C48.5053 49.2088 48.145 49.5224 47.814 49.8665C47.4375 50.2545 47.0935 50.6738 46.7855 51.12C46.0764 52.1399 45.5731 53.2924 45.3047 54.5111C45.2994 54.535 45.2947 54.5605 45.29 54.5849C45.2691 54.6832 45.1943 55.1763 45.1813 55.2837C45.1813 55.2916 45.1813 55.2996 45.1813 55.3081C45.0847 55.8177 45.0249 56.3338 45.0024 56.8522C45.0024 56.8714 45.0024 56.89 45.0024 56.9091C45.0024 63.0898 49.9338 68.1 56.0166 68.1C61.464 68.1 65.9876 64.0813 66.8728 58.8012C66.8916 58.6583 66.9063 58.5143 66.9251 58.3698C67.139 56.4516 66.8932 54.434 66.2056 52.7475ZM53.5109 61.5074C53.5632 61.5324 53.6108 61.5606 53.6636 61.5834L53.6709 61.5882C53.6176 61.5622 53.5642 61.5351 53.5109 61.5074ZM65.1154 53.3331V53.3219C65.1151 53.326 65.1151 53.3301 65.1154 53.3341V53.3331Z" fill="url(#paint0_linear_1186_7637)"/>
+<path d="M66.2063 52.7477C65.7273 51.5787 64.7579 50.3141 63.9966 49.915C64.6162 51.1494 64.9749 52.3874 65.1119 53.3115C65.1119 53.3088 65.1119 53.3115 65.1119 53.3221C65.1116 53.3262 65.1116 53.3302 65.1119 53.3343C66.1509 56.1973 65.5825 59.108 64.7689 60.887C63.5066 63.6394 60.4509 66.4604 55.6706 66.3228C50.5029 66.174 45.9506 62.2776 45.1009 57.1744C44.9441 56.3699 45.1009 55.9608 45.1788 55.3078C45.0837 55.8115 45.0476 55.9571 45 56.8519C45 56.871 45 56.8896 45 56.9087C45 63.0895 49.9314 68.0996 56.0141 68.0996C61.4616 68.0996 65.9851 64.081 66.8704 58.8009C66.8892 58.658 66.9038 58.514 66.9227 58.3694C67.1397 56.4518 66.8939 54.4342 66.2063 52.7477Z" fill="url(#paint1_radial_1186_7637)"/>
+<path d="M66.2063 52.7477C65.7273 51.5787 64.7579 50.3141 63.9966 49.915C64.6162 51.1494 64.9749 52.3874 65.1119 53.3115C65.1119 53.3088 65.1119 53.3115 65.1119 53.3221C65.1116 53.3262 65.1116 53.3302 65.1119 53.3343C66.1509 56.1973 65.5825 59.108 64.7689 60.887C63.5066 63.6394 60.4509 66.4604 55.6706 66.3228C50.5029 66.174 45.9506 62.2776 45.1009 57.1744C44.9441 56.3699 45.1009 55.9608 45.1788 55.3078C45.0837 55.8115 45.0476 55.9571 45 56.8519C45 56.871 45 56.8896 45 56.9087C45 63.0895 49.9314 68.0996 56.0141 68.0996C61.4616 68.0996 65.9851 64.081 66.8704 58.8009C66.8892 58.658 66.9038 58.514 66.9227 58.3694C67.1397 56.4518 66.8939 54.4342 66.2063 52.7477Z" fill="url(#paint2_radial_1186_7637)"/>
+<path d="M60.854 54.0624C60.878 54.0794 60.9 54.0964 60.9225 54.1155C60.6463 53.6162 60.3021 53.159 59.9002 52.7574C56.4763 49.2786 59.0024 45.2121 59.4296 45.0081L59.4338 45.0017C56.6672 46.6489 55.7286 49.6946 55.6423 51.2186C55.7704 51.2095 55.8986 51.1984 56.0293 51.1984C58.0926 51.1989 59.8913 52.3525 60.854 54.0624Z" fill="url(#paint3_radial_1186_7637)"/>
+<path d="M56.0574 54.7568C56.0396 55.0352 55.0717 55.9954 54.7329 55.9954C51.6008 55.9954 51.0925 57.9205 51.0925 57.9205C51.2311 59.5422 52.3417 60.8769 53.6871 61.5836C53.7483 61.616 53.8105 61.6453 53.8727 61.674C53.9804 61.7223 54.0882 61.7675 54.1964 61.8084C54.6575 61.9743 55.1404 62.0688 55.6291 62.089C61.1195 62.3509 62.1825 55.4194 58.2206 53.4066C59.2355 53.227 60.2886 53.642 60.8763 54.0618C59.9137 52.3519 58.1155 51.1978 56.0506 51.1978C55.9198 51.1978 55.7891 51.2089 55.6636 51.2179C54.5582 51.2947 53.5013 51.7084 52.6309 52.405C52.7987 52.5495 52.988 52.7424 53.3875 53.1425C54.1357 53.8907 56.0532 54.6654 56.0574 54.7568Z" fill="url(#paint4_radial_1186_7637)"/>
+<path d="M56.0574 54.7568C56.0396 55.0352 55.0717 55.9954 54.7329 55.9954C51.6008 55.9954 51.0925 57.9205 51.0925 57.9205C51.2311 59.5422 52.3417 60.8769 53.6871 61.5836C53.7483 61.616 53.8105 61.6453 53.8727 61.674C53.9804 61.7223 54.0882 61.7675 54.1964 61.8084C54.6575 61.9743 55.1404 62.0688 55.6291 62.089C61.1195 62.3509 62.1825 55.4194 58.2206 53.4066C59.2355 53.227 60.2886 53.642 60.8763 54.0618C59.9137 52.3519 58.1155 51.1978 56.0506 51.1978C55.9198 51.1978 55.7891 51.2089 55.6636 51.2179C54.5582 51.2947 53.5013 51.7084 52.6309 52.405C52.7987 52.5495 52.988 52.7424 53.3875 53.1425C54.1357 53.8907 56.0532 54.6654 56.0574 54.7568Z" fill="url(#paint5_radial_1186_7637)"/>
+<path d="M52.0962 52.0338C52.1856 52.0927 52.2594 52.14 52.3247 52.1873C52.0738 51.2947 52.0631 50.3501 52.2939 49.4519C51.1623 49.9753 50.2823 50.8031 49.6428 51.5338C49.6951 51.5353 51.2925 51.5029 52.0962 52.0338Z" fill="url(#paint6_radial_1186_7637)"/>
+<path d="M45.1022 57.1743C45.9519 62.2753 50.5041 66.1738 55.6719 66.3226C60.4547 66.4602 63.5105 63.6393 64.7701 60.8868C65.5858 59.1079 66.1521 56.1971 65.1132 53.3341V53.323C65.1132 53.315 65.1132 53.3097 65.1132 53.3124C65.1132 53.315 65.1132 53.3187 65.1132 53.331C65.5038 55.9234 64.2059 58.432 62.1777 60.1323L62.1714 60.1467C58.2189 63.4172 54.4368 62.1201 53.6708 61.5888C53.6185 61.5627 53.5662 61.5356 53.5108 61.508C51.2059 60.3884 50.2542 58.255 50.4581 56.4256C48.5125 56.4256 47.8489 54.7576 47.8489 54.7576C47.8489 54.7576 49.5959 53.492 51.8982 54.5929C54.031 55.6126 56.0337 54.7582 56.0337 54.7582C56.0295 54.6668 54.1126 53.8921 53.367 53.1439C52.9675 52.7443 52.7782 52.5515 52.6103 52.4069C52.5192 52.3287 52.4239 52.2557 52.3248 52.188C52.2595 52.1423 52.1858 52.0934 52.0963 52.0344C51.2932 51.5031 49.6958 51.5334 49.6429 51.5344H49.6388C49.2022 50.9728 49.233 49.1189 49.2576 48.7315C49.2524 48.7076 48.9318 48.9005 48.8916 48.9297C48.5062 49.2093 48.1459 49.523 47.8149 49.867C47.4384 50.2551 47.0944 50.6744 46.7864 51.1205C46.0774 52.1403 45.5741 53.2927 45.3056 54.5111C45.2988 54.5345 44.9092 56.2725 45.1022 57.1743Z" fill="url(#paint7_radial_1186_7637)"/>
+<path d="M59.9009 52.7576C60.3027 53.1586 60.6468 53.6151 60.9232 54.1136C60.9839 54.1599 61.0403 54.2061 61.0884 54.2507C63.5858 56.5887 62.2775 59.8958 62.1797 60.1312C64.208 58.4309 65.5058 55.9218 65.1152 53.3299C63.8697 50.1757 61.7577 48.9037 60.0332 46.1343C59.9459 45.994 59.8591 45.8537 59.7718 45.7055C59.7232 45.621 59.6845 45.546 59.6505 45.4738C59.579 45.3332 59.5239 45.1847 59.4863 45.0312C59.4865 45.0239 59.484 45.0169 59.4794 45.0114C59.4747 45.0059 59.4683 45.0023 59.4612 45.0014C59.4543 44.9995 59.4471 44.9995 59.4403 45.0014L59.4351 45.0046C59.4323 45.0057 59.4296 45.0071 59.4272 45.0088C59.0031 45.215 56.4771 49.2793 59.9009 52.7576Z" fill="url(#paint8_radial_1186_7637)"/>
+<path d="M61.0892 54.251C61.0411 54.2064 60.9846 54.1601 60.924 54.1139C60.9015 54.0964 60.8795 54.0794 60.8555 54.0608C60.2677 53.641 59.2146 53.2265 58.1997 53.4056C62.1616 55.4184 61.0981 62.3488 55.6083 62.0869C55.1194 62.067 54.6362 61.9724 54.175 61.8063C54.0673 61.7654 53.9596 61.7203 53.8519 61.6719C53.7897 61.6432 53.7274 61.614 53.6663 61.5816L53.6736 61.5864C54.4396 62.1177 58.2227 63.4148 62.1742 60.1443L62.1805 60.1299C62.2756 59.8961 63.5839 56.589 61.0892 54.251Z" fill="url(#paint9_radial_1186_7637)"/>
+<path d="M51.07 57.9207C51.07 57.9207 51.5783 55.9956 54.7104 55.9956C55.0492 55.9956 56.0176 55.036 56.0349 54.7576C56.0349 54.7576 54.0322 55.612 51.8994 54.5923C49.5986 53.4913 47.8501 54.757 47.8501 54.757C47.8501 54.757 48.5136 56.425 50.4593 56.425C50.2554 58.2544 51.207 60.3878 53.5119 61.5074C53.5642 61.5324 53.6118 61.5605 53.6646 61.5834C52.3192 60.8772 51.2076 59.5419 51.07 57.9207Z" fill="url(#paint10_radial_1186_7637)"/>
+<path d="M66.2057 52.7475C65.7268 51.5785 64.7573 50.3139 63.996 49.9148C64.6156 51.1492 64.9743 52.3872 65.1113 53.3113C65.1113 53.3113 65.1113 53.3177 65.1113 53.3299C63.8658 50.1757 61.7539 48.9037 60.0294 46.1343C59.942 45.994 59.8552 45.8537 59.7679 45.7055C59.7193 45.621 59.6806 45.546 59.6466 45.4738C59.5751 45.3332 59.52 45.1847 59.4824 45.0312C59.4826 45.0239 59.4802 45.0169 59.4755 45.0114C59.4709 45.0059 59.4644 45.0023 59.4573 45.0014C59.4505 44.9995 59.4433 44.9995 59.4364 45.0014L59.4312 45.0046C59.4284 45.0057 59.4258 45.0071 59.4233 45.0088L59.4275 45.0025C56.6609 46.6497 55.7223 49.6954 55.6361 51.2193C55.7642 51.2103 55.8923 51.1991 56.023 51.1991C58.0874 51.1991 59.8861 52.3527 60.8487 54.0626C60.261 53.6428 59.2079 53.2284 58.193 53.4074C62.1549 55.4202 61.0914 62.3507 55.6015 62.0887C55.1126 62.0688 54.6295 61.9743 54.1683 61.8082C54.0606 61.7673 53.9529 61.7221 53.8452 61.6738C53.7829 61.6451 53.7207 61.6158 53.6595 61.5834L53.6669 61.5882C53.6146 61.5622 53.5623 61.5351 53.5069 61.5074C53.5591 61.5324 53.6067 61.5606 53.6595 61.5834C52.3141 60.8772 51.202 59.542 51.065 57.9208C51.065 57.9208 51.5732 55.9957 54.7053 55.9957C55.0441 55.9957 56.0125 55.0361 56.0298 54.7576C56.0256 54.6662 54.1087 53.8915 53.3631 53.1434C52.9636 52.7438 52.7743 52.5509 52.6064 52.4064C52.5153 52.3282 52.42 52.2551 52.3209 52.1875C52.07 51.2949 52.0593 50.3502 52.2901 49.452C51.1586 49.9754 50.2785 50.8033 49.639 51.5339H49.638C49.2014 50.9722 49.2322 49.1183 49.2568 48.731C49.2516 48.7071 48.931 48.9 48.8908 48.9292C48.5054 49.2088 48.1451 49.5224 47.8142 49.8665C47.4376 50.2545 47.0936 50.6738 46.7856 51.12C46.0766 52.1399 45.5733 53.2924 45.3048 54.5111C45.2996 54.535 45.2949 54.5605 45.2902 54.5849C45.2693 54.6832 45.1762 55.1832 45.1631 55.2906C45.078 55.8073 45.0237 56.3288 45.0005 56.8522C45.0005 56.8714 45.0005 56.89 45.0005 56.9091C45.0005 63.0898 49.9319 68.1 56.0146 68.1C61.4621 68.1 65.9856 64.0813 66.8709 58.8012C66.8897 58.6583 66.9043 58.5143 66.9231 58.3698C67.1391 56.4516 66.8933 54.434 66.2057 52.7475ZM65.1155 53.3219C65.1153 53.326 65.1153 53.3301 65.1155 53.3341V53.3219Z" fill="url(#paint11_linear_1186_7637)"/>
+<defs>
+<linearGradient id="paint0_linear_1186_7637" x1="65.8887" y1="50.9308" x2="46.6588" y2="62.8511" gradientUnits="userSpaceOnUse">
+<stop stop-color="#9658F9"/>
+<stop offset="0.13" stop-color="#9356F4"/>
+<stop offset="0.29" stop-color="#8951E5"/>
+<stop offset="0.46" stop-color="#7A49CD"/>
+<stop offset="0.64" stop-color="#643DAB"/>
+<stop offset="0.82" stop-color="#492E81"/>
+<stop offset="1" stop-color="#291D4F"/>
+</linearGradient>
+<radialGradient id="paint1_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(63.1519 48.6778) rotate(112.511) scale(23.2839 24.3126)">
+<stop stop-color="#9658F9"/>
+<stop offset="0.65" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint2_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(57.8824 59.334) scale(22.9415 23.3129)">
+<stop offset="0.25" stop-color="#AB71FF" stop-opacity="0"/>
+<stop offset="0.42" stop-color="#9462E0" stop-opacity="0.18"/>
+<stop offset="0.73" stop-color="#573B8D" stop-opacity="0.65"/>
+<stop offset="0.94" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint3_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(60.6484 44.5148) scale(16.5843 16.8528)">
+<stop offset="0.18" stop-color="#9658F9"/>
+<stop offset="0.33" stop-color="#7E48EA"/>
+<stop offset="0.37" stop-color="#7542E5"/>
+<stop offset="0.41" stop-color="#6A3DD0"/>
+<stop offset="0.48" stop-color="#5332A2"/>
+<stop offset="0.56" stop-color="#41297E"/>
+<stop offset="0.63" stop-color="#342264"/>
+<stop offset="0.69" stop-color="#2C1E54"/>
+<stop offset="0.74" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint4_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(52.8479 62.089) scale(10.9331 11.1101)">
+<stop offset="0.07" stop-color="#9358FC"/>
+<stop offset="0.13" stop-color="#9857F8"/>
+<stop offset="0.21" stop-color="#A755EB"/>
+<stop offset="0.3" stop-color="#BF52D7"/>
+<stop offset="0.39" stop-color="#E14EBA"/>
+<stop offset="0.42" stop-color="#EF4CAF"/>
+<stop offset="0.74" stop-color="#FF7583"/>
+<stop offset="0.97" stop-color="#FFB753"/>
+</radialGradient>
+<radialGradient id="paint5_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(55.4787 54.1254) rotate(-14.1133) scale(5.79733 6.90775)">
+<stop offset="0.22" stop-color="#FFB653" stop-opacity="0.3"/>
+<stop offset="0.34" stop-color="#FF807A" stop-opacity="0.5"/>
+<stop offset="0.44" stop-color="#FF7781" stop-opacity="0.48"/>
+<stop offset="0.57" stop-color="#FF5C94" stop-opacity="0.43"/>
+<stop offset="0.64" stop-color="#FF4AA2" stop-opacity="0.4"/>
+<stop offset="0.86" stop-color="#9658F9" stop-opacity="0.3"/>
+</radialGradient>
+<radialGradient id="paint6_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(57.5929 48.8515) scale(7.85915 7.9864)">
+<stop offset="0.03" stop-color="#9658F9"/>
+<stop offset="0.62" stop-color="#7542E5"/>
+<stop offset="0.72" stop-color="#6339C2"/>
+<stop offset="0.93" stop-color="#37246B"/>
+<stop offset="1" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint7_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(58.8404 39.1507) rotate(102.662) scale(32.7229 25.3152)">
+<stop offset="0.2" stop-color="#AB71FF"/>
+<stop offset="0.29" stop-color="#A46BFC"/>
+<stop offset="0.41" stop-color="#9059F2"/>
+<stop offset="0.54" stop-color="#7542E5"/>
+<stop offset="0.64" stop-color="#6239BF"/>
+<stop offset="0.94" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint8_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(59.5059 49.1947) rotate(84.0697) scale(24.9532 16.1464)">
+<stop offset="0.09" stop-color="#9658F9"/>
+<stop offset="0.14" stop-color="#8C51F3"/>
+<stop offset="0.26" stop-color="#7542E5"/>
+<stop offset="0.29" stop-color="#6E3ED6"/>
+<stop offset="0.4" stop-color="#5533A6"/>
+<stop offset="0.5" stop-color="#422980"/>
+<stop offset="0.59" stop-color="#342365"/>
+<stop offset="0.67" stop-color="#2C1E55"/>
+<stop offset="0.74" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint9_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(57.4076 51.8087) scale(20.9312 21.2702)">
+<stop stop-color="#9658F9"/>
+<stop offset="0.12" stop-color="#9155F6"/>
+<stop offset="0.25" stop-color="#834CEE"/>
+<stop offset="0.34" stop-color="#7542E5"/>
+<stop offset="0.78" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint10_radial_1186_7637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(62.9339 53.0525) scale(22.862 23.2321)">
+<stop offset="0.19" stop-color="#9658F9"/>
+<stop offset="0.49" stop-color="#834BED"/>
+<stop offset="0.66" stop-color="#7542E5"/>
+<stop offset="0.7" stop-color="#6B3DD2"/>
+<stop offset="0.79" stop-color="#52319F"/>
+<stop offset="0.91" stop-color="#291D4F"/>
+</radialGradient>
+<linearGradient id="paint11_linear_1186_7637" x1="64.5267" y1="48.4823" x2="48.7204" y2="64.0368" gradientUnits="userSpaceOnUse">
+<stop stop-color="#9059FF" stop-opacity="0.9"/>
+<stop offset="0.61" stop-color="#291D4F" stop-opacity="0"/>
+</linearGradient>
+</defs>
+</svg>
diff --git a/browser/components/privatebrowsing/content/assets/moz-vpn.svg b/browser/components/privatebrowsing/content/assets/moz-vpn.svg
new file mode 100644
index 0000000000..038d2952b6
--- /dev/null
+++ b/browser/components/privatebrowsing/content/assets/moz-vpn.svg
@@ -0,0 +1,4 @@
+<!-- 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/. -->
+<svg width="127" height="121" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M97.936 27.343h-45.38c-2.406 0-4.357 1.86-4.357 4.152v64.92c0 2.292 1.951 4.151 4.358 4.151h45.379c2.406 0 4.357-1.859 4.357-4.152V31.495c0-2.293-1.951-4.152-4.357-4.152z" fill="#C4C6FC"/><path d="M97.933 100.566v-.065H52.557a4.405 4.405 0 0 1-3.032-1.198 3.994 3.994 0 0 1-1.257-2.889V31.495a3.99 3.99 0 0 1 1.257-2.888 4.402 4.402 0 0 1 3.032-1.198h45.376a4.4 4.4 0 0 1 3.033 1.197 3.99 3.99 0 0 1 1.258 2.89v64.918a3.99 3.99 0 0 1-1.258 2.89 4.4 4.4 0 0 1-3.033 1.197v.131a4.54 4.54 0 0 0 3.13-1.236 4.119 4.119 0 0 0 1.296-2.982V31.495c0-.553-.114-1.102-.337-1.613a4.199 4.199 0 0 0-.959-1.368 4.46 4.46 0 0 0-1.437-.913 4.613 4.613 0 0 0-1.693-.32H52.557c-.582 0-1.157.108-1.694.32a4.443 4.443 0 0 0-1.436.913 4.208 4.208 0 0 0-.96 1.368 4.042 4.042 0 0 0-.337 1.613v64.92c0 1.118.467 2.19 1.297 2.981a4.542 4.542 0 0 0 3.13 1.236h45.376v-.066z" fill="#C4C7FC"/><path d="M91.93 90.498l6.283-.252a5.237 5.237 0 0 0 3.371-1.442 6.97 6.97 0 0 0 1.977-3.471 6.32 6.32 0 0 1 1.786-3.154 4.55 4.55 0 0 1 2.926-1.26l1.662-.06-.027-.649-1.664.063a5.243 5.243 0 0 0-3.371 1.442 6.967 6.967 0 0 0-1.974 3.471 6.32 6.32 0 0 1-1.788 3.152 4.538 4.538 0 0 1-2.91 1.26l-6.285.237.027.648-.013.015zm1.373-43.162c3.881-2.984 8.995-5.142 13.604-6.985a45.786 45.786 0 0 1 4.477-1.512c1.852-.517 3.871-.93 5.535-.93a6.667 6.667 0 0 1 1.733.198c.413.094.797.28 1.119.543a2.522 2.522 0 0 1 .934 1.979 4.683 4.683 0 0 1-.439 1.837c-.5 1.152-1.402 2.417-2.508 3.68a53.812 53.812 0 0 1-5.646 5.38 150.31 150.31 0 0 1-2.564 2.107 45.061 45.061 0 0 0-1.72 1.43c-2.942 2.639-5.868 5.394-8.416 8.366-2.548 2.972-4.715 6.161-6.139 9.673l.636.232c1.386-3.426 3.513-6.555 6.03-9.494 2.516-2.94 5.421-5.674 8.355-8.306.264-.252.682-.585 1.19-1.008 1.778-1.447 4.734-3.794 7.253-6.328a26.11 26.11 0 0 0 3.251-3.831c.403-.6.741-1.235 1.011-1.898.239-.58.368-1.196.381-1.818a3.098 3.098 0 0 0-.263-1.292 3.235 3.235 0 0 0-.779-1.085 3.526 3.526 0 0 0-1.5-.787 7.427 7.427 0 0 0-1.919-.221c-1.778 0-3.847.43-5.725.955a47.279 47.279 0 0 0-4.551 1.54c-4.622 1.848-9.79 4.018-13.758 7.079l.425.504-.007-.008z" fill="#A6A8E2"/><path d="M15.713 30.845c.421 2.1 1.059 4.129 2.366 5.735a7.799 7.799 0 0 0 2.497 2.017c1.164.574 2.432.933 3.736 1.056 1.119.115 2.244.168 3.369.159 1.587 0 3.196-.074 4.746-.074 1.908 0 3.741.109 5.419.575a9.297 9.297 0 0 1 4.519 2.723l.51-.426a10.016 10.016 0 0 0-4.839-2.92c-1.778-.491-3.675-.597-5.609-.6-1.571 0-3.175.074-4.746.074-1.1.008-2.199-.043-3.292-.154a9.972 9.972 0 0 1-3.487-.976 7.29 7.29 0 0 1-3.08-3.025 14.702 14.702 0 0 1-1.436-4.285l-.67.12h-.002z" fill="#C4C6FC"/><path d="M108.744 31.737h-.339a.511.511 0 0 1-.161.369.564.564 0 0 1-.772 0 .51.51 0 0 1-.16-.369.496.496 0 0 1 .334-.484.551.551 0 0 1 .211-.035c.145 0 .284.055.387.152.102.097.16.23.161.367h.68a1.13 1.13 0 0 0-.207-.65 1.213 1.213 0 0 0-.551-.43 1.283 1.283 0 0 0-.709-.067 1.25 1.25 0 0 0-.629.32 1.163 1.163 0 0 0-.336.6 1.12 1.12 0 0 0 .07.675c.093.214.251.396.452.525a1.273 1.273 0 0 0 1.551-.146c.23-.219.359-.517.359-.827h-.341zM30.279 4.013h-.339a.502.502 0 0 1-.16.37.55.55 0 0 1-.388.154.573.573 0 0 1-.411-.138.522.522 0 0 1-.174-.382.5.5 0 0 1 .174-.382.553.553 0 0 1 .411-.139c.145 0 .284.055.387.152a.51.51 0 0 1 .161.368h.68a1.13 1.13 0 0 0-.207-.65 1.217 1.217 0 0 0-.55-.431 1.284 1.284 0 0 0-.71-.067 1.248 1.248 0 0 0-.629.32 1.154 1.154 0 0 0-.336.6 1.12 1.12 0 0 0 .07.675c.093.214.25.397.452.525.202.129.44.197.682.197a1.27 1.27 0 0 0 .472-.086 1.174 1.174 0 0 0 .665-.634c.062-.143.093-.295.091-.45l-.341-.002zm-2.717 83.044h-.342a.51.51 0 0 1-.161.367.562.562 0 0 1-.386.152.574.574 0 0 1-.412-.139.521.521 0 0 1-.174-.381.5.5 0 0 1 .174-.382.553.553 0 0 1 .412-.139c.145 0 .284.055.387.153a.51.51 0 0 1 .16.369h.68a1.13 1.13 0 0 0-.205-.65 1.217 1.217 0 0 0-.55-.432 1.284 1.284 0 0 0-.71-.068 1.248 1.248 0 0 0-.63.32 1.15 1.15 0 0 0-.336.598c-.048.227-.024.463.07.676.092.214.25.397.451.526.202.129.44.197.683.197a1.26 1.26 0 0 0 .87-.339 1.16 1.16 0 0 0 .265-.38 1.1 1.1 0 0 0 .092-.448h-.338zM6.747 58.645v1.658a.32.32 0 0 0 .11.212.35.35 0 0 0 .46 0 .32.32 0 0 0 .11-.212v-1.658a.311.311 0 0 0-.088-.248.342.342 0 0 0-.252-.106.357.357 0 0 0-.252.106.322.322 0 0 0-.088.248z" fill="#000"/><path d="M6.215 59.797h1.741a.358.358 0 0 0 .26-.084.326.326 0 0 0 .111-.24.312.312 0 0 0-.11-.24.343.343 0 0 0-.26-.084H6.214a.358.358 0 0 0-.26.084.326.326 0 0 0-.111.24.312.312 0 0 0 .11.24.345.345 0 0 0 .261.084zm116.79 3.383v1.658a.317.317 0 0 0 .109.212.351.351 0 0 0 .461 0 .322.322 0 0 0 .11-.212V63.18a.316.316 0 0 0-.088-.249.353.353 0 0 0-.504 0 .319.319 0 0 0-.088.249z" fill="#000"/><path d="M122.473 64.332h1.741a.361.361 0 0 0 .261-.084.335.335 0 0 0 .081-.11.299.299 0 0 0 .029-.13.299.299 0 0 0-.029-.131.335.335 0 0 0-.342-.193h-1.741a.35.35 0 0 0-.222.104.32.32 0 0 0-.089.22c0 .081.032.16.089.22a.35.35 0 0 0 .222.104z" fill="#000"/><path d="M90.088 21.924H44.71c-2.407 0-4.358 1.858-4.358 4.152v64.918c0 2.293 1.95 4.152 4.358 4.152h45.378c2.407 0 4.358-1.859 4.358-4.151v-64.92c0-2.293-1.951-4.151-4.358-4.151z" fill="#fff"/><path d="M90.085 95.146v-.322h-45.37a4.123 4.123 0 0 1-2.843-1.117 3.74 3.74 0 0 1-1.182-2.705V26.078c0-.503.104-1.002.306-1.466.203-.465.5-.888.873-1.243a4.039 4.039 0 0 1 1.306-.83 4.193 4.193 0 0 1 1.54-.29H90.09a4.12 4.12 0 0 1 2.841 1.121 3.74 3.74 0 0 1 1.177 2.708v64.924a3.74 3.74 0 0 1-1.177 2.708 4.122 4.122 0 0 1-2.841 1.121v.648a4.82 4.82 0 0 0 3.319-1.313 4.372 4.372 0 0 0 1.374-3.164V26.078a4.372 4.372 0 0 0-1.376-3.166 4.82 4.82 0 0 0-3.323-1.311h-45.37a4.82 4.82 0 0 0-3.323 1.311 4.372 4.372 0 0 0-1.377 3.166v64.924a4.373 4.373 0 0 0 1.38 3.16 4.82 4.82 0 0 0 3.32 1.31H90.09l-.006-.326z" fill="#000"/><path d="M79.862 43.23a4.71 4.71 0 0 0-1.8-2.065 5.057 5.057 0 0 0-2.698-.774 5.034 5.034 0 0 0-2.293.538 4.769 4.769 0 0 0-1.753 1.507 4.4 4.4 0 0 0-.642 1.348h-6.853a3.305 3.305 0 0 0-.196-.554 2.927 2.927 0 0 0-.265-.504l2.45-2.335c.74.404 1.577.615 2.43.613a4.967 4.967 0 0 0 1.894-.368 4.794 4.794 0 0 0 2.419-2.11 4.45 4.45 0 0 0 .469-3.091 4.611 4.611 0 0 0-1.694-2.675 5.025 5.025 0 0 0-3.089-1.048 5.206 5.206 0 0 0-1.902.363 4.83 4.83 0 0 0-2.167 1.715 4.49 4.49 0 0 0-.81 2.57 4.4 4.4 0 0 0 .609 2.244l-2.482 2.367a5.071 5.071 0 0 0-2.357-.575 5.083 5.083 0 0 0-1.867.352 4.894 4.894 0 0 0-1.584 1.006 4.634 4.634 0 0 0-1.057 1.507 4.453 4.453 0 0 0 .015 3.591c.37.83.984 1.538 1.767 2.042a5 5 0 0 0 2.726.794c.797 0 1.583-.186 2.287-.543a4.805 4.805 0 0 0 1.753-1.501 4.18 4.18 0 0 0 .65-1.341h6.848a4.043 4.043 0 0 0 .429.988l-2.485 2.364a5.073 5.073 0 0 0-2.354-.572 5.02 5.02 0 0 0-2.294.538 4.77 4.77 0 0 0-1.755 1.507 4.52 4.52 0 0 0-.83 2.596 4.43 4.43 0 0 0 .383 1.813 4.775 4.775 0 0 0 1.768 2.044c.734.47 1.59.74 2.473.784a5.084 5.084 0 0 0 2.544-.54 4.793 4.793 0 0 0 1.89-1.709 4.487 4.487 0 0 0 .7-2.392 4.493 4.493 0 0 0-.651-2.31l2.444-2.326c.74.404 1.577.615 2.43.613a5.025 5.025 0 0 0 3.445-1.367 4.56 4.56 0 0 0 1.436-3.282 4.481 4.481 0 0 0-.381-1.822zM66.17 35.524c.17-.381.452-.708.812-.94.37-.243.81-.371 1.26-.369.3 0 .596.056.873.167.375.149.7.392.941.703a2.05 2.05 0 0 1 .22 2.188c-.174.35-.445.648-.783.862a2.28 2.28 0 0 1-1.252.368c-.369 0-.732-.087-1.057-.252a2.226 2.226 0 0 1-.81-.696 2.078 2.078 0 0 1-.204-2.032zm-4.967 10.355a2.163 2.163 0 0 1-.74.894 2.348 2.348 0 0 1-2.294.21 2.22 2.22 0 0 1-.905-.743 2.065 2.065 0 0 1-.201-2.03c.17-.382.452-.71.812-.942a2.34 2.34 0 0 1 2.117-.2c.407.164.755.439 1 .79.245.35.375.763.376 1.184a2 2 0 0 1-.165.837zm7.126 8.68a2.203 2.203 0 0 1-1.224 1.149 2.355 2.355 0 0 1-1.719-.008 2.285 2.285 0 0 1-.994-.781 2.037 2.037 0 0 1-.379-1.2c0-.281.058-.56.171-.819.113-.26.279-.496.487-.694.209-.2.456-.357.729-.464a2.342 2.342 0 0 1 1.733.006c.408.162.756.436 1 .787a2.056 2.056 0 0 1 .196 2.016v.007zm9.11-8.68a2.16 2.16 0 0 1-.74.894 2.344 2.344 0 0 1-2.293.21 2.22 2.22 0 0 1-.905-.743 2.056 2.056 0 0 1-.204-2.03c.17-.382.453-.71.813-.942a2.34 2.34 0 0 1 2.117-.2c.407.164.755.439 1 .79.245.35.375.763.375 1.184.007.287-.05.572-.164.837z" fill="#000"/><path d="M72.541 82.552l-12.655-.075a6.026 6.026 0 0 1-4.074-1.707 5.472 5.472 0 0 1-1.657-3.936 5.478 5.478 0 0 1 1.709-3.915 6.034 6.034 0 0 1 4.096-1.658l12.655.076c.783-.01 1.56.128 2.287.408a5.92 5.92 0 0 1 1.942 1.22 5.601 5.601 0 0 1 1.295 1.84 5.382 5.382 0 0 1-.029 4.345 5.609 5.609 0 0 1-1.319 1.826 5.928 5.928 0 0 1-1.958 1.196c-.73.27-1.51.4-2.292.38z" fill="#3BFFB7"/><path d="M72.54 82.552v-.322l-12.654-.079a5.696 5.696 0 0 1-3.9-1.565 5.17 5.17 0 0 1-1.611-3.728v-.033a5.174 5.174 0 0 1 1.643-3.716 5.7 5.7 0 0 1 3.913-1.535h.037l12.652.078a5.696 5.696 0 0 1 3.9 1.566 5.169 5.169 0 0 1 1.611 3.728v.035a5.173 5.173 0 0 1-1.643 3.716 5.7 5.7 0 0 1-3.913 1.535h-.034v.645h.04a6.39 6.39 0 0 0 4.388-1.725c1.166-1.105 1.827-2.604 1.837-4.17v-.039c0-1.566-.65-3.069-1.81-4.18a6.38 6.38 0 0 0-4.376-1.749l-12.655-.078h-.04a6.39 6.39 0 0 0-4.38 1.728c-1.164 1.104-1.824 2.601-1.835 4.166v.038c0 1.566.65 3.07 1.81 4.18a6.383 6.383 0 0 0 4.376 1.751l12.655.076-.01-.323z" fill="#000"/><path d="M80.756 76.926a5.422 5.422 0 0 1-1.012 3.108 5.838 5.838 0 0 1-2.654 2.05 6.158 6.158 0 0 1-3.402.298 5.976 5.976 0 0 1-3.003-1.552 5.526 5.526 0 0 1-1.591-2.88 5.362 5.362 0 0 1 .356-3.238 5.68 5.68 0 0 1 2.184-2.503 6.097 6.097 0 0 1 3.275-.925 6.134 6.134 0 0 1 2.249.44 5.905 5.905 0 0 1 1.901 1.227 5.591 5.591 0 0 1 1.264 1.827 5.39 5.39 0 0 1 .433 2.148z" fill="#fff"/><path d="M80.756 76.926h-.341a5.172 5.172 0 0 1-1.636 3.71 5.699 5.699 0 0 1-3.902 1.543h-.037a5.697 5.697 0 0 1-3.905-1.563 5.17 5.17 0 0 1-1.614-3.73v-.033a5.158 5.158 0 0 1 1.64-3.719 5.684 5.684 0 0 1 3.916-1.53h.037a5.695 5.695 0 0 1 3.888 1.565 5.17 5.17 0 0 1 1.613 3.716v.033h.68v-.033c0-1.566-.65-3.069-1.808-4.18a6.378 6.378 0 0 0-4.376-1.749h-.034c-1.643 0-3.22.62-4.386 1.724a5.794 5.794 0 0 0-1.837 4.168v.037c0 1.567.651 3.07 1.81 4.18a6.385 6.385 0 0 0 4.376 1.752h.037c1.644 0 3.22-.62 4.387-1.725 1.165-1.104 1.826-2.603 1.836-4.169l-.344.003z" fill="#000"/><path d="M32.962 53.674H18.674v13.612h14.288V53.674z" fill="#fff"/><path d="M32.962 67.289v-.326H19.013V54h13.61v13.29h.339v-.326.326h.341V53.35H18.336v14.26h14.967v-.322h-.341z" fill="#000"/><path d="M23.434 60.936l1.905 1.818a.353.353 0 0 0 .265.093.353.353 0 0 0 .248-.129l2.858-3.63a.313.313 0 0 0-.076-.445.354.354 0 0 0-.47.055l-2.621 3.332-1.627-1.55a.342.342 0 0 0-.24-.096.355.355 0 0 0-.24.095.323.323 0 0 0-.1.228.31.31 0 0 0 .098.23z" fill="#000"/><path d="M34.25 9.925H3.544c-.34 0-.614.261-.614.584v19.25c0 .322.275.584.614.584H34.25a.6.6 0 0 0 .613-.585V10.51c0-.323-.274-.584-.613-.584z" fill="#fff"/><path d="M34.253 30.34v-.322H3.543a.272.272 0 0 1-.187-.074.246.246 0 0 1-.078-.178v-19.26a.242.242 0 0 1 .08-.183.266.266 0 0 1 .193-.076h30.702a.278.278 0 0 1 .193.076.252.252 0 0 1 .08.184v19.251a.246.246 0 0 1-.078.179.271.271 0 0 1-.187.073v.648a.982.982 0 0 0 .672-.267.89.89 0 0 0 .28-.64V10.507a.891.891 0 0 0-.28-.64.982.982 0 0 0-.672-.268H3.543a.98.98 0 0 0-.675.268.899.899 0 0 0-.206.293.863.863 0 0 0-.072.347v19.251c0 .12.024.237.072.347a.91.91 0 0 0 .206.294.98.98 0 0 0 .675.267h30.71v-.325z" fill="#000"/><path d="M3.14 13.65h31.936v-.648H3.14m13.868 4.251h13.025a.348.348 0 0 0 .222-.105.317.317 0 0 0 .09-.22.317.317 0 0 0-.09-.219.348.348 0 0 0-.222-.104H17.007a.358.358 0 0 0-.26.084.324.324 0 0 0-.11.24.312.312 0 0 0 .11.24.345.345 0 0 0 .26.084zm0 3.604h13.025a.348.348 0 0 0 .222-.104.317.317 0 0 0 .09-.22.317.317 0 0 0-.09-.22.348.348 0 0 0-.222-.104H17.007a.358.358 0 0 0-.26.085.324.324 0 0 0-.11.24.312.312 0 0 0 .11.24.343.343 0 0 0 .26.083zm0 3.595h13.025a.348.348 0 0 0 .222-.104.317.317 0 0 0 .09-.22.317.317 0 0 0-.09-.22.348.348 0 0 0-.222-.104H17.007a.358.358 0 0 0-.26.084.324.324 0 0 0-.11.24.312.312 0 0 0 .11.24.343.343 0 0 0 .26.084z" fill="#000"/><path d="M13.853 20.734a3.482 3.482 0 0 1-.632 2.004 3.747 3.747 0 0 1-1.694 1.331 3.954 3.954 0 0 1-2.186.21 3.843 3.843 0 0 1-1.938-.984 3.553 3.553 0 0 1-1.038-1.845 3.444 3.444 0 0 1 .213-2.082 3.645 3.645 0 0 1 1.393-1.618 3.915 3.915 0 0 1 2.101-.608c1.001 0 1.962.378 2.67 1.051a3.519 3.519 0 0 1 1.111 2.54z" fill="#A67BFC"/><path d="M13.853 20.734h-.338c0 .758-.277 1.492-.782 2.078a3.474 3.474 0 0 1-1.988 1.135 3.59 3.59 0 0 1-2.292-.325 3.34 3.34 0 0 1-1.556-1.636 3.133 3.133 0 0 1-.113-2.205 3.295 3.295 0 0 1 1.38-1.773 3.572 3.572 0 0 1 2.248-.537 3.513 3.513 0 0 1 2.095.944c.32.304.573.666.746 1.064.173.398.262.824.262 1.255h.68c0-.777-.242-1.536-.695-2.182a4.085 4.085 0 0 0-1.85-1.447 4.311 4.311 0 0 0-2.382-.223 4.19 4.19 0 0 0-2.11 1.075 3.862 3.862 0 0 0-1.128 2.01 3.755 3.755 0 0 0 .234 2.27c.312.718.84 1.33 1.518 1.762a4.269 4.269 0 0 0 2.29.662c.542 0 1.078-.1 1.579-.298.5-.197.954-.486 1.337-.851s.687-.798.894-1.275c.207-.476.313-.987.313-1.503h-.342z" fill="#000"/><path d="M9.409 22.31h.34v-2.468l1.268.907-1.812 1.3.204.253.206.252 2.172-1.558a.327.327 0 0 0 .126-.252.313.313 0 0 0-.126-.252l-2.172-1.56a.35.35 0 0 0-.355-.026.333.333 0 0 0-.137.12.31.31 0 0 0-.05.17v3.113a.315.315 0 0 0 .19.287.353.353 0 0 0 .355-.035l-.21-.252zm11.895 89.507h32.039v-.648H21.304m-15.11 3.854h32.035v-.648H6.195" fill="#000"/><path d="M53.92 109.041H25.166a5.576 5.576 0 0 1 2.024-3.331 6.082 6.082 0 0 1 3.797-1.312h.228a8.338 8.338 0 0 1 3.535-3.241 8.881 8.881 0 0 1 4.825-.843 8.74 8.74 0 0 1 4.512 1.836 8.093 8.093 0 0 1 2.698 3.904 4.337 4.337 0 0 1 2.183-1.019 4.458 4.458 0 0 1 2.412.285 4.214 4.214 0 0 1 1.856 1.496 3.92 3.92 0 0 1 .693 2.22l-.01.005z" fill="#A77FFA"/><path d="M45.879 111.491v-.322H14.552v.322l.333.058a5.745 5.745 0 0 1 2.085-3.436 6.27 6.27 0 0 1 3.916-1.353h.233a.349.349 0 0 0 .18-.04.328.328 0 0 0 .13-.124 8.755 8.755 0 0 1 3.71-3.398 9.312 9.312 0 0 1 5.063-.884 9.164 9.164 0 0 1 4.734 1.925 8.49 8.49 0 0 1 2.833 4.094.34.34 0 0 0 .236.218.349.349 0 0 0 .32-.072 4.378 4.378 0 0 1 2.201-1.027 4.492 4.492 0 0 1 2.432.288 4.248 4.248 0 0 1 1.871 1.508c.457.666.7 1.443.7 2.238h.68c0-.92-.282-1.821-.81-2.592a4.92 4.92 0 0 0-2.167-1.747 5.205 5.205 0 0 0-2.818-.334 5.076 5.076 0 0 0-2.55 1.19l.23.237.326-.094a9.135 9.135 0 0 0-3.047-4.406 9.854 9.854 0 0 0-5.094-2.072 10.03 10.03 0 0 0-5.448.95 9.427 9.427 0 0 0-3.992 3.658l.296.159.032-.308h-.265c-1.59 0-3.131.534-4.35 1.507a6.4 6.4 0 0 0-2.32 3.817.311.311 0 0 0 .076.249.358.358 0 0 0 .244.117h31.327a.358.358 0 0 0 .24-.096.323.323 0 0 0 .101-.23h-.341z" fill="#000"/><path d="M124.211 85.345a1.358 1.358 0 0 0-.037-.209 1.745 1.745 0 0 0-.728-1.025 1.904 1.904 0 0 0-1.478-.27l-8.036 1.814a1.865 1.865 0 0 1-1.121-.097 1.775 1.775 0 0 1-.72-.545l-.344-.444 11.906-3.4a.325.325 0 0 0 .169-.112.303.303 0 0 0 .067-.186l.076-5.904a.291.291 0 0 0-.037-.156.302.302 0 0 0-.082-.094.33.33 0 0 0-.243-.065h-.04l-3.334.756-.092-.38a3.9 3.9 0 0 0-1.623-2.292 4.248 4.248 0 0 0-3.299-.61 4.117 4.117 0 0 0-2.397 1.55 3.776 3.776 0 0 0-.725 2.672c.021.16.051.32.09.477l.096.378-2.776.628-.881-2.458a.309.309 0 0 0-.138-.166.348.348 0 0 0-.214-.048h-.037l-1.463.33a.332.332 0 0 0-.192.125.298.298 0 0 0-.059.215.091.091 0 0 0 0 .04.298.298 0 0 0 .132.184.33.33 0 0 0 .225.056h.042l1.164-.252.119.338c.156.448 2.646 7.562 2.982 8.508.019.047.029.08.035.095a2.385 2.385 0 0 0 1.259 1.311l.368.162-.17.352c-.14.286-.196.602-.161.915.06.453.297.867.664 1.16.186.15.402.262.635.33a1.97 1.97 0 0 0 .953.024c.443-.1.832-.355 1.087-.714.208-.286.32-.623.324-.97a1.675 1.675 0 0 0-.303-.977l-.317-.46 5.069-1.148-.066.552a1.581 1.581 0 0 0 0 .401c.057.456.3.873.677 1.16a1.884 1.884 0 0 0 1.347.375c.074 0 .148-.02.225-.035.434-.102.814-.35 1.071-.699.256-.348.372-.773.326-1.197l.005.005zm-10.879-11.293a3.39 3.39 0 0 1 2.016-1.306c.134-.032.27-.056.407-.07a3.568 3.568 0 0 1 2.344.581 3.275 3.275 0 0 1 1.36 1.91c.033.128.057.258.072.39a3.144 3.144 0 0 1-.606 2.236 3.43 3.43 0 0 1-2.008 1.293 3.567 3.567 0 0 1-2.751-.514 3.277 3.277 0 0 1-1.435-2.3 3.132 3.132 0 0 1 .601-2.22zm-1.71 9.892l-1.931-5.51 2.847-.644.159.232a4.04 4.04 0 0 0 1.682 1.41 4.281 4.281 0 0 0 2.699.274 4.094 4.094 0 0 0 2.143-1.237c.548-.603.889-1.35.979-2.143l.032-.275 3.064-.678-.066 4.979v.287l-11.608 3.305zm3.686 4.048a1.172 1.172 0 0 1-.823.472c-.284.03-.57-.04-.803-.196a1.12 1.12 0 0 1-.491-.79c-.031-.27.042-.543.207-.765a1.176 1.176 0 0 1 .828-.465c.284-.03.569.04.802.196a1.112 1.112 0 0 1 .489.787c.032.27-.043.54-.209.761zm8.041-1.815a1.172 1.172 0 0 1-.826.472 1.234 1.234 0 0 1-.793-.195 1.121 1.121 0 0 1-.495-.791c-.03-.27.044-.542.208-.765.165-.222.409-.38.686-.445a.895.895 0 0 1 .14-.02c.285-.032.571.037.805.194a1.106 1.106 0 0 1 .489.789c.03.27-.046.541-.214.761z" fill="#C4C6FC"/><path d="M118.943 77.788a3.427 3.427 0 0 1-2.019 1.306 3.571 3.571 0 0 1-2.751-.513 3.276 3.276 0 0 1-1.434-2.3 3.16 3.16 0 0 1 .611-2.234 3.45 3.45 0 0 1 2.006-1.296 3.09 3.09 0 0 1 .407-.07 3.571 3.571 0 0 1 2.344.581 3.276 3.276 0 0 1 1.36 1.91c.033.128.057.258.072.39a3.164 3.164 0 0 1-.596 2.226zm4.406 8.389a1.172 1.172 0 0 1-.826.472 1.239 1.239 0 0 1-.794-.195 1.124 1.124 0 0 1-.494-.791c-.03-.27.044-.542.208-.765a1.19 1.19 0 0 1 .686-.445.894.894 0 0 1 .14-.02c.284-.032.571.037.805.193a1.106 1.106 0 0 1 .489.79c.03.27-.046.541-.214.761zm-8.041 1.815a1.172 1.172 0 0 1-.823.472c-.284.03-.57-.04-.804-.196a1.117 1.117 0 0 1-.49-.79c-.031-.27.042-.543.207-.765a1.173 1.173 0 0 1 .828-.465c.283-.03.569.04.802.196a1.112 1.112 0 0 1 .489.787c.032.27-.043.54-.209.761z" fill="#fff"/><path d="M123.296 75.373l-.066 4.979v.287l-11.608 3.305-1.931-5.51 2.847-.644.159.232a4.04 4.04 0 0 0 1.682 1.41 4.281 4.281 0 0 0 2.699.274 4.094 4.094 0 0 0 2.143-1.237c.547-.603.889-1.35.979-2.143l.032-.275 3.064-.678z" fill="#fff"/><path d="M117.388 74.94a.295.295 0 0 1-.048.201l-1.114 1.722a.322.322 0 0 1-.246.148.328.328 0 0 1-.209-.045l-.905-.532a.334.334 0 0 1-.106-.102.3.3 0 0 1-.002-.334.311.311 0 0 1 .087-.09.35.35 0 0 1 .247-.056.34.34 0 0 1 .121.043l.622.365.941-1.454a.343.343 0 0 1 .53-.045.31.31 0 0 1 .082.176v.002z" fill="#000"/></svg>
diff --git a/browser/components/privatebrowsing/content/assets/private-promo-asset.svg b/browser/components/privatebrowsing/content/assets/private-promo-asset.svg
new file mode 100644
index 0000000000..841384f015
--- /dev/null
+++ b/browser/components/privatebrowsing/content/assets/private-promo-asset.svg
@@ -0,0 +1,144 @@
+<!-- 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/. -->
+<svg width="223" height="190" viewBox="0 0 223 190" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g opacity="0.7">
+<path d="M68.5967 100.461C92.4106 100.461 98.623 125.09 127.096 125.09C153.499 125.09 173.518 92.1083 168.512 83.2681C121.402 138.565 114.671 50.3314 52.776 64.5812C5.21166 77.1428 30.8049 135.313 54.6191 145.071C35.4643 128.807 42.712 100.461 68.5967 100.461Z" fill="url(#paint0_radial_2440_46367)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M40.3116 132.914C30.5066 113.845 29.3939 90.9461 57.6536 83.376C74.9878 79.4944 87.7636 90.6468 100.188 101.493C118.092 117.121 135.266 132.114 164.299 100.567C164.306 100.58 164.312 100.594 164.319 100.607C169.04 93.0728 170.989 85.7731 169.004 82.2681C143.468 112.242 129.796 100.044 112.772 84.8552C98.3892 72.0228 81.6134 57.0554 53.2681 63.5812C16.7912 73.5903 21.6499 112.556 40.3116 132.914Z" fill="url(#paint1_radial_2440_46367)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M38.9555 131.946C20.8066 111.345 18.9847 73.4338 52.782 63.5812C81.1272 57.0554 97.9031 72.0228 112.286 84.8552C129.31 100.044 142.982 112.242 168.518 82.2681C169.673 84.3087 169.495 87.6354 168.213 91.5458C142.041 123.78 123.512 108.262 104.065 91.9765C90.3359 80.4786 76.1493 68.598 58.4934 72.8684C23.329 81.3743 26.3323 110.866 38.9555 131.946Z" fill="url(#paint2_radial_2440_46367)"/>
+</g>
+<path d="M43.8709 37C46.9108 36.9987 49.8963 37.7429 52.5207 39.1563C55.145 40.5697 57.3137 42.6012 58.8038 45.0423C58.8122 45.056 58.8244 45.0675 58.8391 45.0755C58.8539 45.0836 58.8707 45.0879 58.8878 45.088C59.0369 45.088 59.1898 45.088 59.3427 45.088C62.1086 45.0889 64.7874 45.98 66.9159 47.6072C69.0445 49.2345 70.489 51.4957 71 54H13C13.0185 52.4825 13.5141 51.0025 14.4269 49.7392C15.3397 48.4759 16.6304 47.4835 18.1429 46.8821C19.6553 46.2807 21.3245 46.0962 22.9485 46.3508C24.5724 46.6055 26.0814 47.2884 27.2927 48.3169C27.3109 48.3311 27.3338 48.3392 27.3576 48.3398H27.3844C27.3998 48.3357 27.4138 48.328 27.4252 48.3175C27.4365 48.3071 27.4449 48.2941 27.4494 48.2799C28.506 45.0239 30.6746 42.1688 33.6331 40.1388C36.5916 38.1089 40.182 37.0124 43.8709 37.0123" fill="#D9BFFF"/>
+<path d="M67.2322 132.282C67.3284 132.282 67.4208 132.244 67.4891 132.176C67.5574 132.108 67.596 132.015 67.5966 131.919V128.618C67.6004 128.568 67.5939 128.517 67.5774 128.47C67.5609 128.422 67.5348 128.379 67.5006 128.342C67.4665 128.305 67.4252 128.275 67.3791 128.255C67.3331 128.235 67.2835 128.225 67.2332 128.225C67.183 128.225 67.1333 128.235 67.0873 128.255C67.0413 128.275 67 128.305 66.9658 128.342C66.9317 128.379 66.9056 128.422 66.8891 128.47C66.8726 128.517 66.866 128.568 66.8699 128.618V131.919C66.8699 132.015 66.9081 132.108 66.976 132.176C67.0439 132.244 67.1361 132.282 67.2322 132.282V132.282Z" fill="white"/>
+<path d="M67.2314 132.281V132.065C67.2705 132.065 67.308 132.049 67.3358 132.022C67.3637 131.994 67.3796 131.957 67.3802 131.918V128.617C67.3796 128.577 67.3637 128.54 67.336 128.512C67.3082 128.484 67.2707 128.468 67.2314 128.468C67.2119 128.468 67.1927 128.471 67.1748 128.479C67.1568 128.486 67.1406 128.497 67.1269 128.511C67.1133 128.525 67.1025 128.542 67.0953 128.56C67.088 128.578 67.0845 128.597 67.0847 128.617V131.918C67.0845 131.937 67.088 131.956 67.0953 131.974C67.1026 131.992 67.1134 132.009 67.127 132.022C67.1407 132.036 67.157 132.047 67.1749 132.054C67.1928 132.061 67.212 132.065 67.2314 132.065V132.497C67.0781 132.497 66.9311 132.436 66.8227 132.327C66.7144 132.219 66.6535 132.071 66.6535 131.918V128.617C66.6487 128.537 66.6601 128.458 66.687 128.384C66.7139 128.309 66.7557 128.241 66.81 128.183C66.8642 128.125 66.9297 128.079 67.0023 128.048C67.075 128.016 67.1533 128 67.2325 128C67.3116 128 67.3899 128.016 67.4626 128.048C67.5352 128.079 67.6007 128.125 67.6549 128.183C67.7092 128.241 67.751 128.309 67.7779 128.384C67.8048 128.458 67.8162 128.537 67.8114 128.617V131.918C67.8103 132.071 67.7487 132.218 67.6401 132.327C67.5316 132.435 67.3847 132.496 67.2314 132.497V132.281Z" fill="white"/>
+<path d="M65.222 130.27C65.222 130.318 65.2313 130.365 65.2496 130.409C65.2678 130.453 65.2944 130.493 65.3281 130.527C65.3617 130.561 65.4017 130.588 65.4456 130.606C65.4896 130.624 65.5367 130.633 65.5842 130.633H68.8791C68.9291 130.637 68.9795 130.631 69.0269 130.614C69.0743 130.598 69.1178 130.572 69.1547 130.537C69.1915 130.503 69.2209 130.462 69.241 130.416C69.2611 130.37 69.2715 130.32 69.2715 130.269C69.2715 130.219 69.2611 130.169 69.241 130.123C69.2209 130.077 69.1915 130.036 69.1547 130.002C69.1178 129.967 69.0743 129.941 69.0269 129.925C68.9795 129.908 68.9291 129.902 68.8791 129.905H65.5778C65.4826 129.908 65.3921 129.947 65.3256 130.015C65.2592 130.084 65.222 130.175 65.222 130.27Z" fill="white"/>
+<path d="M65.2217 130.27H65.4373C65.437 130.289 65.4406 130.309 65.4479 130.327C65.4551 130.344 65.4659 130.361 65.4796 130.374C65.4933 130.388 65.5095 130.399 65.5275 130.406C65.5454 130.414 65.5646 130.417 65.5839 130.417H68.8787C68.8982 130.417 68.9175 130.414 68.9355 130.406C68.9536 130.399 68.97 130.388 68.9838 130.375C68.9977 130.361 69.0087 130.345 69.0162 130.327C69.0237 130.309 69.0275 130.289 69.0275 130.27C69.0275 130.23 69.0119 130.192 68.984 130.165C68.956 130.137 68.9182 130.121 68.8787 130.121H65.5774C65.558 130.121 65.5388 130.125 65.5208 130.132C65.5029 130.14 65.4866 130.151 65.473 130.165C65.4594 130.179 65.4486 130.195 65.4414 130.213C65.4341 130.231 65.4305 130.25 65.4308 130.27H64.9996C64.999 130.194 65.0135 130.118 65.0423 130.047C65.0711 129.976 65.1136 129.912 65.1673 129.858C65.221 129.804 65.2849 129.76 65.3553 129.731C65.4257 129.702 65.5012 129.687 65.5774 129.687H68.8787C68.9577 129.682 69.0369 129.693 69.1113 129.72C69.1858 129.747 69.2539 129.789 69.3116 129.843C69.3692 129.898 69.4151 129.963 69.4465 130.036C69.4779 130.109 69.4941 130.187 69.4941 130.267C69.4941 130.346 69.4779 130.424 69.4465 130.497C69.4151 130.57 69.3692 130.636 69.3116 130.69C69.2539 130.744 69.1858 130.786 69.1113 130.813C69.0369 130.84 68.9577 130.852 68.8787 130.847H65.5774C65.4242 130.847 65.2772 130.786 65.1688 130.677C65.0604 130.569 64.9996 130.421 64.9996 130.268L65.2217 130.27Z" fill="white"/>
+<path opacity="0.7" d="M44 150.105C44 146.792 46.6863 144.105 50 144.105H174C177.314 144.105 180 146.792 180 150.105V150.105C180 153.419 177.314 156.105 174 156.105H50C46.6863 156.105 44 153.419 44 150.105V150.105Z" fill="#9059FF"/>
+<g filter="url(#filter0_d_2440_46367)">
+<path d="M92.9277 149C96.2414 149 98.9277 146.314 98.9277 143C98.9277 139.686 96.2414 137 92.9277 137C89.614 137 86.9277 139.686 86.9277 143C86.9277 146.314 89.614 149 92.9277 149Z" fill="#D9BFFF"/>
+</g>
+<g filter="url(#filter1_d_2440_46367)">
+<path d="M113.776 149.1C117.09 149.1 119.776 146.413 119.776 143.1C119.776 139.786 117.09 137.1 113.776 137.1C110.463 137.1 107.776 139.786 107.776 143.1C107.776 146.413 110.463 149.1 113.776 149.1Z" fill="#9059FF"/>
+</g>
+<g filter="url(#filter2_d_2440_46367)">
+<path d="M134.7 149.1C138.014 149.1 140.7 146.413 140.7 143.1C140.7 139.786 138.014 137.1 134.7 137.1C131.386 137.1 128.7 139.786 128.7 143.1C128.7 146.413 131.386 149.1 134.7 149.1Z" fill="#D9BFFF"/>
+</g>
+<g filter="url(#filter3_d_2440_46367)">
+<path d="M155.625 149.1C158.939 149.1 161.625 146.413 161.625 143.1C161.625 139.786 158.939 137.1 155.625 137.1C152.311 137.1 149.625 139.786 149.625 143.1C149.625 146.413 152.311 149.1 155.625 149.1Z" fill="#9059FF"/>
+</g>
+<g filter="url(#filter4_d_2440_46367)">
+<path d="M76.8934 137.943C76.8934 144.827 71.3125 150.408 64.4281 150.408C57.5438 150.408 51.9629 144.827 51.9629 137.943C51.9629 131.058 57.5438 125.478 64.4281 125.478C71.3125 125.478 76.8934 131.058 76.8934 137.943Z" fill="#592ACB" stroke="white"/>
+<path d="M68.7879 142.288C66.2695 142.288 66.2289 140.744 64.7666 140.744C63.3043 140.744 63.02 142.288 60.7453 142.288C58.8768 142.288 57.4957 140.216 57.4551 136.682C57.4551 134.448 58.0238 133.758 60.4203 133.758C62.8169 133.758 63.5886 134.936 64.7666 134.936C65.9446 134.936 66.6757 133.758 69.1129 133.758C71.5501 133.758 72.1187 134.448 72.0781 136.682C72.0375 140.216 70.6564 142.288 68.7879 142.288ZM61.6389 136.479C60.136 136.561 59.5267 137.576 59.5267 137.82C59.5267 138.063 60.5015 138.795 61.4764 138.795C62.0451 138.795 62.695 138.632 63.1012 138.429C63.4261 138.267 63.548 137.901 63.4261 137.576C63.1418 136.804 62.4107 136.357 61.6389 136.479ZM67.8943 136.479C67.1225 136.357 66.3914 136.845 66.0664 137.617C65.9446 137.942 66.0664 138.307 66.3914 138.47C66.7976 138.673 67.4475 138.835 68.0162 138.835C68.991 138.835 69.9659 138.104 69.9659 137.86C70.0065 137.576 69.3972 136.561 67.8943 136.479Z" fill="white"/>
+</g>
+<g filter="url(#filter5_d_2440_46367)">
+<path d="M50 49.8127C50 45.9984 53.0921 42.9062 56.9065 42.9062H163.094C166.908 42.9062 170 45.9984 170 49.8127V54.9926H50V49.8127Z" fill="#A67EFA"/>
+<rect x="50.4317" y="36.4317" width="119.137" height="70.7914" rx="6.47482" fill="#25003E" stroke="white" stroke-width="0.863309"/>
+<path d="M50 42.9065C50 39.0921 53.0921 36 56.9065 36H163.094C166.908 36 170 39.0921 170 42.9065V48.0863H50V42.9065Z" fill="#6F4AE7"/>
+<path d="M56.9065 36.4317H163.094C166.669 36.4317 169.568 39.3305 169.568 42.9065V47.6547H50.4317V42.9065C50.4317 39.3305 53.3305 36.4317 56.9065 36.4317Z" stroke="white" stroke-width="0.863309"/>
+<rect opacity="0.4" x="81" y="41" width="57" height="2" rx="1" fill="white"/>
+<g opacity="0.5">
+<rect x="99" y="72.7852" width="21.7561" height="10.8781" rx="1.81301" fill="#6F4AE7"/>
+<rect x="99" y="70.0659" width="21.7561" height="16.3171" rx="1.81301" fill="#6F4AE7"/>
+<rect x="103.986" y="62.3598" width="11.7846" height="20.8496" rx="5.89228" stroke="#6F4AE7" stroke-width="2.71951"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M109.974 82.9467C112.556 82.9467 114.649 80.8533 114.649 78.271C114.649 75.6886 112.556 73.5952 109.974 73.5952C107.391 73.5952 105.298 75.6886 105.298 78.271C105.298 80.8533 107.391 82.9467 109.974 82.9467ZM111.532 77.2313C111.532 77.7487 111.28 78.2073 110.892 78.4908L111.382 80.6809C111.433 80.9101 111.259 81.1277 111.024 81.1277H108.923C108.688 81.1277 108.513 80.9101 108.565 80.6809L109.055 78.4908C108.667 78.2073 108.415 77.7487 108.415 77.2313C108.415 76.3705 109.113 75.6727 109.973 75.6727C110.834 75.6727 111.532 76.3705 111.532 77.2313Z" fill="#D9BFFF"/>
+</g>
+</g>
+<path d="M168.081 90C164.15 89.9981 160.289 91.0488 156.896 93.0442C153.502 95.0396 150.698 97.9076 148.771 101.354C148.76 101.373 148.744 101.389 148.725 101.401C148.706 101.412 148.684 101.418 148.662 101.418C148.47 101.418 148.272 101.418 148.074 101.418C144.497 101.42 141.034 102.678 138.281 104.975C135.529 107.272 133.661 110.464 133 114H208C207.976 111.858 207.335 109.768 206.155 107.985C204.975 106.201 203.305 104.8 201.35 103.951C199.394 103.102 197.236 102.842 195.136 103.201C193.036 103.561 191.084 104.525 189.518 105.977C189.495 105.997 189.465 106.008 189.434 106.009H189.4C189.38 106.003 189.361 105.992 189.347 105.978C189.332 105.963 189.321 105.945 189.315 105.925C187.949 101.328 185.145 97.2971 181.319 94.4313C177.494 91.5655 172.851 90.0175 168.081 90.0174" fill="#D9BFFF"/>
+<path d="M36.9974 105C37.2088 105 37.4116 104.925 37.5615 104.791C37.7114 104.657 37.7962 104.475 37.7974 104.284V97.7753C37.8059 97.6764 37.7916 97.5769 37.7553 97.4832C37.7191 97.3896 37.6617 97.3036 37.5868 97.2308C37.5119 97.158 37.4211 97.0999 37.3201 97.0602C37.2191 97.0205 37.11 97 36.9998 97C36.8896 97 36.7805 97.0205 36.6795 97.0602C36.5785 97.0999 36.4877 97.158 36.4128 97.2308C36.3379 97.3036 36.2805 97.3896 36.2443 97.4832C36.208 97.5769 36.1937 97.6764 36.2022 97.7753V104.284C36.2022 104.474 36.286 104.656 36.4351 104.79C36.5842 104.925 36.7865 105 36.9974 105V105Z" fill="#D9BFFF"/>
+<path d="M36.9983 104.616V104.231C37.0522 104.231 37.104 104.204 37.1424 104.155C37.1808 104.106 37.2027 104.04 37.2035 103.97V98.0969C37.2028 98.0269 37.1809 97.96 37.1426 97.9105C37.1043 97.861 37.0525 97.8327 36.9983 97.8317C36.9715 97.8317 36.945 97.8386 36.9202 97.852C36.8955 97.8654 36.8731 97.885 36.8543 97.9096C36.8354 97.9343 36.8206 97.9636 36.8106 97.9957C36.8006 98.0279 36.7957 98.0623 36.7961 98.0969V103.97C36.7957 104.004 36.8006 104.039 36.8107 104.071C36.8207 104.102 36.8356 104.132 36.8544 104.156C36.8733 104.18 36.8957 104.199 36.9205 104.212C36.9452 104.225 36.9716 104.232 36.9983 104.231V105C36.7869 105 36.5842 104.891 36.4347 104.698C36.2853 104.505 36.2013 104.243 36.2013 103.97V98.0969C36.1946 97.9561 36.2104 97.815 36.2475 97.6823C36.2846 97.5497 36.3423 97.4282 36.4171 97.3255C36.4919 97.2227 36.5822 97.1408 36.6824 97.0848C36.7826 97.0289 36.8906 97 36.9998 97C37.109 97 37.217 97.0289 37.3172 97.0848C37.4174 97.1408 37.5077 97.2227 37.5825 97.3255C37.6573 97.4282 37.715 97.5497 37.7521 97.6823C37.7893 97.815 37.805 97.9561 37.7983 98.0969V103.97C37.7968 104.243 37.7119 104.505 37.5621 104.697C37.4123 104.89 37.2097 104.999 36.9983 105V104.616Z" fill="#D9BFFF"/>
+<path d="M33.3953 101.04C33.3953 101.125 33.412 101.209 33.4444 101.287C33.4768 101.366 33.5242 101.437 33.5841 101.497C33.6439 101.557 33.715 101.604 33.7932 101.637C33.8714 101.669 33.9552 101.686 34.0398 101.686H39.9015C39.9905 101.693 40.0801 101.681 40.1644 101.652C40.2488 101.622 40.3262 101.576 40.3918 101.515C40.4573 101.454 40.5096 101.38 40.5454 101.298C40.5811 101.216 40.5996 101.128 40.5996 101.038C40.5996 100.949 40.5811 100.86 40.5454 100.778C40.5096 100.696 40.4573 100.622 40.3918 100.562C40.3262 100.501 40.2488 100.454 40.1644 100.425C40.0801 100.395 39.9905 100.384 39.9015 100.391H34.0283C33.859 100.395 33.698 100.465 33.5798 100.586C33.4615 100.708 33.3953 100.871 33.3953 101.04Z" fill="#D9BFFF"/>
+<path d="M33.3953 101.005H33.7791C33.7786 101.031 33.785 101.058 33.798 101.083C33.8109 101.107 33.8301 101.13 33.8544 101.149C33.8788 101.167 33.9078 101.182 33.9396 101.192C33.9715 101.202 34.0057 101.207 34.0401 101.207H39.9046C39.9392 101.207 39.9736 101.202 40.0057 101.192C40.0378 101.182 40.067 101.168 40.0917 101.149C40.1163 101.13 40.1359 101.107 40.1492 101.083C40.1626 101.058 40.1695 101.031 40.1695 101.005C40.1695 100.95 40.1416 100.898 40.0919 100.86C40.0422 100.821 39.9749 100.799 39.9046 100.799H34.0286C33.994 100.799 33.9598 100.805 33.9278 100.815C33.8959 100.825 33.867 100.841 33.8427 100.86C33.8184 100.879 33.7993 100.901 33.7864 100.926C33.7735 100.951 33.7671 100.978 33.7676 101.005H33C32.999 100.899 33.0249 100.795 33.0761 100.698C33.1273 100.6 33.203 100.512 33.2986 100.437C33.3942 100.363 33.5079 100.303 33.6332 100.263C33.7585 100.222 33.8929 100.202 34.0286 100.202H39.9046C40.0453 100.195 40.1862 100.211 40.3186 100.248C40.4511 100.285 40.5724 100.343 40.675 100.418C40.7776 100.492 40.8594 100.583 40.9153 100.683C40.9712 100.783 41 100.891 41 101C41 101.109 40.9712 101.217 40.9153 101.318C40.8594 101.418 40.7776 101.508 40.675 101.583C40.5724 101.658 40.4511 101.715 40.3186 101.753C40.1862 101.79 40.0453 101.805 39.9046 101.799H34.0286C33.7558 101.799 33.4942 101.715 33.3013 101.565C33.1084 101.416 33 101.213 33 101.002L33.3953 101.005Z" fill="#D9BFFF"/>
+<path d="M19.4981 107C19.6831 107 19.8605 106.934 19.9916 106.817C20.1228 106.699 20.197 106.54 20.1981 106.374V100.678C20.2055 100.592 20.193 100.505 20.1613 100.423C20.1296 100.341 20.0794 100.266 20.0138 100.202C19.9483 100.138 19.8689 100.087 19.7805 100.053C19.6921 100.018 19.5967 100 19.5002 100C19.4037 100 19.3083 100.018 19.2199 100.053C19.1315 100.087 19.0521 100.138 18.9865 100.202C18.921 100.266 18.8708 100.341 18.8391 100.423C18.8074 100.505 18.7949 100.592 18.8023 100.678V106.374C18.8023 106.54 18.8756 106.699 19.0061 106.817C19.1366 106.934 19.3136 107 19.4981 107V107Z" fill="#D9BFFF"/>
+<path d="M19.4989 106.664V106.327C19.5461 106.327 19.5913 106.303 19.6249 106.261C19.6585 106.218 19.6778 106.16 19.6784 106.099V100.96C19.6778 100.899 19.6586 100.84 19.6251 100.797C19.5916 100.753 19.5463 100.729 19.4989 100.728C19.4754 100.728 19.4522 100.734 19.4306 100.745C19.4089 100.757 19.3893 100.774 19.3728 100.796C19.3564 100.818 19.3434 100.843 19.3347 100.871C19.3259 100.899 19.3216 100.93 19.3219 100.96V106.099C19.3216 106.129 19.3259 106.159 19.3347 106.187C19.3435 106.215 19.3565 106.24 19.373 106.261C19.3895 106.283 19.4091 106.3 19.4308 106.311C19.4524 106.322 19.4756 106.328 19.4989 106.327V107C19.3139 107 19.1365 106.905 19.0058 106.736C18.875 106.567 18.8015 106.338 18.8015 106.099V100.96C18.7957 100.837 18.8094 100.713 18.8419 100.597C18.8744 100.481 18.9249 100.375 18.9904 100.285C19.0558 100.195 19.1348 100.123 19.2225 100.074C19.3102 100.025 19.4047 100 19.5002 100C19.5957 100 19.6902 100.025 19.7779 100.074C19.8656 100.123 19.9446 100.195 20.01 100.285C20.0755 100.375 20.126 100.481 20.1585 100.597C20.191 100.713 20.2047 100.837 20.1989 100.96V106.099C20.1975 106.338 20.1233 106.566 19.9922 106.735C19.8611 106.904 19.6839 106.999 19.4989 107V106.664Z" fill="#D9BFFF"/>
+<path d="M16.3466 103.535C16.3466 103.609 16.3612 103.683 16.3896 103.751C16.4179 103.82 16.4594 103.882 16.5118 103.934C16.5642 103.987 16.6263 104.029 16.6948 104.057C16.7632 104.085 16.8365 104.1 16.9106 104.1H22.0395C22.1175 104.106 22.1958 104.096 22.2696 104.07C22.3434 104.044 22.4112 104.003 22.4685 103.95C22.5259 103.897 22.5716 103.833 22.6029 103.761C22.6342 103.689 22.6504 103.612 22.6504 103.533C22.6504 103.455 22.6342 103.377 22.6029 103.306C22.5716 103.234 22.5259 103.169 22.4685 103.116C22.4112 103.063 22.3434 103.022 22.2696 102.996C22.1958 102.971 22.1175 102.961 22.0395 102.967H16.9005C16.7524 102.97 16.6115 103.031 16.508 103.138C16.4045 103.244 16.3466 103.386 16.3466 103.535Z" fill="#D9BFFF"/>
+<path d="M16.3459 103.504H16.6818C16.6813 103.527 16.6869 103.55 16.6982 103.572C16.7095 103.593 16.7263 103.613 16.7476 103.63C16.7689 103.646 16.7943 103.659 16.8222 103.668C16.8501 103.677 16.88 103.681 16.9101 103.681H22.0416C22.0718 103.681 22.1019 103.677 22.13 103.668C22.1581 103.659 22.1837 103.646 22.2052 103.63C22.2268 103.613 22.2439 103.594 22.2556 103.572C22.2673 103.55 22.2733 103.527 22.2733 103.504C22.2733 103.456 22.2489 103.41 22.2054 103.377C22.162 103.343 22.103 103.324 22.0416 103.324H16.9C16.8698 103.324 16.8398 103.329 16.8119 103.338C16.7839 103.347 16.7586 103.36 16.7374 103.377C16.7161 103.394 16.6994 103.413 16.6881 103.435C16.6768 103.457 16.6712 103.48 16.6717 103.504H16C15.9991 103.412 16.0218 103.32 16.0666 103.235C16.1114 103.15 16.1776 103.073 16.2612 103.007C16.3449 102.942 16.4444 102.89 16.5541 102.855C16.6637 102.819 16.7813 102.801 16.9 102.801H22.0416C22.1646 102.795 22.2879 102.809 22.4038 102.842C22.5197 102.874 22.6259 102.925 22.7156 102.99C22.8054 103.055 22.877 103.134 22.9259 103.222C22.9748 103.31 23 103.404 23 103.5C23 103.595 22.9748 103.69 22.9259 103.778C22.877 103.865 22.8054 103.944 22.7156 104.01C22.6259 104.075 22.5197 104.126 22.4038 104.158C22.2879 104.191 22.1646 104.204 22.0416 104.199H16.9C16.6613 104.199 16.4324 104.125 16.2636 103.994C16.0948 103.863 16 103.686 16 103.501L16.3459 103.504Z" fill="#D9BFFF"/>
+<path d="M29.997 122C30.2612 122 30.5147 121.887 30.7021 121.686C30.8894 121.485 30.9955 121.212 30.997 120.927V111.163C31.0076 111.015 30.9897 110.865 30.9444 110.725C30.8991 110.584 30.8274 110.455 30.7338 110.346C30.6402 110.237 30.5267 110.15 30.4004 110.09C30.2741 110.031 30.1378 110 30 110C29.8622 110 29.7259 110.031 29.5996 110.09C29.4733 110.15 29.3598 110.237 29.2662 110.346C29.1726 110.455 29.1009 110.584 29.0556 110.725C29.0103 110.865 28.9924 111.015 29.003 111.163V120.927C29.003 121.211 29.1077 121.484 29.2941 121.686C29.4805 121.887 29.7334 122 29.997 122V122Z" fill="#D9BFFF"/>
+<path d="M29.9981 121.423V120.847C30.0655 120.847 30.1302 120.806 30.1782 120.732C30.2262 120.659 30.2537 120.559 30.2546 120.455V111.645C30.2537 111.54 30.2264 111.44 30.1785 111.366C30.1306 111.291 30.0659 111.249 29.9981 111.248C29.9646 111.248 29.9314 111.258 29.9005 111.278C29.8696 111.298 29.8416 111.327 29.8181 111.364C29.7945 111.401 29.776 111.445 29.7635 111.494C29.751 111.542 29.7449 111.593 29.7454 111.645V120.455C29.7449 120.507 29.751 120.558 29.7636 120.606C29.7761 120.654 29.7947 120.697 29.8183 120.734C29.8418 120.77 29.8699 120.799 29.9008 120.819C29.9317 120.838 29.9648 120.848 29.9981 120.847V122C29.7339 122 29.4805 121.837 29.2937 121.547C29.1068 121.258 29.0019 120.865 29.0019 120.455V111.645C28.9935 111.434 29.0132 111.223 29.0596 111.024C29.106 110.824 29.1782 110.642 29.2717 110.488C29.3652 110.334 29.478 110.211 29.6033 110.127C29.7285 110.043 29.8635 110 30 110C30.1365 110 30.2715 110.043 30.3967 110.127C30.522 110.211 30.6348 110.334 30.7283 110.488C30.8218 110.642 30.894 110.824 30.9404 111.024C30.9868 111.223 31.0065 111.434 30.9981 111.645V120.455C30.9962 120.865 30.8901 121.257 30.7029 121.546C30.5156 121.835 30.2624 121.998 29.9981 122V121.423Z" fill="#D9BFFF"/>
+<path d="M25 116.003C25 116.134 25.0231 116.263 25.0681 116.383C25.1131 116.504 25.1789 116.614 25.262 116.706C25.3451 116.798 25.4437 116.871 25.5522 116.921C25.6608 116.971 25.7771 116.997 25.8946 116.997H34.0309C34.1546 117.008 34.2788 116.99 34.396 116.944C34.5131 116.899 34.6205 116.827 34.7115 116.734C34.8025 116.64 34.8751 116.527 34.9247 116.4C34.9744 116.274 35 116.138 35 116C35 115.862 34.9744 115.726 34.9247 115.6C34.8751 115.473 34.8025 115.36 34.7115 115.266C34.6205 115.173 34.5131 115.101 34.396 115.056C34.2788 115.01 34.1546 114.992 34.0309 115.003H25.8786C25.6436 115.009 25.4202 115.117 25.256 115.304C25.0918 115.491 24.9999 115.742 25 116.003Z" fill="#D9BFFF"/>
+<path d="M24.593 116.006H25.1687C25.1679 116.039 25.1775 116.072 25.1969 116.103C25.2163 116.134 25.2451 116.162 25.2816 116.185C25.3182 116.209 25.3616 116.228 25.4095 116.24C25.4573 116.253 25.5086 116.259 25.5602 116.258H34.357C34.4089 116.259 34.4604 116.253 34.5086 116.24C34.5567 116.228 34.6006 116.209 34.6375 116.186C34.6745 116.162 34.7038 116.134 34.7239 116.103C34.7439 116.072 34.7542 116.039 34.7542 116.006C34.7542 115.938 34.7124 115.872 34.6379 115.824C34.5634 115.776 34.4623 115.749 34.357 115.749H25.5429C25.491 115.749 25.4396 115.756 25.3918 115.769C25.3439 115.782 25.3005 115.801 25.2641 115.824C25.2276 115.848 25.1989 115.877 25.1796 115.908C25.1602 115.939 25.1507 115.972 25.1515 116.006H24C23.9985 115.874 24.0373 115.744 24.1142 115.622C24.191 115.5 24.3044 115.39 24.4479 115.296C24.5913 115.203 24.7619 115.129 24.9498 115.078C25.1378 115.028 25.3393 115.002 25.5429 115.002H34.357C34.5679 114.994 34.7792 115.013 34.978 115.06C35.1767 115.106 35.3586 115.178 35.5125 115.272C35.6664 115.365 35.7891 115.478 35.8729 115.603C35.9567 115.729 36 115.864 36 116C36 116.136 35.9567 116.271 35.8729 116.397C35.7891 116.522 35.6664 116.635 35.5125 116.728C35.3586 116.822 35.1767 116.894 34.978 116.94C34.7792 116.987 34.5679 117.006 34.357 116.998H25.5429C25.1337 116.998 24.7413 116.893 24.4519 116.706C24.1626 116.519 24 116.266 24 116.002L24.593 116.006Z" fill="#D9BFFF"/>
+<defs>
+<filter id="filter0_d_2440_46367" x="83.9277" y="137" width="18" height="18" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dy="3"/>
+<feGaussianBlur stdDeviation="1.5"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2440_46367"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2440_46367" result="shape"/>
+</filter>
+<filter id="filter1_d_2440_46367" x="104.776" y="137.1" width="18" height="18" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dy="3"/>
+<feGaussianBlur stdDeviation="1.5"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2440_46367"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2440_46367" result="shape"/>
+</filter>
+<filter id="filter2_d_2440_46367" x="125.7" y="137.1" width="18" height="18" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dy="3"/>
+<feGaussianBlur stdDeviation="1.5"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2440_46367"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2440_46367" result="shape"/>
+</filter>
+<filter id="filter3_d_2440_46367" x="146.625" y="137.1" width="18" height="18" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dy="3"/>
+<feGaussianBlur stdDeviation="1.5"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2440_46367"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2440_46367" result="shape"/>
+</filter>
+<filter id="filter4_d_2440_46367" x="47.4629" y="122.978" width="33.9307" height="33.9307" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dy="2"/>
+<feGaussianBlur stdDeviation="2"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2440_46367"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2440_46367" result="shape"/>
+</filter>
+<filter id="filter5_d_2440_46367" x="44" y="36" width="128" height="79.6548" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dx="-2" dy="4"/>
+<feGaussianBlur stdDeviation="2"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2440_46367"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2440_46367" result="shape"/>
+</filter>
+<radialGradient id="paint0_radial_2440_46367" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(-44 204) rotate(-25.5738) scale(236.127 221.678)">
+<stop offset="0.2" stop-color="#E7DFFF"/>
+<stop offset="0.29" stop-color="#E7DFFF"/>
+<stop offset="0.479167" stop-color="#D9BFFF"/>
+<stop offset="0.645833" stop-color="#C689FF"/>
+<stop offset="0.786458" stop-color="#AB71FF"/>
+<stop offset="0.94" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint1_radial_2440_46367" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(-19 182) rotate(-25.8169) scale(205.512 174.246)">
+<stop offset="0.2" stop-color="#E7DFFF"/>
+<stop offset="0.29" stop-color="#E7DFFF"/>
+<stop offset="0.479167" stop-color="#D9BFFF"/>
+<stop offset="0.645833" stop-color="#C689FF"/>
+<stop offset="0.786458" stop-color="#AB71FF"/>
+<stop offset="0.94" stop-color="#291D4F"/>
+</radialGradient>
+<radialGradient id="paint2_radial_2440_46367" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(-47.0003 178.82) rotate(-30.8603) scale(227.744 191.15)">
+<stop offset="0.2" stop-color="#E7DFFF"/>
+<stop offset="0.29" stop-color="#E7DFFF"/>
+<stop offset="0.479167" stop-color="#D9BFFF"/>
+<stop offset="0.645833" stop-color="#C689FF"/>
+<stop offset="0.786458" stop-color="#AB71FF"/>
+<stop offset="0.94" stop-color="#291D4F"/>
+</radialGradient>
+</defs>
+</svg>
diff --git a/browser/components/privatebrowsing/content/assets/vpn-logo.svg b/browser/components/privatebrowsing/content/assets/vpn-logo.svg
new file mode 100644
index 0000000000..01e7c72d8d
--- /dev/null
+++ b/browser/components/privatebrowsing/content/assets/vpn-logo.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg width="81" height="28" viewBox="0 0 81 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M31.5831 7.87499H30.0631V0.661492H31.5831L33.7857 4.73549L35.9778 0.661492H37.5082V7.87499H35.9778V3.38099L33.7857 7.45499L31.5831 3.38099V7.87499ZM44.4587 5.15549C44.4587 6.69899 43.1351 8.00099 41.553 8.00099C39.9606 8.00099 38.6267 6.69899 38.6267 5.15549C38.6267 3.61199 39.9606 2.32049 41.553 2.32049C43.1351 2.32049 44.4587 3.61199 44.4587 5.15549ZM40.0744 5.15549C40.0744 5.99549 40.7465 6.70949 41.553 6.70949C42.3493 6.70949 43.0214 5.99549 43.0214 5.15549C43.0214 4.31549 42.3493 3.61199 41.553 3.61199C40.7465 3.61199 40.0744 4.31549 40.0744 5.15549ZM49.9167 7.87499H45.2428V6.79349L47.9314 3.74849H45.3152V2.44649H49.896V3.52799L47.2075 6.57299H49.9167V7.87499ZM52.3133 1.45013C52.1622 1.60756 51.9557 1.69776 51.7392 1.70099C51.2739 1.70099 50.912 1.32299 50.912 0.860992C50.912 0.430492 51.2739 0.0419922 51.7392 0.0419922C51.952 0.0458124 52.1549 0.133326 52.3054 0.286098C52.4558 0.43887 52.542 0.644974 52.5458 0.860992C52.5481 1.08086 52.4645 1.29269 52.3133 1.45013ZM52.4837 7.87499H50.974V2.44649H52.4837V7.87499ZM55.311 7.87499H53.8013V0.535492H55.311V7.87499ZM58.1382 7.87499H56.6285V0.535492H58.1382V7.87499ZM59.6117 2.91899C60.3666 2.53049 61.1007 2.32049 61.8444 2.32049H61.8453C63.2412 2.32049 64.1925 3.21299 64.1925 4.63049V7.87499H62.7345V7.32899C62.3519 7.76999 61.7419 8.00099 61.1628 8.00099C60.0253 8.00099 59.1464 7.30799 59.1464 6.26849C59.1464 5.19749 60.1287 4.46249 61.3696 4.46249C61.8371 4.46875 62.2996 4.56125 62.7345 4.73549V4.63049C62.7345 4.05299 62.383 3.54899 61.504 3.54899C61.0077 3.54899 60.5424 3.71699 60.1184 3.93749L59.6117 2.91899ZM60.6044 6.22649C60.6044 6.66749 60.987 6.94049 61.504 6.94049C62.0831 6.94049 62.6311 6.66749 62.7345 6.16349V5.65949C62.3933 5.53349 62.0004 5.45999 61.5867 5.45999C61.0387 5.45999 60.6044 5.78549 60.6044 6.22649ZM38.3699 23.0265L43.1231 11.1685H47.1041L40.3484 28H36.3673L29.6116 11.1685H33.6168L38.3699 23.0265ZM52.6052 28H49.0585V11.1685H56.5139C60.1089 11.1685 62.763 13.5205 62.763 16.8525C62.763 20.1845 60.1089 22.5365 56.5139 22.5365H52.6052V28ZM52.6052 14.476V19.229H56.1279C57.8892 19.229 59.0956 18.298 59.0956 16.8525C59.0956 15.407 57.8892 14.476 56.1279 14.476H52.6052ZM68.6777 28H65.1309V11.1685H68.6777L76.6639 22.0465V11.1685H80.2348V28H76.6639L68.6777 17.122V28ZM12.3248 3.76507C12.7126 3.37124 13.2387 3.14999 13.7872 3.14999C14.3357 3.14999 14.8617 3.37124 15.2495 3.76507C15.6374 4.15889 15.8552 4.69304 15.8552 5.24999C15.8552 5.80695 15.6374 6.34109 15.2495 6.73492C14.8617 7.12874 14.3357 7.34999 13.7872 7.34999C13.2387 7.34999 12.7126 7.12874 12.3248 6.73492C11.937 6.34109 11.7191 5.80695 11.7191 5.24999C11.7191 4.69304 11.937 4.15889 12.3248 3.76507ZM9.94918 2.28093C9.3088 3.13416 8.96186 4.17721 8.96163 5.24999C8.96025 6.06425 9.15962 6.8659 9.54156 7.58187L7.46572 9.68887C6.58887 9.20769 5.58681 9.01402 4.59719 9.13447C3.60758 9.25491 2.67898 9.68357 1.93896 10.3615C1.19894 11.0395 0.683822 11.9335 0.46437 12.9208C0.244919 13.908 0.331904 14.94 0.713374 15.875C1.09485 16.8101 1.75208 17.6022 2.59479 18.1426C3.43751 18.683 4.42434 18.9452 5.41972 18.8932C6.41509 18.8411 7.37017 18.4773 8.15374 17.8519C8.93731 17.2264 9.51092 16.3699 9.79576 15.4H17.7786C17.8732 15.7228 18.0005 16.0348 18.1586 16.331L16.0819 18.4397C15.0622 17.88 13.8783 17.7116 12.7465 17.9654C11.6146 18.2192 10.6106 18.8782 9.91791 19.8219C9.22524 20.7656 8.89022 21.931 8.97412 23.1048C9.05802 24.2787 9.55523 25.3827 10.3748 26.215C11.1944 27.0472 12.2817 27.5521 13.4377 27.6373C14.5938 27.7225 15.7414 27.3823 16.6707 26.679C17.6001 25.9756 18.2491 24.9561 18.499 23.8068C18.7489 22.6575 18.5831 21.4552 18.0319 20.4199L20.1086 18.3111C20.9855 18.7923 21.9875 18.986 22.9771 18.8655C23.9667 18.7451 24.8953 18.3164 25.6354 17.6384C26.3754 16.9605 26.8905 16.0665 27.11 15.0792C27.3294 14.092 27.2424 13.06 26.861 12.1249C26.4795 11.1899 25.8222 10.3978 24.9795 9.85737C24.1368 9.31695 23.15 9.05477 22.1546 9.10683C21.1592 9.15889 20.2042 9.52265 19.4206 10.1481C18.637 10.7736 18.0634 11.6301 17.7786 12.6H9.79576C9.70011 12.2745 9.57258 11.963 9.41489 11.6699L11.4916 9.56112C12.4209 10.0715 13.4894 10.258 14.5332 10.0922C15.5769 9.92632 16.5383 9.41724 17.2696 8.64305C18.0009 7.86886 18.4618 6.87235 18.5816 5.80649C18.7013 4.74062 18.4733 3.66431 17.9325 2.74275C17.3917 1.82119 16.568 1.10531 15.5878 0.705001C14.6077 0.304693 13.5252 0.242074 12.5066 0.526759C11.488 0.811443 10.5896 1.4277 9.94918 2.28093ZM12.3248 21.2651C11.937 21.6589 11.7191 22.193 11.7191 22.75C11.7191 23.3069 11.937 23.8411 12.3248 24.2349C12.7126 24.6287 13.2387 24.85 13.7872 24.85C14.3357 24.85 14.8617 24.6287 15.2495 24.2349C15.6374 23.8411 15.8552 23.3069 15.8552 22.75C15.8552 22.193 15.6374 21.6589 15.2495 21.2651C14.8617 20.8712 14.3357 20.65 13.7872 20.65C13.2387 20.65 12.7126 20.8712 12.3248 21.2651ZM4.37872 12.0598C4.62963 11.9543 4.89856 11.9 5.17014 11.9C5.71863 11.9 6.24466 12.1212 6.6325 12.5151C7.02034 12.9089 7.23823 13.443 7.23823 14C7.23823 14.5569 7.02034 15.0911 6.6325 15.4849C6.24466 15.8787 5.71863 16.1 5.17014 16.1C4.89856 16.1 4.62963 16.0457 4.37872 15.9401C4.12781 15.8346 3.89982 15.6799 3.70778 15.4849C3.51574 15.2899 3.36341 15.0584 3.25948 14.8036C3.15555 14.5488 3.10206 14.2758 3.10206 14C3.10206 13.7242 3.15555 13.4511 3.25948 13.1964C3.36341 12.9416 3.51574 12.7101 3.70778 12.5151C3.89982 12.3201 4.12781 12.1654 4.37872 12.0598ZM22.4042 11.9C21.8557 11.9 21.3297 12.1212 20.9418 12.5151C20.554 12.9089 20.3361 13.443 20.3361 14C20.3361 14.5569 20.554 15.0911 20.9418 15.4849C21.3297 15.8787 21.8557 16.1 22.4042 16.1C22.9527 16.1 23.4787 15.8787 23.8665 15.4849C24.2544 15.0911 24.4723 14.5569 24.4723 14C24.4723 13.443 24.2544 12.9089 23.8665 12.5151C23.4787 12.1212 22.9527 11.9 22.4042 11.9Z" fill="white"/>
+</svg>
diff --git a/browser/components/privatebrowsing/jar.mn b/browser/components/privatebrowsing/jar.mn
new file mode 100644
index 0000000000..6fef71a2ac
--- /dev/null
+++ b/browser/components/privatebrowsing/jar.mn
@@ -0,0 +1,9 @@
+# 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/.
+
+browser.jar:
+ content/browser/aboutPrivateBrowsing.css (content/aboutPrivateBrowsing.css)
+ content/browser/aboutPrivateBrowsing.html (content/aboutPrivateBrowsing.html)
+ content/browser/aboutPrivateBrowsing.js (content/aboutPrivateBrowsing.js)
+ content/browser/assets/ (content/assets/*)
diff --git a/browser/components/privatebrowsing/moz.build b/browser/components/privatebrowsing/moz.build
new file mode 100644
index 0000000000..7078f7e4db
--- /dev/null
+++ b/browser/components/privatebrowsing/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+BROWSER_CHROME_MANIFESTS += [
+ "test/browser/browser.ini",
+]
+
+JAR_MANIFESTS += ["jar.mn"]
+
+with Files("**"):
+ BUG_COMPONENT = ("Firefox", "Private Browsing")
diff --git a/browser/components/privatebrowsing/test/browser/browser.ini b/browser/components/privatebrowsing/test/browser/browser.ini
new file mode 100644
index 0000000000..0fa87c93a1
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser.ini
@@ -0,0 +1,75 @@
+[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_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..67d99b2f5c
--- /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", [], true);
+ 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..f9b935cf79
--- /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",
+});
+
+XPCOMUtils.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.iconURI.spec;
+
+ 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..073cb4363f
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_cookie_banners_promo.js
@@ -0,0 +1,82 @@
+const { ASRouter } = ChromeUtils.import(
+ "resource://activity-stream/lib/ASRouter.jsm"
+);
+
+let { MODE_REJECT } = Ci.nsICookieBannerService;
+
+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 pref being default
+ ["cookiebanners.service.mode", MODE_REJECT],
+ ],
+ });
+ 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"
+ );
+ ok(
+ promoImage?.src !== imgSrc,
+ "Cookie banner reduction promo is not shown"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_cookie_banners_promo() {
+ await resetState();
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.promo.cookiebanners.enabled", true]],
+ clear: [["cookiebanners.service.mode"]],
+ });
+ await ASRouter.onPrefChange();
+
+ const { win, tab } = await openTabAndWaitForRender();
+
+ let prefChanged = TestUtils.waitForPrefChange(
+ "cookiebanners.service.mode",
+ value => value === MODE_REJECT
+ );
+ let pageReloaded = BrowserTestUtils.browserLoaded(tab);
+
+ await SpecialPowers.spawn(tab, [promoImgSrc], async function (imgSrc) {
+ const promoImage = content.document.querySelector(
+ ".promo-image-large > img"
+ );
+ ok(promoImage?.src === imgSrc, "Cookie banner reduction promo is shown");
+ let linkEl = content.document.getElementById("private-browsing-promo-link");
+ EventUtils.synthesizeClick(linkEl);
+ });
+
+ await Promise.all([prefChanged, pageReloaded]);
+
+ await SpecialPowers.spawn(tab, [promoImgSrc], async function (imgSrc) {
+ const promoImage = content.document.querySelector(
+ ".promo-image-large > img"
+ );
+ ok(
+ promoImage?.src !== imgSrc,
+ "Cookie banner reduction promo is no longer shown after clicking the link"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+});
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..1c8b172c29
--- /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.textContent,
+ "Private browsing freedom in one click",
+ "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 promoButtonText = content.document.querySelector(
+ "#private-browsing-promo-link"
+ ).textContent;
+
+ ok(promoContainer, "Promo is shown");
+
+ Assert.equal(
+ promoButtonText,
+ "Download Firefox Focus",
+ "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.textContent,
+ "Private browsing freedom in one click",
+ "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..dd5c335bc2
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_default_promo.js
@@ -0,0 +1,148 @@
+/* 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" },
+};
+
+async function resetState() {
+ await Promise.all([
+ ASRouter.resetMessageState(),
+ ASRouter.resetGroupsState(),
+ ASRouter.unblockAll(),
+ ]);
+}
+
+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.textContent,
+ "Next-level privacy on mobile",
+ "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);
+});
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..80333ead74
--- /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.import(
+ "resource://activity-stream/lib/ASRouter.jsm"
+);
+
+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..8f6eb63699
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about_nimbus.js
@@ -0,0 +1,461 @@
+/* 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_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, ".info container should exist");
+ 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 () {
+ is(
+ content.document.querySelector(".info"),
+ undefined,
+ "should remove .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"
+ );
+ ok(
+ content.document.querySelector("#info-title"),
+ "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.querySelector("#info-title"),
+ "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 () {
+ ok(
+ !content.document.querySelector("#info-title"),
+ "Should remove the infoTitle element"
+ );
+ 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.querySelector("#info-title"),
+ "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();
+});
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..667476b918
--- /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.textContent,
+ "Next-level privacy on mobile",
+ "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..375828d291
--- /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.textContent,
+ "You’re in a Private Window",
+ "should render infoBody with fluent"
+ );
+ is(
+ promoLink.textContent,
+ "Stay private with Mozilla VPN",
+ "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..0ebdac69e8
--- /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.is_hidden(
+ 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..4fdc41d330
--- /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.loadURIString(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.loadURIString(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..c365dff5df
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
@@ -0,0 +1,93 @@
+/* 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) {
+ ok(
+ 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..9775153b06
--- /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.loadURIString(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..37813486da
--- /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.loadURIString(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.loadURIString(private_browser, url);
+ await BrowserTestUtils.browserLoaded(private_browser, false, url);
+
+ // Step 3
+ url = prefix + "?action=get&name=test2";
+ BrowserTestUtils.loadURIString(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.loadURIString(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.loadURIString(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.loadURIString(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.loadURIString(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..b436e6e190
--- /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.loadURIString(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.loadURIString(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..5fbb348f3e
--- /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", [], true);
+ 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..6b1253aa67
--- /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", [], true);
+ 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..3be456f7a0
--- /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", [], true);
+ 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..61505b5b1f
--- /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.loadURIString(
+ 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..9e6f556669
--- /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.loadURIString(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..3aea179598
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler.js
@@ -0,0 +1,61 @@
+/* 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
+ ));
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ let promiseFinished = PromiseUtils.defer();
+ setTimeout(function () {
+ let notificationBox = aWindow.gBrowser.getNotificationBox();
+ 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..7516c13564
--- /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.loadURIString(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.loadURIString(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_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..f51d43951a
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js
@@ -0,0 +1,99 @@
+/* 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.loadURIString(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..3f71dd2586
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle.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/. */
+
+// 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.loadURIString(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
+ );
+});
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..866fef69ae
--- /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.loadURIString(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..84cbea6512
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/head.js
@@ -0,0 +1,168 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+ChromeUtils.defineESModuleGetters(this, {
+ ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
+ ExperimentFakes: "resource://testing-common/NimbusTestUtils.sys.mjs",
+ FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
+ PanelTestProvider: "resource://activity-stream/lib/PanelTestProvider.sys.mjs",
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ ASRouter: "resource://activity-stream/lib/ASRouter.jsm",
+});
+
+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) {
+ let win = aParent.OpenBrowserWindow(aOptions);
+ return TestUtils.topicObserved(
+ "browser-delayed-startup-finished",
+ subject => subject == win
+ ).then(() => win);
+}
+
+/**
+ * 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 tmpDir = FileUtils.getDir("TmpD", [], true);
+ let dir = tmpDir.clone();
+ dir.append("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>");
+}