Skip to content

Commit

Permalink
Bug 21952: Implement Onion-Location
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
acatarineu committed Mar 27, 2020
1 parent 69a0624 commit da55135
Show file tree
Hide file tree
Showing 23 changed files with 435 additions and 1 deletion.
12 changes: 12 additions & 0 deletions browser/base/content/browser.js
Expand Up @@ -43,6 +43,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",
Expand Down Expand Up @@ -5934,6 +5935,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
Expand Down Expand Up @@ -6581,6 +6583,16 @@ 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 &&
Expand Down
3 changes: 3 additions & 0 deletions browser/base/content/browser.xul
Expand Up @@ -992,6 +992,9 @@
onclick="FullZoom.reset();"
tooltip="dynamic-shortcut-tooltip"
hidden="true"/>

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

<box id="pageActionSeparator" class="urlbar-page-action"/>
<image id="pageActionButton"
class="urlbar-icon urlbar-page-action"
Expand Down
10 changes: 10 additions & 0 deletions browser/components/BrowserGlue.jsm
Expand Up @@ -216,6 +216,14 @@ let LEGACY_ACTORS = {
},
},

OnionLocation: {
child: {
module: "resource:///modules/OnionLocationChild.jsm",
events: { pageshow: {} },
messages: ["OnionLocation:Refresh"],
},
},

PageInfo: {
child: {
module: "resource:///actors/PageInfoChild.jsm",
Expand Down Expand Up @@ -507,6 +515,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
ContentClick: "resource:///modules/ContentClick.jsm",
FormValidationHandler: "resource:///modules/FormValidationHandler.jsm",
LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm",
OnionLocationParent: "resource:///modules/OnionLocationParent.jsm",
PictureInPicture: "resource://gre/modules/PictureInPicture.jsm",
ReaderParent: "resource:///modules/ReaderParent.jsm",
RemotePrompt: "resource:///modules/RemotePrompt.jsm",
Expand Down Expand Up @@ -605,6 +614,7 @@ const listeners = {
ContentSearch: ["ContentSearch"],
"FormValidation:ShowPopup": ["FormValidationHandler"],
"FormValidation:HidePopup": ["FormValidationHandler"],
"OnionLocation:Set": ["OnionLocationParent"],
"PictureInPicture:Request": ["PictureInPicture"],
"PictureInPicture:Close": ["PictureInPicture"],
"PictureInPicture:Playing": ["PictureInPicture"],
Expand Down
43 changes: 43 additions & 0 deletions browser/components/onionservices/OnionLocationChild.jsm
@@ -0,0 +1,43 @@
// 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 changes: 161 additions & 0 deletions browser/components/onionservices/OnionLocationParent.jsm
@@ -0,0 +1,161 @@
// 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);
}
},
};
@@ -0,0 +1,5 @@
/* Copyright (c) 2020, The Tor Project, Inc. */

.onionlocation-notification-icon {
display: none;
}
27 changes: 27 additions & 0 deletions browser/components/onionservices/content/onionlocation-urlbar.css
@@ -0,0 +1,27 @@
/* Copyright (c) 2020, The Tor Project, Inc. */

#onion-location-button {
list-style-image: url(chrome://browser/content/onionservices/onionlocation.svg);
}

#onion-location-box {
border-radius: 3px;
background-color: #6200A4;
padding-left: 5px;
padding-right: 5px;
color: white;
-moz-context-properties: fill;
fill: white;
}

#onion-location-box:hover {
background-color: #0060DF !important;
}

toolbar[brighttext] #onion-location-box {
background-color: #9400ff;
}

toolbar[brighttext] #onion-location-box:hover {
background-color: #0060DF !important;
}
@@ -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>
3 changes: 3 additions & 0 deletions browser/components/onionservices/content/onionlocation.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,11 @@
# Copyright (c) 2020, The Tor Project, Inc.

<groupbox id="onionServicesGroup" data-category="panePrivacy" data-subcategory="onionservices">
<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>
@@ -0,0 +1,31 @@
// 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;
document.getElementById("onionServicesRadioAlways").label =
TorStrings.onionLocation.always;
document.getElementById("onionServicesRadioAsk").label =
TorStrings.onionLocation.askEverytime;
},
};

Object.defineProperty(this, "OnionLocationPreferences", {
value: OnionLocationPreferences,
enumerable: true,
writable: false,
});
2 changes: 2 additions & 0 deletions browser/components/onionservices/jar.mn
Expand Up @@ -6,3 +6,5 @@ browser.jar:
content/browser/onionservices/onionservices.css (content/onionservices.css)
content/browser/onionservices/savedKeysDialog.js (content/savedKeysDialog.js)
content/browser/onionservices/savedKeysDialog.xul (content/savedKeysDialog.xul)
content/browser/onionservices/onionlocationPreferences.js (content/onionlocationPreferences.js)
content/browser/onionservices/onionlocation.svg (content/onionlocation.svg)
5 changes: 5 additions & 0 deletions browser/components/onionservices/moz.build
@@ -1 +1,6 @@
JAR_MANIFESTS += ['jar.mn']

EXTRA_JS_MODULES += [
'OnionLocationChild.jsm',
'OnionLocationParent.jsm',
]

0 comments on commit da55135

Please sign in to comment.