mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-02 10:00:54 +00:00
Bug 1580962 - Show a SUMO link when an add-on can't be removed r=rpl,fluent-reviewers,flod
Differential Revision: https://phabricator.services.mozilla.com/D48128 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
8a4e19f7d0
commit
be025da362
@ -393,6 +393,8 @@ addon-options-button =
|
||||
## Add-on actions
|
||||
report-addon-button = Report
|
||||
remove-addon-button = Remove
|
||||
# The link will always be shown after the other text.
|
||||
remove-addon-disabled-button = Can’t Be Removed <a data-l10n-name="link">Why?</a>
|
||||
disable-addon-button = Disable
|
||||
enable-addon-button = Enable
|
||||
preferences-addon-button =
|
||||
|
@ -235,7 +235,7 @@
|
||||
|
||||
<template name="panel-item">
|
||||
<link rel="stylesheet" href="chrome://mozapps/content/extensions/panel-item.css">
|
||||
<button><slot></slot></button>
|
||||
<button><slot></slot></button><slot name="support-link"></slot>
|
||||
</template>
|
||||
|
||||
<template name="taar-notice">
|
||||
|
@ -715,26 +715,22 @@ class PanelList extends HTMLElement {
|
||||
// If the menu is opened with the mouse, the active element might be
|
||||
// somewhere else in the document. In that case we should ignore it
|
||||
// to avoid walking unrelated DOM nodes.
|
||||
this.walker.currentNode = this.contains(document.activeElement)
|
||||
this.focusWalker.currentNode = this.contains(document.activeElement)
|
||||
? document.activeElement
|
||||
: this;
|
||||
let nextItem = moveForward
|
||||
? this.walker.nextNode()
|
||||
: this.walker.previousNode();
|
||||
? this.focusWalker.nextNode()
|
||||
: this.focusWalker.previousNode();
|
||||
|
||||
// If the next item wasn't found, try looping to the top/bottom.
|
||||
if (!nextItem) {
|
||||
this.walker.currentNode = this;
|
||||
this.focusWalker.currentNode = this;
|
||||
if (moveForward) {
|
||||
nextItem = this.walker.firstChild();
|
||||
nextItem = this.focusWalker.firstChild();
|
||||
} else {
|
||||
nextItem = this.walker.lastChild();
|
||||
nextItem = this.focusWalker.lastChild();
|
||||
}
|
||||
}
|
||||
|
||||
if (nextItem) {
|
||||
nextItem.focus();
|
||||
}
|
||||
break;
|
||||
} else if (e.key === "Escape") {
|
||||
let { triggeringEvent } = this;
|
||||
@ -768,19 +764,44 @@ class PanelList extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
get walker() {
|
||||
if (!this._walker) {
|
||||
this._walker = document.createTreeWalker(this, NodeFilter.SHOW_ELEMENT, {
|
||||
acceptNode: node => {
|
||||
if (node.disabled || node.hidden || node.localName !== "panel-item") {
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
/**
|
||||
* A TreeWalker that can be used to focus elements. The returned element will
|
||||
* be the element that has gained focus based on the requested movement
|
||||
* through the tree.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* this.focusWalker.currentNode = this;
|
||||
* // Focus and get the first focusable child.
|
||||
* let focused = this.focusWalker.nextNode();
|
||||
* // Focus the second focusable child.
|
||||
* this.focusWalker.nextNode();
|
||||
*/
|
||||
get focusWalker() {
|
||||
if (!this._focusWalker) {
|
||||
this._focusWalker = document.createTreeWalker(
|
||||
this,
|
||||
NodeFilter.SHOW_ELEMENT,
|
||||
{
|
||||
acceptNode: node => {
|
||||
// No need to look at hidden nodes.
|
||||
if (node.hidden) {
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
},
|
||||
});
|
||||
// Focus the node, if it worked then this is the node we want.
|
||||
node.focus();
|
||||
if (node === document.activeElement) {
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
}
|
||||
|
||||
// Continue into child nodes if the parent couldn't be focused.
|
||||
return NodeFilter.FILTER_SKIP;
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
return this._walker;
|
||||
return this._focusWalker;
|
||||
}
|
||||
|
||||
async onShow() {
|
||||
@ -797,11 +818,8 @@ class PanelList extends HTMLElement {
|
||||
triggeringEvent &&
|
||||
triggeringEvent.mozInputSource === MouseEvent.MOZ_SOURCE_KEYBOARD
|
||||
) {
|
||||
this.walker.currentNode = this;
|
||||
let firstItem = this.walker.nextNode();
|
||||
if (firstItem) {
|
||||
firstItem.focus();
|
||||
}
|
||||
this.focusWalker.currentNode = this;
|
||||
this.focusWalker.nextNode();
|
||||
}
|
||||
|
||||
this.sendEvent("shown");
|
||||
@ -895,7 +913,27 @@ class AddonOptions extends HTMLElement {
|
||||
setElementState(el, card, addon, updateInstall) {
|
||||
switch (el.getAttribute("action")) {
|
||||
case "remove":
|
||||
el.hidden = !hasPermission(addon, "uninstall");
|
||||
if (hasPermission(addon, "uninstall")) {
|
||||
// Regular add-on that can be uninstalled.
|
||||
el.disabled = false;
|
||||
el.hidden = false;
|
||||
document.l10n.setAttributes(el, "remove-addon-button");
|
||||
} else if (addon.isBuiltin) {
|
||||
// Likely the built-in themes, can't be removed, that's fine.
|
||||
el.hidden = true;
|
||||
} else {
|
||||
// Likely sideloaded, mention that it can't be removed with a link.
|
||||
el.hidden = false;
|
||||
el.disabled = true;
|
||||
if (!el.querySelector('[slot="support-link"]')) {
|
||||
let link = document.createElement("a", { is: "support-link" });
|
||||
link.setAttribute("data-l10n-name", "link");
|
||||
link.setAttribute("support-page", "cant-remove-addon");
|
||||
link.setAttribute("slot", "support-link");
|
||||
el.appendChild(link);
|
||||
document.l10n.setAttributes(el, "remove-addon-disabled-button");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "report":
|
||||
el.hidden = !isAbuseReportSupported(addon);
|
||||
|
@ -1,3 +1,12 @@
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
::slotted(a) {
|
||||
margin-inline-end: 12px;
|
||||
}
|
||||
|
||||
:host([checked]) {
|
||||
--icon: url("chrome://global/skin/icons/check.svg");
|
||||
-moz-context-properties: fill;
|
||||
|
@ -8,6 +8,11 @@ AddonTestUtils.initMochitest(this);
|
||||
|
||||
let promptService;
|
||||
|
||||
const SUPPORT_URL = Services.urlFormatter.formatURL(
|
||||
Services.prefs.getStringPref("app.support.baseURL")
|
||||
);
|
||||
const REMOVE_SUMO_URL = SUPPORT_URL + "cant-remove-addon";
|
||||
|
||||
const SECTION_INDEXES = {
|
||||
enabled: 0,
|
||||
disabled: 1,
|
||||
@ -34,7 +39,10 @@ function waitForThemeChange(list) {
|
||||
return BrowserTestUtils.waitForEvent(list, "move", () => ++moveCount == 2);
|
||||
}
|
||||
|
||||
add_task(async function enableHtmlViews() {
|
||||
let mockProvider;
|
||||
|
||||
add_task(async function setup() {
|
||||
mockProvider = new MockProvider();
|
||||
promptService = mockPromptService();
|
||||
Services.telemetry.clearEvents();
|
||||
});
|
||||
@ -128,6 +136,9 @@ add_task(async function testExtensionList() {
|
||||
"remove-addon-button",
|
||||
"The button has the remove label"
|
||||
);
|
||||
// There is a support link when the add-on isn't removeable, verify we don't
|
||||
// always include one.
|
||||
ok(!removeButton.querySelector("a"), "There isn't a link in the item");
|
||||
|
||||
// Remove but cancel.
|
||||
let cancelled = BrowserTestUtils.waitForEvent(card, "remove-cancelled");
|
||||
@ -736,6 +747,48 @@ add_task(async function testBuiltInThemeButtons() {
|
||||
await closeView(win);
|
||||
});
|
||||
|
||||
add_task(async function testSideloadRemoveButton() {
|
||||
const id = "sideload@mochi.test";
|
||||
mockProvider.createAddons([
|
||||
{
|
||||
id,
|
||||
name: "Sideloaded",
|
||||
permissions: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
let win = await loadInitialView("extension");
|
||||
let doc = win.document;
|
||||
|
||||
let card = getCardByAddonId(doc, id);
|
||||
|
||||
let moreOptionsPanel = card.querySelector("panel-list");
|
||||
let panelOpened = BrowserTestUtils.waitForEvent(moreOptionsPanel, "shown");
|
||||
moreOptionsPanel.show();
|
||||
await panelOpened;
|
||||
|
||||
// Verify the remove button is visible with a SUMO link.
|
||||
let removeButton = card.querySelector('[action="remove"]');
|
||||
ok(removeButton.disabled, "Remove is disabled");
|
||||
ok(!removeButton.hidden, "Remove is visible");
|
||||
|
||||
let sumoLink = removeButton.querySelector("a");
|
||||
ok(sumoLink, "There's a link");
|
||||
is(
|
||||
doc.l10n.getAttributes(removeButton).id,
|
||||
"remove-addon-disabled-button",
|
||||
"The can't remove text is shown"
|
||||
);
|
||||
sumoLink.focus();
|
||||
is(doc.activeElement, sumoLink, "The link can be focused");
|
||||
|
||||
let newTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, REMOVE_SUMO_URL);
|
||||
sumoLink.click();
|
||||
BrowserTestUtils.removeTab(await newTabOpened);
|
||||
|
||||
await closeView(win);
|
||||
});
|
||||
|
||||
add_task(async function testOnlyTypeIsShown() {
|
||||
let win = await loadInitialView("theme");
|
||||
let doc = win.document;
|
||||
@ -809,8 +862,6 @@ add_task(async function testExtensionGenericIcon() {
|
||||
});
|
||||
|
||||
add_task(async function testSectionHeadingKeys() {
|
||||
let mockProvider = new MockProvider();
|
||||
|
||||
mockProvider.createAddons([
|
||||
{
|
||||
id: "test-theme",
|
||||
|
Loading…
Reference in New Issue
Block a user