Verified Commit 12ee3c2e authored by Alex Catarineu's avatar Alex Catarineu Committed by Pier Angelo Vendrame
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 08787e24
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -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",
@@ -5476,6 +5477,7 @@ var XULBrowserWindow = {
    CFRPageActions.updatePageActions(gBrowser.selectedBrowser);

    AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser);
    OnionLocationParent.updateOnionLocationBadge(gBrowser.selectedBrowser);

    if (!gMultiProcessBrowser) {
      // Bug 1108553 - Cannot rotate images with e10s
@@ -5996,6 +5998,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 &&
+3 −0
Original line number Diff line number Diff line
@@ -353,6 +353,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"
+13 −0
Original line number Diff line number Diff line
@@ -557,6 +557,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",
+39 −0
Original line number Diff line number Diff line
// Copyright (c) 2020, The Tor Project, Inc.

"use strict";

var EXPORTED_SYMBOLS = ["OnionLocationChild"];

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;
      const onionLocationURI = doc.onionLocationURI;
      const refreshURI = docShell.QueryInterface(Ci.nsIRefreshURI);
      if (onionLocationURI && refreshURI) {
        refreshURI.refreshURI(
          onionLocationURI,
          doc.nodePrincipal,
          0,
          false,
          true
        );
      }
    }
  }
}
+168 −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;

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