tor-browser

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

commit 4a56e172c6ebe04a4eee0c0f0f4d327386ba5150
parent 92e4663878cdb6eee04cfd7caca7e27b28d5dcaf
Author: Pier Angelo Vendrame <pierov@torproject.org>
Date:   Mon, 25 Jul 2022 10:40:35 +0200

BB 40926: Implemented the New Identity feature

Diffstat:
Mbrowser/app/profile/001-base-profile.js | 1+
Mbrowser/base/content/appmenu-viewcache.inc.xhtml | 6++++++
Mbrowser/base/content/browser-menubar.inc | 6++++++
Mbrowser/base/content/browser-sets.inc | 3+++
Mbrowser/base/content/browser-sets.js | 3+++
Mbrowser/base/content/browser.js | 5+++++
Mbrowser/base/content/browser.js.globals | 3++-
Mbrowser/base/content/navigator-toolbox.inc.xhtml | 5+++++
Mbrowser/components/customizableui/CustomizeMode.sys.mjs | 1+
Mbrowser/components/moz.build | 1+
Abrowser/components/newidentity/content/newidentity.js | 560+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abrowser/components/newidentity/jar.mn | 2++
Abrowser/components/newidentity/moz.build | 1+
Mbrowser/modules/BrowserWindowTracker.sys.mjs | 5++++-
Abrowser/themes/shared/icons/new_identity.svg | 9+++++++++
Mbrowser/themes/shared/jar.inc.mn | 2++
Mbrowser/themes/shared/toolbarbutton-icons.css | 4++++
Meslint-file-globals.config.mjs | 1+
18 files changed, 616 insertions(+), 2 deletions(-)

