[Extensions] Add remove button to action menu in mv2 deprecation panel
Adds a remove button the the extension's action menu in the manifest v2
deprecation panel. When button is clicked, it triggers the extension
removal process.
Screencast:
https://drive.google.com/file/d/1MjxRrwrySyBFzwEsEw7UXxw9R1HizGZ9/view?usp=sharing
Bug: 337191307
Change-Id: I2563e25b57ef1d37ade4936b2af9bf5bb6a27b6a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5528160
Auto-Submit: Emilia Paz <emiliapaz@chromium.org>
Commit-Queue: Devlin Cronin <rdevlin.cronin@chromium.org>
Reviewed-by: Devlin Cronin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1298956}
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index 7dc57da..8a1130b 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -856,7 +856,7 @@
This extension hasn't published privacy practices, such as how it collects and uses data. Chromium recommends that you remove it.
</message>
- <!-- chrome://settings/extensions page -->
+ <!-- chrome://extensions page -->
<message name="IDS_EXTENSIONS_SC_DESCRIPTION" desc="Detailed message displayed in the Safety Check description section.">
{NUM_EXTENSIONS, plural,
=1 {Chromium recommends that you remove it}
@@ -868,7 +868,7 @@
<message name="IDS_EXTENSIONS_INCOGNITO_WARNING" desc="Warns the user that Chromium cannot prevent extensions from recording history in Incognito mode. Displayed in extensions management UI after an extension is selected to be run in Incognito mode.">
Warning: Chromium cannot prevent extensions from recording your browsing history. To disable this extension in Incognito mode, unselect this option.
</message>
- <message name="IDS_EXTENSIONS_UNINSTALL" desc="The link for uninstalling extensions.">
+ <message name="IDS_EXTENSIONS_UNINSTALL" desc="The label for a control that triggers an extension uninstall.">
Remove from Chromium
</message>
<message name="IDS_EXTENSIONS_SHORTCUT_SCOPE_IN_CHROME" desc="The label to indicate that a shortcut will be triggerable only from within the Chrome application.">
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index f3417ff..5aa0d62 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -834,7 +834,7 @@
This extension hasn't published privacy practices, such as how it collects and uses data. Chrome recommends that you remove it.
</message>
- <!-- chrome://settings/extensions page -->
+ <!-- chrome://extensions page -->
<message name="IDS_EXTENSIONS_SC_DESCRIPTION" desc="Detailed message displayed in the Safety Check description section.">
{NUM_EXTENSIONS, plural,
=1 {Chrome recommends that you remove it}
@@ -846,7 +846,7 @@
<message name="IDS_EXTENSIONS_INCOGNITO_WARNING" desc="Warns the user that Chrome cannot prevent extensions from recording history in Incognito mode. Displayed in extensions management UI after an extension is selected to be run in Incognito mode.">
Warning: Google Chrome cannot prevent extensions from recording your browsing history. To disable this extension in Incognito mode, unselect this option.
</message>
- <message name="IDS_EXTENSIONS_UNINSTALL" desc="The link for uninstalling extensions.">
+ <message name="IDS_EXTENSIONS_UNINSTALL" desc="The label for a control that triggers an extension uninstall.">
Remove from Chrome
</message>
<message name="IDS_EXTENSIONS_SHORTCUT_SCOPE_IN_CHROME" desc="The label to indicate that a shortcut will be triggerable only from within the Chrome application.">
diff --git a/chrome/browser/resources/extensions/item_list.html b/chrome/browser/resources/extensions/item_list.html
index eaceb17..5145136 100644
--- a/chrome/browser/resources/extensions/item_list.html
+++ b/chrome/browser/resources/extensions/item_list.html
@@ -108,7 +108,8 @@
<template is="dom-if" if="[[showMv2DeprecationPanel_]]" restamp>
<div class="items-container panel">
<extensions-mv2-deprecation-panel
- extensions="[[mv2DeprecatedExtensions_]]">
+ extensions="[[mv2DeprecatedExtensions_]]"
+ delegate="[[delegate]]">
</extensions-mv2-deprecation-panel>
</div>
</template>
diff --git a/chrome/browser/resources/extensions/mv2_deprecation_panel.html b/chrome/browser/resources/extensions/mv2_deprecation_panel.html
index 1ad4695..4a5ae5c0 100644
--- a/chrome/browser/resources/extensions/mv2_deprecation_panel.html
+++ b/chrome/browser/resources/extensions/mv2_deprecation_panel.html
@@ -74,9 +74,17 @@
$i18n{mv2DeprecationPanelFindAlternativeButton}
<iron-icon icon="cr:open-in-new" slot="suffix-icon"></iron-icon>
</cr-button>
- <cr-icon-button class="icon-more-vert header-aligned-button">
+ <cr-icon-button class="icon-more-vert header-aligned-button"
+ on-click="onExtensionActionMenuClick_">
</cr-icon-button>
</div>
</template>
+
+ <cr-action-menu id="actionMenu">
+ <button class="dropdown-item" id="removeAction"
+ on-click="onRemoveExtensionActionClicked_">
+ $i18n{mv2DeprecationPanelRemoveExtensionButton}
+ </button>
+ </cr-action-menu>
</div>
</div>
diff --git a/chrome/browser/resources/extensions/mv2_deprecation_panel.ts b/chrome/browser/resources/extensions/mv2_deprecation_panel.ts
index c8fb7d0..dc2e984 100644
--- a/chrome/browser/resources/extensions/mv2_deprecation_panel.ts
+++ b/chrome/browser/resources/extensions/mv2_deprecation_panel.ts
@@ -7,12 +7,21 @@
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import './shared_style.css.js';
+import type {CrActionMenuElement} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
import {sanitizeInnerHtml} from 'chrome://resources/js/parse_html_subset.js';
import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import type {DomRepeatEvent} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import type {ItemDelegate} from './item.js';
import {getTemplate} from './mv2_deprecation_panel.html.js';
+export interface ExtensionsMv2DeprecationPanelElement {
+ $: {
+ actionMenu: CrActionMenuElement,
+ };
+}
+
export class ExtensionsMv2DeprecationPanelElement extends PolymerElement {
static get is() {
return 'extensions-mv2-deprecation-panel';
@@ -24,6 +33,8 @@
static get properties() {
return {
+ delegate: Object,
+
/*
* Extensions to display in the panel.
*/
@@ -49,8 +60,10 @@
}
extensions: chrome.developerPrivate.ExtensionInfo[];
+ delegate: ItemDelegate;
private headerString_: string;
private subtitleString_: string;
+ private lastClickedExtensionId_: string;
/**
* Updates properties after extensions change.
@@ -71,6 +84,25 @@
private getSubtitleString_(): TrustedHTML {
return sanitizeInnerHtml(this.subtitleString_);
}
+
+ /**
+ * Opens the action menu.
+ */
+ private onExtensionActionMenuClick_(
+ event: DomRepeatEvent<chrome.developerPrivate.ExtensionInfo>): void {
+ // Store the id of the extension whose action menu was opened.
+ this.lastClickedExtensionId_ = event.model.item.id;
+ this.$.actionMenu.showAt(event.target as HTMLElement);
+ }
+
+ /**
+ * Triggers an extension removal when the remove button is clicked for an
+ * extension.
+ */
+ private onRemoveExtensionActionClicked_(): void {
+ this.$.actionMenu.close();
+ this.delegate.deleteItem(this.lastClickedExtensionId_);
+ }
}
declare global {
diff --git a/chrome/browser/ui/webui/extensions/extensions_ui.cc b/chrome/browser/ui/webui/extensions/extensions_ui.cc
index 2e1d1b07..7dbf1d4 100644
--- a/chrome/browser/ui/webui/extensions/extensions_ui.cc
+++ b/chrome/browser/ui/webui/extensions/extensions_ui.cc
@@ -338,6 +338,7 @@
IDS_EXTENSIONS_MV2_DEPRECATION_PANEL_DISMISS_BUTTON},
{"mv2DeprecationPanelFindAlternativeButton",
IDS_EXTENSIONS_MV2_DEPRECATION_PANEL_FIND_ALTERNATIVE_BUTTON},
+ {"mv2DeprecationPanelRemoveExtensionButton", IDS_EXTENSIONS_UNINSTALL},
{"shortcutNotSet", IDS_EXTENSIONS_SHORTCUT_NOT_SET},
{"shortcutScopeGlobal", IDS_EXTENSIONS_SHORTCUT_SCOPE_GLOBAL},
{"shortcutScopeLabel", IDS_EXTENSIONS_SHORTCUT_SCOPE_LABEL},
diff --git a/chrome/test/data/webui/extensions/BUILD.gn b/chrome/test/data/webui/extensions/BUILD.gn
index a9c94ead..a55e3a1 100644
--- a/chrome/test/data/webui/extensions/BUILD.gn
+++ b/chrome/test/data/webui/extensions/BUILD.gn
@@ -69,6 +69,7 @@
"//chrome/browser/resources/extensions:build_ts",
"//third_party/lit/v3_0:build_ts",
"//third_party/polymer/v3_0:library",
+ "//ui/webui/resources/cr_elements:build_ts",
"//ui/webui/resources/js:build_ts",
]
ts_definitions = [
diff --git a/chrome/test/data/webui/extensions/mv2_deprecation_panel_test.ts b/chrome/test/data/webui/extensions/mv2_deprecation_panel_test.ts
index b3b3368..28a0cd7 100644
--- a/chrome/test/data/webui/extensions/mv2_deprecation_panel_test.ts
+++ b/chrome/test/data/webui/extensions/mv2_deprecation_panel_test.ts
@@ -6,21 +6,27 @@
import 'chrome://extensions/extensions.js';
import type {ExtensionsMv2DeprecationPanelElement} from 'chrome://extensions/extensions.js';
+import type {CrIconButtonElement} from 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
import {isVisible} from 'chrome://webui-test/test_util.js';
-import {createExtensionInfo} from './test_util.js';
+import {createExtensionInfo, MockItemDelegate} from './test_util.js';
suite('ExtensionsMV2DeprecationPanel', function() {
let panelElement: ExtensionsMv2DeprecationPanelElement;
+ let mockDelegate: MockItemDelegate;
setup(function() {
+ mockDelegate = new MockItemDelegate();
+
panelElement = document.createElement('extensions-mv2-deprecation-panel');
panelElement.extensions = [createExtensionInfo({
name: 'Extension A',
+ id: 'a'.repeat(32),
isAffectedByMV2Deprecation: true,
})];
+ panelElement.delegate = mockDelegate;
document.body.appendChild(panelElement);
return flushTasks();
@@ -59,4 +65,29 @@
extensionRows[1]?.querySelector('.extension-name')?.textContent?.trim(),
'Extension B');
});
+
+ test(
+ 'extension action menu buttons trigger the correct call',
+ async function() {
+ const extension = panelElement.shadowRoot!
+ .querySelectorAll<HTMLElement>('.extension-row')
+ ?.[0];
+ assertTrue(!!extension);
+
+ // Click on the extension's action menu button so we store the extension
+ // id whose action menu was expanded.
+ const actionButton =
+ extension.querySelector<CrIconButtonElement>('cr-icon-button');
+ assertTrue(!!actionButton);
+ actionButton.click();
+
+ // Click on the remove button in the action menu, and verify it
+ // triggered the correct delegate call.
+ const removeAction =
+ panelElement.shadowRoot!.querySelector<HTMLElement>(
+ '#removeAction');
+ assertTrue(!!removeAction);
+ await mockDelegate.testClickingCalls(
+ removeAction, 'deleteItem', [panelElement.extensions[0]?.id]);
+ });
});