[tor-commits] [tor-browser/tor-browser-68.6.0esr-9.5-1] Bug 28005: Implement .onion alias urlbar rewrites

sysrqb at torproject.org sysrqb at torproject.org
Fri Apr 3 02:22:07 UTC 2020


commit 7306a08365be9212f621b396513352d19549c487
Author: Alex Catarineu <acat at torproject.org>
Date:   Thu Feb 13 13:24:33 2020 +0100

    Bug 28005: Implement .onion alias urlbar rewrites
    
    A custom HTTPS Everywhere update channel is installed,
    which provides rules for locally redirecting some memorable
    .tor.onion URLs to non-memorable .onion URLs.
    
    When these redirects occur, we also rewrite the URL in the urlbar
    to display the human-memorable hostname instead of the actual
    .onion.
---
 browser/actors/ClickHandlerChild.jsm               |  20 +++
 browser/actors/ContextMenuChild.jsm                |   4 +
 browser/base/content/browser-places.js             |  12 +-
 browser/base/content/browser.js                    |  37 +++-
 browser/base/content/nsContextMenu.js              |  18 ++
 browser/base/content/tabbrowser.js                 |   7 +
 browser/base/content/utilityOverlay.js             |   2 +
 browser/components/BrowserGlue.jsm                 |   6 +
 .../onionservices/ExtensionMessaging.jsm           |  86 +++++++++
 .../onionservices/HttpsEverywhereControl.jsm       | 119 ++++++++++++
 .../components/onionservices/OnionAliasStore.jsm   | 199 +++++++++++++++++++++
 browser/components/onionservices/moz.build         |   6 +
 browser/components/urlbar/UrlbarInput.jsm          |   8 +-
 browser/modules/ContentClick.jsm                   |   1 +
 docshell/base/nsDocShell.cpp                       |  37 ++++
 docshell/base/nsDocShell.h                         |   4 +
 docshell/base/nsDocShellLoadState.cpp              |   4 +
 docshell/base/nsIDocShell.idl                      |   5 +
 docshell/base/nsIWebNavigation.idl                 |   5 +
 docshell/shistory/nsISHEntry.idl                   |   5 +
 docshell/shistory/nsSHEntry.cpp                    |  18 +-
 docshell/shistory/nsSHEntry.h                      |   1 +
 modules/libpref/init/StaticPrefList.h              |   7 +
 netwerk/dns/effective_tld_names.dat                |   2 +
 .../remotebrowserutils/RemoteWebNavigation.jsm     |   1 +
 toolkit/content/widgets/browser-custom-element.js  |   9 +
 toolkit/modules/RemoteWebProgress.jsm              |   2 +
 toolkit/modules/WebProgressChild.jsm               |   1 +
 toolkit/modules/sessionstore/SessionHistory.jsm    |   5 +
 29 files changed, 622 insertions(+), 9 deletions(-)

