fune/browser/base/content/test/general/browser_extension_sideloading.js

344 lines
11 KiB
JavaScript

const {AddonManagerPrivate} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
// MockAddon mimics the AddonInternal interface and MockProvider implements
// just enough of the AddonManager provider interface to make it look like
// we have sideloaded webextensions so the sideloading flow can be tested.
// MockAddon -> callback
let setCallbacks = new Map();
class MockAddon {
constructor(props) {
this._userDisabled = false;
this.pendingOperations = 0;
this.type = "extension";
for (let name in props) {
if (name == "userDisabled") {
this._userDisabled = props[name];
}
this[name] = props[name];
}
}
markAsSeen() {
this.seen = true;
}
get userDisabled() {
return this._userDisabled;
}
set userDisabled(val) {
this._userDisabled = val;
AddonManagerPrivate.callAddonListeners(val ? "onDisabled" : "onEnabled", this);
let fn = setCallbacks.get(this);
if (fn) {
setCallbacks.delete(this);
fn(val);
}
return val;
}
get permissions() {
return this._userDisabled ? AddonManager.PERM_CAN_ENABLE : AddonManager.PERM_CAN_DISABLE;
}
}
class MockProvider {
constructor(...addons) {
this.addons = new Set(addons);
}
startup() { }
shutdown() { }
getAddonByID(id, callback) {
for (let addon of this.addons) {
if (addon.id == id) {
callback(addon);
return;
}
}
callback(null);
}
getAddonsByTypes(types, callback) {
let addons = [];
if (!types || types.includes("extension")) {
addons = [...this.addons];
}
callback(addons);
}
}
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 promiseSetDisabled(addon) {
return new Promise(resolve => {
setCallbacks.set(addon, resolve);
});
}
add_task(function* () {
// XXX remove this when prompts are enabled by default
yield SpecialPowers.pushPrefEnv({set: [
["extensions.webextPermissionPrompts", true],
]});
// ICON_URL wouldn't ever appear as an actual webextension icon, but
// we're just mocking out the addon here, so all we care about is that
// that it propagates correctly to the popup.
const ICON_URL = "chrome://mozapps/skin/extensions/category-extensions.svg";
const DEFAULT_ICON_URL = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
const ID1 = "addon1@tests.mozilla.org";
let mock1 = new MockAddon({
id: ID1,
name: "Test 1",
userDisabled: true,
seen: false,
userPermissions: {
permissions: ["history"],
hosts: ["https://*/*"],
},
iconURL: ICON_URL,
});
const ID2 = "addon2@tests.mozilla.org";
let mock2 = new MockAddon({
id: ID2,
name: "Test 2",
userDisabled: true,
seen: false,
userPermissions: {
permissions: [],
hosts: [],
},
});
const ID3 = "addon3@tests.mozilla.org";
let mock3 = new MockAddon({
id: ID3,
name: "Test 3",
isWebExtension: true,
userDisabled: true,
seen: false,
userPermissions: {
permissions: [],
hosts: ["<all_urls>"],
}
});
const ID4 = "addon4@tests.mozilla.org";
let mock4 = new MockAddon({
id: ID4,
name: "Test 4",
isWebExtension: true,
userDisabled: true,
seen: false,
userPermissions: {
permissions: [],
hosts: ["<all_urls>"],
}
});
let provider = new MockProvider(mock1, mock2, mock3, mock4);
AddonManagerPrivate.registerProvider(provider, [{
id: "extension",
name: "Extensions",
uiPriority: 4000,
flags: AddonManager.TYPE_UI_VIEW_LIST |
AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL,
}]);
registerCleanupFunction(function*() {
AddonManagerPrivate.unregisterProvider(provider);
// clear out ExtensionsUI state about sideloaded extensions so
// subsequent tests don't get confused.
ExtensionsUI.sideloaded.clear();
ExtensionsUI.emit("change");
});
// Navigate away from the starting page to force about:addons to load
// in a new tab during the tests below.
gBrowser.selectedBrowser.loadURI("about:robots");
yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
registerCleanupFunction(function*() {
// Return to about:blank when we're done
gBrowser.selectedBrowser.loadURI("about:blank");
yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
});
let changePromise = new Promise(resolve => {
ExtensionsUI.on("change", function listener() {
ExtensionsUI.off("change", listener);
resolve();
});
});
ExtensionsUI._checkForSideloaded();
yield changePromise;
// Check for the addons badge on the hamburger menu
let menuButton = document.getElementById("PanelUI-menu-button");
is(menuButton.getAttribute("badge-status"), "addon-alert", "Should have addon alert badge");
// Find the menu entries for sideloaded extensions
yield PanelUI.show();
let addons = document.getElementById("PanelUI-footer-addons");
is(addons.children.length, 4, "Have 4 menu entries for sideloaded extensions");
// Click the first sideloaded extension
let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
addons.children[0].click();
// When we get the permissions prompt, we should be at the extensions
// list in about:addons
let panel = yield popupPromise;
is(gBrowser.currentURI.spec, "about:addons", "Foreground tab is at about:addons");
const VIEW = "addons://list/extension";
let win = gBrowser.selectedBrowser.contentWindow;
ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
// Check the contents of the notification, then choose "Cancel"
let icon = panel.getAttribute("icon");
is(icon, ICON_URL, "Permissions notification has the addon icon");
let disablePromise = promiseSetDisabled(mock1);
panel.secondaryButton.click();
let value = yield disablePromise;
is(value, true, "Addon should remain disabled");
let [addon1, addon2, addon3, addon4] = yield AddonManager.getAddonsByIDs([ID1, ID2, ID3, ID4]);
ok(addon1.seen, "Addon should be marked as seen");
is(addon1.userDisabled, true, "Addon 1 should still be disabled");
is(addon2.userDisabled, true, "Addon 2 should still be disabled");
is(addon3.userDisabled, true, "Addon 3 should still be disabled");
is(addon4.userDisabled, true, "Addon 4 should still be disabled");
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
// Should still have 3 entries in the hamburger menu
yield PanelUI.show();
addons = document.getElementById("PanelUI-footer-addons");
is(addons.children.length, 3, "Have 3 menu entries for sideloaded extensions");
// Click the second sideloaded extension and wait for the notification
popupPromise = promisePopupNotificationShown("addon-webext-permissions");
addons.children[0].click();
panel = yield popupPromise;
// Again we should be at the extentions list in about:addons
is(gBrowser.currentURI.spec, "about:addons", "Foreground tab is at about:addons");
win = gBrowser.selectedBrowser.contentWindow;
ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
// Check the notification contents, this time accept the install
icon = panel.getAttribute("icon");
is(icon, DEFAULT_ICON_URL, "Permissions notification has the default icon");
disablePromise = promiseSetDisabled(mock2);
panel.button.click();
value = yield disablePromise;
is(value, false, "Addon should be set to enabled");
[addon1, addon2, addon3, addon4] = yield AddonManager.getAddonsByIDs([ID1, ID2, ID3, ID4]);
is(addon1.userDisabled, true, "Addon 1 should still be disabled");
is(addon2.userDisabled, false, "Addon 2 should now be enabled");
is(addon3.userDisabled, true, "Addon 3 should still be disabled");
is(addon4.userDisabled, true, "Addon 4 should still be disabled");
// Should still have 2 entries in the hamburger menu
yield PanelUI.show();
addons = document.getElementById("PanelUI-footer-addons");
is(addons.children.length, 2, "Have 2 menu entries for sideloaded extensions");
// Close the hamburger menu and go directly to the addons manager
yield PanelUI.hide();
win = yield BrowserOpenAddonsMgr(VIEW);
let list = win.document.getElementById("addon-list");
// Make sure XBL bindings are applied
list.clientHeight;
let item = list.children.find(_item => _item.value == ID3);
ok(item, "Found entry for sideloaded extension in about:addons");
item.scrollIntoView({behavior: "instant"});
ok(is_visible(item._enableBtn), "Enable button is visible for sideloaded extension");
ok(is_hidden(item._disableBtn), "Disable button is not visible for sideloaded extension");
// When clicking enable we should see the permissions notification
popupPromise = promisePopupNotificationShown("addon-webext-permissions");
BrowserTestUtils.synthesizeMouseAtCenter(item._enableBtn, {},
gBrowser.selectedBrowser);
panel = yield popupPromise;
// Accept the permissions
disablePromise = promiseSetDisabled(mock3);
panel.button.click();
value = yield disablePromise;
is(value, false, "userDisabled should be set on addon 3");
addon3 = yield AddonManager.getAddonByID(ID3);
is(addon3.userDisabled, false, "Addon 3 should be enabled");
// Should still have 1 entry in the hamburger menu
yield PanelUI.show();
addons = document.getElementById("PanelUI-footer-addons");
is(addons.children.length, 1, "Have 1 menu entry for sideloaded extensions");
// Close the hamburger menu and go to the detail page for this addon
yield PanelUI.hide();
win = yield BrowserOpenAddonsMgr(`addons://detail/${encodeURIComponent(ID4)}`);
let button = win.document.getElementById("detail-enable-btn");
// When clicking enable we should see the permissions notification
popupPromise = promisePopupNotificationShown("addon-webext-permissions");
BrowserTestUtils.synthesizeMouseAtCenter(button, {},
gBrowser.selectedBrowser);
panel = yield popupPromise;
// Accept the permissions
disablePromise = promiseSetDisabled(mock4);
panel.button.click();
value = yield disablePromise;
is(value, false, "userDisabled should be set on addon 4");
addon4 = yield AddonManager.getAddonByID(ID4);
is(addon4.userDisabled, false, "Addon 4 should be enabled");
isnot(menuButton.getAttribute("badge-status"), "addon-alert", "Should no longer have addon alert badge");
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
});