diff --git a/browser/base/content/appmenu-viewcache.inc.xhtml b/browser/base/content/appmenu-viewcache.inc.xhtml
index 84eb735b2b21235b3771d9f033c6fc4c4df6c97d..ef83862ef67922cc35172caf33bac22bb2933bd3 100644
--- a/browser/base/content/appmenu-viewcache.inc.xhtml
+++ b/browser/base/content/appmenu-viewcache.inc.xhtml
@@ -58,6 +58,10 @@
                      command="Tools:PrivateBrowsing"
                      hidden="true"/>
       <toolbarseparator/>
+      <toolbarbutton id="appMenu-new-identity"
+                     class="subviewbutton"
+                     key="new-identity-key"/>
+      <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
index fb0780682011940c99a4c3c621c24c2659c498e9..95edea414ec381fc76f30d576ead3e7a386506fd 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 91fac883c66ad5ef6fe639ffd4f7f02816f24e09..17edb35baf6ade987400e962a6715e1bb45fa979 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 86f0a73efa0855ab6775b803ceea1f5082faeeba..5c0cf3869fc78f605123867bfc333725539be5d1 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -227,6 +227,11 @@ XPCOMUtils.defineLazyScriptGetter(
   ["SecurityLevelButton"],
   "chrome://browser/content/securitylevel/securityLevel.js"
 );