diff --git a/browser/actors/ClickHandlerChild.jsm b/browser/actors/ClickHandlerChild.jsm
index 4375be0067ac..263a5fea053e 100644
--- a/browser/actors/ClickHandlerChild.jsm
+++ b/browser/actors/ClickHandlerChild.jsm
@@ -156,6 +156,26 @@ class ClickHandlerChild extends ActorChild {
       json.originPrincipal = ownerDoc.nodePrincipal;
       json.triggeringPrincipal = ownerDoc.nodePrincipal;
 
+      // Check if the link needs to be opened with .tor.onion urlbar rewrites
+      // allowed. Only when the owner doc has allowOnionUrlbarRewrites = true
+      // and the same origin we should allow this.
+      json.allowOnionUrlbarRewrites = false;
+      if (this.mm.docShell.allowOnionUrlbarRewrites) {
+        const sm = Services.scriptSecurityManager;
+        try {
+          let targetURI = Services.io.newURI(href);
+          let isPrivateWin =
+            ownerDoc.nodePrincipal.originAttributes.privateBrowsingId > 0;
+          sm.checkSameOriginURI(
+            docshell.currentDocumentChannel.URI,
+            targetURI,
+            false,
+            isPrivateWin
+          );
+          json.allowOnionUrlbarRewrites = true;
+        } catch (e) {}
+      }
+
       // If a link element is clicked with middle button, user wants to open
       // the link somewhere rather than pasting clipboard content.  Therefore,
       // when it's clicked with middle button, we should prevent multiple
diff --git a/browser/actors/ContextMenuChild.jsm b/browser/actors/ContextMenuChild.jsm
index 546f51e843d6..5915832eae7a 100644
--- a/browser/actors/ContextMenuChild.jsm
+++ b/browser/actors/ContextMenuChild.jsm
@@ -543,6 +543,9 @@ class ContextMenuChild extends ActorChild {
     // The same-origin check will be done in nsContextMenu.openLinkInTab.
     let parentAllowsMixedContent = !!this.docShell.mixedContentChannel;
 
+    let parentAllowsOnionUrlbarRewrites = this.docShell
+      .allowOnionUrlbarRewrites;
+
     // Get referrer attribute from clicked link and parse it
     let referrerAttrValue = Services.netUtils.parseAttributePolicyString(
       aEvent.composedTarget.getAttribute("referrerpolicy")
@@ -659,6 +662,7 @@ class ContextMenuChild extends ActorChild {
       popupNodeSelectors,
       disableSetDesktopBg,
       parentAllowsMixedContent,
+      parentAllowsOnionUrlbarRewrites,
     };
 
     Services.obs.notifyObservers(
diff --git a/browser/base/content/browser-places.js b/browser/base/content/browser-places.js
index 6aa348c609a4..a2dd38a97514 100755
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -440,7 +440,8 @@ var PlacesCommandHook = {
    */
   async bookmarkPage() {
     let browser = gBrowser.selectedBrowser;
-    let url = new URL(browser.currentURI.spec);
+    const uri = browser.currentOnionAliasURI || browser.currentURI;
+    let url = new URL(uri.spec);
     let info = await PlacesUtils.bookmarks.fetch({ url });
     let isNewBookmark = !info;
     let showEditUI = !isNewBookmark || StarUI.showForNewBookmarks;
@@ -544,7 +545,7 @@ var PlacesCommandHook = {
 
     tabs.forEach(tab => {
       let browser = tab.linkedBrowser;
-      let uri = browser.currentURI;
+      let uri = browser.currentOnionAliasURI || browser.currentURI;
       let title = browser.contentTitle || tab.label;
       let spec = uri.spec;
       if (!(spec in uniquePages)) {
@@ -1618,14 +1619,17 @@ var BookmarkingUI = {
   },
 
   onLocationChange: function BUI_onLocationChange() {
-    if (this._uri && gBrowser.currentURI.equals(this._uri)) {
+    const uri =
+      gBrowser.selectedBrowser.currentOnionAliasURI || gBrowser.currentURI;
+    if (this._uri && uri.equals(this._uri)) {
       return;
     }
     this.updateStarState();
   },
 
   updateStarState: function BUI_updateStarState() {
-    this._uri = gBrowser.currentURI;
+    this._uri =
+      gBrowser.selectedBrowser.currentOnionAliasURI || gBrowser.currentURI;
     this._itemGuids.clear();
     let guids = new Set();
 
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 3f9bf006f562..2c89d658d4ec 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -72,6 +72,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
   TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
   TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
   Translation: "resource:///modules/translation/Translation.jsm",
+  OnionAliasStore: "resource:///modules/OnionAliasStore.jsm",
   UITour: "resource:///modules/UITour.jsm",
   UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
   UrlbarInput: "resource:///modules/UrlbarInput.jsm",
@@ -3335,7 +3336,10 @@ function URLBarSetURI(aURI, updatePopupNotifications) {
   // bar if the user has deleted the URL and we'd just put the same URL
   // back. See bug 304198.
   if (value === null) {
-    let uri = aURI || gBrowser.currentURI;
+    let uri =
+      aURI ||
+      gBrowser.selectedBrowser.currentOnionAliasURI ||
+      gBrowser.currentURI;
     // Strip off usernames and passwords for the location bar
     try {
       uri = Services.uriFixup.createExposableURI(uri);
@@ -5897,11 +5901,24 @@ var XULBrowserWindow = {
         this.reloadCommand.removeAttribute("disabled");
       }
 
+      // The onion memorable alias needs to be used in URLBarSetURI, but also in
+      // other parts of the code (like the bookmarks UI), so we save it.
+      if (gBrowser.selectedBrowser.allowOnionUrlbarRewrites) {
+        gBrowser.selectedBrowser.currentOnionAliasURI = OnionAliasStore.getShortURI(
+          aLocationURI
+        );
+      } else {
+        gBrowser.selectedBrowser.currentOnionAliasURI = null;
+      }
+
       // We want to update the popup visibility if we received this notification
       // via simulated locationchange events such as switching between tabs, however
       // if this is a document navigation then PopupNotifications will be updated
       // via TabsProgressListener.onLocationChange and we do not want it called twice
-      URLBarSetURI(aLocationURI, aIsSimulated);
+      URLBarSetURI(
+        gBrowser.selectedBrowser.currentOnionAliasURI || aLocationURI,
+        aIsSimulated
+      );
 
       BookmarkingUI.onLocationChange();
 
@@ -7627,6 +7644,21 @@ function handleLinkClick(event, href, linkNode) {
     } catch (e) {}
   }
 
+  // Check if the link needs to be opened with .tor.onion urlbar rewrites
+  // allowed. Only when the owner doc has allowOnionUrlbarRewrites = true
+  // and the same origin we should allow this.
+  let persistAllowOnionUrlbarRewritesInChildTab = false;
+  if (where == "tab" && gBrowser.docShell.allowOnionUrlbarRewrites) {
+    const sm = Services.scriptSecurityManager;
+    try {
+      let tURI = makeURI(href);
+      let isPrivateWin =
+        doc.nodePrincipal.originAttributes.privateBrowsingId > 0;
+      sm.checkSameOriginURI(referrerURI, tURI, false, isPrivateWin);
+      persistAllowOnionUrlbarRewritesInChildTab = true;
+    } catch (e) {}
+  }
+
   // first get document wide referrer policy, then
   // get referrer attribute from clicked link and parse it and
   // allow per element referrer to overrule the document wide referrer if enabled
@@ -7659,6 +7691,7 @@ function handleLinkClick(event, href, linkNode) {
     triggeringPrincipal: doc.nodePrincipal,
     csp,
     frameOuterWindowID,
+    allowOnionUrlbarRewrites: persistAllowOnionUrlbarRewritesInChildTab,
   };
 
   // The new tab/window must use the same userContextId
diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js
index e7106e5e8fc1..55da2efb7782 100644
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -93,6 +93,7 @@ function openContextMenu(aMessage) {
     disableSetDesktopBackground: data.disableSetDesktopBg,
     loginFillInfo: data.loginFillInfo,
     parentAllowsMixedContent: data.parentAllowsMixedContent,
+    parentAllowsOnionUrlbarRewrites: data.parentAllowsOnionUrlbarRewrites,
     userContextId: data.userContextId,
     webExtContextData: data.webExtContextData,
   };
@@ -1122,9 +1123,26 @@ nsContextMenu.prototype = {
       } catch (e) {}
     }
 
+    // Check if the link needs to be opened with .tor.onion urlbar rewrites
+    // allowed. Only when parent has allowOnionUrlbarRewrites = true
+    // and the same origin we should allow this.
+    let persistAllowOnionUrlbarRewrites = false;
+
+    if (gContextMenuContentData.parentAllowsOnionUrlbarRewrites) {
+      const sm = Services.scriptSecurityManager;
+      try {
+        let targetURI = this.linkURI;
+        let isPrivateWin =
+          this.browser.contentPrincipal.originAttributes.privateBrowsingId > 0;
+        sm.checkSameOriginURI(referrerURI, targetURI, false, isPrivateWin);
+        persistAllowOnionUrlbarRewrites = true;
+      } catch (e) {}
+    }
+
     let params = {
       allowMixedContent: persistAllowMixedContentInChildTab,
       userContextId: parseInt(event.target.getAttribute("data-usercontextid")),
+      allowOnionUrlbarRewrites: persistAllowOnionUrlbarRewrites,
     };
 
     openLinkIn(this.linkURL, "tab", this._openLinkInParameters(params));
diff --git a/browser/base/content/tabbrowser.js b/browser/base/content/tabbrowser.js
index 4dd07ff14e7b..53c463fd3263 100644
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -1589,6 +1589,7 @@
       var aRelatedToCurrent;
       var aAllowInheritPrincipal;
       var aAllowMixedContent;
+      var aAllowOnionUrlbarRewrites;
       var aSkipAnimation;
       var aForceNotRemote;
       var aPreferredRemoteType;
@@ -1618,6 +1619,7 @@
         aRelatedToCurrent = params.relatedToCurrent;
         aAllowInheritPrincipal = !!params.allowInheritPrincipal;
         aAllowMixedContent = params.allowMixedContent;
+        aAllowOnionUrlbarRewrites = params.allowOnionUrlbarRewrites;
         aSkipAnimation = params.skipAnimation;
         aForceNotRemote = params.forceNotRemote;
         aPreferredRemoteType = params.preferredRemoteType;
@@ -1658,6 +1660,7 @@
         relatedToCurrent: aRelatedToCurrent,
         skipAnimation: aSkipAnimation,
         allowMixedContent: aAllowMixedContent,
+        allowOnionUrlbarRewrites: aAllowOnionUrlbarRewrites,
         forceNotRemote: aForceNotRemote,
         createLazyBrowser: aCreateLazyBrowser,
         preferredRemoteType: aPreferredRemoteType,
@@ -2530,6 +2533,7 @@
       {
         allowInheritPrincipal,
         allowMixedContent,
+        allowOnionUrlbarRewrites,
         allowThirdPartyFixup,
         bulkOrderedOpen,
         charset,
@@ -2902,6 +2906,9 @@
         if (allowMixedContent) {
           flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;
         }
+        if (allowOnionUrlbarRewrites) {
+          flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES;
+        }
         if (!allowInheritPrincipal) {
           flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
         }
diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js
index 9a1010b2afa9..cc46b14561b4 100644
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -370,6 +370,7 @@ function openLinkIn(url, where, params) {
   var aRelatedToCurrent = params.relatedToCurrent;
   var aAllowInheritPrincipal = !!params.allowInheritPrincipal;
   var aAllowMixedContent = params.allowMixedContent;
+  var aAllowOnionUrlbarRewrites = params.allowOnionUrlbarRewrites;
   var aForceAllowDataURI = params.forceAllowDataURI;
   var aInBackground = params.inBackground;
   var aInitiatingDoc = params.initiatingDoc;
@@ -680,6 +681,7 @@ function openLinkIn(url, where, params) {
         relatedToCurrent: aRelatedToCurrent,
         skipAnimation: aSkipTabAnimation,
         allowMixedContent: aAllowMixedContent,
+        allowOnionUrlbarRewrites: aAllowOnionUrlbarRewrites,
         userContextId: aUserContextId,
         originPrincipal: aPrincipal,
         triggeringPrincipal: aTriggeringPrincipal,
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 4e851289fbe9..628c6f5db151 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -494,6 +494,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
   ShellService: "resource:///modules/ShellService.jsm",
   TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
   TabUnloader: "resource:///modules/TabUnloader.jsm",
+  OnionAliasStore: "resource:///modules/OnionAliasStore.jsm",
   UIState: "resource://services-sync/UIState.jsm",
   UITour: "resource:///modules/UITour.jsm",
   WebChannel: "resource://gre/modules/WebChannel.jsm",
@@ -1839,6 +1840,7 @@ BrowserGlue.prototype = {
 
     Normandy.uninit();
     RFPHelper.uninit();
+    OnionAliasStore.uninit();
   },
 
   // Set up a listener to enable/disable the screenshots extension
@@ -2109,6 +2111,10 @@ BrowserGlue.prototype = {
       RFPHelper.init();
     });
 
+    Services.tm.idleDispatchToMainThread(() => {
+      OnionAliasStore.init();
+    });
+
     ChromeUtils.idleDispatch(() => {
       Blocklist.loadBlocklistAsync();
     });
diff --git a/browser/components/onionservices/ExtensionMessaging.jsm b/browser/components/onionservices/ExtensionMessaging.jsm
new file mode 100644
index 000000000000..b5d69df93807
--- /dev/null
+++ b/browser/components/onionservices/ExtensionMessaging.jsm
@@ -0,0 +1,86 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["ExtensionMessaging"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { ExtensionUtils } = ChromeUtils.import(
+  "resource://gre/modules/ExtensionUtils.jsm"
+);
+const { MessageChannel } = ChromeUtils.import(
+  "resource://gre/modules/MessageChannel.jsm"
+);
+const { AddonManager } = ChromeUtils.import(
+  "resource://gre/modules/AddonManager.jsm"
+);
+
+class ExtensionMessaging {
+  constructor() {
+    this._callback = null;
+    this._handlers = new Map();
+    this._messageManager = Services.cpmm;
+  }
+
+  async sendMessage(msg, extensionId) {
+    this._init();
+
+    const addon = await AddonManager.getAddonByID(extensionId);
+    if (!addon) {
+      throw new Error(`extension '${extensionId} does not exist`);
+    }
+    await addon.startupPromise;
+
+    const channelId = ExtensionUtils.getUniqueId();
+    return new Promise((resolve, reject) => {
+      this._handlers.set(channelId, { resolve, reject });
+      this._messageManager.sendAsyncMessage("MessageChannel:Messages", [
+        {
+          messageName: "Extension:Message",
+          sender: {
+            id: extensionId,
+            extensionId,
+          },
+          recipient: { extensionId },
+          data: new StructuredCloneHolder(msg),
+          channelId,
+          responseType: MessageChannel.RESPONSE_FIRST,
+        },
+      ]);
+    });
+  }
+
+  unload() {
+    if (this._callback) {
+      this._handlers.clear();
+      this._messageManager.removeMessageListener(
+        "MessageChannel:Response",
+        this._callback
+      );
+      this._callback = null;
+    }
+  }
+
+  _onMessage({ data }) {
+    const channelId = data.messageName;
+    if (this._handlers.has(channelId)) {
+      const { resolve, reject } = this._handlers.get(channelId);
+      this._handlers.delete(channelId);
+      if (data.error) {
+        reject(new Error(data.error.message));
+      } else {
+        resolve(data.value);
+      }
+    }
+  }
+
+  _init() {
+    if (this._callback === null) {
+      this._callback = this._onMessage.bind(this);
+      this._messageManager.addMessageListener(
+        "MessageChannel:Response",
+        this._callback
+      );
+    }
+  }
+}
diff --git a/browser/components/onionservices/HttpsEverywhereControl.jsm b/browser/components/onionservices/HttpsEverywhereControl.jsm
new file mode 100644
index 000000000000..87954461bab1
--- /dev/null
+++ b/browser/components/onionservices/HttpsEverywhereControl.jsm
@@ -0,0 +1,119 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["HttpsEverywhereControl"];
+
+const { ExtensionMessaging } = ChromeUtils.import(
+  "resource:///modules/ExtensionMessaging.jsm"
+);
+const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
+
+const EXTENSION_ID = "https-everywhere-eff at eff.org";
+const SECUREDROP_TOR_ONION_CHANNEL = {
+  name: "SecureDropTorOnion",
+  jwk: {
+    kty: "RSA",
+    e: "AQAB",
+    n:
+      "y0iWTVev1uYDVhLdc5uSHWke-9JlbxzqIsGkS95Pk5NsxdlkdbHpqaPr-5xL5FspX8QGo3HAT5hrUcPV_kz8x-HwGEm2-p9BQ6-yEPtr5QXQGGzNoizmj7HH-b0y5qx8iUFwAJ__PJWK4IwSgjIQqHMjmkOLc9N4bmRPULi6ZZMb-97FdeZsh34dVy2tpIzZaijRfRQSfeZkwRXZzOY-siGfOAzY_UcrHFli5zroTZAyDaetFm1z2-TdFLOvN8fi0o3mBbCB9SqhOUImPwSTNWTp2D6bHhI91mt7gr6fLnzHMGrTMh2DQ4vjt_98pe7WTUzuRCLa9Awb6JJgbYA4ySV1akAU0_iq6oCAE9PZbUgUw9UAH1ctRFml87F3HORUMMj5ZCLwRIrEXqrCJbV4f-Ius-ZO2wwlYTsEv8TmUzISpMwVjGOIpXwFIb65R_EX3_vIopauSoyZkvk3klly0Qe6KTy_gg1CZ_h2ZXPpLMwqlfFTDBPv2q8gyuzgkYXQes3FX-PbJ9Dsl5QO4icuEjV2NQ7iPwwIPAtj9cpqCD8-p9TqTKaZjOFC9-ryJpWsivGCbvN2JotmJ5Ax9rmnAMvQM09muCetFj_ZIgllcpeahaw6gxVXlSYIhb0J9V878KNuRJ2yPJFlBmgpFexvCcz8Jqs6JUfIrmUAGUXG9nE",
+  },
+  update_path_prefix: "https://redshiftzero.github.io/securedrop-httpse/",
+  scope:
+    "^https?:\\/\\/[a-z0-9-]+(?:\\.[a-z0-9-]+)*\\.securedrop\\.tor\\.onion\\/",
+  replaces_default_rulesets: false,
+};
+
+class HttpsEverywhereControl {
+  constructor() {
+    this._extensionMessaging = null;
+  }
+
+  async _sendMessage(type, object) {
+    return this._extensionMessaging.sendMessage(
+      {
+        type,
+        object,
+      },
+      EXTENSION_ID
+    );
+  }
+
+  static async wait(seconds = 1) {
+    return new Promise(resolve => setTimeout(resolve, seconds * 1000));
+  }
+
+  /**
+   * Installs the .tor.onion update channel in https-everywhere
+   */
+  async installTorOnionUpdateChannel(retries = 5) {
+    this._init();
+
+    // TODO: https-everywhere store is initialized asynchronously, so sending a message
+    // immediately results in a `store.get is undefined` error.
+    // For now, let's wait a bit and retry a few times if there is an error, but perhaps
+    // we could suggest https-everywhere to send a message when that happens and listen
+    // for that here.
+    await HttpsEverywhereControl.wait();
+
+    try {
+      // TODO: we may want a way to "lock" this update channel, so that it cannot be modified
+      // by the user via UI, but I think this is not possible at the time of writing via
+      // the existing messages in https-everywhere.
+      await this._sendMessage(
+        "create_update_channel",
+        SECUREDROP_TOR_ONION_CHANNEL.name
+      );
+    } catch (e) {
+      if (retries <= 0) {
+        throw new Error("Could not install SecureDropTorOnion update channel");
+      }
+      await this.installTorOnionUpdateChannel(retries - 1);
+      return;
+    }
+
+    await this._sendMessage(
+      "update_update_channel",
+      SECUREDROP_TOR_ONION_CHANNEL
+    );
+  }
+
+  /**
+   * Returns the .tor.onion rulesets available in https-everywhere
+   */
+  async getTorOnionRules() {
+    return this._sendMessage("get_simple_rules_ending_with", ".tor.onion");
+  }
+
+  /**
+   * Returns the timestamp of the last .tor.onion update channel update.
+   */
+  async getRulesetTimestamp() {
+    const rulesets = await this._sendMessage("get_ruleset_timestamps");
+    const securedrop =
+      rulesets &&
+      rulesets.find(([{ name }]) => name === SECUREDROP_TOR_ONION_CHANNEL.name);
+    if (securedrop) {
+      const [
+        updateChannel, // This has the same structure as SECUREDROP_TOR_ONION_CHANNEL
+        lastUpdatedTimestamp, // An integer, 0 if the update channel was never updated
+      ] = securedrop;
+      void updateChannel; // Ignore eslint unused warning for ruleset
+      return lastUpdatedTimestamp;
+    }
+    return null;
+  }
+
+  unload() {
+    if (this._extensionMessaging) {
+      this._extensionMessaging.unload();
+      this._extensionMessaging = null;
+    }
+  }
+
+  _init() {
+    if (!this._extensionMessaging) {
+      this._extensionMessaging = new ExtensionMessaging();
+    }
+  }
+}
diff --git a/browser/components/onionservices/OnionAliasStore.jsm b/browser/components/onionservices/OnionAliasStore.jsm
new file mode 100644
index 000000000000..7e006e59490b
--- /dev/null
+++ b/browser/components/onionservices/OnionAliasStore.jsm
@@ -0,0 +1,199 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["OnionAliasStore"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { XPCOMUtils } = ChromeUtils.import(
+  "resource://gre/modules/XPCOMUtils.jsm"
+);
+const { setTimeout, clearTimeout } = ChromeUtils.import(
+  "resource://gre/modules/Timer.jsm"
+);
+const { HttpsEverywhereControl } = ChromeUtils.import(
+  "resource:///modules/HttpsEverywhereControl.jsm"
+);
+
+// Logger adapted from CustomizableUI.jsm
+const kPrefOnionAliasDebug = "browser.onionalias.debug";
+XPCOMUtils.defineLazyPreferenceGetter(
+  this,
+  "gDebuggingEnabled",
+  kPrefOnionAliasDebug,
+  false,
+  (pref, oldVal, newVal) => {
+    if (typeof log != "undefined") {
+      log.maxLogLevel = newVal ? "all" : "log";
+    }
+  }
+);
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+  let scope = {};
+  ChromeUtils.import("resource://gre/modules/Console.jsm", scope);
+  let consoleOptions = {
+    maxLogLevel: gDebuggingEnabled ? "all" : "log",
+    prefix: "OnionAlias",
+  };
+  return new scope.ConsoleAPI(consoleOptions);
+});
+
+function observe(topic, callback) {
+  let observer = {
+    observe(aSubject, aTopic, aData) {
+      if (topic === aTopic) {
+        callback(aSubject, aData);
+      }
+    },
+  };
+  Services.obs.addObserver(observer, topic);
+  return () => Services.obs.removeObserver(observer, topic);
+}
+
+class _OnionAliasStore {
+  static get RULESET_CHECK_INTERVAL() {
+    return 1000 * 60; // 1 minute
+  }
+
+  static get RULESET_CHECK_INTERVAL_FAST() {
+    return 1000 * 5; // 5 seconds
+  }
+
+  constructor() {
+    this._onionMap = new Map();
+    this._rulesetTimeout = null;
+    this._removeObserver = () => {};
+    this._canLoadRules = false;
+    this._rulesetTimestamp = null;
+  }
+
+  async _periodicRulesetCheck() {
+    // TODO: it would probably be preferable to listen to some message broadcasted by
+    // the https-everywhere extension when some update channel is updated, instead of
+    // polling every N seconds.
+    log.debug("Checking for new rules");
+    const ts = await this.httpsEverywhereControl.getRulesetTimestamp();
+    log.debug(
+      `Found ruleset timestamp ${ts}, current is ${this._rulesetTimestamp}`
+    );
+    if (ts !== this._rulesetTimestamp) {
+      this._rulesetTimestamp = ts;
+      log.debug("New rules found, updating");
+      // We clear the mappings even if we cannot load the rules from https-everywhere,
+      // since we cannot be sure if the stored mappings are correct anymore.
+      this._clear();
+      if (this._canLoadRules) {
+        await this._loadRules();
+      }
+    }
+    // If the timestamp is 0, that means the update channel was not yet updated, so
+    // we schedule a check soon.
+    this._rulesetTimeout = setTimeout(
+      () => this._periodicRulesetCheck(),
+      ts === 0
+        ? _OnionAliasStore.RULESET_CHECK_INTERVAL_FAST
+        : _OnionAliasStore.RULESET_CHECK_INTERVAL
+    );
+  }
+
+  async init() {
+    this.httpsEverywhereControl = new HttpsEverywhereControl();
+
+    // Install update channel
+    await this.httpsEverywhereControl.installTorOnionUpdateChannel();
+
+    // Setup .tor.onion rule loading.
+    // The http observer is a fallback, and is removed in _loadRules() as soon as we are able
+    // to load some rules from HTTPS Everywhere.
+    this._loadHttpObserver();
+    try {
+      await this.httpsEverywhereControl.getTorOnionRules();
+      this._canLoadRules = true;
+    } catch (e) {
+      // Loading rules did not work, probably because "get_simple_rules_ending_with" is not yet
+      // working in https-everywhere. Use an http observer as a fallback for learning the rules.
+      log.debug("Could not load rules, using http observer as fallback");
+    }
+
+    // Setup checker for https-everywhere ruleset updates
+    this._periodicRulesetCheck();
+  }
+
+  /**
+   * Loads the .tor.onion mappings from https-everywhere.
+   */
+  async _loadRules() {
+    const rules = await this.httpsEverywhereControl.getTorOnionRules();
+    // Remove http observer if we are able to load some rules directly.
+    if (rules.length) {
+      this._removeObserver();
+      this._removeObserver = () => {};
+    }
+    this._clear();
+    log.debug(`Loading ${rules.length} rules`, rules);
+    for (const rule of rules) {
+      // Here we are trusting that the securedrop ruleset follows some conventions so that we can
+      // assume there is a host mapping from `rule.host` to the hostname of the URL in `rule.to`.
+      try {
+        const url = new URL(rule.to);
+        const shortHost = rule.host;
+        const longHost = url.hostname;
+        this._addMapping(shortHost, longHost);
+      } catch (e) {
+        log.error("Could not process rule:", rule);
+      }
+    }
+  }
+
+  /**
+   * Loads a http observer to listen for local redirects for populating
+   * the .tor.onion -> .onion mappings. Should only be used if we cannot ask https-everywhere
+   * directly for the mappings.
+   */
+  _loadHttpObserver() {
+    this._removeObserver = observe("http-on-before-connect", channel => {
+      if (
+        channel.isMainDocumentChannel &&
+        channel.originalURI.host.endsWith(".tor.onion")
+      ) {
+        this._addMapping(channel.originalURI.host, channel.URI.host);
+      }
+    });
+  }
+
+  uninit() {
+    this._clear();
+    this._removeObserver();
+    this._removeObserver = () => {};
+    if (this.httpsEverywhereControl) {
+      this.httpsEverywhereControl.unload();
+      delete this.httpsEverywhereControl;
+    }
+    clearTimeout(this._rulesetTimeout);
+    this._rulesetTimeout = null;
+    this._rulesetTimestamp = null;
+  }
+
+  _clear() {
+    this._onionMap.clear();
+  }
+
+  _addMapping(shortOnionHost, longOnionHost) {
+    this._onionMap.set(longOnionHost, shortOnionHost);
+  }
+
+  getShortURI(onionURI) {
+    if (
+      (onionURI.schemeIs("http") || onionURI.schemeIs("https")) &&
+      this._onionMap.has(onionURI.host)
+    ) {
+      return onionURI
+        .mutate()
+        .setHost(this._onionMap.get(onionURI.host))
+        .finalize();
+    }
+    return null;
+  }
+}
+
+let OnionAliasStore = new _OnionAliasStore();
diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build
index 7e103239c8d6..e4b6d73f8f40 100644
--- a/browser/components/onionservices/moz.build
+++ b/browser/components/onionservices/moz.build
@@ -1 +1,7 @@
 JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_JS_MODULES += [
+    'ExtensionMessaging.jsm',
+    'HttpsEverywhereControl.jsm',
+    'OnionAliasStore.jsm',
+]
diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm
index 131d2ecc8ca8..8fd87bb8b026 100644
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -1022,7 +1022,13 @@ class UrlbarInput {
     }
 
     let uri;
-    if (this.getAttribute("pageproxystate") == "valid") {
+    // When we rewrite .onion to an alias, gBrowser.currentURI will be different than
+    // the URI displayed in the urlbar. We need to use the urlbar value to copy the
+    // alias instead of the actual .onion URI that is loaded.
+    if (
+      this.getAttribute("pageproxystate") == "valid" &&
+      !this.window.gBrowser.selectedBrowser.allowOnionUrlbarRewrites
+    ) {
       uri = this.window.gBrowser.currentURI;
     } else {
       // The value could be:
diff --git a/browser/modules/ContentClick.jsm b/browser/modules/ContentClick.jsm
index 767c67880995..872b06488c1a 100644
--- a/browser/modules/ContentClick.jsm
+++ b/browser/modules/ContentClick.jsm
@@ -88,6 +88,7 @@ var ContentClick = {
       charset: browser.characterSet,
       referrerInfo: E10SUtils.deserializeReferrerInfo(json.referrerInfo),
       allowMixedContent: json.allowMixedContent,
+      allowOnionUrlbarRewrites: json.allowOnionUrlbarRewrites,
       isContentWindowPrivate: json.isContentWindowPrivate,
       originPrincipal: json.originPrincipal,
       triggeringPrincipal: json.triggeringPrincipal,
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
index cc329a50c109..80678a04e95c 100644
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -6543,6 +6543,18 @@ void nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel,
     return;
   }
 
+  if (!mAllowOnionUrlbarRewrites) {
+    nsAutoCString oldHost;
+    nsAutoCString newHost;
+    if (NS_SUCCEEDED(oldURI->GetHost(oldHost)) &&
+        StringEndsWith(oldHost, NS_LITERAL_CSTRING(".tor.onion")) &&
+        NS_SUCCEEDED(newURI->GetHost(newHost)) &&
+        StringEndsWith(newHost, NS_LITERAL_CSTRING(".onion")) &&
+        !StringEndsWith(newHost, NS_LITERAL_CSTRING(".tor.onion"))) {
+      mAllowOnionUrlbarRewrites = true;
+    }
+  }
+
   // Below a URI visit is saved (see AddURIVisit method doc).
   // The visit chain looks something like:
   //   ...
@@ -9744,6 +9756,10 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
       // We're making history navigation or a reload. Make sure our history ID
       // points to the same ID as SHEntry's docshell ID.
       mHistoryID = aLoadState->SHEntry()->DocshellID();
+      // Loading from session history may not call DoURILoad(), so we set this
+      // flag here.
+      mAllowOnionUrlbarRewrites = aLoadState->HasLoadFlags(
+          INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES);
     }
   }
 
@@ -10386,6 +10402,13 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
     }
   }
 
+  mAllowOnionUrlbarRewrites =
+      aLoadState->HasLoadFlags(
+          INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES) ||
+      (mAllowOnionUrlbarRewrites && GetCurrentDocChannel() &&
+       NS_SUCCEEDED(
+           nsContentUtils::CheckSameOrigin(GetCurrentDocChannel(), channel)));
+
   // hack
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
   nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal(
@@ -11733,6 +11756,7 @@ nsresult nsDocShell::AddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel,
                 triggeringPrincipal,  // Channel or provided principal
                 principalToInherit, csp, mHistoryID, mDynamicallyCreated);
 
+  entry->SetAllowOnionUrlbarRewrites(mAllowOnionUrlbarRewrites);
   entry->SetOriginalURI(originalURI);
   entry->SetResultPrincipalURI(resultPrincipalURI);
   entry->SetLoadReplace(loadReplace);
@@ -11913,6 +11937,10 @@ nsresult nsDocShell::LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType) {
     srcdoc = VoidString();
   }
 
+  if (aEntry->GetAllowOnionUrlbarRewrites()) {
+    flags |= INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES;
+  }
+
   // If there is no valid triggeringPrincipal, we deny the load
   MOZ_ASSERT(triggeringPrincipal,
              "need a valid triggeringPrincipal to load from history");
@@ -13817,3 +13845,12 @@ nsDocShell::SetWatchedByDevtools(bool aWatched) {
   mWatchedByDevtools = aWatched;
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsDocShell::GetAllowOnionUrlbarRewrites(bool* aAllowOnionUrlbarRewrites) {
+  NS_ENSURE_ARG(aAllowOnionUrlbarRewrites);
+  *aAllowOnionUrlbarRewrites =
+      StaticPrefs::browser_urlbar_onionRewrites_enabled() &&
+      mAllowOnionUrlbarRewrites;
+  return NS_OK;
+}
diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
index 4a81946f7bf4..7de079c0903e 100644
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -153,6 +153,9 @@ class nsDocShell final : public nsDocLoader,
 
     // Whether the load was triggered by user interaction.
     INTERNAL_LOAD_FLAGS_IS_USER_TRIGGERED = 0x1000,
+
+    // Whether rewriting the urlbar to a short .onion alias is allowed.
+    INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES = 0x2000,
   };
 
   // Event type dispatched by RestorePresentation
@@ -1235,6 +1238,7 @@ class nsDocShell final : public nsDocLoader,
   bool mCSSErrorReportingEnabled : 1;
   bool mAllowAuth : 1;
   bool mAllowKeywordFixup : 1;
+  bool mAllowOnionUrlbarRewrites : 1;
   bool mIsOffScreenBrowser : 1;
   bool mIsActive : 1;
   bool mDisableMetaRefreshWhenInactive : 1;
diff --git a/docshell/base/nsDocShellLoadState.cpp b/docshell/base/nsDocShellLoadState.cpp
index d2e21edceeda..80143b49f3fa 100644
--- a/docshell/base/nsDocShellLoadState.cpp
+++ b/docshell/base/nsDocShellLoadState.cpp
@@ -417,6 +417,10 @@ void nsDocShellLoadState::CalculateLoadURIFlags() {
     mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
   }
 
+  if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES) {
+    mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES;
+  }
+
   if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD) {
     mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FIRST_LOAD;
   }
diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl
index 542232892c5b..cc0013e80c10 100644
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -1195,4 +1195,9 @@ interface nsIDocShell : nsIDocShellTreeItem
    * Whether developer tools are watching activity in this docshell.
    */
   [infallible] attribute boolean watchedByDevtools;
+
+  /**
+   * Whether rewriting the urlbar to a short .onion alias is allowed.
+   */
+  [infallible] readonly attribute boolean allowOnionUrlbarRewrites;
 };
diff --git a/docshell/base/nsIWebNavigation.idl b/docshell/base/nsIWebNavigation.idl
index cabae3f31d0f..123129d2c259 100644
--- a/docshell/base/nsIWebNavigation.idl
+++ b/docshell/base/nsIWebNavigation.idl
@@ -227,6 +227,11 @@ interface nsIWebNavigation : nsISupports
   const unsigned long LOAD_FLAGS_IS_REDIRECT = 0x800000;
 
   /**
+   * Allow rewriting the urlbar to a short .onion alias.
+   */
+  const unsigned long LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES = 0x1000000;
+
+  /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing this interface.  If it can't be loaded here
    * however, the URI dispatcher will go through its normal process of content
diff --git a/docshell/shistory/nsISHEntry.idl b/docshell/shistory/nsISHEntry.idl
index 12dbf8172262..f5863b532a28 100644
--- a/docshell/shistory/nsISHEntry.idl
+++ b/docshell/shistory/nsISHEntry.idl
@@ -230,6 +230,11 @@ interface nsISHEntry : nsISupports
     [infallible] attribute boolean persist;
 
     /**
+     * Whether rewriting the urlbar to a short .onion alias is allowed.
+     */
+    [infallible] attribute boolean allowOnionUrlbarRewrites;
+
+    /**
      * Set/Get the visual viewport scroll position if session history is
      * changed through anchor navigation or pushState.
      */
diff --git a/docshell/shistory/nsSHEntry.cpp b/docshell/shistory/nsSHEntry.cpp
index e39b5888c157..27f21a23d2ba 100644
--- a/docshell/shistory/nsSHEntry.cpp
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -42,7 +42,8 @@ nsSHEntry::nsSHEntry()
       mIsSrcdocEntry(false),
       mScrollRestorationIsManual(false),
       mLoadedInThisProcess(false),
-      mPersist(true) {}
+      mPersist(true),
+      mAllowOnionUrlbarRewrites(false) {}
 
 nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
     : mShared(aOther.mShared),
@@ -68,7 +69,8 @@ nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
       mIsSrcdocEntry(aOther.mIsSrcdocEntry),
       mScrollRestorationIsManual(false),
       mLoadedInThisProcess(aOther.mLoadedInThisProcess),
-      mPersist(aOther.mPersist) {}
+      mPersist(aOther.mPersist),
+      mAllowOnionUrlbarRewrites(aOther.mAllowOnionUrlbarRewrites) {}
 
 nsSHEntry::~nsSHEntry() {
   // Null out the mParent pointers on all our kids.
@@ -855,3 +857,15 @@ nsSHEntry::SetPersist(bool aPersist) {
   mPersist = aPersist;
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsSHEntry::GetAllowOnionUrlbarRewrites(bool* aAllowOnionUrlbarRewrites) {
+  *aAllowOnionUrlbarRewrites = mAllowOnionUrlbarRewrites;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetAllowOnionUrlbarRewrites(bool aAllowOnionUrlbarRewrites) {
+  mAllowOnionUrlbarRewrites = aAllowOnionUrlbarRewrites;
+  return NS_OK;
+}
diff --git a/docshell/shistory/nsSHEntry.h b/docshell/shistory/nsSHEntry.h
index 4a9d7e466b22..aeb42b307739 100644
--- a/docshell/shistory/nsSHEntry.h
+++ b/docshell/shistory/nsSHEntry.h
@@ -61,6 +61,7 @@ class nsSHEntry final : public nsISHEntry {
   bool mScrollRestorationIsManual;
   bool mLoadedInThisProcess;
   bool mPersist;
+  bool mAllowOnionUrlbarRewrites;
 };
 
 #endif /* nsSHEntry_h */
diff --git a/modules/libpref/init/StaticPrefList.h b/modules/libpref/init/StaticPrefList.h
index 47f4626f7e6b..29732ac3e884 100644
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -2650,6 +2650,13 @@ VARCACHE_PREF(
    bool, false
 )
 
+// Whether rewriting the urlbar to a short .onion alias is allowed.
+VARCACHE_PREF(
+  "browser.urlbar.onionRewrites.enabled",
+   browser_urlbar_onionRewrites_enabled,
+  bool, true
+)
+
 //---------------------------------------------------------------------------
 // ChannelClassifier prefs
 //---------------------------------------------------------------------------
diff --git a/netwerk/dns/effective_tld_names.dat b/netwerk/dns/effective_tld_names.dat
index 70e198aa3712..0c53819d860d 100644
--- a/netwerk/dns/effective_tld_names.dat
+++ b/netwerk/dns/effective_tld_names.dat
@@ -5495,6 +5495,8 @@ pro.om
 
 // onion : https://tools.ietf.org/html/rfc7686
 onion
+tor.onion
+securedrop.tor.onion
 
 // org : https://en.wikipedia.org/wiki/.org
 org
diff --git a/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm b/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm
index 6d971c10180a..76ce03dc6897 100644
--- a/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm
+++ b/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm
@@ -60,6 +60,7 @@ RemoteWebNavigation.prototype = {
   LOAD_FLAGS_ALLOW_POPUPS: 32768,
   LOAD_FLAGS_BYPASS_CLASSIFIER: 65536,
   LOAD_FLAGS_FORCE_ALLOW_COOKIES: 131072,
+  LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES: 16777216,
 
   STOP_NETWORK: 1,
   STOP_CONTENT: 2,
diff --git a/toolkit/content/widgets/browser-custom-element.js b/toolkit/content/widgets/browser-custom-element.js
index f31058012cbb..d26c8f8f0d1d 100644
--- a/toolkit/content/widgets/browser-custom-element.js
+++ b/toolkit/content/widgets/browser-custom-element.js
@@ -283,6 +283,8 @@
 
       this._mayEnableCharacterEncodingMenu = null;
 
+      this._allowOnionUrlbarRewrites = false;
+
       this._contentPrincipal = null;
 
       this._csp = null;
@@ -677,6 +679,12 @@
         : this.docShell.mayEnableCharacterEncodingMenu;
     }
 
+    get allowOnionUrlbarRewrites() {
+      return this.isRemoteBrowser
+        ? this._allowOnionUrlbarRewrites
+        : this.docShell.allowOnionUrlbarRewrites;
+    }
+
     get contentPrincipal() {
       return this.isRemoteBrowser
         ? this._contentPrincipal
@@ -1870,6 +1878,7 @@
             "_textZoom",
             "_isSyntheticDocument",
             "_innerWindowID",
+            "_allowOnionUrlbarRewrites",
           ]
         );
       }
diff --git a/toolkit/modules/RemoteWebProgress.jsm b/toolkit/modules/RemoteWebProgress.jsm
index 0f3c53a17a8c..558c31515570 100644
--- a/toolkit/modules/RemoteWebProgress.jsm
+++ b/toolkit/modules/RemoteWebProgress.jsm
@@ -281,6 +281,8 @@ class RemoteWebProgressManager {
           this._browser._isSyntheticDocument = json.synthetic;
           this._browser._innerWindowID = json.innerWindowID;
           this._browser._contentRequestContextID = json.requestContextID;
+          this._browser._allowOnionUrlbarRewrites =
+            json.allowOnionUrlbarRewrites;
         }
 
         this.onLocationChange(webProgress, request, location, flags);
diff --git a/toolkit/modules/WebProgressChild.jsm b/toolkit/modules/WebProgressChild.jsm
index 60a1aa88e8ef..26250e361bd4 100644
--- a/toolkit/modules/WebProgressChild.jsm
+++ b/toolkit/modules/WebProgressChild.jsm
@@ -167,6 +167,7 @@ class WebProgressChild {
       json.requestContextID = this.mm.content.document.documentLoadGroup
         ? this.mm.content.document.documentLoadGroup.requestContextID
         : null;
+      json.allowOnionUrlbarRewrites = this.mm.docShell.allowOnionUrlbarRewrites;
 
       if (AppConstants.MOZ_CRASHREPORTER && CrashReporter.enabled) {
         let uri = aLocationURI;
diff --git a/toolkit/modules/sessionstore/SessionHistory.jsm b/toolkit/modules/sessionstore/SessionHistory.jsm
index e54ab3369cd8..463f147a9ca4 100644
--- a/toolkit/modules/sessionstore/SessionHistory.jsm
+++ b/toolkit/modules/sessionstore/SessionHistory.jsm
@@ -287,6 +287,7 @@ var SessionHistoryInternal = {
     }
 
     entry.persist = shEntry.persist;
+    entry.allowOnionUrlbarRewrites = shEntry.allowOnionUrlbarRewrites;
 
     return entry;
   },
@@ -559,6 +560,10 @@ var SessionHistoryInternal = {
       }
     }
 
+    if (entry.allowOnionUrlbarRewrites) {
+      shEntry.allowOnionUrlbarRewrites = entry.allowOnionUrlbarRewrites;
+    }
+
     return shEntry;
   },
 





More information about the tor-commits mailing list