[tbb-commits] [tor-browser] 54/57: Bug 21952: Implement Onion-Location

gitolite role git at cupani.torproject.org
Mon Dec 5 13:02:30 UTC 2022


This is an automated email from the git hooks/post-receive script.

richard pushed a commit to branch tor-browser-102.5.0esr-12.0-2
in repository tor-browser.

commit 5637511a2e4ecf0b56312ed24456cfc0333f4b8e
Author: Alex Catarineu <acat at torproject.org>
AuthorDate: Thu Mar 5 22:16:39 2020 +0100

    Bug 21952: Implement Onion-Location
    
    Whenever a valid Onion-Location HTTP header (or corresponding HTML
    <meta> http-equiv attribute) is found in a document load, we either
    redirect to it (if the user opted-in via preference) or notify the
    presence of an onionsite alternative with a badge in the urlbar.
---
 browser/base/content/browser.js                    |  12 ++
 browser/base/content/navigator-toolbox.inc.xhtml   |   3 +
 browser/components/BrowserGlue.jsm                 |  13 ++
 .../onionservices/OnionLocationChild.jsm           |  48 ++++++
 .../onionservices/OnionLocationParent.jsm          | 169 +++++++++++++++++++++
 .../content/onionlocation-urlbar.inc.xhtml         |  10 ++
 .../onionservices/content/onionlocation.css        |  46 ++++++
 .../onionservices/content/onionlocation.svg        |   3 +
 .../content/onionlocationPreferences.inc.xhtml     |  11 ++
 .../content/onionlocationPreferences.js            |  34 +++++
 browser/components/onionservices/jar.mn            |   3 +
 browser/components/onionservices/moz.build         |   5 +
 browser/components/preferences/privacy.inc.xhtml   |   2 +
 browser/components/preferences/privacy.js          |  17 +++
 browser/themes/shared/browser-shared.css           |   1 +
 dom/base/Document.cpp                              |  69 ++++++++-
 dom/base/Document.h                                |   2 +
 dom/webidl/Document.webidl                         |   8 +
 mobile/android/geckoview/api.txt                   |   3 +
 .../mozilla/geckoview/GeckoRuntimeSettings.java    |  33 ++++
 modules/libpref/init/StaticPrefList.yaml           |   5 +
 xpcom/ds/StaticAtoms.py                            |   1 +
 22 files changed, 497 insertions(+), 1 deletion(-)

diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 66ac2d0f1d7c..24c5939750ee 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -48,6 +48,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
   OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.jsm",
+  OnionLocationParent: "resource:///modules/OnionLocationParent.jsm",
   PageActions: "resource:///modules/PageActions.jsm",
   PageThumbs: "resource://gre/modules/PageThumbs.jsm",
   PanelMultiView: "resource:///modules/PanelMultiView.jsm",
@@ -5520,6 +5521,7 @@ var XULBrowserWindow = {
     CFRPageActions.updatePageActions(gBrowser.selectedBrowser);
 
     AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser);