+XPCOMUtils.defineLazyScriptGetter(
+  this,
+  ["NewIdentityButton"],
+  "chrome://browser/content/newidentity.js"
+);
 XPCOMUtils.defineLazyScriptGetter(
   this,
   "gEditItemOverlay",
@@ -1779,6 +1784,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
@@ -2501,6 +2509,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 4e216ac825089d39cf5758261320b52d2a3ff21f..cadf68c9167913d967a85a64abeb8164fbb5d3b4 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 ad06358d78e7043f7bfc102c20fae217ca6c3bc5..44d1ae436e1e1a3ca500e3fbea1b5a33fa21bf1d 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/newIdentityDialog.css b/browser/components/newidentity/content/newIdentityDialog.css
new file mode 100644
index 0000000000000000000000000000000000000000..850e7c7da625c616e9e71a44aac65391298eb656
--- /dev/null
+++ b/browser/components/newidentity/content/newIdentityDialog.css
@@ -0,0 +1,7 @@
+/* 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/. */
+
+#infoTitle {
+  font-weight: 600;
+}
diff --git a/browser/components/newidentity/content/newIdentityDialog.js b/browser/components/newidentity/content/newIdentityDialog.js
new file mode 100644
index 0000000000000000000000000000000000000000..7b29fdaabd193ea178bb187dc6269465279ad69c
--- /dev/null
+++ b/browser/components/newidentity/content/newIdentityDialog.js
@@ -0,0 +1,24 @@
+/* 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/. */
+
+document.addEventListener("dialogaccept", () => {
+  const retvals = window.arguments[0];
+  retvals.confirmed = true;
+  retvals.neverAskAgain = document.querySelector("#neverAskAgain").checked;
+});
+
+document.addEventListener("DOMContentLoaded", () => {
+  const { NewIdentityStrings } = window.arguments[0];
+  const dialog = document.querySelector("#newIdentityDialog");
+
+  dialog.querySelector("#infoTitle").textContent =
+    NewIdentityStrings.new_identity_prompt_title;
+  dialog.querySelector("#infoBody").textContent =
+    NewIdentityStrings.new_identity_prompt;
+  dialog.querySelector("#neverAskAgain").label =
+    NewIdentityStrings.new_identity_ask_again;
+  const accept = dialog.getButton("accept");
+  accept.label = NewIdentityStrings.new_identity_restart;
+  accept.classList.add("danger-button");
+});
diff --git a/browser/components/newidentity/content/newIdentityDialog.xhtml b/browser/components/newidentity/content/newIdentityDialog.xhtml
new file mode 100644
index 0000000000000000000000000000000000000000..067c64015a31753803fcb624fd4e48b3b86b4827
--- /dev/null
+++ b/browser/components/newidentity/content/newIdentityDialog.xhtml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<!-- based on resetProfile.xhtml -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css"?>
+<?xml-stylesheet href="chrome://global/content/commonDialog.css"?>
+<?xml-stylesheet href="chrome://global/skin/commonDialog.css"?>
+<?xml-stylesheet href="chrome://browser/content/newIdentityDialog.css"?>
+
+<window id="newIdentityDialogWindow"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        xmlns:html="http://www.w3.org/1999/xhtml"
+        xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        aria-describedby="infoBody">
+<dialog id="newIdentityDialog"
+        buttons="accept,cancel"
+        defaultButton="accept">
+
+  <linkset>
+    <!-- Without this document.l10n is not initialized, and we need it for the
+    cancel button. -->
+    <html:link rel="localization" href="branding/brand.ftl"/>
+  </linkset>
+
+  <div xmlns="http://www.w3.org/1999/xhtml">
+    <div id="dialogGrid">
+      <div class="dialogRow" id="infoRow">
+        <div id="iconContainer">
+          <xul:image id="infoIcon"/>
+        </div>
+        <div id="infoContainer">
+          <xul:description id="infoTitle"/>
+          <xul:description id="infoBody" context="contentAreaContextMenu" noinitialfocus="true"/>
+          <xul:checkbox id="neverAskAgain"/>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <script src="chrome://browser/content/newIdentityDialog.js"/>
+</dialog>
+</window>
diff --git a/browser/components/newidentity/content/newidentity.js b/browser/components/newidentity/content/newidentity.js
new file mode 100644
index 0000000000000000000000000000000000000000..71347523c974f21116163bcb897566e0ce8fda7f
--- /dev/null
+++ b/browser/components/newidentity/content/newidentity.js
@@ -0,0 +1,563 @@
+"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_title: "Reset your 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_restart: `Restart ${brandShortName}`,
+    new_identity_ask_again: "Never ask me again",
+    new_identity_menu_accesskey: "I",
+  };
+  let bundle = null;
+  try {
+    bundle = Services.strings.createBundle(
+      "chrome://browser/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
+    );
+    strings.new_identity_restart = strings.new_identity_restart.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",
+      maxLogLevelPref: "browser.new_identity.log_level",
+      prefix: "NewIdentity",
+    };
+    return new ConsoleAPI(consoleOptions);
+  })();
+
+  const topics = Object.freeze({
+    newIdentityRequested: "new-identity-requested",
+  });
+
+  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();
+      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("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.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 => {
+        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");
+      return new Promise(resolve => {
+        // 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?
+        const win = OpenBrowserWindow();
+        // 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(), { 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(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#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 {
+    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 = "browser.new_identity.confirm_newnym";
+        const shouldConfirm = Services.prefs.getBoolPref(prefConfirm, true);
+        if (shouldConfirm) {
+          const params = {
+            NewIdentityStrings,
+            confirmed: false,
+            neverAskAgain: false,
+          };
+          await window.gDialogBox.open(
+            "chrome://browser/content/newIdentityDialog.xhtml",
+            params
+          );
+          Services.prefs.setBoolPref(prefConfirm, !params.neverAskAgain);
+          if (!params.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 0000000000000000000000000000000000000000..6e4642e1f5f34edf07d2a6cfce88ff718206577a
--- /dev/null
+++ b/browser/components/newidentity/jar.mn
@@ -0,0 +1,5 @@
+browser.jar:
+    content/browser/newidentity.js             (content/newidentity.js)
+    content/browser/newIdentityDialog.xhtml    (content/newIdentityDialog.xhtml)
+    content/browser/newIdentityDialog.css      (content/newIdentityDialog.css)
+    content/browser/newIdentityDialog.js       (content/newIdentityDialog.js)
diff --git a/browser/components/newidentity/moz.build b/browser/components/newidentity/moz.build
new file mode 100644
index 0000000000000000000000000000000000000000..2661ad7cb9f3df2b3953a6e1e999aee82d29be78
--- /dev/null
+++ b/browser/components/newidentity/moz.build
@@ -0,0 +1 @@
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/browser/locales/en-US/chrome/browser/newIdentity.properties b/browser/locales/en-US/chrome/browser/newIdentity.properties
new file mode 100644
index 0000000000000000000000000000000000000000..b44dd74c17df4b9f560127f099be683158b3fc90
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/newIdentity.properties
@@ -0,0 +1,10 @@
+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_title = Reset your identity?
+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_restart = Restart %S
+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/locales/jar.mn b/browser/locales/jar.mn
index 5fedacc5571c2720007c81ef28b8b5ead77dda59..13c4bcac05e6c67e453e7a2c422ee95b2aed0d4c 100644
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -40,6 +40,7 @@
     locale/browser/feeds/subscribe.properties       (%chrome/browser/feeds/subscribe.properties)
     locale/browser/syncSetup.properties         (%chrome/browser/syncSetup.properties)
     locale/browser/securityLevel.properties        (%chrome/browser/securityLevel.properties)
+    locale/browser/newIdentity.properties          (%chrome/browser/newIdentity.properties)
 % locale browser-region @AB_CD@ %locale/browser-region/
 # the following files are browser-specific overrides
     locale/browser/netError.dtd                (%chrome/overrides/netError.dtd)
diff --git a/browser/themes/shared/icons/new_identity.svg b/browser/themes/shared/icons/new_identity.svg
new file mode 100644
index 0000000000000000000000000000000000000000..096ff169c02f318c7968e77986743ca1e5086eb8
--- /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-.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
index 72831926f3b1e2f3269997ee7965abb33f56beec..e8e8aed56127bfbfe677a4fd7dbf1bdffe6c8b86 100644
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -260,3 +260,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 a4d40af3b42110b24aee7aa4c0ece9e195e4a93f..8e285fdfd7c2503dc257d79a7459c589902c0f1d 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");
 }