Bug 1438363 - Show a doorhanger when an extension first hides a tab r?aswan,dao
MozReview-Commit-ID: DQCr3SSaZTV
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -728,9 +728,25 @@
secondarybuttonaccesskey="&homepageControlled.disableButton.accesskey;"
closebuttonhidden="true"
dropmarkerhidden="true"
checkboxhidden="true">
<popupnotificationcontent orient="vertical">
<description id="extension-homepage-notification-description"/>
</popupnotificationcontent>
</popupnotification>
+ <popupnotification id="extension-tab-hide-notification"
+ class="extension-controlled-notification"
+ popupid="extension-tab-hide"
+ hidden="true"
+ label="&tabHideControlled.header.message;"
+ buttonlabel="&tabHideControlled.keepButton.label;"
+ buttonaccesskey="&tabHideControlled.keepButton.accesskey;"
+ secondarybuttonlabel="&tabHideControlled.disableButton.label;"
+ secondarybuttonaccesskey="&tabHideControlled.disableButton.accesskey;"
+ closebuttonhidden="true"
+ dropmarkerhidden="true"
+ checkboxhidden="true">
+ <popupnotificationcontent orient="vertical">
+ <description id="extension-tab-hide-notification-description"/>
+ </popupnotificationcontent>
+ </popupnotification>
</panel>
--- a/browser/components/extensions/ExtensionControlledPopup.jsm
+++ b/browser/components/extensions/ExtensionControlledPopup.jsm
@@ -44,30 +44,38 @@ class ExtensionControlledPopup {
* @param {object} opts Options for configuring popup.
* @param {string} opts.confirmedType
* The type to use for storing a user's confirmation in
* ExtensionSettingsStore.
* @param {string} opts.observerTopic
* An observer topic to trigger the popup on with Services.obs. If the
* doorhanger should appear on a specific window include it as the
* subject in the observer event.
+ * @param {string} opts.anchorId
+ * The id to anchor the popupnotification on. If it is not provided
+ * then it will anchor to a browser action or the app menu.
* @param {string} opts.popupnotificationId
* The id for the popupnotification element in the markup. This
* element should be defined in panelUI.inc.xul.
* @param {string} opts.settingType
* The setting type to check in ExtensionSettingsStore to retrieve
* the controlling extension.
* @param {string} opts.settingKey
* The setting key to check in ExtensionSettingsStore to retrieve
* the controlling extension.
* @param {string} opts.descriptionId
* The id of the element where the description should be displayed.
* @param {string} opts.descriptionMessageId
* The message id to be used for the description. The translated
* string will have the add-on's name and icon injected into it.
+ * @param {string} opts.getLocalizedDescription
+ * A function to get the localized message string. This
+ * function is passed doc, message and addonDetails (the
+ * add-on's icon and name). If not provided, then the add-on's
+ * icon and name are added to the description.
* @param {string} opts.learnMoreMessageId
* The message id to be used for the text of a "learn more" link which
* will be placed after the description.
* @param {string} opts.learnMoreLink
* The name of the SUMO page to link to, this is added to
* app.support.baseURL.
* @param {function} opts.onObserverAdded
* A callback that is triggered when an observer is registered to
@@ -81,21 +89,23 @@ class ExtensionControlledPopup {
* user decides to disable the extension. If this function is async
* then the extension won't be disabled until it is fulfilled.
* This function gets two arguments, the ExtensionControlledPopup
* instance for the panel and the window that the popup appears on.
*/
constructor(opts) {
this.confirmedType = opts.confirmedType;
this.observerTopic = opts.observerTopic;
+ this.anchorId = opts.anchorId;
this.popupnotificationId = opts.popupnotificationId;
this.settingType = opts.settingType;
this.settingKey = opts.settingKey;
this.descriptionId = opts.descriptionId;
this.descriptionMessageId = opts.descriptionMessageId;
+ this.getLocalizedDescription = opts.getLocalizedDescription;
this.learnMoreMessageId = opts.learnMoreMessageId;
this.learnMoreLink = opts.learnMoreLink;
this.onObserverAdded = opts.onObserverAdded;
this.onObserverRemoved = opts.onObserverRemoved;
this.beforeDisableAddon = opts.beforeDisableAddon;
this.observerRegistered = false;
}
@@ -145,58 +155,65 @@ class ExtensionControlledPopup {
Services.obs.addObserver(this, this.observerTopic);
this.observerRegistered = true;
if (this.onObserverAdded) {
this.onObserverAdded();
}
}
}
- async open(targetWindow) {
+ // The extensionId will be looked up in ExtensionSettingsStore if it is not
+ // provided using this.settingType and this.settingKey.
+ async open(targetWindow, extensionId) {
await ExtensionSettingsStore.initialize();
// Remove the observer since it would open the same dialog again the next time
// the observer event fires.
this.removeObserver();
- let item = ExtensionSettingsStore.getSetting(
- this.settingType, this.settingKey);
+ if (!extensionId) {
+ let item = ExtensionSettingsStore.getSetting(
+ this.settingType, this.settingKey);
+ extensionId = item && item.id;
+ }
// The item should have an extension and the user shouldn't have confirmed
// the change here, but just to be sure check that it is still controlled
// and the user hasn't already confirmed the change.
// If there is no id, then the extension is no longer in control.
- if (!item || !item.id || this.userHasConfirmed(item.id)) {
+ if (!extensionId || this.userHasConfirmed(extensionId)) {
return;
}
// Find the elements we need.
let win = targetWindow || this.topWindow;
let doc = win.document;
let panel = doc.getElementById("extension-notification-panel");
let popupnotification = doc.getElementById(this.popupnotificationId);
let urlBarWasFocused = win.gURLBar.focused;
if (!popupnotification) {
throw new Error(`No popupnotification found for id "${this.popupnotificationId}"`);
}
- let addon = await AddonManager.getAddonByID(item.id);
+ let addon = await AddonManager.getAddonByID(extensionId);
this.populateDescription(doc, addon);
// Setup the command handler.
let handleCommand = async (event) => {
panel.hidePopup();
if (event.originalTarget.getAttribute("anonid") == "button") {
// Main action is to keep changes.
- await this.setConfirmation(item.id);
+ await this.setConfirmation(extensionId);
} else {
// Secondary action is to restore settings.
- await this.beforeDisableAddon(this, win);
+ if (this.beforeDisableAddon) {
+ await this.beforeDisableAddon(this, win);
+ }
addon.userDisabled = true;
}
// If the page this is appearing on is the New Tab page then the URL bar may
// have been focused when the doorhanger stole focus away from it. Once an
// action is taken the focus state should be restored to what the user was
// expecting.
if (urlBarWasFocused) {
@@ -204,27 +221,33 @@ class ExtensionControlledPopup {
}
};
panel.addEventListener("command", handleCommand);
panel.addEventListener("popuphidden", () => {
popupnotification.hidden = true;
panel.removeEventListener("command", handleCommand);
}, {once: true});
- // Look for a browserAction on the toolbar.
- let action = CustomizableUI.getWidget(
- `${makeWidgetId(item.id)}-browser-action`);
- if (action) {
- action = action.areaType == "toolbar" && action.forWindow(win).node;
+ let anchorButton;
+ if (this.anchorId) {
+ // If there's an anchorId, use that right away.
+ anchorButton = doc.getElementById(this.anchorId);
+ } else {
+ // Look for a browserAction on the toolbar.
+ let action = CustomizableUI.getWidget(
+ `${makeWidgetId(extensionId)}-browser-action`);
+ if (action) {
+ action = action.areaType == "toolbar" && action.forWindow(win).node;
+ }
+
+ // Anchor to a toolbar browserAction if found, otherwise use the menu button.
+ anchorButton = action || doc.getElementById("PanelUI-menu-button");
}
-
- // Anchor to a toolbar browserAction if found, otherwise use the menu button.
let anchor = doc.getAnonymousElementByAttribute(
- action || doc.getElementById("PanelUI-menu-button"),
- "class", "toolbarbutton-icon");
+ anchorButton, "class", "toolbarbutton-icon");
panel.hidden = false;
popupnotification.hidden = false;
panel.openPopup(anchor);
}
getAddonDetails(doc, addon) {
const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
@@ -240,18 +263,23 @@ class ExtensionControlledPopup {
}
populateDescription(doc, addon) {
let description = doc.getElementById(this.descriptionId);
description.textContent = "";
let addonDetails = this.getAddonDetails(doc, addon);
let message = strBundle.GetStringFromName(this.descriptionMessageId);
- description.appendChild(
- BrowserUtils.getLocalizedFragment(doc, message, addonDetails));
+ if (this.getLocalizedDescription) {
+ description.appendChild(
+ this.getLocalizedDescription(doc, message, addonDetails));
+ } else {
+ description.appendChild(
+ BrowserUtils.getLocalizedFragment(doc, message, addonDetails));
+ }
let link = doc.createElement("label");
link.setAttribute("class", "learnMore text-link");
link.href = Services.urlFormatter.formatURLPref("app.support.baseURL") + this.learnMoreLink;
link.textContent = strBundle.GetStringFromName(this.learnMoreMessageId);
description.appendChild(link);
}
}
--- a/browser/components/extensions/parent/ext-tabs.js
+++ b/browser/components/extensions/parent/ext-tabs.js
@@ -1,12 +1,16 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
+ChromeUtils.defineModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+ChromeUtils.defineModuleGetter(this, "ExtensionControlledPopup",
+ "resource:///modules/ExtensionControlledPopup.jsm");
ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
ChromeUtils.defineModuleGetter(this, "PromiseUtils",
"resource://gre/modules/PromiseUtils.jsm");
ChromeUtils.defineModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm");
@@ -16,16 +20,36 @@ XPCOMUtils.defineLazyGetter(this, "strBu
});
var {
ExtensionError,
} = ExtensionUtils;
const TABHIDE_PREFNAME = "extensions.webextensions.tabhide.enabled";
+const TAB_HIDE_CONFIRMED_TYPE = "tabHideNotification";
+
+
+XPCOMUtils.defineLazyGetter(this, "tabHidePopup", () => {
+ return new ExtensionControlledPopup({
+ confirmedType: TAB_HIDE_CONFIRMED_TYPE,
+ anchorId: "alltabs-button",
+ popupnotificationId: "extension-tab-hide-notification",
+ descriptionId: "extension-tab-hide-notification-description",
+ descriptionMessageId: "tabHideControlled.message",
+ getLocalizedDescription: (doc, message, addonDetails) => {
+ let image = doc.createElement("image");
+ image.setAttribute("class", "extension-controlled-icon alltabs-icon");
+ return BrowserUtils.getLocalizedFragment(doc, message, addonDetails, image);
+ },
+ learnMoreMessageId: "tabHideControlled.learnMore",
+ learnMoreLink: "extension-hiding-tabs",
+ });
+});
+
function showHiddenTabs(id) {
let windowsEnum = Services.wm.getEnumerator("navigator:browser");
while (windowsEnum.hasMoreElements()) {
let win = windowsEnum.getNext();
if (win.closed || !win.gBrowser) {
continue;
}
@@ -308,16 +332,21 @@ this.tabs = class extends ExtensionAPI {
static onUpdate(id, manifest) {
if (!manifest.permissions || !manifest.permissions.includes("tabHide")) {
showHiddenTabs(id);
}
}
static onDisable(id) {
showHiddenTabs(id);
+ tabHidePopup.clearConfirmation(id);
+ }
+
+ static onUninstall(id) {
+ tabHidePopup.clearConfirmation(id);
}
getAPI(context) {
let {extension} = context;
let {tabManager} = extension;
function getTabOrActive(tabId) {
@@ -1222,15 +1251,19 @@ this.tabs = class extends ExtensionAPI {
for (let tab of tabs) {
if (tab.ownerGlobal && !tab.hidden) {
tab.ownerGlobal.gBrowser.hideTab(tab, extension.id);
if (tab.hidden) {
hidden.push(tabTracker.getId(tab));
}
}
}
+ if (hidden.length > 0) {
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ tabHidePopup.open(win, extension.id);
+ }
return hidden;
},
},
};
return self;
}
};
--- a/browser/components/extensions/test/browser/browser_ext_tabs_hide.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_hide.js
@@ -30,16 +30,128 @@ add_task(async function test_pref_disabl
background,
};
let extension = ExtensionTestUtils.loadExtension(extdata);
await extension.startup();
await extension.awaitFinish("pref-test");
await extension.unload();
});
+async function doorhangerTest(testFn) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.webextensions.tabhide.enabled", true]],
+ });
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["tabs", "tabHide"],
+ icons: {
+ 48: "addon-icon.png",
+ },
+ },
+ background() {
+ browser.test.onMessage.addListener(async (msg, data) => {
+ let tabs = await browser.tabs.query(data);
+ await browser.tabs[msg](tabs.map(t => t.id));
+ browser.test.sendMessage("done");
+ });
+ },
+ useAddonManager: "temporary",
+ });
+
+ await extension.startup();
+
+ // Open some tabs so we can hide them.
+ let firstTab = gBrowser.selectedTab;
+ let tabs = [
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/?one", true, true),
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/?two", true, true),
+ ];
+ gBrowser.selectedTab = firstTab;
+
+ await testFn(extension);
+
+ BrowserTestUtils.removeTab(tabs[0]);
+ BrowserTestUtils.removeTab(tabs[1]);
+
+ await extension.unload();
+}
+
+add_task(function test_doorhanger_keep() {
+ return doorhangerTest(async function(extension) {
+ is(gBrowser.visibleTabs.length, 3, "There are 3 visible tabs");
+
+ // Hide the first tab, expect the doorhanger.
+ let panel = document.getElementById("extension-notification-panel");
+ let popupShown = promisePopupShown(panel);
+ extension.sendMessage("hide", {url: "*://*/?one"});
+ await extension.awaitMessage("done");
+ await popupShown;
+
+ is(gBrowser.visibleTabs.length, 2, "There are 2 visible tabs now");
+ is(panel.anchorNode.closest("toolbarbutton").id,
+ "alltabs-button", "The doorhanger is anchored to the all tabs button");
+
+ // Click the Keep Tabs Hidden button.
+ let popupnotification = document.getElementById("extension-tab-hide-notification");
+ let popupHidden = promisePopupHidden(panel);
+ document.getAnonymousElementByAttribute(
+ popupnotification, "anonid", "button").click();
+ await popupHidden;
+
+ // Hide another tab and ensure the popup didn't open.
+ extension.sendMessage("hide", {url: "*://*/?two"});
+ await extension.awaitMessage("done");
+ is(panel.state, "closed", "The popup is still closed");
+ is(gBrowser.visibleTabs.length, 1, "There's one visible tab now");
+
+ extension.sendMessage("show", {});
+ await extension.awaitMessage("done");
+ });
+});
+
+add_task(function test_doorhanger_disable() {
+ return doorhangerTest(async function(extension) {
+ is(gBrowser.visibleTabs.length, 3, "There are 3 visible tabs");
+
+ // Hide the first tab, expect the doorhanger.
+ let panel = document.getElementById("extension-notification-panel");
+ let popupShown = promisePopupShown(panel);
+ extension.sendMessage("hide", {url: "*://*/?one"});
+ await extension.awaitMessage("done");
+ await popupShown;
+
+ is(gBrowser.visibleTabs.length, 2, "There are 2 visible tabs now");
+ is(panel.anchorNode.closest("toolbarbutton").id,
+ "alltabs-button", "The doorhanger is anchored to the all tabs button");
+
+ // verify the contents of the description.
+ let popupnotification = document.getElementById("extension-tab-hide-notification");
+ let description = popupnotification.querySelector("description");
+ let addon = await AddonManager.getAddonByID(extension.id);
+ ok(description.textContent.includes(addon.name),
+ "The extension name is in the description");
+ let images = Array.from(description.querySelectorAll("image"));
+ is(images.length, 2, "There are two images");
+ ok(images.some(img => img.src.includes("addon-icon.png")),
+ "There's an icon for the extension");
+ ok(images.some(img => getComputedStyle(img).backgroundImage.includes("arrow-dropdown-16.svg")),
+ "There's an icon for the all tabs menu");
+
+ // Click the Disable Extension button.
+ let popupHidden = promisePopupHidden(panel);
+ document.getAnonymousElementByAttribute(
+ popupnotification, "anonid", "secondarybutton").click();
+ await popupHidden;
+
+ is(gBrowser.visibleTabs.length, 3, "There are 3 visible tabs again");
+ is(addon.userDisabled, true, "The extension is now disabled");
+ });
+});
+
add_task(async function test_tabs_showhide() {
await SpecialPowers.pushPrefEnv({
set: [["extensions.webextensions.tabhide.enabled", true]],
});
async function background() {
browser.test.onMessage.addListener(async (msg, data) => {
switch (msg) {
@@ -71,16 +183,17 @@ add_task(async function test_tabs_showhi
}
}
});
}
let extdata = {
manifest: {permissions: ["tabs", "tabHide"]},
background,
+ useAddonManager: "temporary", // So the doorhanger can find the addon.
};
let extension = ExtensionTestUtils.loadExtension(extdata);
await extension.startup();
let sessData = {
windows: [{
tabs: [
{entries: [{url: "about:blank", triggeringPrincipal_base64}]},
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -985,16 +985,22 @@ you can use these alternative items. Oth
<!ENTITY homepageControlled.message "An extension has changed what you see as your home page. You can restore your settings if you do not want this change.">
<!ENTITY homepageControlled.header.message "Your home page has changed.">
<!ENTITY homepageControlled.keepButton.label "Keep Changes">
<!ENTITY homepageControlled.keepButton.accesskey "K">
<!ENTITY homepageControlled.disableButton.label "Disable Extension">
<!ENTITY homepageControlled.disableButton.accesskey "D">
+<!ENTITY tabHideControlled.header.message "Access Your Hidden Tabs">
+<!ENTITY tabHideControlled.keepButton.label "Keep Tabs Hidden">
+<!ENTITY tabHideControlled.keepButton.accesskey "K">
+<!ENTITY tabHideControlled.disableButton.label "Disable Extension">
+<!ENTITY tabHideControlled.disableButton.accesskey "D">
+
<!ENTITY pageActionButton.tooltip "Page actions">
<!ENTITY pageAction.addToUrlbar.label "Add to Address Bar">
<!ENTITY pageAction.removeFromUrlbar.label "Remove from Address Bar">
<!ENTITY pageAction.allowInUrlbar.label "Show in Address Bar">
<!ENTITY pageAction.disallowInUrlbar.label "Don’t Show in Address Bar">
<!ENTITY pageAction.manageExtension.label "Manage Extension…">
<!ENTITY pageAction.sendTabToDevice.label "Send Tab to Device">
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/addons/extension-controlled.inc.css
@@ -0,0 +1,33 @@
+#extension-notification-panel > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0;
+}
+
+.extension-controlled-notification > .popup-notification-body-container > .popup-notification-body {
+ width: 30em;
+}
+
+.extension-controlled-notification > .popup-notification-body-container > .popup-notification-body > hbox > vbox > .popup-notification-description {
+ font-size: 1.3em;
+ font-weight: lighter;
+}
+
+.extension-controlled-notification {
+ margin-bottom: 0;
+}
+
+.extension-controlled-notification > popupnotificationcontent > description > .extension-controlled-icon {
+ height: 16px;
+ width: 16px;
+ vertical-align: bottom;
+}
+
+.extension-controlled-icon.alltabs-icon {
+ background: url("chrome://global/skin/icons/arrow-dropdown-16.svg");
+ /* This icon has a lot of extra space to the sides, reduce that a little. */
+ margin: 0 -1px 1px;
+}
+
+.extension-controlled-notification > .popup-notification-body-container > .popup-notification-body > .popup-notification-warning,
+.extension-controlled-notification > .popup-notification-body-container > .popup-notification-icon {
+ display: none;
+}
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -1,13 +1,14 @@
/* 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/. */
%include downloads/indicator.inc.css
+%include addons/extension-controlled.inc.css
%filter substitution
%define navbarTabsShadowSize 1px
%define themeTransition background-color 0.1s cubic-bezier(.17,.67,.83,.67)
:root {
/* Note: Setting this to 0 (without px) breaks CSS calculations for OSX. */
--space-above-tabbar: 0px;
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -310,46 +310,16 @@ panelview:not([mainview]) .toolbarbutton
text-align: start;
display: -moz-box;
}
.cui-widget-panel > .panel-arrowcontainer > .panel-arrowcontent {
padding: 4px 0;
}
-/* START notification popups for extension controlled content */
-#extension-notification-panel > .panel-arrowcontainer > .panel-arrowcontent {
- padding: 0;
-}
-
-.extension-controlled-notification > .popup-notification-body-container > .popup-notification-body {
- width: 30em;
-}
-
-.extension-controlled-notification > .popup-notification-body-container > .popup-notification-body > hbox > vbox > .popup-notification-description {
- font-size: 1.3em;
- font-weight: lighter;
-}
-
-.extension-controlled-notification {
- margin-bottom: 0;
-}
-
-.extension-controlled-notification > popupnotificationcontent > description > .extension-controlled-icon {
- height: 16px;
- width: 16px;
- vertical-align: bottom;
-}
-
-.extension-controlled-notification > .popup-notification-body-container > .popup-notification-body > .popup-notification-warning,
-.extension-controlled-notification > .popup-notification-body-container > .popup-notification-icon {
- display: none;
-}
-/* END notification popups for extension controlled content */
-
#appMenu-popup > .panel-arrowcontainer > .panel-arrowcontent,
panel[photon] > .panel-arrowcontainer > .panel-arrowcontent {
padding: 0;
}
#appMenu-popup panelview,
#customizationui-widget-multiview panelview:not([extension]) {
min-width: @menuPanelWidth@;
--- a/toolkit/locales/en-US/chrome/global/extensions.properties
+++ b/toolkit/locales/en-US/chrome/global/extensions.properties
@@ -33,12 +33,16 @@ saveaspdf.saveasdialog.title = Save As
#LOCALIZATION NOTE (newTabControlled.message2) %S is the icon and name of the extension which updated the New Tab page.
newTabControlled.message2 = An extension, %S, changed the page you see when you open a new tab.
newTabControlled.learnMore = Learn more
#LOCALIZATION NOTE (homepageControlled.message) %S is the icon and name of the extension which updated the homepage.
homepageControlled.message = An extension, %S, changed what you see when you open your homepage and new windows.
homepageControlled.learnMore = Learn more
+#LOCALIZATION NOTE (tabHideControlled.message) %1$S is the icon and name of the extension which hid tabs, %2$S is the icon of the all tabs button.
+tabHideControlled.message = An extension, %1$S, is hiding some of your tabs. You can still access all of your tabs from %2$S.
+tabHideControlled.learnMore = Learn more
+
# LOCALIZATION NOTE (defaultTheme.name): This is displayed in about:addons -> Appearance
defaultTheme.name=Default
defaultTheme.description=The default theme.
--- a/toolkit/themes/shared/icons/arrow-dropdown-16.svg
+++ b/toolkit/themes/shared/icons/arrow-dropdown-16.svg
@@ -1,6 +1,6 @@
<!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
- <path fill="context-fill" fill-opacity="context-fill-opacity" d="M8,12L3,7,4,6l4,4,4-4,1,1Z"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="M8 12a1 1 0 0 1-.707-.293l-5-5a1 1 0 0 1 1.414-1.414L8 9.586l4.293-4.293a1 1 0 0 1 1.414 1.414l-5 5A1 1 0 0 1 8 12z"></path>
</svg>