+    OnionLocationParent.updateOnionLocationBadge(gBrowser.selectedBrowser);
 
     if (!gMultiProcessBrowser) {
       // Bug 1108553 - Cannot rotate images with e10s
@@ -6039,6 +6041,16 @@ var CombinedStopReload = {
 
 var TabsProgressListener = {
   onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+    // Clear OnionLocation UI
+    if (
+      aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
+      aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
+      aRequest &&
+      aWebProgress.isTopLevel
+    ) {
+      OnionLocationParent.onStateChange(aBrowser);
+    }
+
     // Collect telemetry data about tab load times.
     if (
       aWebProgress.isTopLevel &&
diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml
index 1cd54285c9a9..8e199e658a41 100644
--- a/browser/base/content/navigator-toolbox.inc.xhtml
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
@@ -352,6 +352,9 @@
                      onclick="FullZoom.reset(); FullZoom.resetScalingZoom();"
                      tooltip="dynamic-shortcut-tooltip"
                      hidden="true"/>
+
+#include ../../components/onionservices/content/onionlocation-urlbar.inc.xhtml
+
               <hbox id="pageActionButton"
                     class="urlbar-page-action"
                     role="button"
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index fe46dec2c42d..5b60b15029c0 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -582,6 +582,19 @@ let JSWINDOWACTORS = {
     allFrames: true,
   },
 
+  OnionLocation: {
+    parent: {
+      moduleURI: "resource:///modules/OnionLocationParent.jsm",
+    },
+    child: {
+      moduleURI: "resource:///modules/OnionLocationChild.jsm",
+      events: {
+        pageshow: { mozSystemGroup: true },
+      },
+    },
+    messageManagerGroups: ["browsers"],
+  },
+
   PageInfo: {
     child: {
       moduleURI: "resource:///actors/PageInfoChild.jsm",
diff --git a/browser/components/onionservices/OnionLocationChild.jsm b/browser/components/onionservices/OnionLocationChild.jsm
new file mode 100644
index 000000000000..23e1823f5a09
--- /dev/null
+++ b/browser/components/onionservices/OnionLocationChild.jsm
@@ -0,0 +1,48 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["OnionLocationChild"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+class OnionLocationChild extends JSWindowActorChild {
+  handleEvent(event) {
+    this.onPageShow(event);
+  }
+
+  onPageShow(event) {
+    if (event.target != this.document) {
+      return;
+    }
+    const onionLocationURI = this.document.onionLocationURI;
+    if (onionLocationURI) {
+      this.sendAsyncMessage("OnionLocation:Set");
+    }
+  }
+
+  receiveMessage(aMessage) {
+    if (aMessage.name == "OnionLocation:Refresh") {
+      const doc = this.document;
+      const docShell = this.docShell;
+      let onionLocationURI = doc.onionLocationURI;
+      const refreshURI = docShell.QueryInterface(Ci.nsIRefreshURI);
+      if (onionLocationURI && refreshURI) {
+        const docUrl = new URL(doc.URL);
+        let onionUrl = new URL(onionLocationURI.asciiSpec);
+        // Keep consistent with Location
+        if (!onionUrl.hash && docUrl.hash) {
+          onionUrl.hash = docUrl.hash;
+          onionLocationURI = Services.io.newURI(onionUrl.toString());
+        }
+        refreshURI.refreshURI(
+          onionLocationURI,
+          doc.nodePrincipal,
+          0,
+          false,
+          true
+        );
+      }
+    }
+  }
+}
diff --git a/browser/components/onionservices/OnionLocationParent.jsm b/browser/components/onionservices/OnionLocationParent.jsm
new file mode 100644
index 000000000000..4ac4a5d0775e
--- /dev/null
+++ b/browser/components/onionservices/OnionLocationParent.jsm
@@ -0,0 +1,169 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["OnionLocationParent"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+// Prefs
+const NOTIFICATION_PREF = "privacy.prioritizeonions.showNotification";
+const PRIORITIZE_ONIONS_PREF = "privacy.prioritizeonions.enabled";
+
+// Element IDs
+const ONIONLOCATION_BOX_ID = "onion-location-box";
+const ONIONLOCATION_BUTTON_ID = "onion-location-button";
+const ONIONLOCATION_LABEL_ID = "onion-label";
+
+// Notification IDs
+const NOTIFICATION_ID = "onion-location";
+const NOTIFICATION_ANCHOR_ID = "onionlocation";
+
+// Strings
+const STRING_ONION_AVAILABLE = TorStrings.onionLocation.onionAvailable;
+const NOTIFICATION_CANCEL_LABEL = TorStrings.onionLocation.notNow;
+const NOTIFICATION_CANCEL_ACCESSKEY = TorStrings.onionLocation.notNowAccessKey;
+const NOTIFICATION_OK_LABEL = TorStrings.onionLocation.alwaysPrioritize;
+const NOTIFICATION_OK_ACCESSKEY =
+  TorStrings.onionLocation.alwaysPrioritizeAccessKey;
+const NOTIFICATION_TITLE = TorStrings.onionLocation.tryThis;
+const NOTIFICATION_DESCRIPTION = TorStrings.onionLocation.description;
+const NOTIFICATION_LEARN_MORE_URL =
+  TorStrings.onionLocation.learnMoreURLNotification;
+
+class OnionLocationParent extends JSWindowActorParent {
+  // Listeners are added in BrowserGlue.jsm
+  receiveMessage(aMsg) {
+    switch (aMsg.name) {
+      case "OnionLocation:Set":
+        let browser = this.browsingContext.embedderElement;
+        OnionLocationParent.setOnionLocation(browser);
+        break;
+    }
+  }
+
+  static buttonClick(event) {
+    if (event.button !== 0) {
+      return;
+    }
+    const win = event.target.ownerGlobal;
+    if (win.gBrowser) {
+      const browser = win.gBrowser.selectedBrowser;
+      OnionLocationParent.redirect(browser);
+    }
+  }
+
+  static redirect(browser) {
+    let windowGlobal = browser.browsingContext.currentWindowGlobal;
+    let actor = windowGlobal.getActor("OnionLocation");
+    if (actor) {
+      actor.sendAsyncMessage("OnionLocation:Refresh", {});
+      OnionLocationParent.setDisabled(browser);
+    }
+  }
+
+  static onStateChange(browser) {
+    delete browser._onionLocation;
+    OnionLocationParent.hideNotification(browser);
+  }
+
+  static setOnionLocation(browser) {
+    browser._onionLocation = true;
+    let tabBrowser = browser.getTabBrowser();
+    if (tabBrowser && browser === tabBrowser.selectedBrowser) {
+      OnionLocationParent.updateOnionLocationBadge(browser);
+    }
+  }
+
+  static hideNotification(browser) {
+    const win = browser.ownerGlobal;
+    if (browser._onionLocationPrompt) {
+      win.PopupNotifications.remove(browser._onionLocationPrompt);
+    }
+  }
+
+  static showNotification(browser) {
+    const mustShow = Services.prefs.getBoolPref(NOTIFICATION_PREF, true);
+    if (!mustShow) {
+      return;
+    }
+
+    const win = browser.ownerGlobal;
+    Services.prefs.setBoolPref(NOTIFICATION_PREF, false);
+
+    const mainAction = {
+      label: NOTIFICATION_OK_LABEL,
+      accessKey: NOTIFICATION_OK_ACCESSKEY,
+      callback() {
+        Services.prefs.setBoolPref(PRIORITIZE_ONIONS_PREF, true);
+        OnionLocationParent.redirect(browser);
+        win.openPreferences("privacy-onionservices");
+      },
+    };
+
+    const cancelAction = {
+      label: NOTIFICATION_CANCEL_LABEL,
+      accessKey: NOTIFICATION_CANCEL_ACCESSKEY,
+      callback: () => {},
+    };
+
+    const options = {
+      autofocus: true,
+      persistent: true,
+      removeOnDismissal: false,
+      eventCallback(aTopic) {
+        if (aTopic === "removed") {
+          delete browser._onionLocationPrompt;
+          delete browser.onionpopupnotificationanchor;
+        }
+      },
+      learnMoreURL: NOTIFICATION_LEARN_MORE_URL,
+      displayURI: {
+        hostPort: NOTIFICATION_TITLE, // This is hacky, but allows us to have a title without extra markup/css.
+      },
+      hideClose: true,
+      popupIconClass: "onionlocation-notification-icon",
+    };
+
+    // A hacky way of setting the popup anchor outside the usual url bar icon box
+    // onionlocationpopupnotificationanchor comes from `${ANCHOR_ID}popupnotificationanchor`
+    // From https://searchfox.org/mozilla-esr68/rev/080f9ed47742644d2ff84f7aa0b10aea5c44301a/browser/components/newtab/lib/CFRPageActions.jsm#488
+    browser.onionlocationpopupnotificationanchor = win.document.getElementById(
+      ONIONLOCATION_BUTTON_ID
+    );
+
+    browser._onionLocationPrompt = win.PopupNotifications.show(
+      browser,
+      NOTIFICATION_ID,
+      NOTIFICATION_DESCRIPTION,
+      NOTIFICATION_ANCHOR_ID,
+      mainAction,
+      [cancelAction],
+      options
+    );
+  }
+
+  static setEnabled(browser) {
+    const win = browser.ownerGlobal;
+    const label = win.document.getElementById(ONIONLOCATION_LABEL_ID);
+    label.textContent = STRING_ONION_AVAILABLE;
+    const elem = win.document.getElementById(ONIONLOCATION_BOX_ID);
+    elem.removeAttribute("hidden");
+  }
+
+  static setDisabled(browser) {
+    const win = browser.ownerGlobal;
+    const elem = win.document.getElementById(ONIONLOCATION_BOX_ID);
+    elem.setAttribute("hidden", true);
+  }
+
+  static updateOnionLocationBadge(browser) {
+    if (browser._onionLocation) {
+      OnionLocationParent.setEnabled(browser);
+      OnionLocationParent.showNotification(browser);
+    } else {
+      OnionLocationParent.setDisabled(browser);
+    }
+  }
+}
diff --git a/browser/components/onionservices/content/onionlocation-urlbar.inc.xhtml b/browser/components/onionservices/content/onionlocation-urlbar.inc.xhtml
new file mode 100644
index 000000000000..b612a4236f3c
--- /dev/null
+++ b/browser/components/onionservices/content/onionlocation-urlbar.inc.xhtml
@@ -0,0 +1,10 @@
+# Copyright (c) 2020, The Tor Project, Inc.
+
+<hbox id="onion-location-box"
+      class="urlbar-icon-wrapper urlbar-page-action"
+      role="button"
+      hidden="true"
+      onclick="OnionLocationParent.buttonClick(event);">
+  <image id="onion-location-button" role="presentation"/>
+  <hbox id="onion-label-container"><label id="onion-label"/></hbox>
+</hbox>
diff --git a/browser/components/onionservices/content/onionlocation.css b/browser/components/onionservices/content/onionlocation.css
new file mode 100644
index 000000000000..b90b2b9b8d46
--- /dev/null
+++ b/browser/components/onionservices/content/onionlocation.css
@@ -0,0 +1,46 @@
+/* Copyright (c) 2020, The Tor Project, Inc. */
+
+#onion-location-box {
+  background-color: var(--purple-60);
+  -moz-context-properties: fill;
+  fill: white;
+}
+
+#onion-location-box:hover {
+  background-color: var(--purple-70);
+}
+
+#onion-location-box:active {
+  background-color: var(--purple-80);
+}
+
+ at media (prefers-color-scheme: dark) {
+  #onion-location-box {
+    background-color: var(--purple-50);
+  }
+
+  #onion-location-box:hover {
+    background-color: var(--purple-60);
+  }
+
+  #onion-location-box:active {
+    background-color: var(--purple-70);
+  }
+}
+
+#onion-location-button {
+  list-style-image: url(chrome://browser/content/onionservices/onionlocation.svg);
+  padding-inline-start: 0.5em;
+}
+
+label#onion-label {
+  margin: 0;
+  padding-block: 0;
+  padding-inline: 0.5em;
+  color: white;
+  font-weight: normal;
+}
+
+.onionlocation-notification-icon {
+  display: none;
+}
diff --git a/browser/components/onionservices/content/onionlocation.svg b/browser/components/onionservices/content/onionlocation.svg
new file mode 100644
index 000000000000..37f40ac1812f
--- /dev/null
+++ b/browser/components/onionservices/content/onionlocation.svg
@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <path fill="context-fill" fill-opacity="context-fill-opacity" d="m8.016411 14.54499v-0.969784c3.071908-0.0089 5.559239-2.501304 5.559239-5.575429 0-3.073903-2.487331-5.566336-5.559239-5.575206v-0.9697843c3.607473 0.00909 6.528802 2.935521 6.528802 6.544991 0 3.609691-2.921329 6.536342-6.528802 6.545213zm0-3.394356c1.732661-0.0091 3.135111-1.415756 3.135111-3.150857 0-1.734878-1.402451-3.141542-3.135111-3.150634v-0.9695626c2.268448 0.00887 4.104895 1.849753 4.104895 4.120197 0 2.270666- [...]
+</svg>
\ No newline at end of file
diff --git a/browser/components/onionservices/content/onionlocationPreferences.inc.xhtml b/browser/components/onionservices/content/onionlocationPreferences.inc.xhtml
new file mode 100644
index 000000000000..c285f403f99b
--- /dev/null
+++ b/browser/components/onionservices/content/onionlocationPreferences.inc.xhtml
@@ -0,0 +1,11 @@
+# Copyright (c) 2020, The Tor Project, Inc.
+
+<groupbox id="onionServicesGroup" data-category="panePrivacy" data-subcategory="onionservices" hidden="true">
+  <label><html:h2 id="onionServicesTitle"></html:h2></label>
+  <label><label class="tail-with-learn-more" id="prioritizeOnionsDesc"></label><label
+  class="learnMore" is="text-link" id="onionServicesLearnMore"></label></label>
+  <radiogroup id="prioritizeOnionsRadioGroup" aria-labelledby="prioritizeOnionsDesc" preference="privacy.prioritizeonions.enabled">
+    <radio id="onionServicesRadioAlways" value="true"/>
+    <radio id="onionServicesRadioAsk" value="false"/>
+  </radiogroup>
+</groupbox>
diff --git a/browser/components/onionservices/content/onionlocationPreferences.js b/browser/components/onionservices/content/onionlocationPreferences.js
new file mode 100644
index 000000000000..005e37d4a991
--- /dev/null
+++ b/browser/components/onionservices/content/onionlocationPreferences.js
@@ -0,0 +1,34 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+ChromeUtils.defineModuleGetter(
+  this,
+  "TorStrings",
+  "resource:///modules/TorStrings.jsm"
+);
+
+const OnionLocationPreferences = {
+  init() {
+    document.getElementById("onionServicesTitle").textContent =
+      TorStrings.onionLocation.onionServicesTitle;
+    document.getElementById("prioritizeOnionsDesc").textContent =
+      TorStrings.onionLocation.prioritizeOnionsDescription;
+    const learnMore = document.getElementById("onionServicesLearnMore");
+    learnMore.textContent = TorStrings.onionLocation.learnMore;
+    learnMore.href = TorStrings.onionLocation.learnMoreURL;
+    if (TorStrings.onionLocation.learnMoreURL.startsWith("about:")) {
+      learnMore.setAttribute("useoriginprincipal", "true");
+    }
+    document.getElementById("onionServicesRadioAlways").label =
+      TorStrings.onionLocation.always;
+    document.getElementById("onionServicesRadioAsk").label =
+      TorStrings.onionLocation.askEverytime;
+  },
+};
+
+Object.defineProperty(this, "OnionLocationPreferences", {
+  value: OnionLocationPreferences,
+  enumerable: true,
+  writable: false,
+});
diff --git a/browser/components/onionservices/jar.mn b/browser/components/onionservices/jar.mn
index 9d6ce88d1841..e778abd206b3 100644
--- a/browser/components/onionservices/jar.mn
+++ b/browser/components/onionservices/jar.mn
@@ -7,3 +7,6 @@ browser.jar:
     content/browser/onionservices/onionservices.css                (content/onionservices.css)
     content/browser/onionservices/savedKeysDialog.js               (content/savedKeysDialog.js)
     content/browser/onionservices/savedKeysDialog.xhtml            (content/savedKeysDialog.xhtml)
+    content/browser/onionservices/onionlocationPreferences.js      (content/onionlocationPreferences.js)
+    content/browser/onionservices/onionlocation.svg                (content/onionlocation.svg)
+    skin/classic/browser/onionlocation.css                         (content/onionlocation.css)
diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build
index 2661ad7cb9f3..27f9d2da4a9e 100644
--- a/browser/components/onionservices/moz.build
+++ b/browser/components/onionservices/moz.build
@@ -1 +1,6 @@
 JAR_MANIFESTS += ["jar.mn"]
+
+EXTRA_JS_MODULES += [
+    "OnionLocationChild.jsm",
+    "OnionLocationParent.jsm",
+]
diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml
index 37f77e3b70da..d322527a9853 100644
--- a/browser/components/preferences/privacy.inc.xhtml
+++ b/browser/components/preferences/privacy.inc.xhtml
@@ -14,6 +14,8 @@
   <html:h1 data-l10n-id="privacy-header"/>
 </hbox>
 
+#include ../onionservices/content/onionlocationPreferences.inc.xhtml
+
 <!-- Tracking / Content Blocking -->
 <groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" aria-describedby="contentBlockingDescription">
   <label id="contentBlockingHeader"><html:h2 data-l10n-id="content-blocking-enhanced-tracking-protection"/></label>
diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js
index d584fb5e4624..d6a77393b155 100644
--- a/browser/components/preferences/privacy.js
+++ b/browser/components/preferences/privacy.js
@@ -61,6 +61,12 @@ XPCOMUtils.defineLazyScriptGetter(
   "chrome://browser/content/securitylevel/securityLevel.js"
 );
 
+XPCOMUtils.defineLazyScriptGetter(
+  this,
+  ["OnionLocationPreferences"],
+  "chrome://browser/content/onionservices/onionlocationPreferences.js"
+);
+
 XPCOMUtils.defineLazyPreferenceGetter(
   this,
   "OS_AUTH_ENABLED",
@@ -138,6 +144,9 @@ Preferences.addAll([
   // Do not track
   { id: "privacy.donottrackheader.enabled", type: "bool" },
 
+  // Onion Location
+  { id: "privacy.prioritizeonions.enabled", type: "bool" },
+
   // Media
   { id: "media.autoplay.default", type: "int" },
 
@@ -339,6 +348,13 @@ var gPrivacyPane = {
     window.addEventListener("unload", unload);
   },
 
+  /**
+   * Show the OnionLocation preferences UI
+   */
+  _initOnionLocation() {
+    OnionLocationPreferences.init();
+  },
+
   /**
    * Whether the prompt to restart Firefox should appear when changing the autostart pref.
    */
@@ -536,6 +552,7 @@ var gPrivacyPane = {
     this._initTrackingProtectionExtensionControl();
     OnionServicesAuthPreferences.init();
     this._initSecurityLevel();
+    this._initOnionLocation();
 
     Services.telemetry.setEventRecordingEnabled("pwmgr", true);
 
diff --git a/browser/themes/shared/browser-shared.css b/browser/themes/shared/browser-shared.css
index c8dac0afb49a..e771c46dcbf1 100644
--- a/browser/themes/shared/browser-shared.css
+++ b/browser/themes/shared/browser-shared.css
@@ -22,6 +22,7 @@
 @import url("chrome://browser/skin/customizableui/customizeMode.css");
 @import url("chrome://browser/skin/UITour.css");
 @import url("chrome://browser/skin/torconnect-urlbar.css");
+ at import url("chrome://browser/skin/onionlocation.css");
 
 @namespace html url("http://www.w3.org/1999/xhtml");
 
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
index b1be884fe828..9001556d0232 100644
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -2925,6 +2925,7 @@ void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
   // mDocumentURI.
   mDocumentBaseURI = nullptr;
   mChromeXHRDocBaseURI = nullptr;
+  mOnionLocationURI = nullptr;
 
   // Check if the current document is the top-level DevTools document.
   // For inner DevTools frames, mIsDevToolsDocument will be set when
@@ -6828,6 +6829,57 @@ void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
   }
 }
 
+static bool IsValidOnionLocation(nsIURI* aDocumentURI,
+                                 nsIURI* aOnionLocationURI) {
+  if (!aDocumentURI || !aOnionLocationURI) {
+    return false;
+  }
+
+  // Current URI
+  nsAutoCString host;
+  if (!aDocumentURI->SchemeIs("https")) {
+    return false;
+  }
+  NS_ENSURE_SUCCESS(aDocumentURI->GetAsciiHost(host), false);
+  if (StringEndsWith(host, ".onion"_ns)) {
+    // Already in the .onion site
+    return false;
+  }
+
+  // Target URI
+  if (!aOnionLocationURI->SchemeIs("http") &&
+      !aOnionLocationURI->SchemeIs("https")) {
+    return false;
+  }
+  nsCOMPtr<nsIEffectiveTLDService> eTLDService =
+      do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+  if (!eTLDService) {
+    NS_ENSURE_SUCCESS(aOnionLocationURI->GetAsciiHost(host), false);
+    // This should not happen, but in the unlikely case, still check if it is a
+    // .onion and in case allow it.
+    return StringEndsWith(host, ".onion"_ns);
+  }
+  NS_ENSURE_SUCCESS(eTLDService->GetBaseDomain(aOnionLocationURI, 0, host),
+                    false);
+  if (!StringEndsWith(host, ".onion"_ns)) {
+    return false;
+  }
+
+  // Ignore v2
+  if (host.Length() == 22) {
+    const char* cur = host.BeginWriting();
+    // We have already checked that it ends by ".onion"
+    const char* end = host.EndWriting() - 6;
+    bool base32 = true;
+    for (; cur < end && base32; ++cur) {
+      base32 = isalpha(*cur) || ('2' <= *cur && *cur <= '7');
+    }
+    return !base32;
+  }
+
+  return true;
+}
+
 void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
   if (!aHeaderField) {
     NS_ERROR("null headerField");
@@ -6903,6 +6955,21 @@ void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
   if (aHeaderField == nsGkAtoms::handheldFriendly) {
     mViewportType = Unknown;
   }
+
+  if (aHeaderField == nsGkAtoms::headerOnionLocation && !aData.IsEmpty()) {
+    nsCOMPtr<nsIURI> onionURI;
+    if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(onionURI), aData)) &&
+        IsValidOnionLocation(Document::GetDocumentURI(), onionURI)) {
+      if (StaticPrefs::privacy_prioritizeonions_enabled()) {
+        nsCOMPtr<nsIRefreshURI> refresher(mDocumentContainer);
+        if (refresher) {
+          refresher->RefreshURI(onionURI, NodePrincipal(), 0);
+        }
+      } else {
+        mOnionLocationURI = onionURI;
+      }
+    }
+  }
 }
 
 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
