Bug 1784292 - Anchor extension popups to the unified extensions button. r=mconley,mixedpuppy

Differential Revision: https://phabricator.services.mozilla.com/D154408
This commit is contained in:
William Durand 2022-08-19 06:25:13 +00:00
parent 1160e6a61a
commit cb31c16d87
6 changed files with 282 additions and 44 deletions

View File

@ -270,8 +270,6 @@ var gXPInstallObserver = {
return;
}
const anchorID = "addons-notification-icon";
// Make notifications persistent
var options = {
displayURI: installInfo.originatingURI,
@ -279,6 +277,14 @@ var gXPInstallObserver = {
hideClose: true,
};
if (gUnifiedExtensions.isEnabled) {
options.popupOptions = {
position: "bottomcenter topright",
x: 2,
y: 0,
};
}
let acceptInstallation = () => {
for (let install of installInfo.installs) {
install.install();
@ -422,7 +428,7 @@ var gXPInstallObserver = {
browser,
"addon-install-confirmation",
messageString,
anchorID,
gUnifiedExtensions.getPopupAnchorID(browser, window),
action,
[secondaryAction],
options
@ -496,7 +502,6 @@ var gXPInstallObserver = {
return;
}
const anchorID = "addons-notification-icon";
var messageString, action;
var brandShortName = brandBundle.getString("brandShortName");
@ -509,6 +514,14 @@ var gXPInstallObserver = {
timeout: Date.now() + 30000,
};
if (gUnifiedExtensions.isEnabled) {
options.popupOptions = {
position: "bottomcenter topright",
x: 2,
y: 0,
};
}
switch (aTopic) {
case "addon-install-disabled": {
notificationID = "xpinstall-disabled";
@ -550,7 +563,7 @@ var gXPInstallObserver = {
browser,
notificationID,
messageString,
anchorID,
gUnifiedExtensions.getPopupAnchorID(browser, window),
action,
secondaryActions,
options
@ -597,7 +610,7 @@ var gXPInstallObserver = {
browser,
notificationID,
messageString,
anchorID,
gUnifiedExtensions.getPopupAnchorID(browser, window),
null,
null,
options
@ -728,7 +741,7 @@ var gXPInstallObserver = {
browser,
notificationID,
messageString,
anchorID,
gUnifiedExtensions.getPopupAnchorID(browser, window),
action,
[dontAllowAction, neverAllowAction],
options
@ -793,7 +806,7 @@ var gXPInstallObserver = {
browser,
notificationID,
messageString,
anchorID,
gUnifiedExtensions.getPopupAnchorID(browser, window),
action,
[secondaryAction],
options
@ -876,7 +889,7 @@ var gXPInstallObserver = {
browser,
notificationID,
messageString,
anchorID,
gUnifiedExtensions.getPopupAnchorID(browser, window),
action,
null,
options
@ -950,7 +963,7 @@ var gXPInstallObserver = {
browser,
notificationID,
messageString,
anchorID,
gUnifiedExtensions.getPopupAnchorID(browser, window),
action,
secondaryActions,
options
@ -1291,22 +1304,46 @@ var gUnifiedExtensions = {
return;
}
const unifiedExtensionsEnabled = Services.prefs.getBoolPref(
"extensions.unifiedExtensions.enabled",
false
);
if (unifiedExtensionsEnabled) {
if (this.isEnabled) {
MozXULElement.insertFTLIfNeeded("preview/unifiedExtensions.ftl");
this._button = document.getElementById("unified-extensions-button");
// TODO: Bug 1778684 - Auto-hide button when there is no active extension.
this._button.hidden = !unifiedExtensionsEnabled;
this._button.hidden = false;
}
this._initialized = true;
},
get isEnabled() {
return Services.prefs.getBoolPref(
"extensions.unifiedExtensions.enabled",
false
);
},
getPopupAnchorID(aBrowser, aWindow) {
if (this.isEnabled) {
const anchorID = "unified-extensions-button";
const attr = anchorID + "popupnotificationanchor";
if (!aBrowser[attr]) {
// A hacky way of setting the popup anchor outside the usual url bar
// icon box, similar to how it was done for CFR.
// See: https://searchfox.org/mozilla-central/rev/847b64cc28b74b44c379f9bff4f415b97da1c6d7/toolkit/modules/PopupNotifications.jsm#42
aBrowser[attr] = aWindow.document.getElementById(
anchorID
// Anchor on the toolbar icon to position the popup right below the
// button.
).firstElementChild;
}
return anchorID;
}
return "addons-notification-icon";
},
get button() {
return this._button;
},

View File

@ -403,3 +403,4 @@ https_first_disabled = true
[browser_unified_extensions.js]
[browser_unified_extensions_accessibility.js]
[browser_unified_extensions_context_menu.js]
[browser_unified_extensions_doorhangers.js]

View File

@ -0,0 +1,100 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
loadTestSubscript("head_unified_extensions.js");
let win;
add_setup(async function() {
// Only load a new window with the unified extensions feature enabled once to
// speed up the execution of this test file.
win = await promiseEnableUnifiedExtensions();
registerCleanupFunction(async () => {
await BrowserTestUtils.closeWindow(win);
});
});
const verifyPermissionsPrompt = async (win, expectedAnchorID) => {
const ext = ExtensionTestUtils.loadExtension({
manifest: {
optional_permissions: ["history"],
},
background: async () => {
await browser.tabs.create({
url: browser.runtime.getURL("content.html"),
active: true,
});
},
files: {
"content.html": `<!DOCTYPE html><script src="content.js"></script>`,
"content.js": async () => {
browser.test.onMessage.addListener(async msg => {
browser.test.assertEq(
msg,
"grant-permission",
"expected message to grant permission"
);
const granted = await new Promise(resolve => {
browser.test.withHandlingUserInput(() => {
resolve(
browser.permissions.request({ permissions: ["history"] })
);
});
});
browser.test.assertTrue(granted, "permission request succeeded");
browser.test.sendMessage("ok");
});
browser.test.sendMessage("ready");
},
},
});
await BrowserTestUtils.withNewTab({ gBrowser: win.gBrowser }, async () => {
await ext.startup();
await ext.awaitMessage("ready");
const popupPromise = promisePopupNotificationShown(
"addon-webext-permissions",
win
);
ext.sendMessage("grant-permission");
const panel = await popupPromise;
const notification = win.PopupNotifications.getNotification(
"addon-webext-permissions"
);
ok(notification, "expected notification");
is(
// We access the parent element because the anchor is on the icon, not on
// the unified extensions button itself.
notification.anchorElement.id ||
notification.anchorElement.parentElement.id,
expectedAnchorID,
"expected the right anchor ID"
);
panel.button.click();
await ext.awaitMessage("ok");
await ext.unload();
});
};
add_task(async function test_permissions_prompt_with_pref_enabled() {
await verifyPermissionsPrompt(win, "unified-extensions-button");
});
add_task(async function test_permissions_prompt_with_pref_disabled() {
const anotherWindow = await promiseDisableUnifiedExtensions();
await verifyPermissionsPrompt(anotherWindow, "addons-notification-icon");
await BrowserTestUtils.closeWindow(anotherWindow);
});

View File

@ -421,7 +421,7 @@ var ExtensionsUI = {
return false;
}
let popupOptions = {
let options = {
hideClose: true,
popupIconURL: icon || DEFAULT_EXTENSION_ICON,
popupIconClass: icon ? "" : "addon-warning-icon",
@ -454,14 +454,25 @@ var ExtensionsUI = {
},
];
if (browser.ownerGlobal.gUnifiedExtensions.isEnabled) {
options.popupOptions = {
position: "bottomcenter topright",
x: 2,
y: 0,
};
}
window.PopupNotifications.show(
browser,
"addon-webext-permissions",
strings.header,
"addons-notification-icon",
browser.ownerGlobal.gUnifiedExtensions.getPopupAnchorID(
browser,
window
),
action,
secondaryActions,
popupOptions
options
);
});
@ -472,7 +483,7 @@ var ExtensionsUI = {
showDefaultSearchPrompt(target, strings, icon) {
return new Promise(resolve => {
let popupOptions = {
let options = {
hideClose: true,
popupIconURL: icon || DEFAULT_EXTENSION_ICON,
persistent: true,
@ -503,14 +514,26 @@ var ExtensionsUI = {
];
let { browser, window } = getTabBrowser(target);
if (browser.ownerGlobal.gUnifiedExtensions.isEnabled) {
options.popupOptions = {
position: "bottomcenter topright",
x: 2,
y: 0,
};
}
window.PopupNotifications.show(
browser,
"addon-webext-defaultsearch",
strings.text,
"addons-notification-icon",
browser.ownerGlobal.gUnifiedExtensions.getPopupAnchorID(
browser,
window
),
action,
secondaryActions,
popupOptions
options
);
});
},

View File

@ -528,6 +528,9 @@ PopupNotifications.prototype = {
* extraAttr:
* An optional string value which will be given to the
* extraAttr attribute on the notification's anchorElement
* popupOptions:
* An optional object containing popup options passed to
* `openPopup()` when defined.
* @returns the Notification object corresponding to the added notification.
*/
show: function PopupNotifications_show(
@ -1336,7 +1339,14 @@ PopupNotifications.prototype = {
this._popupshownListener = this._popupshownListener.bind(this);
target.addEventListener("popupshown", this._popupshownListener, true);
this.panel.openPopup(anchorElement, "bottomcenter topleft", 0, 0);
let popupOptions = notificationsToShow.findLast(
n => n.options?.popupOptions
)?.options?.popupOptions;
if (popupOptions) {
this.panel.openPopup(anchorElement, popupOptions);
} else {
this.panel.openPopup(anchorElement, "bottomcenter topleft", 0, 0);
}
});
},

View File

@ -61,7 +61,9 @@ function getObserverTopic(aNotificationId) {
async function waitForProgressNotification(
aPanelOpen = false,
aExpectedCount = 1,
wantDisabled = true
wantDisabled = true,
expectedAnchorID = "addons-notification-icon",
win = window
) {
let notificationId = PROGRESS_NOTIFICATION;
info("Waiting for " + notificationId + " notification");
@ -87,7 +89,7 @@ async function waitForProgressNotification(
panelEventPromise = Promise.resolve();
} else {
panelEventPromise = new Promise(resolve => {
PopupNotifications.panel.addEventListener(
win.PopupNotifications.panel.addEventListener(
"popupshowing",
function() {
resolve();
@ -102,14 +104,14 @@ async function waitForProgressNotification(
await waitForTick();
info("Saw a notification");
ok(PopupNotifications.isPanelOpen, "Panel should be open");
ok(win.PopupNotifications.isPanelOpen, "Panel should be open");
is(
PopupNotifications.panel.childNodes.length,
win.PopupNotifications.panel.childNodes.length,
aExpectedCount,
"Should be the right number of notifications"
);
if (PopupNotifications.panel.childNodes.length) {
let nodes = Array.from(PopupNotifications.panel.childNodes);
if (win.PopupNotifications.panel.childNodes.length) {
let nodes = Array.from(win.PopupNotifications.panel.childNodes);
let notification = nodes.find(
n => n.id == notificationId + "-notification"
);
@ -119,9 +121,16 @@ async function waitForProgressNotification(
wantDisabled,
"The install button should be disabled?"
);
let n = win.PopupNotifications.getNotification(PROGRESS_NOTIFICATION);
is(
n?.anchorElement?.id || n?.anchorElement?.parentElement?.id,
expectedAnchorID,
"expected the right anchor ID"
);
}
return PopupNotifications.panel;
return win.PopupNotifications.panel;
}
function acceptAppMenuNotificationWhenShown(
@ -205,7 +214,12 @@ function acceptAppMenuNotificationWhenShown(
});
}
async function waitForNotification(aId, aExpectedCount = 1) {
async function waitForNotification(
aId,
aExpectedCount = 1,
expectedAnchorID = "addons-notification-icon",
win = window
) {
info("Waiting for " + aId + " notification");
let topic = getObserverTopic(aId);
@ -228,14 +242,14 @@ async function waitForNotification(aId, aExpectedCount = 1) {
}
let panelEventPromise = new Promise(resolve => {
PopupNotifications.panel.addEventListener(
win.PopupNotifications.panel.addEventListener(
"PanelUpdated",
function eventListener(e) {
// Skip notifications that are not the one that we are supposed to be looking for
if (!e.detail.includes(aId)) {
return;
}
PopupNotifications.panel.removeEventListener(
win.PopupNotifications.panel.removeEventListener(
"PanelUpdated",
eventListener
);
@ -249,20 +263,27 @@ async function waitForNotification(aId, aExpectedCount = 1) {
await waitForTick();
info("Saw a " + aId + " notification");
ok(PopupNotifications.isPanelOpen, "Panel should be open");
ok(win.PopupNotifications.isPanelOpen, "Panel should be open");
is(
PopupNotifications.panel.childNodes.length,
win.PopupNotifications.panel.childNodes.length,
aExpectedCount,
"Should be the right number of notifications"
);
if (PopupNotifications.panel.childNodes.length) {
let nodes = Array.from(PopupNotifications.panel.childNodes);
if (win.PopupNotifications.panel.childNodes.length) {
let nodes = Array.from(win.PopupNotifications.panel.childNodes);
let notification = nodes.find(n => n.id == aId + "-notification");
ok(notification, "Should have seen the " + aId + " notification");
}
await SimpleTest.promiseFocus(PopupNotifications.window);
return PopupNotifications.panel;
let n = win.PopupNotifications.getNotification(aId);
is(
n?.anchorElement?.id || n?.anchorElement?.parentElement?.id,
expectedAnchorID,
"expected the right anchor ID"
);
}
await SimpleTest.promiseFocus(win.PopupNotifications.window);
return win.PopupNotifications.panel;
}
function waitForNotificationClose() {
@ -431,6 +452,7 @@ var TESTS = [
await addon.uninstall();
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
await SpecialPowers.popPrefEnv();
},
async function test_blockedInstallDomain() {
@ -1198,7 +1220,10 @@ var TESTS = [
async function test_failedSecurity() {
SpecialPowers.pushPrefEnv({
set: [[PREF_INSTALL_REQUIREBUILTINCERTS, false]],
set: [
[PREF_INSTALL_REQUIREBUILTINCERTS, false],
["extensions.postDownloadThirdPartyPrompt", false],
],
});
setupRedirect({
@ -1334,7 +1359,6 @@ var TESTS = [
await addon.uninstall();
await removeTabAndWaitForNotificationClose();
await SpecialPowers.popPrefEnv();
},
async function test_incognito_checkbox_new_window() {
@ -1428,9 +1452,52 @@ var TESTS = [
await addon.uninstall();
await SpecialPowers.popPrefEnv();
await BrowserTestUtils.closeWindow(win);
},
async function test_blockedInstallDomain_with_unified_extensions() {
SpecialPowers.pushPrefEnv({
set: [["extensions.unifiedExtensions.enabled", true]],
});
let win = await BrowserTestUtils.openNewBrowserWindow();
await SimpleTest.promiseFocus(win);
await new Promise(resolve => {
win.requestIdleCallback(resolve);
});
await TestUtils.waitForCondition(
() => win.gUnifiedExtensions._initialized,
"Wait gUnifiedExtensions to have been initialized"
);
let progressPromise = waitForProgressNotification(
false,
1,
true,
"unified-extensions-button",
win
);
let notificationPromise = waitForNotification(
"addon-install-failed",
1,
"unified-extensions-button",
win
);
let triggers = encodeURIComponent(
JSON.stringify({
XPI: TESTROOT2 + "webmidi_permission.xpi",
})
);
BrowserTestUtils.openNewForegroundTab(
win.gBrowser,
TESTROOT + "installtrigger.html?" + triggers
);
await progressPromise;
await notificationPromise;
await BrowserTestUtils.closeWindow(win);
await SpecialPowers.popPrefEnv();
},
];
var gTestStart = null;