blob: 2e767208298aefca099e7891efd26b43511092c7 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'chrome://resources/cr_elements/cr_expand_button/cr_expand_button.js';
import 'chrome://resources/cr_elements/cr_shared_style.css.js';
import 'chrome://resources/polymer/v3_0/iron-collapse/iron-collapse.js';
import './shared_style.css.js';
import {CrActionMenuElement} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js';
import {CrExpandButtonElement} from 'chrome://resources/cr_elements/cr_expand_button/cr_expand_button.js';
import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
import {DomRepeatEvent, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {ItemDelegate} from './item.js';
import {getTemplate} from './review_panel.html.js';
export interface ReviewItemDelegate {
setItemSafetyCheckWarningAcknowledged(id: string): void;
uninstallItem(id: string): Promise<void>;
}
export interface ExtensionsReviewPanelElement {
$: {
makeExceptionMenu: CrActionMenuElement,
reviewPanelContainer: HTMLDivElement,
expandButton: CrExpandButtonElement,
safetyHubTitleContainer: HTMLElement,
headingText: HTMLElement,
secondaryText: HTMLElement,
removeAllButton: CrButtonElement,
};
}
const ExtensionsReviewPanelElementBase = I18nMixin(PolymerElement);
export class ExtensionsReviewPanelElement extends
ExtensionsReviewPanelElementBase {
static get is() {
return 'extensions-review-panel';
}
static get template() {
return getTemplate();
}
static get properties() {
return {
delegate: Object,
extensions: {
type: Array,
notify: true,
},
/**
* The string for the primary header label.
*/
headerString_: String,
/**
* The string for secondary text under the header string.
*/
subtitleString_: String,
/**
* The text of the safety check completion state.
*/
completionMessage_: String,
/**
* List of potentially unsafe extensions. This list being empty
* indicates that there are no unsafe extensions to review.
*/
unsafeExtensions_: Array,
shouldShowSafetyHubHeader_: {
type: Boolean,
computed: 'computeShouldShowSafetyHubHeader_(shouldHideUnsafePanel_)',
},
/**
* Indicates whether to show completion info after user has finished the
* review process.
*/
shouldShowCompletionInfo_: {
type: Boolean,
computed:
'computeShouldShowCompletionInfo_(extensions.*, reviewPanelShown_)',
},
/**
* Indicates whether to show the potentially unsafe extensions or not.
*/
shouldShowUnsafeExtensions_: {
type: Boolean,
computed: 'computeShouldShowUnsafeExtensions_(extensions.*)',
},
/**
* Indicates whether to show any part of the Review Panel.
*/
shouldHideUnsafePanel_: {
type: Boolean,
computed:
'computeShouldHideUnsafePanel_(shouldShowUnsafeExtensions_, shouldShowCompletionInfo_)',
},
/**
* Indicates if the list of unsafe extensions is expanded or collapsed.
*/
unsafeExtensionsReviewListExpanded_: {
type: Boolean,
value: true,
},
/**
* Indicates if any potential unsafe extensions has been kept or removed.
*/
numberOfExtensionsChanged_: {
type: Number,
value: 1,
},
/**
* Indicates if the review panel has ever been shown.
*/
reviewPanelShown_: {
type: Boolean,
value: false,
},
/**
* The latest id of an extension whose action menu (Keep the extension)
* was expanded.
* */
lastClickedExtensionId_: String,
};
}
static get observers() {
return ['onExtensionsChanged_(extensions.*)'];
}
delegate: ItemDelegate&ReviewItemDelegate;
extensions: chrome.developerPrivate.ExtensionInfo[];
private numberOfExtensionsChanged_: number;
private reviewPanelShown_: boolean;
private completionMetricLogged_: boolean;
private unsafeExtensions_: chrome.developerPrivate.ExtensionInfo[];
private headerString_: string;
private subtitleString_: string;
private unsafeExtensionsReviewListExpanded_: boolean;
private completionMessage_: string;
private shouldShowSafetyHubHeader_: boolean;
private shouldShowCompletionInfo_: boolean;
private shouldShowUnsafeExtensions_: boolean;
private shouldHideUnsafePanel_: boolean;
private lastClickedExtensionId_: string;
private async onExtensionsChanged_() {
this.unsafeExtensions_ = this.getUnsafeExtensions_(this.extensions);
this.headerString_ =
await PluralStringProxyImpl.getInstance().getPluralString(
'safetyCheckTitle', this.unsafeExtensions_.length);
this.subtitleString_ =
await PluralStringProxyImpl.getInstance().getPluralString(
'safetyCheckDescription', this.unsafeExtensions_.length);
this.completionMessage_ =
await PluralStringProxyImpl.getInstance().getPluralString(
'safetyCheckAllDoneForNow', this.numberOfExtensionsChanged_);
}
private getUnsafeExtensions_(extensions:
chrome.developerPrivate.ExtensionInfo[]):
chrome.developerPrivate.ExtensionInfo[] {
return extensions?.filter(
extension =>
!!(extension.safetyCheckText &&
extension.safetyCheckText.panelString &&
!extension.controlledInfo &&
extension.acknowledgeSafetyCheckWarning !== true));
}
/**
* Determines whether or not to show the completion info after the user
* finished reviewing extensions.
*/
private computeShouldShowCompletionInfo_(): boolean {
const updatedUnsafeExtensions =
this.getUnsafeExtensions_(this.extensions) || [];
if (this.reviewPanelShown_ && updatedUnsafeExtensions.length === 0) {
if (!this.completionMetricLogged_) {
this.completionMetricLogged_ = true;
chrome.metricsPrivate.recordUserAction('SafetyCheck.ReviewCompletion');
}
return true;
} else {
return false;
}
}
private computeShouldShowUnsafeExtensions_(): boolean {
const updatedUnsafeExtensions =
this.getUnsafeExtensions_(this.extensions) || [];
if (updatedUnsafeExtensions.length !== 0) {
if (!this.shouldShowUnsafeExtensions_) {
chrome.metricsPrivate.recordUserAction('SafetyCheck.ReviewPanelShown');
}
this.completionMetricLogged_ = false;
this.reviewPanelShown_ = true;
return true;
} else {
return false;
}
}
private computeShouldShowSafetyHubHeader_(): boolean {
return loadTimeData.getBoolean('safetyHubShowReviewPanel') &&
!this.shouldHideUnsafePanel_;
}
private computeShouldHideUnsafePanel_(): boolean {
return !(
this.shouldShowUnsafeExtensions_ || this.shouldShowCompletionInfo_);
}
/**
* Opens the extension action menu.
*/
private onMakeExceptionMenuClick_(
e: DomRepeatEvent<chrome.developerPrivate.ExtensionInfo>) {
this.lastClickedExtensionId_ = e.model.item.id;
this.$.makeExceptionMenu.showAt(e.target as HTMLElement);
}
/**
* Acknowledges the extension safety check warning.
*/
private onKeepExtensionClick_() {
chrome.metricsPrivate.recordUserAction(
'SafetyCheck.ReviewPanelKeepClicked');
this.$.makeExceptionMenu.close();
if (this.lastClickedExtensionId_) {
this.delegate.setItemSafetyCheckWarningAcknowledged(
this.lastClickedExtensionId_);
}
}
private getRemoveButtonA11yLabel_(extensionName: string): string {
return loadTimeData.substituteString(
this.i18n('safetyCheckRemoveButtonA11yLabel'), extensionName);
}
private getOptionMenuA11yLabel_(extensionName: string) {
return loadTimeData.substituteString(
this.i18n('safetyCheckOptionMenuA11yLabel'), extensionName);
}
private async onRemoveExtensionClick_(
e: DomRepeatEvent<chrome.developerPrivate.ExtensionInfo>): Promise<void> {
chrome.metricsPrivate.recordUserAction(
'SafetyCheck.ReviewPanelRemoveClicked');
try {
await this.delegate.uninstallItem(e.model.item.id);
} catch (_) {
// The error was almost certainly the user canceling the dialog.
// Do nothing.
}
}
private async onRemoveAllClick_(event: Event): Promise<void> {
chrome.metricsPrivate.recordUserAction(
'SafetyCheck.ReviewPanelRemoveAllClicked');
event.stopPropagation();
try {
this.numberOfExtensionsChanged_ = this.unsafeExtensions_.length;
await this.delegate.deleteItems(
this.unsafeExtensions_.map(extension => extension.id));
} catch (_) {
// The error was almost certainly the user canceling the dialog.
// Reset `numberOfExtensionsChanged_`.
this.numberOfExtensionsChanged_ = 1;
}
}
}
declare global {
interface HTMLElementTagNameMap {
'extensions-review-panel': ExtensionsReviewPanelElement;
}
}
customElements.define(
ExtensionsReviewPanelElement.is, ExtensionsReviewPanelElement);