Unverified Commit 57097125 authored by Alex Catarineu's avatar Alex Catarineu Committed by Matthew Finkel
Browse files

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.
parent eec5a2bb
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -44,6 +44,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",
@@ -5422,6 +5423,7 @@ var XULBrowserWindow = {
    Services.obs.notifyObservers(null, "touchbar-location-change", location);
    UpdateBackForwardCommands(gBrowser.webNavigation);
    ReaderParent.updateReaderButton(gBrowser.selectedBrowser);
    OnionLocationParent.updateOnionLocationBadge(gBrowser.selectedBrowser);

    if (!gMultiProcessBrowser) {
      // Bug 1108553 - Cannot rotate images with e10s
@@ -5964,6 +5966,16 @@ const AccessibilityRefreshBlocker = {

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 &&
+3 −0
Original line number Diff line number Diff line
@@ -1077,6 +1077,9 @@
                       onclick="FullZoom.reset();"
                       tooltip="dynamic-shortcut-tooltip"
                       hidden="true"/>

#include ../../components/onionservices/content/onionlocation-urlbar.inc.xhtml

                <box id="pageActionSeparator" class="urlbar-page-action"/>
                <image id="pageActionButton"
                       class="urlbar-icon urlbar-page-action"
+9 −0
Original line number Diff line number Diff line
@@ -539,6 +539,13 @@ let LEGACY_ACTORS = {
      observers: ["keyword-uri-fixup"],
    },
  },
  OnionLocation: {
    child: {
      module: "resource:///modules/OnionLocationChild.jsm",
      events: { pageshow: {} },
      messages: ["OnionLocation:Refresh"],
    },
  },
};

if (AppConstants.TOR_BROWSER_UPDATE) {
@@ -713,6 +720,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
XPCOMUtils.defineLazyModuleGetters(this, {
  AboutLoginsParent: "resource:///modules/AboutLoginsParent.jsm",
  AsyncPrefs: "resource://gre/modules/AsyncPrefs.jsm",
  OnionLocationParent: "resource:///modules/OnionLocationParent.jsm",
  PluginManager: "resource:///actors/PluginParent.jsm",
  ReaderParent: "resource:///modules/ReaderParent.jsm",
});
@@ -816,6 +824,7 @@ const listeners = {
    "AboutLogins:VulnerableLogins": ["AboutLoginsParent"],
    "Reader:FaviconRequest": ["ReaderParent"],
    "Reader:UpdateReaderButton": ["ReaderParent"],
    "OnionLocation:Set": ["OnionLocationParent"],
  },

  observe(subject, topic, data) {
+43 −0
Original line number Diff line number Diff line
// Copyright (c) 2020, The Tor Project, Inc.

"use strict";

var EXPORTED_SYMBOLS = ["OnionLocationChild"];

const { ActorChild } = ChromeUtils.import(
  "resource://gre/modules/ActorChild.jsm"
);

class OnionLocationChild extends ActorChild {
  handleEvent(event) {
    this.onPageShow(event);
  }

  onPageShow(event) {
    if (event.target != this.content.document) {
      return;
    }
    const onionLocationURI = this.content.document.onionLocationURI;
    if (onionLocationURI) {
      this.mm.sendAsyncMessage("OnionLocation:Set");
    }
  }

  receiveMessage(aMessage) {
    if (aMessage.name == "OnionLocation:Refresh") {
      const doc = this.content.document;
      const docShell = this.mm.docShell;
      const onionLocationURI = doc.onionLocationURI;
      const refreshURI = docShell.QueryInterface(Ci.nsIRefreshURI);
      if (onionLocationURI && refreshURI) {
        refreshURI.refreshURI(
          onionLocationURI,
          doc.nodePrincipal,
          0,
          false,
          true
        );
      }
    }
  }
}
+161 −0
Original line number Diff line number Diff line
// 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.learnMoreURL;

var OnionLocationParent = {
  // Listeners are added in BrowserGlue.jsm
  receiveMessage(aMsg) {
    switch (aMsg.name) {
      case "OnionLocation:Set":
        this.setOnionLocation(aMsg.target);
        break;
    }
  },

  buttonClick(event) {
    if (event.button != 0) {
      return;
    }
    const win = event.target.ownerGlobal;
    const browser = win.gBrowser.selectedBrowser;
    this.redirect(browser);
  },

  redirect(browser) {
    browser.messageManager.sendAsyncMessage("OnionLocation:Refresh");
    this.setDisabled(browser);
  },

  onStateChange(browser) {
    delete browser._onionLocation;
    this.hideNotification(browser);
  },

  setOnionLocation(browser) {
    const win = browser.ownerGlobal;
    browser._onionLocation = true;
    if (browser === win.gBrowser.selectedBrowser) {
      this.updateOnionLocationBadge(browser);
    }
  },

  hideNotification(browser) {
    const win = browser.ownerGlobal;
    if (browser._onionLocationPrompt) {
      win.PopupNotifications.remove(browser._onionLocationPrompt);
    }
  },

  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
    );
  },

  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");
  },

  setDisabled(browser) {
    const win = browser.ownerGlobal;
    const elem = win.document.getElementById(ONIONLOCATION_BOX_ID);
    elem.setAttribute("hidden", true);
  },

  updateOnionLocationBadge(browser) {
    if (browser._onionLocation) {
      this.setEnabled(browser);
      this.showNotification(browser);
    } else {
      this.setDisabled(browser);
    }
  },
};
Loading