@@ -11036,7 +11103,7 @@ void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
     static const char* const headers[] = {
         "default-style", "content-style-type", "content-language",
         "content-disposition", "refresh", "x-dns-prefetch-control",
-        "x-frame-options", "origin-trial",
+        "x-frame-options", "origin-trial", "onion-location",
         // add more http headers if you need
         // XXXbz don't add content-location support without reading bug
         // 238654 and its dependencies/dups first.
diff --git a/dom/base/Document.h b/dom/base/Document.h
index 2d9bbba59bce..91dfe3de8028 100644
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -3401,6 +3401,7 @@ class Document : public nsINode,
   void ReleaseCapture() const;
   void MozSetImageElement(const nsAString& aImageElementId, Element* aElement);
   nsIURI* GetDocumentURIObject() const;
+  nsIURI* GetOnionLocationURI() const { return mOnionLocationURI; }
   // Not const because all the fullscreen goop is not const
   const char* GetFullscreenError(CallerType);
   bool FullscreenEnabled(CallerType aCallerType) {
@@ -4413,6 +4414,7 @@ class Document : public nsINode,
   nsCOMPtr<nsIURI> mChromeXHRDocURI;
   nsCOMPtr<nsIURI> mDocumentBaseURI;
   nsCOMPtr<nsIURI> mChromeXHRDocBaseURI;
+  nsCOMPtr<nsIURI> mOnionLocationURI;
 
   // The base domain of the document for third-party checks.
   nsCString mBaseDomain;
diff --git a/dom/webidl/Document.webidl b/dom/webidl/Document.webidl
index 3d089ddbe848..9c5f78784cb1 100644
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -748,3 +748,11 @@ partial interface Document {
   [ChromeOnly]
   Wireframe? getWireframe(optional boolean aIncludeNodes = false);
 };
+
+/**
+ * Extension to allows chrome JS to know whether the document has a valid
+ * Onion-Location that we could redirect to.
+ */
+partial interface Document {
+  [ChromeOnly] readonly attribute URI? onionLocationURI;
+};
diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt
index af1b7796e6d2..707a65a7eebc 100644
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -807,6 +807,7 @@ package org.mozilla.geckoview {
     method public boolean getLoginAutofillEnabled();
     method public boolean getPauseForDebuggerEnabled();
     method public int getPreferredColorScheme();
+    method public boolean getPrioritizeOnions();
     method public boolean getRemoteDebuggingEnabled();
     method @Nullable public GeckoRuntime getRuntime();
     method @Nullable public Rect getScreenSizeOverride();
@@ -832,6 +833,7 @@ package org.mozilla.geckoview {
     method public void setLocales(@Nullable String[]);
     method @NonNull public GeckoRuntimeSettings setLoginAutofillEnabled(boolean);
     method @NonNull public GeckoRuntimeSettings setPreferredColorScheme(int);
+    method @NonNull public GeckoRuntimeSettings setPrioritizeOnions(boolean);
     method @NonNull public GeckoRuntimeSettings setRemoteDebuggingEnabled(boolean);
     method @NonNull public GeckoRuntimeSettings setSpoofEnglish(boolean);
     method @NonNull public GeckoRuntimeSettings setTorSecurityLevel(int);
@@ -871,6 +873,7 @@ package org.mozilla.geckoview {
     method @NonNull public GeckoRuntimeSettings.Builder locales(@Nullable String[]);
     method @NonNull public GeckoRuntimeSettings.Builder loginAutofillEnabled(boolean);
     method @NonNull public GeckoRuntimeSettings.Builder pauseForDebugger(boolean);
+    method @NonNull public GeckoRuntimeSettings.Builder prioritizeOnions(boolean);
     method @NonNull public GeckoRuntimeSettings.Builder preferredColorScheme(int);
     method @NonNull public GeckoRuntimeSettings.Builder remoteDebuggingEnabled(boolean);
     method @NonNull public GeckoRuntimeSettings.Builder screenSizeOverride(int, int);
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
index 5cc92aa81834..90df323d667d 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
@@ -475,6 +475,17 @@ public final class GeckoRuntimeSettings extends RuntimeSettings {
       getSettings().mSecurityLevel.set(level);
       return this;
     }
+
+    /**
+     * Sets whether the browser should prioritize .onion sites when available.
+     *
+     * @param flag  True if we should prioritize .onion sites, false otherwise
+     * @return This Builder instance.
+     */
+    public @NonNull Builder prioritizeOnions(final boolean flag) {
+      getSettings().mPrioritizeOnions.set(flag);
+      return this;
+    }
   }
 
   private GeckoRuntime mRuntime;
@@ -526,6 +537,8 @@ public final class GeckoRuntimeSettings extends RuntimeSettings {
   /* package */ final Pref<Integer> mSpoofEnglish = new Pref<>("privacy.spoof_english", 0);
   /* package */ final Pref<Integer> mSecurityLevel =
       new Pref<>("browser.security_level.security_slider", 4);
+  /* package */ final Pref<Boolean> mPrioritizeOnions =
+      new Pref<>("privacy.prioritizeonions.enabled", false);
 
   /* package */ int mPreferredColorScheme = COLOR_SCHEME_SYSTEM;
 
@@ -1310,6 +1323,26 @@ public final class GeckoRuntimeSettings extends RuntimeSettings {
     return this;
   }
 
+  /**
+   * Gets whether we should prioritize .onion sites.
+   *
+   * @return Whether we should prioritize .onion sites.
+   */
+  public boolean getPrioritizeOnions() {
+    return mPrioritizeOnions.get();
+  }
+
+  /**
+   * Sets whether we should prioritize .onion sites.
+   *
+   * @param flag Whether we should prioritize .onion sites.
+   * @return This GeckoRuntimeSettings instance.
+   */
+  public @NonNull GeckoRuntimeSettings setPrioritizeOnions(final boolean flag) {
+    mPrioritizeOnions.commit(flag);
+    return this;
+  }
+
   @Override // Parcelable
   public void writeToParcel(final Parcel out, final int flags) {
     super.writeToParcel(out, flags);
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
index 5df8f94e761e..23a237ee71b9 100644
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -11850,6 +11850,11 @@
   value: ""
   mirror: never
 
+- name: privacy.prioritizeonions.enabled
+  type: RelaxedAtomicBool
+  value: false
+  mirror: always
+
 #---------------------------------------------------------------------------
 # Prefs starting with "prompts."
 #---------------------------------------------------------------------------
diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py
index 740b3f6e187d..503a62d5f17b 100644
--- a/xpcom/ds/StaticAtoms.py
+++ b/xpcom/ds/StaticAtoms.py
@@ -826,6 +826,7 @@ STATIC_ATOMS = [
     Atom("oninputsourceschange", "oninputsourceschange"),
     Atom("oninstall", "oninstall"),
     Atom("oninvalid", "oninvalid"),
+    Atom("headerOnionLocation", "onion-location"),
     Atom("onkeydown", "onkeydown"),
     Atom("onkeypress", "onkeypress"),
     Atom("onkeyup", "onkeyup"),

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the tbb-commits mailing list