This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 3d5527beccc5982dc1cd761de21bf90a251aeead Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Mon Jul 25 10:40:35 2022 +0200
Bug 40926: Implemented the New Identity feature --- browser/app/profile/001-base-profile.js | 2 +- browser/base/content/appmenu-viewcache.inc.xhtml | 4 + browser/base/content/browser-menubar.inc | 4 + browser/base/content/browser-sets.inc | 1 + browser/base/content/browser.js | 10 + browser/base/content/navigator-toolbox.inc.xhtml | 2 + browser/components/moz.build | 1 + .../components/newidentity/content/newidentity.js | 558 +++++++++++++++++++++ browser/components/newidentity/jar.mn | 13 + .../locale/en-US/newIdentity.properties | 8 + browser/components/newidentity/moz.build | 1 + browser/installer/package-manifest.in | 2 + browser/themes/shared/icons/new_identity.svg | 9 + browser/themes/shared/jar.inc.mn | 2 + browser/themes/shared/toolbarbutton-icons.css | 4 + 15 files changed, 620 insertions(+), 1 deletion(-)
diff --git a/browser/app/profile/001-base-profile.js b/browser/app/profile/001-base-profile.js index 1858d29c98c1..6e443412ab34 100644 --- a/browser/app/profile/001-base-profile.js +++ b/browser/app/profile/001-base-profile.js @@ -324,7 +324,7 @@ pref("extensions.webextensions.restrictedDomains", ""); pref("extensions.postDownloadThirdPartyPrompt", false);
// Toolbar layout -pref("browser.uiCustomization.state", "{"placements":{"widget-overflow-fixed-list":[],"PersonalToolbar":["personal-bookmarks"],"nav-bar":["back-button","forward-button","stop-reload-button","urlbar-container","security-level-button","downloads-button"],"TabsToolbar":["tabbrowser-tabs","new-tab-button","alltabs-button"],"toolbar-menubar":["menubar-items"],"PanelUI-contents":["home-button","edit-controls","zoom-controls","new-window-button","sav [...] +pref("browser.uiCustomization.state", "{"placements":{"widget-overflow-fixed-list":[],"PersonalToolbar":["personal-bookmarks"],"nav-bar":["back-button","forward-button","stop-reload-button","urlbar-container","security-level-button","new-identity-button","downloads-button"],"TabsToolbar":["tabbrowser-tabs","new-tab-button","alltabs-button"],"toolbar-menubar":["menubar-items"],"PanelUI-contents":["home-button","edit-controls","zoom-controls","n [...]
// Enforce certificate pinning, see: https://bugs.torproject.org/16206 pref("security.cert_pinning.enforcement_level", 2); diff --git a/browser/base/content/appmenu-viewcache.inc.xhtml b/browser/base/content/appmenu-viewcache.inc.xhtml index 29269b96ee06..a67f89bd5f70 100644 --- a/browser/base/content/appmenu-viewcache.inc.xhtml +++ b/browser/base/content/appmenu-viewcache.inc.xhtml @@ -177,6 +177,10 @@ class="subviewbutton" command="Browser:RestoreLastSession"/> <toolbarseparator/> + <toolbarbutton id="appMenu-new-identity" + class="subviewbutton" + key="new-identity-key"/> + <toolbarseparator/> <toolbarbutton id="appMenuClearRecentHistory" data-l10n-id="appmenu-clear-history" class="subviewbutton" diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc index 1adea92456c7..973f3c5c98f6 100644 --- a/browser/base/content/browser-menubar.inc +++ b/browser/base/content/browser-menubar.inc @@ -28,6 +28,10 @@ <menuitem id="menu_newPrivateWindow" command="Tools:PrivateBrowsing" key="key_privatebrowsing" data-l10n-id="menu-file-new-private-window"/> + <menuseparator/> + <menuitem id="menu_newIdentity" + key="new-identity-key"/> + <menuseparator/> <menuitem id="menu_openLocation" hidden="true" command="Browser:OpenLocation" diff --git a/browser/base/content/browser-sets.inc b/browser/base/content/browser-sets.inc index 91fac883c66a..17edb35baf6a 100644 --- a/browser/base/content/browser-sets.inc +++ b/browser/base/content/browser-sets.inc @@ -388,4 +388,5 @@ modifiers="accel,alt" internal="true"/> #endif + <key id="new-identity-key" modifiers="accel shift" key="U" oncommand="NewIdentityButton.onCommand(event)"/> </keyset> diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index ac8b1d100ad8..d14f5c1439dc 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -228,6 +228,11 @@ XPCOMUtils.defineLazyScriptGetter( ["SecurityLevelButton"], "chrome://browser/content/securitylevel/securityLevel.js" ); +XPCOMUtils.defineLazyScriptGetter( + this, + ["NewIdentityButton"], + "chrome://browser/content/newidentity.js" +); XPCOMUtils.defineLazyScriptGetter( this, "gEditItemOverlay", @@ -1780,6 +1785,9 @@ var gBrowserInit = { // Init the SecuritySettingsButton SecurityLevelButton.init();
+ // Init the NewIdentityButton + NewIdentityButton.init(); + // Certain kinds of automigration rely on this notification to complete // their tasks BEFORE the browser window is shown. SessionStore uses it to // restore tabs into windows AFTER important parts like gMultiProcessBrowser @@ -2502,6 +2510,8 @@ var gBrowserInit = {
SecurityLevelButton.uninit();
+ NewIdentityButton.uninit(); + gAccessibilityServiceIndicator.uninit();
if (gToolbarKeyNavEnabled) { diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml index 4e216ac82508..cadf68c91679 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -537,6 +537,8 @@ ondragover="newWindowButtonObserver.onDragOver(event)" ondragenter="newWindowButtonObserver.onDragOver(event)"/>
+ <toolbarbutton id="new-identity-button" class="toolbarbutton-1 chromeclass-toolbar-additional"/> + <toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional" observes="View:FullScreen" type="checkbox" diff --git a/browser/components/moz.build b/browser/components/moz.build index 09c7d2a3767e..12a2187c638b 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -38,6 +38,7 @@ DIRS += [ "extensions", "pagedata", "migration", + "newidentity", "newtab", "originattributes", "places", diff --git a/browser/components/newidentity/content/newidentity.js b/browser/components/newidentity/content/newidentity.js new file mode 100644 index 000000000000..67a00dbc6f75 --- /dev/null +++ b/browser/components/newidentity/content/newidentity.js @@ -0,0 +1,558 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["NewIdentityButton"]; + +/* globals CustomizableUI Services gFindBarInitialized gFindBar + OpenBrowserWindow PrivateBrowsingUtils XPCOMUtils + */ + +XPCOMUtils.defineLazyGetter(this, "NewIdentityStrings", () => { + const brandBundle = Services.strings.createBundle( + "chrome://branding/locale/brand.properties" + ); + const brandShortName = brandBundle.GetStringFromName("brandShortName"); + + let strings = { + new_identity: "New Identity", + new_identity_sentence_case: "New identity", + new_identity_prompt: `${brandShortName} will close all windows and tabs. All website sessions will be lost. \nRestart ${brandShortName} now to reset your identity?`, + new_identity_ask_again: "Never ask me again", + new_identity_menu_accesskey: "I", + }; + let bundle = null; + try { + bundle = Services.strings.createBundle( + "chrome://newidentity/locale/newIdentity.properties" + ); + } catch (e) { + console.warn("Could not load the New Identity strings"); + } + if (bundle) { + for (const key of Object.keys(strings)) { + try { + strings[key] = bundle.GetStringFromName(key); + } catch (e) {} + } + strings.new_identity_prompt = strings.new_identity_prompt.replaceAll( + "%S", + brandShortName + ); + } + return strings; +}); + +// Use a lazy getter because NewIdentityButton is declared more than once +// otherwise. +XPCOMUtils.defineLazyGetter(this, "NewIdentityButton", () => { + // Logger adapted from CustomizableUI.jsm + const logger = (() => { + const { ConsoleAPI } = ChromeUtils.import( + "resource://gre/modules/Console.jsm" + ); + const consoleOptions = { + maxLogLevel: "info", + prefix: "NewIdentity", + }; + return new ConsoleAPI(consoleOptions); + })(); + + const topics = Object.freeze({ + newIdentityRequested: "new-identity-requested", + }); + + class NewIdentityImpl { + async run() { + logger.debug("Disabling JS"); + this.disableAllJS(); + await this.clearState(); + this.broadcast(); + this.openNewWindow(); + this.closeOldWindow(); + } + + // Disable JS (as a defense-in-depth measure) + + disableAllJS() { + logger.info("Disabling JavaScript"); + const enumerator = Services.wm.getEnumerator("navigator:browser"); + while (enumerator.hasMoreElements()) { + const win = enumerator.getNext(); + this.disableWindowJS(win); + } + } + + disableWindowJS(win) { + const browsers = win.gBrowser?.browsers || []; + for (const browser of browsers) { + if (!browser) { + continue; + } + this.disableBrowserJS(browser); + try { + browser.webNavigation?.stop(browser.webNavigation.STOP_ALL); + } catch (e) { + logger.warn("Could not stop navigation", e, browser.currentURI); + } + } + } + + disableBrowserJS(browser) { + if (!browser) { + return; + } + // Does the following still apply? + // Solution from: https://bugzilla.mozilla.org/show_bug.cgi?id=409737 + // XXX: This kills the entire window. We need to redirect + // focus and inform the user via a lightbox. + const eventSuppressor = browser.contentWindow?.windowUtils; + if (browser.browsingContext) { + browser.browsingContext.allowJavascript = false; + } + try { + // My estimation is that this does not get the inner iframe windows, + // but that does not matter, because iframes should be destroyed + // on the next load. + // Should we log when browser.contentWindow is null? + if (browser.contentWindow) { + browser.contentWindow.name = null; + browser.contentWindow.window.name = null; + } + } catch (e) { + logger.warn("Failed to reset window.name", e); + } + eventSuppressor?.suppressEventHandling(true); + } + + // Clear state + + async clearState() { + logger.info("Clearing the state"); + this.closeTabs(); + this.clearSearchBar(); + this.clearPrivateSessionHistory(); + this.clearHTTPAuths(); + this.clearCryptoTokens(); + this.clearOCSPCache(); + this.clearSecuritySettings(); + this.clearImageCaches(); + this.clearStorage(); + this.clearPreferencesAndPermissions(); + await this.clearData(); + this.clearConnections(); + this.clearPrivateSession(); + } + + clearSiteSpecificZoom() { + Services.prefs.setBoolPref( + "browser.zoom.siteSpecific", + !Services.prefs.getBoolPref("browser.zoom.siteSpecific") + ); + Services.prefs.setBoolPref( + "browser.zoom.siteSpecific", + !Services.prefs.getBoolPref("browser.zoom.siteSpecific") + ); + } + + closeTabs() { + logger.info("Closing tabs"); + if ( + !Services.prefs.getBoolPref("extensions.torbutton.close_newnym", true) + ) { + logger.info("Not closing tabs"); + return; + } + // TODO: muck around with browser.tabs.warnOnClose.. maybe.. + logger.info("Closing tabs..."); + const enumerator = Services.wm.getEnumerator("navigator:browser"); + const windowsToClose = []; + while (enumerator.hasMoreElements()) { + const win = enumerator.getNext(); + const browser = win.gBrowser; + if (!browser) { + logger.warn("No browser for possible window to close"); + continue; + } + const tabsToRemove = []; + for (const b of browser.browsers) { + const tab = browser.getTabForBrowser(b); + if (tab) { + tabsToRemove.push(tab); + } else { + logger.warn("Browser has a null tab", b); + } + } + if (win == window) { + browser.addWebTab("about:blank"); + } else { + // It is a bad idea to alter the window list while iterating + // over it, so add this window to an array and close it later. + windowsToClose.push(win); + } + // Close each tab except the new blank one that we created. + tabsToRemove.forEach(aTab => browser.removeTab(aTab)); + } + // Close all XUL windows except this one. + logger.info("Closing windows..."); + windowsToClose.forEach(aWin => aWin.close()); + logger.info("Closed all tabs"); + + // This clears the undo tab history. + const tabs = Services.prefs.getIntPref( + "browser.sessionstore.max_tabs_undo" + ); + Services.prefs.setIntPref("browser.sessionstore.max_tabs_undo", 0); + Services.prefs.setIntPref("browser.sessionstore.max_tabs_undo", tabs); + } + + clearSearchBar() { + logger.info("Clearing searchbox"); + // Bug #10800: Trying to clear search/find can cause exceptions + // in unknown cases. Just log for now. + try { + const searchBar = window.document.getElementById("searchbar"); + if (searchBar) { + searchBar.textbox.reset(); + } + } catch (e) { + logger.error("Exception on clearing search box", e); + } + try { + if (gFindBarInitialized) { + const findbox = gFindBar.getElement("findbar-textbox"); + findbox.reset(); + gFindBar.close(); + } + } catch (e) { + logger.error("Exception on clearing find bar", e); + } + } + + clearPrivateSessionHistory() { + logger.info("Emitting Private Browsing Session clear event"); + Services.obs.notifyObservers(null, "browser:purge-session-history"); + } + + clearHTTPAuths() { + if ( + !Services.prefs.getBoolPref( + "extensions.torbutton.clear_http_auth", + true + ) + ) { + logger.info("Skipping HTTP Auths, because disabled"); + return; + } + logger.info("Clearing HTTP Auths"); + const auth = Cc["@mozilla.org/network/http-auth-manager;1"].getService( + Ci.nsIHttpAuthManager + ); + auth.clearAll(); + } + + clearCryptoTokens() { + logger.info("Clearing Crypto Tokens"); + // Clear all crypto auth tokens. This includes calls to PK11_LogoutAll(), + // nsNSSComponent::LogoutAuthenticatedPK11() and clearing the SSL session + // cache. + const sdr = Cc["@mozilla.org/security/sdr;1"].getService( + Ci.nsISecretDecoderRing + ); + sdr.logoutAndTeardown(); + } + + clearOCSPCache() { + // nsNSSComponent::Observe() watches security.OCSP.enabled, which calls + // setValidationOptions(), which in turn calls setNonPkixOcspEnabled() which, + // if security.OCSP.enabled is set to 0, calls CERT_DisableOCSPChecking(), + // which calls CERT_ClearOCSPCache(). + // See: https://mxr.mozilla.org/comm-esr24/source/mozilla/security/manager/ssl/src/n... + const ocsp = Services.prefs.getIntPref("security.OCSP.enabled"); + Services.prefs.setIntPref("security.OCSP.enabled", 0); + Services.prefs.setIntPref("security.OCSP.enabled", ocsp); + } + + clearSecuritySettings() { + // Clear site security settings + const sss = Cc["@mozilla.org/ssservice;1"].getService( + Ci.nsISiteSecurityService + ); + sss.clearAll(); + } + + clearImageCaches() { + logger.info("Clearing Image Cache"); + // In Firefox 18 and newer, there are two image caches: one that is used + // for regular browsing, and one that is used for private browsing. + this.clearImageCacheRB(); + this.clearImageCachePB(); + } + + clearImageCacheRB() { + try { + const imgTools = Cc["@mozilla.org/image/tools;1"].getService( + Ci.imgITools + ); + const imgCache = imgTools.getImgCacheForDocument(null); + // Evict all but chrome cache + imgCache.clearCache(false); + } catch (e) { + // FIXME: This can happen in some rare cases involving XULish image data + // in combination with our image cache isolation patch. Sure isn't + // a good thing, but it's not really a super-cookie vector either. + // We should fix it eventually. + logger.error("Exception on image cache clearing", e); + } + } + + clearImageCachePB() { + const imgTools = Cc["@mozilla.org/image/tools;1"].getService( + Ci.imgITools + ); + try { + // Try to clear the private browsing cache. To do so, we must locate a + // content document that is contained within a private browsing window. + let didClearPBCache = false; + const enumerator = Services.wm.getEnumerator("navigator:browser"); + while (!didClearPBCache && enumerator.hasMoreElements()) { + const win = enumerator.getNext(); + let browserDoc = win.document.documentElement; + if (!browserDoc.hasAttribute("privatebrowsingmode")) { + continue; + } + const tabbrowser = win.gBrowser; + if (!tabbrowser) { + continue; + } + for (const browser of tabbrowser.browsers) { + const doc = browser.contentDocument; + if (doc) { + const imgCache = imgTools.getImgCacheForDocument(doc); + // Evict all but chrome cache + imgCache.clearCache(false); + didClearPBCache = true; + break; + } + } + } + } catch (e) { + logger.error("Exception on private browsing image cache clearing", e); + } + } + + clearStorage() { + logger.info("Clearing Disk and Memory Caches"); + try { + Services.cache2.clear(); + } catch (e) { + logger.error("Exception on cache clearing", e); + } + + logger.info("Clearing Cookies and DOM Storage"); + Services.cookies.removeAll(); + } + + clearPreferencesAndPermissions() { + logger.info("Clearing Content Preferences"); + ChromeUtils.defineModuleGetter( + this, + "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm" + ); + const pbCtxt = PrivateBrowsingUtils.privacyContextFromWindow(window); + const cps = Cc["@mozilla.org/content-pref/service;1"].getService( + Ci.nsIContentPrefService2 + ); + cps.removeAllDomains(pbCtxt); + this.clearSiteSpecificZoom(); + + logger.info("Clearing permissions"); + try { + Services.perms.removeAll(); + } catch (e) { + // Actually, this catch does not appear to be needed. Leaving it in for + // safety though. + logger.error("Cannot clear permissions", e); + } + + logger.info("Syncing prefs"); + // Force prefs to be synced to disk + Services.prefs.savePrefFile(null); + } + + async clearData() { + logger.info("Calling the clearDataService"); + const flags = + Services.clearData.CLEAR_ALL ^ Services.clearData.CLEAR_PASSWORDS; + return new Promise((resolve, reject) => { + Services.clearData.deleteData(flags, { + onDataDeleted(code) { + if (code !== Cr.NS_OK) { + logger.error(`Error while calling the clearDataService: ${code}`); + } + // We always resolve, because we do not want to interrupt the new + // identity procedure. + resolve(); + }, + }); + }); + } + + clearConnections() { + logger.info("Closing open connections"); + // Clear keep-alive + Services.obs.notifyObservers(this, "net:prune-all-connections"); + } + + clearPrivateSession() { + logger.info("Ending any remaining private browsing sessions."); + Services.obs.notifyObservers(null, "last-pb-context-exited"); + } + + // Broadcast as a hook to clear other data + + broadcast() { + logger.info("Broadcasting the new identity"); + Services.obs.notifyObservers({}, topics.newIdentityRequested); + } + + // Window management + + openNewWindow() { + logger.info("Opening a new window"); + // Open a new window with the default homepage + // We could pass {private: true} but we do not because we enforce + // browser.privatebrowsing.autostart = true. + // What about users that change settings? + OpenBrowserWindow(); + } + + closeOldWindow() { + logger.info("Closing the old window"); + + // Run garbage collection and cycle collection after window is gone. + // This ensures that blob URIs are forgotten. + window.addEventListener("unload", function(event) { + logger.debug("Initiating New Identity GC pass"); + // Clear out potential pending sInterSliceGCTimer: + window.windowUtils.runNextCollectorTimer(); + // Clear out potential pending sICCTimer: + window.windowUtils.runNextCollectorTimer(); + // Schedule a garbage collection in 4000-1000ms... + window.windowUtils.garbageCollect(); + // To ensure the GC runs immediately instead of 4-10s from now, we need + // to poke it at least 11 times. + // We need 5 pokes for GC, 1 poke for the interSliceGC, and 5 pokes for + // CC. + // See nsJSContext::RunNextCollectorTimer() in + // https://mxr.mozilla.org/mozilla-central/source/dom/base/nsJSEnvironment.cpp#.... + // XXX: We might want to make our own method for immediate full GC... + for (let poke = 0; poke < 11; poke++) { + window.windowUtils.runNextCollectorTimer(); + } + // And now, since the GC probably actually ran *after* the CC last time, + // run the whole thing again. + window.windowUtils.garbageCollect(); + for (let poke = 0; poke < 11; poke++) { + window.windowUtils.runNextCollectorTimer(); + } + logger.debug("Completed New Identity GC pass"); + }); + + // Close the current window for added safety + window.close(); + } + } + + let newIdentityInProgress = false; + return { + topics, + + init() { + // We first search in the DOM for the identity button. If it does not + // exist it may be in the toolbox palette. In the latter case we still + // need to initialize the button in case it is added back later through + // customization. + const button = + document.getElementById("new-identity-button") || + window.gNavToolbox.palette.querySelector("#new-identity-button"); + if (button) { + button.setAttribute("tooltiptext", NewIdentityStrings.new_identity); + // Include an equal label, shown in the overflow menu or during + // customization. + button.setAttribute("label", NewIdentityStrings.new_identity); + button.addEventListener("command", () => { + this.onCommand(); + }); + } + const viewCache = document.getElementById("appMenu-viewCache").content; + const appButton = viewCache.querySelector("#appMenu-new-identity"); + if (appButton) { + appButton.setAttribute( + "label", + NewIdentityStrings.new_identity_sentence_case + ); + appButton.addEventListener("command", () => { + this.onCommand(); + }); + } + const menu = document.querySelector("#menu_newIdentity"); + if (menu) { + menu.setAttribute("label", NewIdentityStrings.new_identity); + menu.setAttribute( + "accesskey", + NewIdentityStrings.new_identity_menu_accesskey + ); + menu.addEventListener("command", () => { + this.onCommand(); + }); + } + }, + + uninit() {}, + + async onCommand() { + try { + // Ignore if there's a New Identity in progress to avoid race + // conditions leading to failures (see bug 11783 for an example). + if (newIdentityInProgress) { + return; + } + newIdentityInProgress = true; + + const prefConfirm = "extensions.torbutton.confirm_newnym"; + const shouldConfirm = Services.prefs.getBoolPref(prefConfirm, true); + if (shouldConfirm) { + // Display two buttons, both with string titles. + const flags = Services.prompt.STD_YES_NO_BUTTONS; + const askAgain = { value: false }; + const confirmed = + Services.prompt.confirmEx( + null, + "", + NewIdentityStrings.new_identity_prompt, + flags, + null, + null, + null, + NewIdentityStrings.new_identity_ask_again, + askAgain + ) == 0; + Services.prefs.setBoolPref(prefConfirm, !askAgain.value); + if (!confirmed) { + return; + } + } + + const impl = new NewIdentityImpl(); + await impl.run(); + } catch (e) { + // If something went wrong make sure we have the New Identity button + // enabled (again). + logger.error("Unexpected error", e); + window.alert("New Identity unexpected error: " + e); + } finally { + newIdentityInProgress = false; + } + }, + }; +}); diff --git a/browser/components/newidentity/jar.mn b/browser/components/newidentity/jar.mn new file mode 100644 index 000000000000..57b30b32c0e1 --- /dev/null +++ b/browser/components/newidentity/jar.mn @@ -0,0 +1,13 @@ +browser.jar: + content/browser/newidentity.js (content/newidentity.js) + +newidentity.jar: +# We need to list at least one locale here, to make Firefox load the localized +# copy of properties at chrome://newidentity/locale/newIdentity.properties. +# Ideally, we should use @AB_CD@.jar to automatically copy all the locales +# Firefox is built with. But we only provide English here, and injecting the +# translated files directly to the omni.ja works better for us, for the time +# being. In addition to inject the properties files, we also add the +# corresponding locale line to chrome/chrome.manifest. +% locale newidentity en-US %locale/en-US/ + locale/en-US/newIdentity.properties (locale/en-US/newIdentity.properties) diff --git a/browser/components/newidentity/locale/en-US/newIdentity.properties b/browser/components/newidentity/locale/en-US/newIdentity.properties new file mode 100644 index 000000000000..54eeca135a5e --- /dev/null +++ b/browser/components/newidentity/locale/en-US/newIdentity.properties @@ -0,0 +1,8 @@ +new_identity = New Identity +# This is the string for the hamburger menu +new_identity_sentence_case = New identity +# %S is the application name. Keep it as a placeholder +new_identity_prompt = %S will close all windows and tabs. All website sessions will be lost. \nRestart %S now to reset your identity? +new_identity_ask_again = Never ask me again +# Shown in the File menu (use Alt to show File, if you do not see) +new_identity_menu_accesskey = I diff --git a/browser/components/newidentity/moz.build b/browser/components/newidentity/moz.build new file mode 100644 index 000000000000..2661ad7cb9f3 --- /dev/null +++ b/browser/components/newidentity/moz.build @@ -0,0 +1 @@ +JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 61dc9789a2eb..9b5a82add399 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -245,6 +245,8 @@ #endif
; Base Browser +@RESPATH@/browser/chrome/newidentity.manifest +@RESPATH@/browser/chrome/newidentity/ @RESPATH@/browser/chrome/securitylevel.manifest @RESPATH@/browser/chrome/securitylevel/ @RESPATH@/browser/components/SecurityLevel.manifest diff --git a/browser/themes/shared/icons/new_identity.svg b/browser/themes/shared/icons/new_identity.svg new file mode 100644 index 000000000000..096ff169c02f --- /dev/null +++ b/browser/themes/shared/icons/new_identity.svg @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <g fill="context-fill" fill-opacity="context-fill-opacity"> + <path d="m13.5383 14.5627c-.1712-.0053-.3194-.1334-.3505-.3028-.0419-.294-.1441-.5789-.3001-.8369-.2583-.1558-.5436-.2579-.838-.2998-.1694-.0313-.2974-.1793-.3026-.3501-.0053-.1708.1136-.3146.2813-.3402.2944-.0329.5762-.1254.8284-.272.1426-.2476.2313-.5243.2608-.8129.0237-.1679.1662-.2884.3372-.2851.1699.0042.3181.1295.3517.2973.0471.2931.1533.5763.312.8323.2565.1573.5396.263.8326.3109.1682.0345.2929.1836.2958.3536.0028.17-.1171.3116-.2843.3357-.2894.0285-.5669.1172-.8147.2604-.1 [...] + <path d="m6.49858 2.99992c-.14675-.00459-.27377-.11436-.3004-.25961-.03593-.25196-.12354-.49621-.25729-.71731-.22137-.13358-.46594-.22109-.71822-.25699-.14526-.02682-.25492-.15363-.25945-.30004-.00454-.14641.09737-.26967.24112-.29164.25236-.02817.49393-.10747.71013-.233093.12217-.2123.19825-.449454.22353-.696834.0203-.143878.14242-.24714456.28897-.24434753.14565.00358504.27273.11100153.30149.25484453.0404.251183.13139.493923.2674.713349.21988.134841.46256.225461.71364.266481.1441 [...] + <path d="m1.82093 5.3609c-.15279-.00473-.28512-.11875-.31315-.26981-.02739-.18014-.08781-.35525-.1782-.51643-.16152-.09021-.336989-.15052-.517512-.17788-.151437-.02794-.265749-.16003-.270474-.31254-.004724-.15251.101518-.2809.251381-.30378.181146-.02145.355265-.07593.513815-.16075.08209-.15545.13363-.32622.15197-.50355.02095-.15059.14903-.25861.3025-.25512.15164.00368.28404.11525.31428.26484.03021.18029.09338.35503.18632.51538.16048.09192.33508.15452.51517.18469.1503.0308.26181.1 [...] + <path clip-rule="evenodd" d="m15.3213 1.06694c.2441-.244076.2441-.639804 0-.883882-.2441-.2440775-.6398-.2440774-.8839 0l-5.96506 5.965062h-.50519c-1.996-1.09517-4.49023.42233-6.49079 1.63948-.41545.25277-.80961.49258-1.173597.69335-.16756.10002-.289261.26641-.30145394.48048-.01219156.21407.06079654.41038.21802994.56743l1.243691 1.24224 2.37084-1.02603c.15392-.06661.30331.14022.18601.25753l-1.66213 1.6621 1.46329 1.4616 1.66126-1.6613c.1173-.1173.32413.0321.25752.186l-1.02482 2.3 [...] + </g> +</svg> diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn index 4a88a1cc318d..ae8d82c09a88 100644 --- a/browser/themes/shared/jar.inc.mn +++ b/browser/themes/shared/jar.inc.mn @@ -259,3 +259,5 @@ skin/classic/browser/privatebrowsing/favicon.svg (../shared/privatebrowsing/favicon.svg)
skin/classic/browser/syncedtabs/sidebar.css (../shared/syncedtabs/sidebar.css) + + skin/classic/browser/new_identity.svg (../shared/icons/new_identity.svg) diff --git a/browser/themes/shared/toolbarbutton-icons.css b/browser/themes/shared/toolbarbutton-icons.css index a4d40af3b421..8e285fdfd7c2 100644 --- a/browser/themes/shared/toolbarbutton-icons.css +++ b/browser/themes/shared/toolbarbutton-icons.css @@ -263,6 +263,10 @@ toolbar { list-style-image: url("chrome://browser/skin/new-tab.svg"); }
+#new-identity-button { + list-style-image: url("chrome://browser/skin/new_identity.svg"); +} + #privatebrowsing-button { list-style-image: url("chrome://browser/skin/privateBrowsing.svg"); }