diff --git a/browser/app/profile/001-base-profile.js b/browser/app/profile/001-base-profile.js @@ -782,6 +782,7 @@ pref("privacy.globalprivacycontrol.pbmode.enabled", true); pref("dom.text-recognition.enabled", false); // Log levels +pref("browser.new_identity.log_level", "Info"); #ifdef XP_WIN pref("browser.taskbar.lists.enabled", false); diff --git a/browser/base/content/appmenu-viewcache.inc.xhtml b/browser/base/content/appmenu-viewcache.inc.xhtml @@ -63,6 +63,12 @@ key="key_privatebrowsing" command="Tools:PrivateBrowsing"/> <toolbarseparator/> + <toolbarbutton id="appMenu-new-identity" + class="subviewbutton" + data-l10n-id="appmenuitem-new-identity" + key="new-identity-key" + command="cmd_newIdentity"/> + <toolbarseparator/> <toolbarbutton id="appMenu-bookmarks-button" class="subviewbutton subviewbutton-nav" data-l10n-id="library-bookmarks-menu" diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc @@ -26,6 +26,12 @@ <menuitem id="menu_newPrivateWindow" command="Tools:PrivateBrowsing" key="key_privatebrowsing" data-l10n-id="menu-file-new-private-window"/> + <menuseparator/> + <menuitem id="menu_newIdentity" + command="cmd_newIdentity" + key="new-identity-key" + data-l10n-id="menu-new-identity"/> + <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 @@ -116,6 +116,8 @@ #ifdef XP_MACOSX <command id="zoomWindow" data-l10n-id="window-zoom-command" /> #endif + + <command id="cmd_newIdentity" /> </commandset> #include ../../components/places/content/placesCommands.inc.xhtml <commandset id="splitViewCommands"> @@ -413,4 +415,5 @@ modifiers="accel,alt" internal="true"/> #endif + <key id="new-identity-key" modifiers="accel shift" key="U" command="cmd_newIdentity"/> </keyset> diff --git a/browser/base/content/browser-sets.js b/browser/base/content/browser-sets.js @@ -267,6 +267,9 @@ document.addEventListener( case "zoomWindow": zoomWindow(); break; + case "cmd_newIdentity": + NewIdentityButton.onCommand(event); + break; } }); diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js @@ -273,6 +273,11 @@ XPCOMUtils.defineLazyScriptGetter( ); XPCOMUtils.defineLazyScriptGetter( this, + ["NewIdentityButton"], + "chrome://browser/content/newidentity.js" +); +XPCOMUtils.defineLazyScriptGetter( + this, "gEditItemOverlay", "chrome://browser/content/places/editBookmark.js" ); diff --git a/browser/base/content/browser.js.globals b/browser/base/content/browser.js.globals @@ -239,5 +239,6 @@ "ToolbarDropHandler", "ProfilesDatastoreService", "gTrustPanelHandler", - "SecurityLevelButton" + "SecurityLevelButton", + "NewIdentityButton" ] diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml @@ -578,6 +578,11 @@ command="cmd_newNavigator" tooltip="dynamic-shortcut-tooltip"/> + <toolbarbutton id="new-identity-button" + command="cmd_newIdentity" + class="toolbarbutton-1 chromeclass-toolbar-additional" + data-l10n-id="toolbar-new-identity"/> + <toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional" observes="View:FullScreen" command="View:FullScreen" diff --git a/browser/components/customizableui/CustomizeMode.sys.mjs b/browser/components/customizableui/CustomizeMode.sys.mjs @@ -252,6 +252,7 @@ export class CustomizeMode { "Browser:NewUserContextTab", "Tools:PrivateBrowsing", "zoomWindow", + "cmd_newIdentity", ]); /** diff --git a/browser/components/moz.build b/browser/components/moz.build @@ -47,6 +47,7 @@ DIRS += [ "migration", "mozcachedohttp", "multilineeditor", + "newidentity", # Exclude newtab component. tor-browser#43886. "originattributes", "pagedata", diff --git a/browser/components/newidentity/content/newidentity.js b/browser/components/newidentity/content/newidentity.js @@ -0,0 +1,560 @@ +"use strict"; + +// Use a lazy getter because NewIdentityButton is declared more than once +// otherwise. +ChromeUtils.defineLazyGetter(this, "NewIdentityButton", () => { + // Logger adapted from CustomizableUI.jsm + const logger = (() => { + const consoleOptions = { + maxLogLevelPref: "browser.new_identity.log_level", + prefix: "NewIdentity", + }; + return console.createInstance(consoleOptions); + })(); + + const topics = Object.freeze({ + newIdentityRequested: "new-identity-requested", + }); + + /** + * This class contains the actual implementation of the various step involved + * when running new identity. + */ + class NewIdentityImpl { + async run() { + this.disableAllJS(); + await this.clearState(); + await this.openNewWindow(); + this.closeOldWindow(); + this.broadcast(); + } + + // 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(); + await this.reloadAddons(); + 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() { + if ( + !Services.prefs.getBoolPref("browser.new_identity.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( + "browser.new_identity.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/nsNSSComponent.cpp + 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.defineESModuleGetters(this, { + PrivateBrowsingUtils: + "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", + }); + 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 => { + 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"); + } + + async reloadAddons() { + logger.info("Reloading add-ons to clear their temporary state."); + // Reload all active extensions except search engines, which would throw. + const addons = await AddonManager.getAddonsByTypes(["extension"]); + const isSearchEngine = async addon => + (await (await fetch(addon.getResourceURI("manifest.json").spec)).json()) + ?.chrome_settings_overrides?.search_provider; + const reloadIfNeeded = async addon => + addon.isActive && !(await isSearchEngine(addon)) && addon.reload(); + await Promise.all(addons.map(addon => reloadIfNeeded(addon))); + } + + // 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"); + return new Promise(resolve => { + // Open a new window forcing the about:privatebrowsing page (tor-browser#41765) + // unless user explicitly overrides this policy (tor-browser #42236) + const trustedHomePref = "browser.startup.homepage.new_identity"; + const homeURL = HomePage.get(); + const defaultHomeURL = HomePage.getDefault(); + const isTrustedHome = + homeURL === defaultHomeURL || + homeURL === "chrome://browser/content/blanktab.html" || // about:blank + homeURL === Services.prefs.getStringPref(trustedHomePref, ""); + const isCustomHome = + Services.prefs.getIntPref("browser.startup.page") === 1; + const win = OpenBrowserWindow({ + private: isCustomHome && isTrustedHome ? "private" : "no-home", + }); + // This mechanism to know when the new window is ready is used by + // OpenBrowserWindow itself (see its definition in browser.js). + win.addEventListener( + "MozAfterPaint", + () => { + resolve(); + if (isTrustedHome || !isCustomHome) { + return; + } + const tbl = win.TabsProgressListener; + const { onLocationChange } = tbl; + tbl.onLocationChange = (...args) => { + tbl.onLocationChange = onLocationChange; + tbl.onLocationChange(...args); + const url = URL.parse(homeURL); + if (!url) { + // malformed URL, bail out + return; + } + + let displayAddress = url.hostname; + if (!displayAddress) { + // no host, use full address and truncate if too long + const MAX_LEN = 32; + displayAddress = url.href; + if (displayAddress.length > MAX_LEN) { + displayAddress = `${displayAddress.substring(0, MAX_LEN)}…`; + } + } + const callback = () => { + Services.prefs.setStringPref(trustedHomePref, homeURL); + win.BrowserHome(); + }; + const notificationBox = win.gBrowser.getNotificationBox(); + notificationBox.appendNotification( + "new-identity-safe-home", + { + label: { + "l10n-id": "new-identity-blocked-home-notification", + "l10n-args": { url: displayAddress }, + }, + priority: notificationBox.PRIORITY_INFO_MEDIUM, + }, + [ + { + "l10n-id": "new-identity-blocked-home-ignore-button", + callback, + }, + ] + ); + }; + }, + { once: true } + ); + }); + } + + 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 () { + 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#1970. + // 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 { + 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 = "browser.new_identity.confirm_newnym"; + const shouldConfirm = Services.prefs.getBoolPref(prefConfirm, true); + if (shouldConfirm) { + const [titleString, bodyString, checkboxString, restartString] = + await document.l10n.formatValues([ + { id: "new-identity-dialog-title" }, + { id: "new-identity-dialog-description" }, + { id: "restart-warning-dialog-do-not-warn-checkbox" }, + { id: "restart-warning-dialog-restart-button" }, + ]); + const flags = + Services.prompt.BUTTON_POS_0 * + Services.prompt.BUTTON_TITLE_IS_STRING + + Services.prompt.BUTTON_POS_0_DEFAULT + + Services.prompt.BUTTON_DEFAULT_IS_DESTRUCTIVE + + Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL; + const propBag = await Services.prompt.asyncConfirmEx( + window.browsingContext, + Services.prompt.MODAL_TYPE_INTERNAL_WINDOW, + titleString, + bodyString, + flags, + restartString, + null, + null, + checkboxString, + false + ); + if (propBag.get("buttonNumClicked") !== 0) { + return; + } + if (propBag.get("checked")) { + Services.prefs.setBoolPref(prefConfirm, false); + } + } + + 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 @@ -0,0 +1,2 @@ +browser.jar: + content/browser/newidentity.js (content/newidentity.js) diff --git a/browser/components/newidentity/moz.build b/browser/components/newidentity/moz.build @@ -0,0 +1 @@ +JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/modules/BrowserWindowTracker.sys.mjs b/browser/modules/BrowserWindowTracker.sys.mjs @@ -345,7 +345,10 @@ export const BrowserWindowTracker = { let loadURIString; if (isPrivate && lazy.PrivateBrowsingUtils.enabled) { windowFeatures += ",private"; - if (!args && !lazy.PrivateBrowsingUtils.permanentPrivateBrowsing) { + if ( + (!args && !lazy.PrivateBrowsingUtils.permanentPrivateBrowsing) || + args?.private === "no-home" + ) { // Force the new window to load about:privatebrowsing instead of the // default home page. loadURIString = "about:privatebrowsing"; diff --git a/browser/themes/shared/icons/new_identity.svg 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-.1462.2521-.2386.5335-.2717.8274-.025.167-.1675.2861-.3379.2822z"/> + <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.14417.02957.25114.15744.25358.30313.00244.1457-.10035.26707-.24368.28774-.2481.02441-.48592.10041-.69835.22319-.1253.2161-.20449.45729-.23284.7092-.0214.14312-.14361.24521-.28963.24193z"/> + <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.164.26435.31577.00254.15176-.10462.27819-.25404.29971-.17764.01914-.34855.07141-.50396.15412-.08502.1582-.13963.33194-.16114.5127-.022.14911-.14912.25571-.30131.25265z"/> + <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.3682 1.25462 1.2531c.15724.157.35379.23.56815.2178.19095-.0561.35851-.1561.45869-.3234.20012-.3592.43577-.7455.68321-1.1511 1.22241-2.0039 2.73233-4.47901 1.66484-6.47533v-.49654zm-7.46715 6.55077c1.12692 1.12692.64113 2.69369-.05278 3.70149h-.50137l-3.13-3.1492v-.5c1.00858-.68566 2.56556-1.17088 3.68415-.05229z" fill-rule="evenodd"/> + </g> +</svg> diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn @@ -332,3 +332,5 @@ skin/classic/browser/illustrations/market-opt-in.svg (../shared/illustrations/market-opt-in.svg) skin/classic/browser/illustrations/yelpRealtime-opt-in.svg (../shared/illustrations/yelpRealtime-opt-in.svg) + + 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 @@ -87,6 +87,10 @@ 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"); } diff --git a/eslint-file-globals.config.mjs b/eslint-file-globals.config.mjs @@ -134,6 +134,7 @@ export default [ "testing/mochitest/browser-test.js", "toolkit/components/printing/content/printPreviewPagination.js", "toolkit/components/printing/content/printUtils.js", + "browser/components/newidentity/content/newidentity.js", ], languageOptions: { globals: mozilla.environments["browser-window"].globals,