--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -705,16 +705,18 @@
onclick="PageProxyClickHandler(event);"/>
<image id="sharing-icon" mousethrough="always"/>
<image id="tracking-protection-icon"/>
<box id="blocked-permissions-container" align="center">
<image data-permission-id="geo" class="blocked-permission-icon geo-icon" role="button"
tooltiptext="&urlbar.geolocationBlocked.tooltip;"/>
<image data-permission-id="desktop-notification" class="blocked-permission-icon desktop-notification-icon" role="button"
tooltiptext="&urlbar.webNotificationsBlocked.tooltip;"/>
+ <image data-permission-id="handler-registration" class="blocked-permission-icon handler-registration-icon" role="button"
+ tooltiptext="&urlbar.handlerRegistrationBlocked.tooltip;"/>
<image data-permission-id="camera" class="blocked-permission-icon camera-icon" role="button"
tooltiptext="&urlbar.cameraBlocked.tooltip;"/>
<image data-permission-id="indexedDB" class="blocked-permission-icon indexedDB-icon" role="button"
tooltiptext="&urlbar.indexedDBBlocked.tooltip;"/>
<image data-permission-id="microphone" class="blocked-permission-icon microphone-icon" role="button"
tooltiptext="&urlbar.microphoneBlocked.tooltip;"/>
<image data-permission-id="screen" class="blocked-permission-icon screen-icon" role="button"
tooltiptext="&urlbar.screenBlocked.tooltip;"/>
@@ -747,16 +749,18 @@
<image id="servicesInstall-notification-icon" class="notification-anchor-icon service-icon" role="button"
tooltiptext="&urlbar.servicesNotificationAnchor.tooltip;"/>
<image id="translate-notification-icon" class="notification-anchor-icon translation-icon" role="button"
tooltiptext="&urlbar.translateNotificationAnchor.tooltip;"/>
<image id="translated-notification-icon" class="notification-anchor-icon translation-icon in-use" role="button"
tooltiptext="&urlbar.translatedNotificationAnchor.tooltip;"/>
<image id="eme-notification-icon" class="notification-anchor-icon drm-icon" role="button"
tooltiptext="&urlbar.emeNotificationAnchor.tooltip;"/>
+ <image id="handler-registration-notification-icon" class="notification-anchor-icon handler-registration-icon" role="button"
+ tooltiptext="&urlbar.handlerRegistrationNotificationAnchor.tooltip;"/>
</box>
<image id="connection-icon"/>
<hbox id="identity-icon-labels">
<label id="identity-icon-label" class="plain" flex="1"/>
<label id="identity-icon-country-label" class="plain"/>
</hbox>
</box>
<box id="urlbar-display-box" align="center">
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -7,17 +7,16 @@ support-files =
app_subframe_bug575561.html
aboutHome_content_script.js
audio.ogg
browser_bug479408_sample.html
browser_bug678392-1.html
browser_bug678392-2.html
browser_bug970746.xhtml
browser_fxa_web_channel.html
- browser_registerProtocolHandler_notification.html
browser_star_hsts.sjs
browser_tab_dragdrop2_frame1.xul
browser_web_channel.html
browser_web_channel_iframe.html
bug1262648_string_with_newlines.dtd
bug592338.html
bug792517-2.html
bug792517.html
@@ -416,17 +415,16 @@ skip-if = true # Bug 1005420 - fails int
[browser_visibleTabs_bookmarkAllTabs.js]
[browser_visibleTabs_contextMenu.js]
[browser_visibleTabs_tabPreview.js]
skip-if = (os == "win" && !debug)
[browser_web_channel.js]
[browser_windowopen_reflows.js]
[browser_zbug569342.js]
skip-if = e10s || debug # Bug 1094240 - has findbar-related failures
-[browser_registerProtocolHandler_notification.js]
[browser_addCertException.js]
[browser_e10s_about_page_triggeringprincipal.js]
support-files =
file_about_child.html
file_about_parent.html
[browser_e10s_switchbrowser.js]
[browser_e10s_about_process.js]
[browser_e10s_chrome_process.js]
deleted file mode 100644
--- a/browser/base/content/test/general/browser_registerProtocolHandler_notification.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
-<html>
- <head>
- <title>Protocol registrar page</title>
- <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
- <meta content="utf-8" http-equiv="encoding">
- </head>
- <body>
- <script type="text/javascript">
- navigator.registerProtocolHandler("testprotocol",
- "https://example.com/foobar?uri=%s",
- "Test Protocol");
- </script>
- </body>
-</html>
deleted file mode 100644
--- a/browser/base/content/test/general/browser_registerProtocolHandler_notification.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-function test() {
- waitForExplicitFinish();
- let notificationValue = "Protocol Registration: testprotocol";
- let testURI = "http://example.com/browser/" +
- "browser/base/content/test/general/browser_registerProtocolHandler_notification.html";
-
- waitForCondition(function() {
- // Do not start until the notification is up
- let notificationBox = window.gBrowser.getNotificationBox();
- let notification = notificationBox.getNotificationWithValue(notificationValue);
- return notification;
- },
- function() {
-
- let notificationBox = window.gBrowser.getNotificationBox();
- let notification = notificationBox.getNotificationWithValue(notificationValue);
- ok(notification, "Notification box should be displayed");
- if (notification == null) {
- finish();
- return;
- }
- is(notification.type, "info", "We expect this notification to have the type of 'info'.");
- isnot(notification.image, null, "We expect this notification to have an icon.");
-
- let buttons = notification.getElementsByClassName("notification-button-default");
- is(buttons.length, 1, "We expect see one default button.");
-
- buttons = notification.getElementsByClassName("notification-button");
- is(buttons.length, 1, "We expect see one button.");
-
- let button = buttons[0];
- isnot(button.label, null, "We expect the add button to have a label.");
- todo_isnot(button.accesskey, null, "We expect the add button to have a accesskey.");
-
- finish();
- }, "Still can not get notification after retry 100 times.", 100);
-
- window.gBrowser.selectedBrowser.loadURI(testURI);
-}
--- a/browser/components/feeds/WebContentConverter.js
+++ b/browser/components/feeds/WebContentConverter.js
@@ -2,16 +2,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PermissionUI",
+ "resource:///modules/PermissionUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
+ "resource:///modules/SitePermissions.jsm");
+
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
function LOG(str) {
dump("*** " + str + "\n");
}
@@ -217,16 +222,82 @@ const Utils = {
resolveContentType(aContentType) {
if (aContentType in this._mappings)
return this._mappings[aContentType];
return aContentType;
}
};
+function HandlerRegistrationRequest(aBrowser, aPrincipal, aURIString, aTitle, aType) {
+ this._browser = aBrowser;
+ this._principal = aPrincipal;
+ this._handlerURI = aURIString;
+ this._title = aTitle;
+ this._type = aType;
+}
+
+HandlerRegistrationRequest.prototype = {
+ get browser() {
+ return this._browser;
+ },
+
+ get permissionKey() {
+ return "handler-registration";
+ },
+
+ get principal() {
+ return this._principal;
+ },
+
+ get uri() {
+ return this._handlerURI;
+ },
+
+ get type() {
+ return this._type;
+ },
+
+ get title() {
+ return this._title;
+ },
+
+ _setAsDefaultHandler() {
+ // create handler app and save it into handler database
+ let handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(Ci.nsIWebHandlerApp);
+ handler.name = this._title;
+ handler.uriTemplate = this._handlerURI;
+
+ let eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ let handlerInfo = eps.getProtocolHandlerInfo(this._type);
+
+ handlerInfo.possibleApplicationHandlers.appendElement(handler, false);
+ handlerInfo.alwaysAskBeforeHandling = false;
+ handlerInfo.preferredApplicationHandler = handler;
+ handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+
+ let hs = Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService);
+ hs.store(handlerInfo);
+ },
+
+ allow() {
+ this._setAsDefaultHandler();
+ },
+
+ cancel() {
+ SitePermissions.set(this.principal.URI,
+ this.permissionKey,
+ SitePermissions.BLOCK,
+ SitePermissions.SCOPE_PERSISTENT);
+ },
+};
+
function WebContentConverterRegistrar() {
this._contentTypes = {};
this._autoHandleContentTypes = {};
}
WebContentConverterRegistrar.prototype = {
get stringBundle() {
let sb = Services.strings.createBundle(STRING_BUNDLE_URI);
@@ -411,59 +482,24 @@ WebContentConverterRegistrar.prototype =
// would have some way to tell what's going wrong.
Services.console.
logStringMessage("Web page denied access to register a protocol handler inside private browsing mode");
return;
}
Utils.checkProtocolHandlerAllowed(aProtocol, aURIString,
haveWindow ? aBrowserOrWindow : null);
-
- // Now Ask the user and provide the proper callback
- let message = this._getFormattedString("addProtocolHandler",
- [aTitle, uri.host, aProtocol]);
-
- let notificationIcon = uri.prePath + "/favicon.ico";
- let notificationValue = "Protocol Registration: " + aProtocol;
- let addButton = {
- label: this._getString("addProtocolHandlerAddButton"),
- accessKey: this._getString("addProtocolHandlerAddButtonAccesskey"),
- protocolInfo: { protocol: aProtocol, uri: uri.spec, name: aTitle },
-
- callback(aNotification, aButtonInfo) {
- let protocol = aButtonInfo.protocolInfo.protocol;
- let name = aButtonInfo.protocolInfo.name;
-
- let handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
- createInstance(Ci.nsIWebHandlerApp);
- handler.name = name;
- handler.uriTemplate = aButtonInfo.protocolInfo.uri;
-
- let eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
- getService(Ci.nsIExternalProtocolService);
- let handlerInfo = eps.getProtocolHandlerInfo(protocol);
- handlerInfo.possibleApplicationHandlers.appendElement(handler, false);
-
- // Since the user has agreed to add a new handler, chances are good
- // that the next time they see a handler of this type, they're going
- // to want to use it. Reset the handlerInfo to ask before the next
- // use.
- handlerInfo.alwaysAskBeforeHandling = true;
-
- let hs = Cc["@mozilla.org/uriloader/handler-service;1"].
- getService(Ci.nsIHandlerService);
- hs.store(handlerInfo);
- }
- };
- let notificationBox = browser.getTabBrowser().getNotificationBox(browser);
- notificationBox.appendNotification(message,
- notificationValue,
- notificationIcon,
- notificationBox.PRIORITY_INFO_LOW,
- [addButton]);
+ let handlerRegistrationRequest =
+ new HandlerRegistrationRequest(browser,
+ browser.contentPrincipal,
+ aURIString,
+ aTitle,
+ aProtocol);
+ let requestPrompt = new PermissionUI.HandlerRegistrationPermissionPrompt(handlerRegistrationRequest);
+ requestPrompt.prompt();
},
/**
* See nsIWebContentHandlerRegistrar
* If a DOM window is provided, then the request came from content, so we
* prompt the user to confirm the registration.
*/
registerContentHandler(aContentType, aURIString, aTitle, aWindowOrBrowser) {
--- a/browser/components/feeds/moz.build
+++ b/browser/components/feeds/moz.build
@@ -1,16 +1,17 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
MOCHITEST_CHROME_MANIFESTS += ['test/chrome/chrome.ini']
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
MOCHITEST_MANIFESTS += ['test/mochitest.ini']
JAR_MANIFESTS += ['jar.mn']
XPIDL_SOURCES += [
'nsIFeedResultService.idl',
'nsIWebContentConverterRegistrar.idl',
]
new file mode 100644
--- /dev/null
+++ b/browser/components/feeds/test/browser/browser.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ head.js
+ protocolHandler.html
+ superLongProtocolHandler.html
+ multipleRegistration.html
+
+[browser_registerHandlerWithPermissionUI.js]
+[browser_handlerRegistrationPermissionControl.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/feeds/test/browser/browser_handlerRegistrationPermissionControl.js
@@ -0,0 +1,169 @@
+"use strict";
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+let gPermsSvc = Services.perms;
+
+const kTestObj = {
+ url: kTestRoot + "protocolHandler.html",
+ protocolType: "testprotocol",
+ permissionKey: "handler-registration",
+ handlerName: "testprotocol handler",
+ handlerUriTemplate: "https://example.com/foobar?uri=%s",
+};
+const CONTROL_CENTER_PANEL = gIdentityHandler._identityPopup;
+
+registerCleanupFunction(function() {
+ // traverse all permissions and remove the permission we create in tests.
+ let permissionKey = "handler-registration";
+ let permsEnum = gPermsSvc.enumerator;
+ while (permsEnum.hasMoreElements()) {
+ let perm = permsEnum.getNext().QueryInterface(Ci.nsIPermission);
+ if (perm.type == permissionKey) {
+ gPermsSvc.removeFromPrincipal(perm.principal);
+ }
+ }
+
+ // cleanup all handler info we create in the tests
+ removeHandlerInfoForProtocol(kTestObj.protocolType);
+});
+
+function* openIdentityPopup() {
+ let shownPromise =
+ BrowserTestUtils.waitForEvent(CONTROL_CENTER_PANEL, "popupshown");
+ gIdentityHandler._identityBox.click();
+ return shownPromise;
+}
+
+function* closeIdentityPopup() {
+ let closePromise =
+ BrowserTestUtils.waitForEvent(CONTROL_CENTER_PANEL, "popuphidden");
+ gIdentityHandler._identityPopup.hidePopup();
+ return closePromise;
+}
+
+let permissionElementsGetter = {
+ permissionsList() {
+ return document.getElementById("identity-popup-permission-list");
+ },
+ permissionLabels() {
+ return this.permissionsList().querySelectorAll(".identity-popup-permission-label");
+ },
+ cancelButtons() {
+ return this.permissionsList().querySelectorAll(".identity-popup-permission-remove-button");
+ },
+}
+
+/**
+ * Test handler-registration permission control with Control Center.
+ */
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab(
+ kTestObj.url,
+ function* (browser) {
+ let principal = browser.contentPrincipal;
+
+ yield clickButtonOnPrompt(false);
+
+ // check permissions' label on control center
+ isnot(CONTROL_CENTER_PANEL, null, "control center panel should not be null.");
+ yield openIdentityPopup();
+
+ let labels = permissionElementsGetter.permissionLabels();
+ is(labels.length, 1,
+ "Should be only one permission in the permission list of control center.");
+
+ // click the cancel button to remove the permission
+ let cancelButtons = permissionElementsGetter.cancelButtons();
+ cancelButtons[0].click();
+
+ labels = permissionElementsGetter.permissionLabels();
+ is(labels.length, 0,
+ "Should be no permissions in the permission list of control center.");
+
+ is(gPermsSvc.testExactPermissionFromPrincipal(principal, kTestObj.permissionKey),
+ gPermsSvc.UNKNOWN_ACTION,
+ "Handler registration permission should be removed.");
+
+ yield closeIdentityPopup();
+
+ // cleanup permissions and handler info
+ gPermsSvc.removeFromPrincipal(principal, kTestObj.permissionKey);
+ removeHandlerInfoForProtocol(kTestObj.protocolType);
+ }
+ );
+});
+
+/**
+ * Test handler-registration permission control with page info's permission tab.
+ */
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab(
+ kTestObj.url,
+ function* (browser) {
+ let principal = browser.contentPrincipal;
+ yield clickButtonOnPrompt(false);
+
+ let pageInfo;
+ let shownPromise = new Promise(resolve => {
+ function observer(subject, topic, data) {
+ let observedWindow = subject.QueryInterface(Ci.nsIDOMWindow);
+ Services.obs.removeObserver(observer, "page-info-dialog-loaded");
+ pageInfo.onFinished.push(() => {
+ // check the radio buttons status of page info's permission tab
+ let permissionRadioGroup = pageInfo.document.
+ getElementById(kTestObj.permissionKey + "RadioGroup");
+ isnot(permissionRadioGroup, null,
+ "Radio button group should not be null.");
+ let permissionRadioAlwaysAsk = pageInfo.document.
+ getElementById(kTestObj.permissionKey + "#0");
+ isnot(permissionRadioAlwaysAsk, null,
+ "AlwaysAsk radio button should not be null.");
+ let permissionRadioBlock = pageInfo.document.
+ getElementById(kTestObj.permissionKey + "#2");
+ isnot(permissionRadioBlock, null,
+ "Block radio button should not be null.");
+ is(permissionRadioGroup.selectedItem, permissionRadioBlock,
+ "Should be selected on Block radio buttion.");
+ is(gPermsSvc.testExactPermissionFromPrincipal(principal, kTestObj.permissionKey),
+ gPermsSvc.DENY_ACTION,
+ "Permission should be DENY ACTION");
+
+ // click AlwaysAsk radio button and then test the permission status
+ permissionRadioAlwaysAsk.click();
+ is(permissionRadioGroup.selectedItem, permissionRadioAlwaysAsk,
+ "Should be selected on AlwaysAsk radio buttion.");
+ is(gPermsSvc.testExactPermissionFromPrincipal(principal, kTestObj.permissionKey),
+ gPermsSvc.UNKNOWN_ACTION,
+ "Permission should be UNKNOWN ACTION");
+ let useDefaultCheckbox = pageInfo.document.
+ getElementById(kTestObj.permissionKey + "Def");
+ isnot(useDefaultCheckbox, null,
+ "useDefault checkbox should not be null");
+ ok(useDefaultCheckbox.checked, "useDefault checkbox should be checked");
+
+ // click Block radio button and then test the permission status
+ useDefaultCheckbox.click();
+ permissionRadioBlock.click();
+ is(permissionRadioGroup.selectedItem, permissionRadioBlock,
+ "Should be selected on Block radio buttion.");
+ is(gPermsSvc.testExactPermissionFromPrincipal(principal, kTestObj.permissionKey),
+ gPermsSvc.DENY_ACTION,
+ "Permission should be DENY ACTION");
+
+ pageInfo.close();
+ });
+ resolve(observedWindow);
+ }
+ Services.obs.addObserver(observer, "page-info-dialog-loaded", false);
+ });
+ pageInfo = BrowserPageInfo(browser.currentURI.spec, "permTab");
+ let win = yield shownPromise;
+ yield BrowserTestUtils.domWindowClosed(win);
+
+ // cleanup permissions and handlerInfo
+ gPermsSvc.removeFromPrincipal(principal, kTestObj.permissionKey);
+ removeHandlerInfoForProtocol(kTestObj.protocolType);
+ }
+ );
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/feeds/test/browser/browser_registerHandlerWithPermissionUI.js
@@ -0,0 +1,308 @@
+"use strict";
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+let gPermsSvc = Services.perms;
+
+const kDefaultHandler = {
+ uri: Services.io.newURI("https://test1.example.org/testprotocol=%s"),
+ name: "testprotocol default handler",
+};
+
+const kTestInfos = [
+ {
+ page: "protocolHandler.html",
+ protocolType: "testprotocol",
+ handlerName: "Test Protocol",
+ },
+ {
+ page: "superLongProtocolHandler.html",
+ protocolType: "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryverylongprotocol",
+ handlerName: "Super Long Test Protocol",
+ },
+];
+
+const kMultiRegistrationTestInfos = [
+ {
+ page: "multipleRegistration.html",
+ protocolType: "testprotocol1",
+ handlerName: "Test Protocol 1",
+ },
+ {
+ page: "multipleRegistration.html",
+ protocolType: "testprotocol2",
+ handlerName: "Test Protocol 2",
+ },
+];
+
+function createTestObj(testInfo) {
+ return {
+ url: kTestRoot + testInfo.page,
+ protocolType: testInfo.protocolType,
+ permissionKey: "handler-registration",
+ notificationID: "handler-registration",
+ handlerName: testInfo.handlerName,
+ handlerUriTemplate: "https://example.com/foobar?uri=%s",
+ expectedURL: "https://example.com/foobar?uri=" + testInfo.protocolType + "%3Atest",
+ };
+}
+
+function setupDefaultHandler(aProtocolType) {
+ let handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(Ci.nsIWebHandlerApp);
+ handler.name = kDefaultHandler.name
+ handler.uriTemplate = kDefaultHandler.uri.spec;
+ let handlerInfo = getHandlerInfo(aProtocolType);
+ handlerInfo.possibleApplicationHandlers.appendElement(handler, false);
+ handlerInfo.alwaysAskBeforeHandling = false;
+ handlerInfo.preferredApplicationHandler = handler;
+ handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+ gHandlerSvc.store(handlerInfo);
+}
+
+function checkHandlerInfoProperties(aProtocolType, aHandlerName, aUriTemplate) {
+ let handlerInfo = getHandlerInfo(aProtocolType);
+ is(handlerInfo.preferredAction, Ci.nsIHandlerInfo.useHelperApp,
+ "The preferredAction should be useHelperApp.");
+ ok(handlerInfo.preferredApplicationHandler,
+ "The preferredApplicationHandler should exist.");
+ is(handlerInfo.preferredApplicationHandler.name, aHandlerName,
+ "The preferredAppliacitonHandler name should be " + aHandlerName + ".");
+ is(handlerInfo.preferredApplicationHandler.QueryInterface(Ci.nsIWebHandlerApp).uriTemplate,
+ aUriTemplate,
+ "The preferredApplicationHandler uriTemplate should be '" + aUriTemplate + "'.");
+ ok(!handlerInfo.alwaysAskBeforeHandling,
+ "alwaysAskBeforeHandling should be false");
+}
+
+function* checkLinkIsHandledCorrectly(aTestObj, aBrowser) {
+ // Middle-click a testprotocol link and check the new tab is correct
+ let link = "#link";
+ const expectedURL = aTestObj.expectedURL;
+
+ let promiseTabOpened =
+ BrowserTestUtils.waitForNewTab(gBrowser, expectedURL);
+ yield BrowserTestUtils.synthesizeMouseAtCenter(link, {button: 1}, aBrowser);
+ let tab = yield promiseTabOpened;
+ yield BrowserTestUtils.switchTab(gBrowser, tab);
+ is(gURLBar.value, expectedURL,
+ "the expected URL is displayed in the location bar");
+ yield BrowserTestUtils.removeTab(tab);
+
+ // Click the testprotocol link and check the url in the current tab.
+ let stopPromise = BrowserTestUtils.browserStopped(aBrowser);
+ yield BrowserTestUtils.synthesizeMouseAtCenter(link, {}, aBrowser);
+ yield stopPromise;
+ is(gURLBar.value, expectedURL,
+ "the expected URL is displayed in the location bar");
+}
+
+registerCleanupFunction(function() {
+ // traverse all permissions and remove the permission we create in tests.
+ let permissionKey = "handler-registration";
+ let permsEnum = gPermsSvc.enumerator;
+ while (permsEnum.hasMoreElements()) {
+ let perm = permsEnum.getNext().QueryInterface(Ci.nsIPermission);
+ if (perm.type == permissionKey) {
+ gPermsSvc.removeFromPrincipal(perm.principal);
+ }
+ }
+
+ // cleanup all handler info we create in the tests
+ let testInfos = kTestInfos;
+ testInfos.concat(kMultiRegistrationTestInfos);
+ for (let testInfo of testInfos) {
+ removeHandlerInfoForProtocol(testInfo.protocolType);
+ }
+});
+
+/**
+ * Test the notification actions' label and access key.
+ */
+add_task(function*() {
+ let testObj = createTestObj(kTestInfos[0]);
+ yield BrowserTestUtils.withNewTab(
+ testObj.url,
+ function* (browser) {
+ let notification =
+ PopupNotifications.getNotification(testObj.notificationID, browser);
+
+ ok(notification, "Should have gotten the notification");
+ is(notification.options.checkbox.checked, false,
+ "The checkbox should be not checked.");
+ is(notification.mainAction.label, "Allow",
+ "The main action should have the right label.");
+ is(notification.mainAction.accessKey, "A",
+ "The main action should have the right access key.");
+ is(notification.secondaryActions.length, 1,
+ "There should only be 1 secondary action.");
+ is(notification.secondaryActions[0].label, "Never Allow",
+ "The secondary action should have the right label.");
+ is(notification.secondaryActions[0].accessKey, "N",
+ "The secondary action should have the right access key.");
+
+ let removePromise =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
+ notification.remove();
+ yield removePromise;
+ }
+ );
+});
+
+/**
+ * Test the Never Allow action. This test includes following situations
+ * 1. without any default handlers.
+ * 2. with a default handler.
+ * 3. super long protocol type without any default handlers.
+ * 4. super long protocol type with a default handler.
+ */
+add_task(function*() {
+ for (let testInfo of kTestInfos) {
+ let testObj = createTestObj(testInfo);
+ for (let useDefaultHandler of [false, true]) {
+ yield BrowserTestUtils.withNewTab(
+ testObj.url,
+ function* (browser) {
+ let principal = browser.contentPrincipal;
+
+ // setup a default handler if needed
+ if (useDefaultHandler) {
+ setupDefaultHandler(testObj.protocolType);
+ }
+
+ // check the current permission before clicking Never Allow
+ let curPerm = gPermsSvc.testExactPermissionFromPrincipal(principal,
+ testObj.permissionKey);
+ is(curPerm, gPermsSvc.UNKNOWN_ACTION,
+ "Permission for " + testObj.permissionKey +
+ " should be no permission before clicking 'Never Allow'.");
+
+ // click the Never Allow button
+ yield clickButtonOnPrompt(false);
+
+ // check the permission after clicking Never Allow
+ curPerm = gPermsSvc.testExactPermissionFromPrincipal(principal,
+ testObj.permissionKey);
+ is(curPerm, gPermsSvc.DENY_ACTION,
+ "Permission for " + testObj.permissionKey +
+ " should be DENY after clicking 'Never Allow'.");
+
+ // check permission of the default registrar and handler information
+ if (useDefaultHandler) {
+ checkHandlerInfoProperties(testObj.protocolType,
+ kDefaultHandler.name,
+ kDefaultHandler.uri.spec);
+ }
+
+ // cleanup
+ gPermsSvc.removeFromPrincipal(principal, testObj.permissionKey);
+ removeHandlerInfoForProtocol(testObj.protocolType);
+ }
+ );
+ }
+ }
+});
+
+/**
+ * test the Allow action and check the protocol link are handled by
+ * registered handler. This test includes following situations.
+ * 1. without any default handlers.
+ * 2. with a default handler.
+ * 3. super long protocol type without any default handlers.
+ * 4. super long protocol type with a default handler.
+ */
+add_task(function*() {
+ for (let testInfo of kTestInfos) {
+ let testObj = createTestObj(testInfo);
+ for (let useDefaultHandler of [false, true]) {
+ yield BrowserTestUtils.withNewTab(
+ testObj.url,
+ function* (browser) {
+ let principal = browser.contentPrincipal;
+
+ // check the permission before clicking Allow
+ let curPerm = gPermsSvc.testExactPermissionFromPrincipal(principal,
+ testObj.permissionKey);
+ is(curPerm, gPermsSvc.UNKNOWN_ACTION,
+ "Permission for " + testObj.permissionKey +
+ " should be no permission before clicking 'Allow'.");
+
+ // setup a default handler if needed
+ if (useDefaultHandler) {
+ setupDefaultHandler(testObj.protocolType);
+ }
+
+ // click the Allow button
+ yield clickButtonOnPrompt(true);
+
+ // check the permission after clicking Allow
+ curPerm = gPermsSvc.testExactPermissionFromPrincipal(principal,
+ testObj.permissionKey);
+
+ // bug 1270416, we don't save allow permission for handler registration
+ is(curPerm, gPermsSvc.UNKNOWN_ACTION,
+ "Permission for " + testObj.permissionKey +
+ " should be no permission after clicking 'Allow'.");
+
+ checkHandlerInfoProperties(testObj.protocolType,
+ testObj.handlerName,
+ testObj.handlerUriTemplate);
+
+ yield checkLinkIsHandledCorrectly(testObj, browser);
+
+ // cleanup
+ gPermsSvc.removeFromPrincipal(principal, testObj.permissionKey);
+ removeHandlerInfoForProtocol(testObj.protocolType);
+ }
+ );
+ }
+ }
+});
+
+/**
+ * Test the notification actions' label and access key.
+ */
+add_task(function*() {
+ let testObj1 = createTestObj(kMultiRegistrationTestInfos[0]);
+ let testObj2 = createTestObj(kMultiRegistrationTestInfos[1]);
+ yield BrowserTestUtils.withNewTab(
+ testObj1.url,
+ function* (browser) {
+ let principal = browser.contentPrincipal;
+ let curPerm = gPermsSvc.testExactPermissionFromPrincipal(principal,
+ testObj1.permissionKey);
+ is(curPerm, gPermsSvc.UNKNOWN_ACTION,
+ "Permission for " + testObj1.permissionKey +
+ " should be UNKNOWN.");
+
+ curPerm = gPermsSvc.testExactPermissionFromPrincipal(principal,
+ testObj2.permissionKey);
+ is(curPerm, gPermsSvc.UNKNOWN_ACTION,
+ "Permission for " + testObj2.permissionKey +
+ " should be UNKNOWN.");
+
+ yield clickButtonOnPrompt(false);
+
+ // Since the notificationID is the same for all handler registration,
+ // the notification for testObj1.permissionKey will be replaced by the
+ // notification for testObj2.permissionKey.
+ curPerm = gPermsSvc.testExactPermissionFromPrincipal(principal,
+ testObj1.permissionKey);
+ is(curPerm, gPermsSvc.DENY_ACTION,
+ "Permission for " + testObj1.permissionKey +
+ " should be DENY after clicking 'Never Allow'.");
+
+ curPerm = gPermsSvc.testExactPermissionFromPrincipal(principal,
+ testObj2.permissionKey);
+ is(curPerm, gPermsSvc.DENY_ACTION,
+ "Permission for " + testObj2.permissionKey +
+ " should be DENY after clicking 'Never Allow'.");
+
+ // cleanup
+ gPermsSvc.removeFromPrincipal(principal, testObj1.permissionKey);
+ removeHandlerInfoForProtocol(testObj1.protocolType);
+ gPermsSvc.removeFromPrincipal(principal, testObj2.permissionKey);
+ removeHandlerInfoForProtocol(testObj2.protocolType);
+ }
+ );
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/feeds/test/browser/head.js
@@ -0,0 +1,33 @@
+let gExtProtocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+let gHandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService);
+
+const kTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content",
+ "https://example.com");
+
+function* clickButtonOnPrompt(aAllow) {
+ let removePromise =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
+ let popupNotifications = PopupNotifications.panel.childNodes;
+ is(popupNotifications.length, 1, "Should be showing a <xul:popupnotification>");
+ let popupNotification = popupNotifications[0];
+ if (aAllow) {
+ popupNotification.checkbox.click();
+ popupNotification.button.click();
+ } else {
+ popupNotification.secondaryButton.click();
+ }
+ return removePromise;
+}
+
+function getHandlerInfo(aProtocolType) {
+ return gExtProtocolSvc.getProtocolHandlerInfo(aProtocolType);
+}
+
+function removeHandlerInfoForProtocol(aProtocolType) {
+ let handlerInfo = getHandlerInfo(aProtocolType);
+ if (gHandlerSvc.exists(handlerInfo)) {
+ gHandlerSvc.remove(handlerInfo);
+ }
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/feeds/test/browser/multipleRegistration.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Multiple registration</title>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ </head>
+ <body>
+ <script type="text/javascript">
+ navigator.registerProtocolHandler("testprotocol1",
+ "https://example.com/foobar?uri=%s",
+ "Test Protocol 1");
+ navigator.registerProtocolHandler("testprotocol2",
+ "https://example.com/foobar?uri=%s",
+ "Test Protocol 2");
+ </script>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/feeds/test/browser/protocolHandler.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Protocol handler</title>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ </head>
+ <body>
+ <script type="text/javascript">
+ navigator.registerProtocolHandler("testprotocol",
+ "https://example.com/foobar?uri=%s",
+ "Test Protocol");
+ </script>
+ <a id="link" href="testprotocol:test">testprotocol link</a>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/feeds/test/browser/superLongProtocolHandler.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Protocol handler</title>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ </head>
+ <body>
+ <script type="text/javascript">
+ navigator.registerProtocolHandler(
+ "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryverylongprotocol",
+ "https://example.com/foobar?uri=%s",
+ "Super Long Test Protocol");
+ </script>
+ <a id="link" href="veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryverylongprotocol:test">testprotocol link</a>
+ </body>
+</html>
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler.js
@@ -1,41 +1,45 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This test makes sure that the web pages can't register protocol handlers
// inside the private browsing mode.
add_task(function* test() {
- let notificationValue = "Protocol Registration: testprotocol";
+ let notificationValue = "handler-registration";
let testURI = "http://example.com/browser/" +
"browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler_page.html";
let doTest = Task.async(function* (aIsPrivateMode, aWindow) {
- let tab = aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab(testURI);
- yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ let tab = aWindow.gBrowser.addTab("about:blank");
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield BrowserTestUtils.loadURI(tab.linkedBrowser, testURI);
+ yield loadPromise;
- let promiseFinished = PromiseUtils.defer();
- setTimeout(function() {
- let notificationBox = aWindow.gBrowser.getNotificationBox();
- let notification = notificationBox.getNotificationWithValue(notificationValue);
-
- if (aIsPrivateMode) {
+ if (aIsPrivateMode) {
+ let promiseFinished = PromiseUtils.defer();
+ setTimeout(function() {
+ let notification = PopupNotifications.getNotification(notificationValue,
+ tab.linkedBrowser);
// Make sure the notification is correctly displayed without a remember control
ok(!notification, "Notification box should not be displayed inside of private browsing mode");
- } else {
- // Make sure the notification is correctly displayed with a remember control
- ok(notification, "Notification box should be displaying outside of private browsing mode");
- }
-
- promiseFinished.resolve();
- }, 100); // remember control is added in a setTimeout(0) call
-
- yield promiseFinished.promise;
+ promiseFinished.resolve();
+ }, 100); // remember control is added in a setTimeout(0) call
+ yield promiseFinished.promise;
+ } else {
+ let shownPromise = BrowserTestUtils.waitForEvent(aWindow.PopupNotifications.panel, "popupshown");
+ yield BrowserTestUtils.switchTab(aWindow.gBrowser, tab);
+ yield shownPromise;
+ let notification = PopupNotifications.getNotification(notificationValue,
+ tab.linkedBrowser);
+ // Make sure the notification is correctly displayed with a remember control
+ ok(notification, "Notification box should be displaying outside of private browsing mode");
+ }
});
// test first when not on private mode
let win = yield BrowserTestUtils.openNewBrowserWindow();
yield doTest(false, win);
// then test when on private mode
let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -205,16 +205,17 @@ These should match what Safari and other
<!ENTITY goEndCap.tooltip "Go to the address in the Location Bar">
<!ENTITY printButton.label "Print">
<!ENTITY printButton.tooltip "Print this page">
<!ENTITY urlbar.viewSiteInfo.label "View site information">
<!ENTITY urlbar.defaultNotificationAnchor.tooltip "Open message panel">
<!ENTITY urlbar.geolocationNotificationAnchor.tooltip "Open location request panel">
+<!ENTITY urlbar.handlerRegistrationNotificationAnchor.tooltip "Open default application setting request panel">
<!ENTITY urlbar.addonsNotificationAnchor.tooltip "Open add-on installation message panel">
<!ENTITY urlbar.indexedDBNotificationAnchor.tooltip "Open offline storage message panel">
<!ENTITY urlbar.passwordNotificationAnchor.tooltip "Open save password message panel">
<!ENTITY urlbar.pluginsNotificationAnchor.tooltip "Manage plug-in use">
<!ENTITY urlbar.webNotificationAnchor.tooltip "Change whether you can receive notifications from the site">
<!ENTITY urlbar.webRTCShareDevicesNotificationAnchor.tooltip "Manage sharing your camera and/or microphone with the site">
<!ENTITY urlbar.webRTCShareMicrophoneNotificationAnchor.tooltip "Manage sharing your microphone with the site">
@@ -224,16 +225,17 @@ These should match what Safari and other
<!ENTITY urlbar.translateNotificationAnchor.tooltip "Translate this page">
<!ENTITY urlbar.translatedNotificationAnchor.tooltip "Manage page translation">
<!ENTITY urlbar.emeNotificationAnchor.tooltip "Manage use of DRM software">
<!ENTITY urlbar.cameraBlocked.tooltip "You have blocked your camera for this website.">
<!ENTITY urlbar.microphoneBlocked.tooltip "You have blocked your microphone for this website.">
<!ENTITY urlbar.screenBlocked.tooltip "You have blocked this website from sharing your screen.">
<!ENTITY urlbar.geolocationBlocked.tooltip "You have blocked location information for this website.">
+<!ENTITY urlbar.handlerRegistrationBlocked.tooltip "You have blocked setting default application by this website.">
<!ENTITY urlbar.indexedDBBlocked.tooltip "You have blocked data storage for this website.">
<!ENTITY urlbar.webNotificationsBlocked.tooltip "You have blocked notifications for this website.">
<!ENTITY urlbar.openHistoryPopup.tooltip "Show history">
<!ENTITY urlbar.zoomReset.tooltip "Reset zoom level">
<!ENTITY searchItem.title "Search">
--- a/browser/locales/en-US/chrome/browser/feeds/subscribe.properties
+++ b/browser/locales/en-US/chrome/browser/feeds/subscribe.properties
@@ -41,12 +41,21 @@ feedSubscriptionFeed1=This is a “feed” of frequently changing content on this site.
feedSubscriptionAudioPodcast1=This is a “podcast” of frequently changing content on this site.
feedSubscriptionVideoPodcast1=This is a “video podcast” of frequently changing content on this site.
feedSubscriptionFeed2=You can subscribe to this feed to receive updates when this content changes.
feedSubscriptionAudioPodcast2=You can subscribe to this podcast to receive updates when this content changes.
feedSubscriptionVideoPodcast2=You can subscribe to this video podcast to receive updates when this content changes.
# Protocol Handling
-# "Add %appName (%appDomain) as an application for %protocolType links?"
-addProtocolHandler=Add %S (%S) as an application for %S links?
-addProtocolHandlerAddButton=Add Application
-addProtocolHandlerAddButtonAccesskey=A
\ No newline at end of file
+# for the case that default handler is not a web app.
+# "Would you like %originalDefaultAppName to replace %appName (%appDomain) as the default application for %protocolType links?"
+replaceDefaultProtocolHandler=Would you like %S to replace %S (%S) as the default application for %S links?
+# for the case that default handler is a web app.
+# "Would you like %originalDefaultAppName (%originalDefaultAppDomain) to replace %appName (%appDomain) as the default application for %protocolType links?"
+replaceDefaultWebProtocolHandler=Would you like %S (%S) to replace %S (%S) as the default application for %S links?
+# "Set %appName (%appDomain) as the default application for %protocolType links?"
+setDefaultProtocolHandler=Set %S (%S) as the default application for %S links?
+handlerRegistration.allow.label=Allow
+handlerRegistration.allow.accessKey=A
+handlerRegistration.neverAllow.label=Never Allow
+handlerRegistration.neverAllow.accessKey=N
+handlerRegistration.checkbox.label=Allow setting default application
--- a/browser/locales/en-US/chrome/browser/sitePermissions.properties
+++ b/browser/locales/en-US/chrome/browser/sitePermissions.properties
@@ -30,8 +30,9 @@ permission.desktop-notification2.label =
permission.image.label = Load Images
permission.camera.label = Use the Camera
permission.microphone.label = Use the Microphone
permission.screen.label = Share the Screen
permission.install.label = Install Add-ons
permission.popup.label = Open Pop-up Windows
permission.geo.label = Access Your Location
permission.indexedDB.label = Maintain Offline Storage
+permission.handler-registration.label = Set the Default Application for Protocols
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -296,17 +296,18 @@ this.PermissionPromptPrototype = {
callback: state => {
if (promptAction.callback) {
promptAction.callback();
}
if (this.permissionKey) {
// Permanently store permission.
- if (state && state.checkboxChecked) {
+ if (state && state.checkboxChecked &&
+ this.permissionKey != "handler-registration") {
let scope = SitePermissions.SCOPE_PERSISTENT;
// Only remember permission for session if in PB mode.
if (PrivateBrowsingUtils.isBrowserPrivate(this.browser)) {
scope = SitePermissions.SCOPE_SESSION;
}
SitePermissions.set(this.principal.URI,
this.permissionKey,
promptAction.action,
@@ -591,8 +592,134 @@ DesktopNotificationPermissionPrompt.prot
action: SitePermissions.BLOCK,
},
];
},
};
PermissionUI.DesktopNotificationPermissionPrompt =
DesktopNotificationPermissionPrompt;
+
+XPCOMUtils.defineLazyGetter(this, "gHandlerStringBundle", function() {
+ return Services.strings.createBundle("chrome://browser/locale/feeds/subscribe.properties");
+});
+
+/**
+ * Creates a PermissionPrompt for a nsIContentPermissionRequest for
+ * the Handler Registration(registerProtocolHandler/registerContentHandler) API.
+ *
+ * @param request (nsIContentPermissionRequest)
+ * The request for a permission from content.
+ * @return {PermissionPrompt} (see documentation in header)
+ */
+function HandlerRegistrationPermissionPrompt(request) {
+ this.request = request;
+}
+
+HandlerRegistrationPermissionPrompt.prototype = {
+ __proto__: PermissionPromptForRequestPrototype,
+
+ get browser() {
+ return this.request.browser;
+ },
+
+ get principal() {
+ return this.request.principal;
+ },
+
+ get permissionKey() {
+ return this.request.permissionKey;
+ },
+
+ get notificationID() {
+ return "handler-registration";
+ },
+
+ get anchorID() {
+ return "handler-registration-notification-icon";
+ },
+
+ get message() {
+ let eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ let handlerInfo = eps.getProtocolHandlerInfo(this.request.type);
+
+ // for the case if we already have a default handler for the protocol
+ if (handlerInfo &&
+ handlerInfo.preferredApplicationHandler &&
+ handlerInfo.preferredAction == Ci.nsIHandlerInfo.useHelperApp) {
+ let defaultHandler = handlerInfo.preferredApplicationHandler;
+ if (defaultHandler.uriTemplate != this.request.uri) {
+ // for the case if the default handler is a web app.
+ if (defaultHandler instanceof Ci.nsIWebHandlerApp) {
+ let originalURI = Services.io.newURI(defaultHandler.uriTemplate);
+ let params = [
+ defaultHandler.name,
+ originalURI.host,
+ this.request.title,
+ this.request.principal.URI.host,
+ this.request.type
+ ];
+ return gHandlerStringBundle.
+ formatStringFromName("replaceDefaultWebProtocolHandler",
+ params,
+ params.length);
+ }
+ let params = [
+ defaultHandler.name,
+ this.request.title,
+ this.request.principal.URI.host,
+ this.request.type
+ ];
+ return gHandlerStringBundle.
+ formatStringFromName("replaceDefaultProtocolHandler",
+ params,
+ params.length);
+ }
+ }
+
+ // for the case we don't have a default handler
+ let params = [
+ this.request.title,
+ this.request.principal.URI.host,
+ this.request.type
+ ];
+ return gHandlerStringBundle.
+ formatStringFromName("setDefaultProtocolHandler",
+ params,
+ params.length);
+ },
+
+ get popupOptions() {
+ let options = {};
+ options.checkbox = {
+ show: true,
+ checked: false,
+ label: gHandlerStringBundle.GetStringFromName("handlerRegistration.checkbox.label"),
+ uncheckedState: {
+ disableMainAction: true,
+ },
+ };
+ return options;
+ },
+
+ get promptActions() {
+ let promptActions;
+ promptActions = [
+ {
+ label: gHandlerStringBundle.GetStringFromName("handlerRegistration.allow.label"),
+ accessKey: gHandlerStringBundle.GetStringFromName("handlerRegistration.allow.accessKey"),
+ action: SitePermissions.ALLOW,
+ expireType: SitePermissions.SCOPE_PERSISTENT,
+ },
+ {
+ label: gHandlerStringBundle.GetStringFromName("handlerRegistration.neverAllow.label"),
+ accessKey: gHandlerStringBundle.GetStringFromName("handlerRegistration.neverAllow.accessKey"),
+ action: SitePermissions.BLOCK,
+ expireType: SitePermissions.SCOPE_PERSISTENT,
+ },
+ ];
+ return promptActions;
+ },
+};
+
+PermissionUI.HandlerRegistrationPermissionPrompt =
+ HandlerRegistrationPermissionPrompt;
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -596,14 +596,19 @@ var gPermissionObject = {
SitePermissions.BLOCK : SitePermissions.ALLOW;
}
},
"geo": {
exactHostMatch: true
},
- "indexedDB": {}
+ "indexedDB": {},
+
+ "handler-registration": {
+ exactHostMatch: true,
+ states: [ SitePermissions.UNKNOWN, SitePermissions.BLOCK ],
+ }
};
XPCOMUtils.defineLazyPreferenceGetter(SitePermissions, "temporaryPermissionExpireTime",
"privacy.temporary_permission_expire_time_ms", 3600 * 1000);
--- a/browser/modules/test/xpcshell/test_SitePermissions.js
+++ b/browser/modules/test/xpcshell/test_SitePermissions.js
@@ -3,18 +3,18 @@
*/
"use strict";
Components.utils.import("resource:///modules/SitePermissions.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
add_task(function* testPermissionsListing() {
Assert.deepEqual(SitePermissions.listPermissions().sort(),
- ["camera", "cookie", "desktop-notification", "geo", "image",
- "indexedDB", "install", "microphone", "popup", "screen"],
+ ["camera", "cookie", "desktop-notification", "geo", "handler-registration",
+ "image", "indexedDB", "install", "microphone", "popup", "screen"],
"Correct list of all permissions");
});
add_task(function* testGetAllByURI() {
// check that it returns an empty array on an invalid URI
// like a file URI, which doesn't support site permissions
let wrongURI = Services.io.newURI("file:///example.js")
Assert.deepEqual(SitePermissions.getAllByURI(wrongURI), []);
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -81,16 +81,25 @@
list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-osx);
%elif defined(MOZ_WIDGET_GTK)
list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-linux-detailed);
%else
list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-windows-detailed);
%endif
}
+.popup-notification-icon[popupid="handler-registration"],
+.handler-registration-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#handler-registration)
+}
+
+.handler-registration-icon.blocked-permission-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#handler-registration-blocked);
+}
+
.popup-notification-icon[popupid="indexedDB-permissions-prompt"],
.indexedDB-icon {
list-style-image: url(chrome://browser/skin/notification-icons.svg#indexedDB);
}
.indexedDB-icon.blocked-permission-icon {
list-style-image: url(chrome://browser/skin/notification-icons.svg#indexedDB-blocked);
}
--- a/browser/themes/shared/notification-icons.svg
+++ b/browser/themes/shared/notification-icons.svg
@@ -50,16 +50,17 @@
<defs>
<path id="camera-icon" d="m 2,23 a 3,3 0 0 0 3,3 l 14,0 a 3,3 0 0 0 3,-3 l 0,-4 6,5.5 c 0.5,0.5 1,0.7 2,0.5 l 0,-18 c -1,-0.2 -1.5,0 -2,0.5 l -6,5.5 0,-4 a 3,3 0 0 0 -3,-3 l -14,0 a 3,3 0 0 0 -3,3 z" />
<path id="desktop-notification-icon" d="m 2,20 a 4,4 0 0 0 4,4 l 13,0 7,7 0,-7 a 4,4 0 0 0 4,-4 l 0,-12 a 4,4 0 0 0 -4,-4 l -20,0 a 4,4 0 0 0 -4,4 z m 5,-2 a 1,1 0 1 1 0,-2 l 10,0 a 1,1 0 1 1 0,2 z m 0,-4 a 1,1 0 1 1 0,-2 l 14,0 a 1,1 0 1 1 0,2 z m 0,-4 a 1,1 0 1 1 0,-2 l 18,0 a 1,1 0 1 1 0,2 z" />
<path id="geo-linux-icon" d="m 2,15.9 a 14,14 0 1 1 0,0.2 z m 4,2.1 a 10,10 0 0 0 8,8 l 0,-4 4,0 0,4 a 10,10 0 0 0 8,-8 l -4,0 0,-4 4,0 a 10,10 0 0 0 -8,-8 l 0,4 -4,0 0,-4 a 10,10 0 0 0 -8,8 l 4,0 0,4 z" />
<path id="geo-linux-detailed-icon" d="m 2,15.9 a 14,14 0 1 1 0,0.2 z m 3,2.1 a 11,11 0 0 0 9,9 l 1,-5 2,0 1,5 a 11,11 0 0 0 9,-9 l -5,-1 0,-2 5,-1 a 11,11 0 0 0 -9,-9 l -1,5 -2,0 -1,-5 a 11,11 0 0 0 -9,9 l 5,1 0,2 z" />
<path id="geo-osx-icon" d="m 0,16 16,0 0,16 12,-28 z" />
<path id="geo-windows-icon" d="m 2,14 0,4 2,0 a 12,12 0 0 0 10,10 l 0,2 4,0 0,-2 a 12,12 0 0 0 10,-10 l 2,0 0,-4 -2,0 a 12,12 0 0 0 -10,-10 l 0,-2 -4,0 0,2 a 12,12 0 0 0 -10,10 z m 4,1.9 a 10,10 0 1 1 0,0.2 z m 4,0 a 6,6 0 1 1 0,0.2 z" />
<path id="geo-windows-detailed-icon" d="m 2,14.5 0,3 2,0.5 a 12,12 0 0 0 10,10 l 0.5,2 3,0 0.5,-2 a 12,12 0 0 0 10,-10 l 2,-0.5 0,-3 -2,-0.5 a 12,12 0 0 0 -10,-10 l -0.5,-2 -3,0 -0.5,2 a 12,12 0 0 0 -10,10 z m 4,1.4 a 10,10 0 1 1 0,0.2 z m 3,0 a 7,7 0 1 1 0,0.2 z" />
+ <path id="handler-registration-icon" d="m 2,20 a 4,4 0 0 0 4,4 l 13,0 7,7 0,-7 a 4,4 0 0 0 4,-4 l 0,-12 a 4,4 0 0 0 -4,-4 l -20,0 a 4,4 0 0 0 -4,4 z m 5,-2 a 1,1 0 1 1 0,-2 l 10,0 a 1,1 0 1 1 0,2 z m 0,-4 a 1,1 0 1 1 0,-2 l 14,0 a 1,1 0 1 1 0,2 z m 0,-4 a 1,1 0 1 1 0,-2 l 18,0 a 1,1 0 1 1 0,2 z" />
<path id="indexedDB-icon" d="m 2,24 a 4,4 0 0 0 4,4 l 2,0 0,-4 -2,0 0,-16 20,0 0,16 -2,0 0,4 2,0 a 4,4 0 0 0 4,-4 l 0,-16 a 4,4 0 0 0 -4,-4 l -20,0 a 4,4 0 0 0 -4,4 z m 8,-2 6,7 6,-7 -4,0 0,-8 -4,0 0,8 z" />
<path id="login-icon" d="m 2,26 0,4 6,0 0,-2 2,0 0,-2 1,0 0,-1 2,0 0,-3 2,0 2.5,-2.5 1.5,1.5 3,-3 a 8,8 0 1 0 -8,-8 l -3,3 2,2 z m 20,-18.1 a 2,2 0 1 1 0,0.2 z" />
<path id="login-detailed-icon" d="m 1,27 0,3.5 a 0.5,0.5 0 0 0 0.5,0.5 l 5,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1.5 1.5,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1.5 1,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1 1,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-2 2,0 2.5,-2.5 q 0.5,-0.5 1,0 l 1,1 c 0.5,0.5 1,0.5 1.5,-0.5 l 1,-2 a 9,9 0 1 0 -8,-8 l -2,1 c -1,0.5 -1,1 -0.5,1.5 l 1.5,1.5 q 0.5,0.5 0,1 z m 21,-19.1 a 2,2 0 1 1 0,0.2 z" />
<path id="microphone-icon" d="m 8,14 0,4 a 8,8 0 0 0 6,7.7 l 0,2.3 -2,0 a 2,2 0 0 0 -2,2 l 12,0 a 2,2 0 0 0 -2,-2 l -2,0 0,-2.3 a 8,8 0 0 0 6,-7.7 l 0,-4 -2,0 0,4 a 6,6 0 0 1 -12,0 l 0,-4 z m 4,4 a 4,4 0 0 0 8,0 l 0,-12 a 4,4 0 0 0 -8,0 z" />
<path id="microphone-detailed-icon" d="m 8,18 a 8,8 0 0 0 6,7.7 l 0,2.3 -1,0 a 3,2 0 0 0 -3,2 l 12,0 a 3,2 0 0 0 -3,-2 l -1,0 0,-2.3 a 8,8 0 0 0 6,-7.7 l 0,-4 a 1,1 0 0 0 -2,0 l 0,4 a 6,6 0 0 1 -12,0 l 0,-4 a 1,1 0 0 0 -2,0 z m 4,0 a 4,4 0 0 0 8,0 l 0,-12 a 4,4 0 0 0 -8,0 z" />
<path id="plugin-icon" d="m 2,26 a 2,2 0 0 0 2,2 l 24,0 a 2,2 0 0 0 2,-2 l 0,-16 a 2,2 0 0 0 -2,-2 l -24,0 a 2,2 0 0 0 -2,2 z m 2,-20 10,0 0,-2 a 2,2 0 0 0 -2,-2 l -6,0 a 2,2 0 0 0 -2,2 z m 14,0 10,0 0,-2 a 2,2 0 0 0 -2,-2 l -6,0 a 2,2 0 0 0 -2,2 z" />
<path id="popup-icon" d="m 2,24 a 4,4 0 0 0 4,4 l 8,0 a 10,10 0 0 1 -2,-4 l -4,0 a 2,2 0 0 1 -2,-2 l 0,-12 18,0 0,2 a 10,10 0 0 1 4,2 l 0,-8 a 4,4 0 0 0 -4,-4 l -18,0 a 4,4 0 0 0 -4,4 z m 12,-2.1 a 8,8 0 1 1 0,0.2 m 10.7,-4.3 a 5,5 0 0 0 -6.9,6.9 z m -5.4,8.4 a 5,5 0 0 0 6.9,-6.9 z" />
<path id="screen-icon" d="m 2,18 a 2,2 0 0 0 2,2 l 2,0 0,-6 a 4,4 0 0 1 4,-4 l 14,0 0,-6 a 2,2 0 0 0 -2,-2 l -18,0 a 2,2 0 0 0 -2,2 z m 6,10 a 2,2 0 0 0 2,2 l 18,0 a 2,2 0 0 0 2,-2 l 0,-14 a 2,2 0 0 0 -2,-2 l -18,0 a 2,2 0 0 0 -2,2 z" />
@@ -78,16 +79,18 @@
<use id="geo-osx" xlink:href="#geo-osx-icon" />
<use id="geo-osx-blocked" class="blocked" xlink:href="#geo-osx-icon" />
<use id="geo-linux" xlink:href="#geo-linux-icon" />
<use id="geo-linux-blocked" class="blocked" xlink:href="#geo-linux-icon" />
<use id="geo-linux-detailed" xlink:href="#geo-linux-detailed-icon" />
<use id="geo-windows" xlink:href="#geo-windows-icon" />
<use id="geo-windows-blocked" class="blocked" xlink:href="#geo-windows-icon" />
<use id="geo-windows-detailed" xlink:href="#geo-windows-detailed-icon" />
+ <use id="handler-registration" xlink:href="#handler-registration-icon" />
+ <use id="handler-registration-blocked" class="blocked" xlink:href="#handler-registration-icon" />
<use id="indexedDB" xlink:href="#indexedDB-icon" />
<use id="indexedDB-blocked" class="blocked" xlink:href="#indexedDB-icon" />
<use id="login" xlink:href="#login-icon" />
<use id="login-highlighted" class="highlighted" xlink:href="#login-icon" />
<use id="login-detailed" xlink:href="#login-detailed-icon" />
<use id="microphone" xlink:href="#microphone-icon" />
<use id="microphone-sharing" xlink:href="#microphone-icon"/>
<use id="microphone-indicator" xlink:href="#microphone-icon"/>
--- a/uriloader/exthandler/tests/mochitest/browser.ini
+++ b/uriloader/exthandler/tests/mochitest/browser.ini
@@ -1,8 +1,5 @@
[DEFAULT]
head = head.js
-support-files =
- protocolHandler.html
[browser_download_always_ask_preferred_app.js]
[browser_remember_download_option.js]
-[browser_web_protocol_handlers.js]
deleted file mode 100644
--- a/uriloader/exthandler/tests/mochitest/browser_web_protocol_handlers.js
+++ /dev/null
@@ -1,76 +0,0 @@
-let testURL = "http://example.com/browser/" +
- "uriloader/exthandler/tests/mochitest/protocolHandler.html";
-
-add_task(function*() {
- // Load a page registering a protocol handler.
- let browser = gBrowser.selectedBrowser;
- browser.loadURI(testURL);
- yield BrowserTestUtils.browserLoaded(browser, testURL);
-
- // Register the protocol handler by clicking the notificationbar button.
- let notificationValue = "Protocol Registration: testprotocol";
- let getNotification = () =>
- gBrowser.getNotificationBox().getNotificationWithValue(notificationValue);
- yield BrowserTestUtils.waitForCondition(getNotification);
- let notification = getNotification();
- let button =
- notification.getElementsByClassName("notification-button-default")[0];
- ok(button, "got registration button");
- button.click();
-
- // Set the new handler as default.
- const protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
- getService(Ci.nsIExternalProtocolService);
- let protoInfo = protoSvc.getProtocolHandlerInfo("testprotocol");
- is(protoInfo.preferredAction, protoInfo.useHelperApp,
- "using a helper application is the preferred action");
- ok(!protoInfo.preferredApplicationHandler, "no preferred handler is set");
- let handlers = protoInfo.possibleApplicationHandlers;
- is(1, handlers.length, "only one handler registered for testprotocol");
- let handler = handlers.queryElementAt(0, Ci.nsIHandlerApp);
- ok(handler instanceof Ci.nsIWebHandlerApp, "the handler is a web handler");
- is(handler.uriTemplate, "https://example.com/foobar?uri=%s",
- "correct url template")
- protoInfo.preferredApplicationHandler = handler;
- protoInfo.alwaysAskBeforeHandling = false;
- const handlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].
- getService(Ci.nsIHandlerService);
- handlerSvc.store(protoInfo);
-
- // Middle-click a testprotocol link and check the new tab is correct
- let link = "#link";
- const expectedURL = "https://example.com/foobar?uri=testprotocol%3Atest";
-
- let promiseTabOpened =
- BrowserTestUtils.waitForNewTab(gBrowser, expectedURL);
- yield BrowserTestUtils.synthesizeMouseAtCenter(link, {button: 1}, browser);
- let tab = yield promiseTabOpened;
- gBrowser.selectedTab = tab;
- is(gURLBar.value, expectedURL,
- "the expected URL is displayed in the location bar");
- yield BrowserTestUtils.removeTab(tab);
-
- // Shift-click the testprotocol link and check the new window.
- let newWindowPromise = BrowserTestUtils.waitForNewWindow();
- yield BrowserTestUtils.synthesizeMouseAtCenter(link, {shiftKey: true},
- browser);
- let win = yield newWindowPromise;
- yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
- yield BrowserTestUtils.waitForCondition(() => win.gBrowser.currentURI.spec == expectedURL);
- is(win.gURLBar.value, expectedURL,
- "the expected URL is displayed in the location bar");
- yield BrowserTestUtils.closeWindow(win);
-
- // Click the testprotocol link and check the url in the current tab.
- let loadPromise = BrowserTestUtils.browserLoaded(browser);
- yield BrowserTestUtils.synthesizeMouseAtCenter(link, {}, browser);
- yield loadPromise;
- yield BrowserTestUtils.waitForCondition(() => gURLBar.value != testURL);
- is(gURLBar.value, expectedURL,
- "the expected URL is displayed in the location bar");
-
- // Cleanup.
- protoInfo.preferredApplicationHandler = null;
- handlers.removeElementAt(0);
- handlerSvc.store(protoInfo);
-});