зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1317470 Show permission prompts for background webextension updates r=florian
MozReview-Commit-ID: I55ePPFPuuE --HG-- extra : rebase_source : a0b6aef83b3c315be7088e990c12181a5d022b8d
This commit is contained in:
Родитель
336f27142f
Коммит
cafe95ca01
|
@ -502,7 +502,8 @@ const gExtensionsNotifications = {
|
|||
|
||||
updateAlerts() {
|
||||
let sideloaded = ExtensionsUI.sideloaded;
|
||||
if (sideloaded.size == 0) {
|
||||
let updates = ExtensionsUI.updates;
|
||||
if (sideloaded.size + updates.size == 0) {
|
||||
gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_ADDONS);
|
||||
} else {
|
||||
gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_ADDONS,
|
||||
|
@ -519,6 +520,23 @@ const gExtensionsNotifications = {
|
|||
const DEFAULT_EXTENSION_ICON =
|
||||
"chrome://mozapps/skin/extensions/extensionGeneric.svg";
|
||||
let items = 0;
|
||||
for (let update of updates) {
|
||||
if (++items > 4) {
|
||||
break;
|
||||
}
|
||||
let button = document.createElement("toolbarbutton");
|
||||
button.setAttribute("label", `"${update.addon.name}" requires new permissions`);
|
||||
|
||||
let icon = update.addon.iconURL || DEFAULT_EXTENSION_ICON;
|
||||
button.setAttribute("image", icon);
|
||||
|
||||
button.addEventListener("click", evt => {
|
||||
ExtensionsUI.showUpdate(gBrowser, update);
|
||||
});
|
||||
|
||||
container.appendChild(button);
|
||||
}
|
||||
|
||||
for (let addon of sideloaded) {
|
||||
if (++items > 4) {
|
||||
break;
|
||||
|
|
|
@ -120,6 +120,9 @@ support-files =
|
|||
file_install_extensions.html
|
||||
browser_webext_permissions.xpi
|
||||
browser_webext_nopermissions.xpi
|
||||
browser_webext_update1.xpi
|
||||
browser_webext_update2.xpi
|
||||
browser_webext_update.json
|
||||
!/image/test/mochitest/blue.png
|
||||
!/toolkit/components/passwordmgr/test/browser/form_basic.html
|
||||
!/toolkit/components/passwordmgr/test/browser/insecure_test.html
|
||||
|
@ -306,6 +309,7 @@ skip-if = os == "mac" # decoder doctor isn't implemented on osx
|
|||
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_extension_sideloading.js]
|
||||
[browser_extension_update.js]
|
||||
[browser_favicon_change.js]
|
||||
[browser_favicon_change_not_in_document.js]
|
||||
[browser_findbarClose.js]
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
const {AddonManagerPrivate} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
|
||||
|
||||
const URL_BASE = "https://example.com/browser/browser/base/content/test/general";
|
||||
const ID = "update@tests.mozilla.org";
|
||||
|
||||
function promiseViewLoaded(tab, viewid) {
|
||||
let win = tab.linkedBrowser.contentWindow;
|
||||
if (win.gViewController && !win.gViewController.isLoading &&
|
||||
win.gViewController.currentViewId == viewid) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
function listener() {
|
||||
if (win.gViewController.currentViewId != viewid) {
|
||||
return;
|
||||
}
|
||||
win.document.removeEventListener("ViewChanged", listener);
|
||||
resolve();
|
||||
}
|
||||
win.document.addEventListener("ViewChanged", listener);
|
||||
});
|
||||
}
|
||||
|
||||
function promisePopupNotificationShown(name) {
|
||||
return new Promise(resolve => {
|
||||
function popupshown() {
|
||||
let notification = PopupNotifications.getNotification(name);
|
||||
if (!notification) { return; }
|
||||
|
||||
ok(notification, `${name} notification shown`);
|
||||
ok(PopupNotifications.isPanelOpen, "notification panel open");
|
||||
|
||||
PopupNotifications.panel.removeEventListener("popupshown", popupshown);
|
||||
resolve(PopupNotifications.panel.firstChild);
|
||||
}
|
||||
|
||||
PopupNotifications.panel.addEventListener("popupshown", popupshown);
|
||||
});
|
||||
}
|
||||
|
||||
function getBadgeStatus() {
|
||||
let menuButton = document.getElementById("PanelUI-menu-button");
|
||||
return menuButton.getAttribute("badge-status");
|
||||
}
|
||||
|
||||
function promiseUpdateDownloaded(addon) {
|
||||
return new Promise(resolve => {
|
||||
let listener = {
|
||||
onDownloadEnded(install) {
|
||||
if (install.addon.id == addon.id) {
|
||||
AddonManager.removeInstallListener(listener);
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
};
|
||||
AddonManager.addInstallListener(listener);
|
||||
});
|
||||
}
|
||||
|
||||
function promiseUpgrade(addon) {
|
||||
return new Promise(resolve => {
|
||||
let listener = {
|
||||
onInstallEnded(install, newAddon) {
|
||||
if (newAddon.id == addon.id) {
|
||||
AddonManager.removeInstallListener(listener);
|
||||
resolve(newAddon);
|
||||
}
|
||||
},
|
||||
};
|
||||
AddonManager.addInstallListener(listener);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* () {
|
||||
yield SpecialPowers.pushPrefEnv({set: [
|
||||
// Turn on background updates
|
||||
["extensions.update.enabled", true],
|
||||
|
||||
// Point updates to the local mochitest server
|
||||
["extensions.update.background.url", `${URL_BASE}/browser_webext_update.json`],
|
||||
|
||||
// We don't have pre-pinned certificates for the local mochitest server
|
||||
["extensions.install.requireBuiltInCerts", false],
|
||||
["extensions.update.requireBuiltInCerts", false],
|
||||
|
||||
// XXX remove this when prompts are enabled by default
|
||||
["extensions.webextPermissionPrompts", true],
|
||||
]});
|
||||
|
||||
// Install version 1.0 of the test extension
|
||||
let url1 = `${URL_BASE}/browser_webext_update1.xpi`;
|
||||
let install = yield AddonManager.getInstallForURL(url1, null, "application/x-xpinstall");
|
||||
ok(install, "Created install");
|
||||
|
||||
let addon = yield new Promise(resolve => {
|
||||
install.addListener({
|
||||
onInstallEnded(_install, _addon) {
|
||||
resolve(_addon);
|
||||
},
|
||||
});
|
||||
install.install();
|
||||
});
|
||||
|
||||
ok(addon, "Addon was installed");
|
||||
is(getBadgeStatus(), "", "Should not start out with an addon alert badge");
|
||||
|
||||
// Trigger an update check and wait for the update for this addon
|
||||
// to be downloaded.
|
||||
let updatePromise = promiseUpdateDownloaded(addon);
|
||||
AddonManagerPrivate.backgroundUpdateCheck();
|
||||
yield updatePromise;
|
||||
|
||||
is(getBadgeStatus(), "addon-alert", "Should have addon alert badge");
|
||||
|
||||
// Find the menu entry for the update
|
||||
yield PanelUI.show();
|
||||
|
||||
let addons = document.getElementById("PanelUI-footer-addons");
|
||||
is(addons.children.length, 1, "Have a menu entry for the update");
|
||||
|
||||
// Click the menu item
|
||||
let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
|
||||
let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
|
||||
addons.children[0].click();
|
||||
|
||||
// about:addons should load and go to the list of extensions
|
||||
let tab = yield tabPromise;
|
||||
is(tab.linkedBrowser.currentURI.spec, "about:addons");
|
||||
|
||||
const VIEW = "addons://list/extension";
|
||||
yield promiseViewLoaded(tab, VIEW);
|
||||
let win = tab.linkedBrowser.contentWindow;
|
||||
ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
|
||||
is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
|
||||
|
||||
// Wait for the permission prompt and cancel it
|
||||
let panel = yield popupPromise;
|
||||
panel.secondaryButton.click();
|
||||
|
||||
addon = yield AddonManager.getAddonByID(ID);
|
||||
is(addon.version, "1.0", "Should still be running the old version");
|
||||
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
|
||||
// Alert badge and hamburger menu items should be gone
|
||||
is(getBadgeStatus(), "", "Addon alert badge should be gone");
|
||||
|
||||
yield PanelUI.show();
|
||||
addons = document.getElementById("PanelUI-footer-addons");
|
||||
is(addons.children.length, 0, "Update menu entries should be gone");
|
||||
yield PanelUI.hide();
|
||||
|
||||
// Re-check for an update
|
||||
updatePromise = promiseUpdateDownloaded(addon);
|
||||
yield AddonManagerPrivate.backgroundUpdateCheck();
|
||||
yield updatePromise;
|
||||
|
||||
is(getBadgeStatus(), "addon-alert", "Should have addon alert badge");
|
||||
|
||||
// Find the menu entry for the update
|
||||
yield PanelUI.show();
|
||||
|
||||
addons = document.getElementById("PanelUI-footer-addons");
|
||||
is(addons.children.length, 1, "Have a menu entry for the update");
|
||||
|
||||
// Click the menu item
|
||||
tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
|
||||
popupPromise = promisePopupNotificationShown("addon-webext-permissions");
|
||||
addons.children[0].click();
|
||||
|
||||
// Wait for about:addons to load
|
||||
tab = yield tabPromise;
|
||||
is(tab.linkedBrowser.currentURI.spec, "about:addons");
|
||||
|
||||
yield promiseViewLoaded(tab, VIEW);
|
||||
win = tab.linkedBrowser.contentWindow;
|
||||
ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
|
||||
is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
|
||||
|
||||
// Wait for the permission prompt and accept it this time
|
||||
updatePromise = promiseUpgrade(addon);
|
||||
panel = yield popupPromise;
|
||||
panel.button.click();
|
||||
|
||||
addon = yield updatePromise;
|
||||
is(addon.version, "2.0", "Should have upgraded to the new version");
|
||||
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
|
||||
is(getBadgeStatus(), "", "Addon alert badge should be gone");
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"addons": {
|
||||
"update@tests.mozilla.org": {
|
||||
"updates": [
|
||||
{
|
||||
"version": "2.0",
|
||||
"update_link": "https://example.com/browser/browser/base/content/test/general/browser_webext_update2.xpi",
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"strict_min_version": "1",
|
||||
"advisory_max_version": "55.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -26,9 +26,11 @@ const HTML_NS = "http://www.w3.org/1999/xhtml";
|
|||
|
||||
this.ExtensionsUI = {
|
||||
sideloaded: new Set(),
|
||||
updates: new Set(),
|
||||
|
||||
init() {
|
||||
Services.obs.addObserver(this, "webextension-permission-prompt", false);
|
||||
Services.obs.addObserver(this, "webextension-update-permissions", false);
|
||||
|
||||
this._checkForSideloaded();
|
||||
},
|
||||
|
@ -60,11 +62,7 @@ this.ExtensionsUI = {
|
|||
});
|
||||
},
|
||||
|
||||
showSideloaded(browser, addon) {
|
||||
addon.markAsSeen();
|
||||
this.sideloaded.delete(addon);
|
||||
this.emit("change");
|
||||
|
||||
showAddonsManager(browser, info) {
|
||||
let loadPromise = new Promise(resolve => {
|
||||
let listener = (subject, topic) => {
|
||||
if (subject.location.href == "about:addons") {
|
||||
|
@ -76,16 +74,41 @@ this.ExtensionsUI = {
|
|||
});
|
||||
let tab = browser.addTab("about:addons");
|
||||
browser.selectedTab = tab;
|
||||
loadPromise.then(win => {
|
||||
|
||||
return loadPromise.then(win => {
|
||||
win.loadView("addons://list/extension");
|
||||
return this.showPermissionsPrompt(browser.selectedBrowser, info);
|
||||
});
|
||||
},
|
||||
|
||||
showSideloaded(browser, addon) {
|
||||
addon.markAsSeen();
|
||||
this.sideloaded.delete(addon);
|
||||
this.emit("change");
|
||||
|
||||
let info = {
|
||||
addon,
|
||||
permissions: addon.userPermissions,
|
||||
icon: addon.iconURL,
|
||||
type: "sideload",
|
||||
};
|
||||
this.showPermissionsPrompt(browser.selectedBrowser, info).then(answer => {
|
||||
this.showAddonsManager(browser, info).then(answer => {
|
||||
addon.userDisabled = !answer;
|
||||
});
|
||||
},
|
||||
|
||||
showUpdate(browser, info) {
|
||||
info.type = "update";
|
||||
this.showAddonsManager(browser, info).then(answer => {
|
||||
if (answer) {
|
||||
info.resolve();
|
||||
} else {
|
||||
info.reject();
|
||||
}
|
||||
// At the moment, this prompt will re-appear next time we do an update
|
||||
// check. See bug 1332360 for proposal to avoid this.
|
||||
this.updates.delete(info);
|
||||
this.emit("change");
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -101,15 +124,26 @@ this.ExtensionsUI = {
|
|||
progressNotification.remove();
|
||||
}
|
||||
|
||||
this.showPermissionsPrompt(target, info).then(answer => {
|
||||
let reply = answer => {
|
||||
Services.obs.notifyObservers(subject, "webextension-permission-response",
|
||||
JSON.stringify(answer));
|
||||
});
|
||||
};
|
||||
|
||||
let perms = info.addon.userPermissions;
|
||||
if (!perms) {
|
||||
reply(true);
|
||||
} else {
|
||||
info.permissions = perms;
|
||||
this.showPermissionsPrompt(target, info).then(reply);
|
||||
}
|
||||
} else if (topic == "webextension-update-permissions") {
|
||||
this.updates.add(subject.wrappedJSObject);
|
||||
this.emit("change");
|
||||
}
|
||||
},
|
||||
|
||||
showPermissionsPrompt(target, info) {
|
||||
let perms = info.addon.userPermissions;
|
||||
let perms = info.permissions;
|
||||
if (!perms) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
@ -147,6 +181,11 @@ this.ExtensionsUI = {
|
|||
acceptKey = "E";
|
||||
cancelText = "Disable";
|
||||
cancelKey = "D";
|
||||
} else if (info.type == "update") {
|
||||
header = "";
|
||||
text = `${name} has been updated. You must approve new permissions before the updated version will install.`;
|
||||
acceptText = "Update";
|
||||
acceptKey = "U";
|
||||
}
|
||||
|
||||
let formatPermission = perm => {
|
||||
|
|
|
@ -1376,6 +1376,35 @@ var AddonManagerInternal = {
|
|||
return uri.replace(/\+/g, "%2B");
|
||||
},
|
||||
|
||||
_updatePromptHandler(info) {
|
||||
let oldPerms = info.existingAddon.userPermissions || {hosts: [], permissions: []};
|
||||
let newPerms = info.addon.userPermissions;
|
||||
|
||||
// See bug 1331769: should we do something more complicated to
|
||||
// compare host permissions?
|
||||
// e.g., if we go from <all_urls> to a specific host or from
|
||||
// a *.domain.com to specific-host.domain.com that's actually a
|
||||
// drop in permissions but the simple test below will cause a prompt.
|
||||
let difference = {
|
||||
hosts: newPerms.hosts.filter(perm => !oldPerms.hosts.includes(perm)),
|
||||
permissions: newPerms.permissions.filter(perm => !oldPerms.permissions.includes(perm)),
|
||||
};
|
||||
|
||||
// If there are no new permissions, just go ahead with the update
|
||||
if (difference.hosts.length == 0 && difference.permissions.length == 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let subject = {wrappedJSObject: {
|
||||
addon: info.addon,
|
||||
permissions: difference,
|
||||
resolve, reject
|
||||
}};
|
||||
Services.obs.notifyObservers(subject, "webextension-update-permissions", null);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Performs a background update check by starting an update for all add-ons
|
||||
* that can be updated.
|
||||
|
@ -1430,6 +1459,7 @@ var AddonManagerInternal = {
|
|||
// XXX we really should resolve when this install is done,
|
||||
// not when update-available check completes, no?
|
||||
logger.debug(`Starting upgrade install of ${aAddon.id}`);
|
||||
aInstall.promptHandler = (...args) => AddonManagerInternal._updatePromptHandler(...args);
|
||||
aInstall.install();
|
||||
}
|
||||
},
|
||||
|
|
Загрузка…
Ссылка в новой задаче