зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1308309 Prompt for webextensions permissions r=florian,rhelmer
MozReview-Commit-ID: 6rTGvjKcx3H --HG-- extra : rebase_source : b17acde9a1ebf4a378b5f4e02f411b2aef8cde10 extra : intermediate-source : a9f326b034571a273b0087a3c3d9c7738093d903 extra : source : 320fb02a40dc18208bb00cef3ba7e1de2ca89938
This commit is contained in:
Родитель
83d7a52a60
Коммит
005c2ba3de
|
@ -71,3 +71,11 @@
|
|||
<popupnotification id="addon-install-confirmation-notification" hidden="true">
|
||||
<popupnotificationcontent id="addon-install-confirmation-content" orient="vertical"/>
|
||||
</popupnotification>
|
||||
|
||||
<popupnotification id="addon-webext-permissions-notification" hidden="true">
|
||||
<popupnotificationcontent orient="vertical">
|
||||
<description id="addon-webext-perm-header" class="addon-webext-perm-header"/>
|
||||
<label id="addon-webext-perm-text" class="addon-webext-perm-text"/>
|
||||
<html:ul id="addon-webext-perm-list" class="addon-webext-perm-list"/>
|
||||
</popupnotificationcontent>
|
||||
</popupnotification>
|
||||
|
|
|
@ -117,6 +117,9 @@ support-files =
|
|||
file_bug1045809_2.html
|
||||
file_csp_block_all_mixedcontent.html
|
||||
file_csp_block_all_mixedcontent.js
|
||||
file_install_extensions.html
|
||||
browser_webext_permissions.xpi
|
||||
browser_webext_nopermissions.xpi
|
||||
!/image/test/mochitest/blue.png
|
||||
!/toolkit/components/passwordmgr/test/browser/form_basic.html
|
||||
!/toolkit/components/passwordmgr/test/browser/insecure_test.html
|
||||
|
@ -298,6 +301,7 @@ skip-if = os == "mac" # decoder doctor isn't implemented on osx
|
|||
[browser_duplicateIDs.js]
|
||||
[browser_drag.js]
|
||||
skip-if = true # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
|
||||
[browser_extension_permissions.js]
|
||||
[browser_favicon_change.js]
|
||||
[browser_favicon_change_not_in_document.js]
|
||||
[browser_findbarClose.js]
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
"use strict";
|
||||
|
||||
const BASE = getRootDirectory(gTestPath)
|
||||
.replace("chrome://mochitests/content/", "https://example.com/");
|
||||
|
||||
const PAGE = `${BASE}/file_install_extensions.html`;
|
||||
const PERMS_XPI = `${BASE}/browser_webext_permissions.xpi`;
|
||||
const NO_PERMS_XPI = `${BASE}/browser_webext_nopermissions.xpi`;
|
||||
const ID = "permissions@test.mozilla.org";
|
||||
|
||||
const DEFAULT_EXTENSION_ICON = "chrome://browser/content/extension.svg";
|
||||
|
||||
function promisePopupNotificationShown(name) {
|
||||
return new Promise(resolve => {
|
||||
PopupNotifications.panel.addEventListener("popupshown", () => {
|
||||
let notification = PopupNotifications.getNotification(name);
|
||||
ok(notification, `${name} notification shown`);
|
||||
ok(PopupNotifications.isPanelOpen, "notification panel open");
|
||||
|
||||
resolve(PopupNotifications.panel.firstChild);
|
||||
}, {once: true});
|
||||
});
|
||||
}
|
||||
|
||||
function promiseGetAddonByID(id) {
|
||||
return new Promise(resolve => {
|
||||
AddonManager.getAddonByID(id, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
function checkNotification(panel, url) {
|
||||
let icon = panel.getAttribute("icon");
|
||||
|
||||
let uls = panel.firstChild.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "ul");
|
||||
is(uls.length, 1, "Found the permissions list");
|
||||
let ul = uls[0];
|
||||
|
||||
let headers = panel.firstChild.getElementsByClassName("addon-webext-perm-text");
|
||||
is(headers.length, 1, "Found the header");
|
||||
let header = headers[0];
|
||||
|
||||
if (url == PERMS_XPI) {
|
||||
// The icon should come from the extension, don't bother with the precise
|
||||
// path, just make sure we've got a jar url pointing to the right path
|
||||
// inside the jar.
|
||||
ok(icon.startsWith("jar:file://"), "Icon is a jar url");
|
||||
ok(icon.endsWith("/icon.png"), "Icon is icon.png inside a jar");
|
||||
|
||||
is(header.getAttribute("hidden"), "", "Permission list header is visible");
|
||||
is(ul.childElementCount, 4, "Permissions list has 4 entries");
|
||||
// Real checking of the contents here is deferred until bug 1316996 lands
|
||||
} else if (url == NO_PERMS_XPI) {
|
||||
// This extension has no icon, it should have the default
|
||||
is(icon, DEFAULT_EXTENSION_ICON, "Icon is the default extension icon");
|
||||
|
||||
is(header.getAttribute("hidden"), "true", "Permission list header is hidden");
|
||||
is(ul.childElementCount, 0, "Permissions list has 0 entries");
|
||||
}
|
||||
}
|
||||
|
||||
const INSTALL_FUNCTIONS = [
|
||||
function installMozAM(url) {
|
||||
return ContentTask.spawn(gBrowser.selectedBrowser, url, function*(cUrl) {
|
||||
return content.wrappedJSObject.installMozAM(cUrl);
|
||||
});
|
||||
},
|
||||
];
|
||||
|
||||
add_task(function* () {
|
||||
yield SpecialPowers.pushPrefEnv({set: [
|
||||
["extensions.webapi.testing", true],
|
||||
["extensions.install.requireBuiltInCerts", false],
|
||||
|
||||
// XXX remove this when prompts are enabled by default
|
||||
["extensions.webextPermissionPrompts", true],
|
||||
]});
|
||||
|
||||
function* runOnce(installFn, url, cancel) {
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
|
||||
|
||||
let installPromise = installFn(url);
|
||||
|
||||
let panel = yield promisePopupNotificationShown("addon-webext-permissions");
|
||||
checkNotification(panel, url);
|
||||
|
||||
if (cancel) {
|
||||
panel.secondaryButton.click();
|
||||
} else {
|
||||
panel.button.click();
|
||||
}
|
||||
|
||||
let result = yield installPromise;
|
||||
let addon = yield promiseGetAddonByID(ID);
|
||||
if (cancel) {
|
||||
is(result, "onInstallCancelled", "Installation was cancelled");
|
||||
is(addon, null, "Extension is not installed");
|
||||
} else {
|
||||
is(result, "onInstallEnded", "Installation completed");
|
||||
isnot(addon, null, "Extension is installed");
|
||||
addon.uninstall();
|
||||
}
|
||||
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
|
||||
for (let installFn of INSTALL_FUNCTIONS) {
|
||||
yield runOnce(installFn, NO_PERMS_XPI, true);
|
||||
yield runOnce(installFn, PERMS_XPI, true);
|
||||
yield runOnce(installFn, PERMS_XPI, false);
|
||||
}
|
||||
});
|
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
function installMozAM(url) {
|
||||
return navigator.mozAddonManager.createInstall({url}).then(install => new Promise(resolve => {
|
||||
const EVENTS = [
|
||||
"onDownloadCancelled",
|
||||
"onDownloadFailed",
|
||||
"onInstallCancelled",
|
||||
"onInstallEnded",
|
||||
"onInstallFailed",
|
||||
];
|
||||
for (let event of EVENTS) {
|
||||
install.addEventListener(event, () => { resolve(event); });
|
||||
}
|
||||
install.install();
|
||||
}));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -57,6 +57,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-s
|
|||
["ContentSearch", "resource:///modules/ContentSearch.jsm"],
|
||||
["DateTimePickerHelper", "resource://gre/modules/DateTimePickerHelper.jsm"],
|
||||
["DirectoryLinksProvider", "resource:///modules/DirectoryLinksProvider.jsm"],
|
||||
["ExtensionsUI", "resource:///modules/ExtensionsUI.jsm"],
|
||||
["Feeds", "resource:///modules/Feeds.jsm"],
|
||||
["FileUtils", "resource://gre/modules/FileUtils.jsm"],
|
||||
["FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"],
|
||||
|
@ -1065,6 +1066,8 @@ BrowserGlue.prototype = {
|
|||
}
|
||||
});
|
||||
|
||||
ExtensionsUI.init();
|
||||
|
||||
let signingRequired;
|
||||
if (AppConstants.MOZ_REQUIRE_SIGNING) {
|
||||
signingRequired = true;
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["ExtensionsUI"];
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const DEFAULT_EXENSION_ICON = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
this.ExtensionsUI = {
|
||||
init() {
|
||||
Services.obs.addObserver(this, "webextension-permission-prompt", false);
|
||||
},
|
||||
|
||||
observe(subject, topic, data) {
|
||||
if (topic == "webextension-permission-prompt") {
|
||||
let {target, info} = subject.wrappedJSObject;
|
||||
this.showPermissionsPrompt(target, info).then(answer => {
|
||||
Services.obs.notifyObservers(subject, "webextension-permission-response",
|
||||
JSON.stringify(answer));
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
showPermissionsPrompt(target, info) {
|
||||
let perms = info.addon.userPermissions;
|
||||
if (!perms) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let win = target.ownerGlobal;
|
||||
|
||||
let name = info.addon.name;
|
||||
if (name.length > 50) {
|
||||
name = name.slice(0, 49) + "…";
|
||||
}
|
||||
|
||||
// The strings below are placeholders, they will switch over to the
|
||||
// bundle.get*String() calls as part of bug 1316996.
|
||||
|
||||
// let bundle = win.gNavigatorBundle;
|
||||
// let header = bundle.getFormattedString("webextPerms.header", [name])
|
||||
// let listHeader = bundle.getString("webextPerms.listHeader");
|
||||
let header = "Add ADDON?".replace("ADDON", name);
|
||||
let listHeader = "It can:";
|
||||
|
||||
let formatPermission = perm => {
|
||||
try {
|
||||
// return bundle.getString(`webextPerms.description.${perm}`);
|
||||
return `localized description of permission ${perm}`;
|
||||
} catch (err) {
|
||||
// return bundle.getFormattedString("webextPerms.description.unknown",
|
||||
// [perm]);
|
||||
return `localized description of unknown permission ${perm}`;
|
||||
}
|
||||
};
|
||||
|
||||
let formatHostPermission = perm => {
|
||||
if (perm == "<all_urls>") {
|
||||
// return bundle.getString("webextPerms.hostDescription.allUrls");
|
||||
return "localized description of <all_urls> host permission";
|
||||
}
|
||||
let match = /^[htps*]+:\/\/([^/]+)\//.exec(perm);
|
||||
if (!match) {
|
||||
throw new Error("Unparseable host permission");
|
||||
}
|
||||
if (match[1].startsWith("*.")) {
|
||||
let domain = match[1].slice(2);
|
||||
// return bundle.getFormattedString("webextPerms.hostDescription.wildcard", [domain]);
|
||||
return `localized description of wildcard host permission for ${domain}`;
|
||||
}
|
||||
|
||||
// return bundle.getFormattedString("webextPerms.hostDescription.oneSite", [match[1]]);
|
||||
return `localized description of single host permission for ${match[1]}`;
|
||||
};
|
||||
|
||||
let msgs = [
|
||||
...perms.permissions.map(formatPermission),
|
||||
...perms.hosts.map(formatHostPermission),
|
||||
];
|
||||
|
||||
// let acceptText = bundle.getString("webextPerms.accept.label");
|
||||
// let acceptKey = bundle.getString("webextPerms.accept.accessKey");
|
||||
// let cancelText = bundle.getString("webextPerms.cancel.label");
|
||||
// let cancelKey = bundle.getString("webextPerms.cancel.accessKey");
|
||||
let acceptText = "Add extension";
|
||||
let acceptKey = "A";
|
||||
let cancelText = "Cancel";
|
||||
let cancelKey = "C";
|
||||
|
||||
let rendered = false;
|
||||
let popupOptions = {
|
||||
hideClose: true,
|
||||
popupIconURL: info.icon,
|
||||
persistent: true,
|
||||
|
||||
eventCallback(topic) {
|
||||
if (topic == "showing") {
|
||||
// This check can be removed when bug 1325223 is resolved.
|
||||
if (rendered) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let doc = this.browser.ownerDocument;
|
||||
doc.getElementById("addon-webext-perm-header").textContent = header;
|
||||
|
||||
let list = doc.getElementById("addon-webext-perm-list");
|
||||
while (list.firstChild) {
|
||||
list.firstChild.remove();
|
||||
}
|
||||
|
||||
let listHeaderEl = doc.getElementById("addon-webext-perm-text");
|
||||
listHeaderEl.value = listHeader;
|
||||
listHeaderEl.hidden = (msgs.length == 0);
|
||||
|
||||
for (let msg of msgs) {
|
||||
let item = doc.createElementNS(HTML_NS, "li");
|
||||
item.textContent = msg;
|
||||
list.appendChild(item);
|
||||
}
|
||||
rendered = true;
|
||||
} else if (topic == "dismissed") {
|
||||
rendered = false;
|
||||
} else if (topic == "swapping") {
|
||||
rendered = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
return new Promise(resolve => {
|
||||
win.PopupNotifications.show(target, "addon-webext-permissions", "",
|
||||
"addons-notification-icon",
|
||||
{
|
||||
label: acceptText,
|
||||
accessKey: acceptKey,
|
||||
callback: () => resolve(true),
|
||||
},
|
||||
[
|
||||
{
|
||||
label: cancelText,
|
||||
accessKey: cancelKey,
|
||||
callback: () => resolve(false),
|
||||
},
|
||||
], popupOptions);
|
||||
});
|
||||
},
|
||||
};
|
|
@ -26,6 +26,7 @@ EXTRA_JS_MODULES += [
|
|||
'ContentWebRTC.jsm',
|
||||
'DirectoryLinksProvider.jsm',
|
||||
'E10SUtils.jsm',
|
||||
'ExtensionsUI.jsm',
|
||||
'Feeds.jsm',
|
||||
'FormSubmitObserver.jsm',
|
||||
'FormValidationHandler.jsm',
|
||||
|
|
|
@ -833,6 +833,11 @@ menuitem.bookmark-item {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.addon-webext-perm-header {
|
||||
font-weight: bold;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
/* Notification icon box */
|
||||
|
||||
.notification-anchor-icon:-moz-focusring {
|
||||
|
|
|
@ -3091,6 +3091,11 @@ menulist.translate-infobar-element > .menulist-dropmarker {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.addon-webext-perm-header {
|
||||
font-weight: bold;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
/* Status panel */
|
||||
|
||||
.statuspanel-label {
|
||||
|
|
|
@ -2136,6 +2136,11 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.addon-webext-perm-header {
|
||||
font-weight: bold;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
/* Notification icon box */
|
||||
|
||||
.notification-anchor-icon:-moz-focusring {
|
||||
|
|
|
@ -377,6 +377,12 @@ this.ExtensionData = class {
|
|||
hosts: this.whiteListedHosts.pat,
|
||||
apis: [...this.apiNames],
|
||||
};
|
||||
|
||||
if (Array.isArray(this.manifest.content_scripts)) {
|
||||
for (let entry of this.manifest.content_scripts) {
|
||||
result.hosts.push(...entry.matches);
|
||||
}
|
||||
}
|
||||
const EXP_PATTERN = /^experiments\.\w+/;
|
||||
result.permissions = [...this.permissions]
|
||||
.filter(p => !result.hosts.includes(p) && !EXP_PATTERN.test(p));
|
||||
|
|
|
@ -47,6 +47,7 @@ const UNKNOWN_XPCOM_ABI = "unknownABI";
|
|||
|
||||
const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion";
|
||||
const PREF_WEBAPI_TESTING = "extensions.webapi.testing";
|
||||
const PREF_WEBEXT_PREF_PROMPTS = "extensions.webextPermissionPrompts";
|
||||
|
||||
const UPDATE_REQUEST_VERSION = 2;
|
||||
const CATEGORY_UPDATE_PARAMS = "extension-update-params";
|
||||
|
@ -78,6 +79,8 @@ Cu.import("resource://gre/modules/AsyncShutdown.jsm");
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
|
||||
"resource://gre/modules/Preferences.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
|
||||
|
@ -2801,6 +2804,30 @@ var AddonManagerInternal = {
|
|||
|
||||
return AddonManagerInternal.getInstallForURL(options.url, "application/x-xpinstall",
|
||||
options.hash).then(install => {
|
||||
if (Preferences.get(PREF_WEBEXT_PREF_PROMPTS, false)) {
|
||||
install._permHandler = info => new Promise((resolve, reject) => {
|
||||
const observer = {
|
||||
observe(subject, topic, data) {
|
||||
if (topic == "webextension-permission-response" &&
|
||||
subject.wrappedJSObject.info.addon == info.addon) {
|
||||
let answer = JSON.parse(data);
|
||||
Services.obs.removeObserver(this, "webextension-permission-response");
|
||||
if (answer) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "webextension-permission-response", false);
|
||||
|
||||
let subject = {wrappedJSObject: {target, info}};
|
||||
Services.obs.notifyObservers(subject, "webextension-permission-prompt", null);
|
||||
});
|
||||
}
|
||||
|
||||
let id = this.nextInstall++;
|
||||
let listener = this.makeListener(id, target.messageManager);
|
||||
install.addListener(listener);
|
||||
|
|
|
@ -87,6 +87,12 @@ XPCOMUtils.defineLazyGetter(this, "CertUtils", function() {
|
|||
return certUtils;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "IconDetails", () => {
|
||||
const {ExtensionUtils} = Cu.import("resource://gre/modules/ExtensionUtils.jsm", {});
|
||||
return ExtensionUtils.IconDetails;
|
||||
});
|
||||
|
||||
|
||||
Cu.importGlobalProperties(["URL"]);
|
||||
|
||||
const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
|
||||
|
@ -5597,6 +5603,18 @@ class AddonInstall {
|
|||
}).bind(this));
|
||||
}
|
||||
|
||||
getIcon(desiredSize = 64) {
|
||||
if (!this.addon.icons || !this.file) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let {icon} = IconDetails.getPreferredIcon(this.addon.icons, null, desiredSize);
|
||||
if (icon.startsWith("chrome://")) {
|
||||
return icon;
|
||||
}
|
||||
return buildJarURI(this.file, icon).spec;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be called when the XPI is ready to be installed,
|
||||
* i.e., when a download finishes or when a local file has been verified.
|
||||
|
@ -5608,8 +5626,9 @@ class AddonInstall {
|
|||
Task.spawn((function*() {
|
||||
if (this.permHandler) {
|
||||
let info = {
|
||||
existinAddon: this.existingAddon,
|
||||
addon: this.addon,
|
||||
existingAddon: this.existingAddon ? this.existingAddon.wrapper : null,
|
||||
addon: this.addon.wrapper,
|
||||
icon: this.getIcon(),
|
||||
};
|
||||
|
||||
try {
|
||||
|
@ -7525,6 +7544,10 @@ AddonWrapper.prototype = {
|
|||
return (addon._installLocation.name == KEY_APP_PROFILE);
|
||||
},
|
||||
|
||||
get userPermissions() {
|
||||
return addonFor(this).userPermissions;
|
||||
},
|
||||
|
||||
isCompatibleWith(aAppVersion, aPlatformVersion) {
|
||||
return addonFor(this).isCompatibleWith(aAppVersion, aPlatformVersion);
|
||||
},
|
||||
|
|
Загрузка…
Ссылка в новой задаче