blob: 69e57120f74ac3d102b7038a35bf1482cdff97a7 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* 'settings-privacy-review-page' is the settings page that helps users review
* various privacy settings.
*/
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/shared_style_css.m.js';
import '../../prefs/prefs.js';
import '../../settings_shared_css.js';
import 'chrome://resources/cr_elements/cr_view_manager/cr_view_manager.js';
import './privacy_review_clear_on_exit_fragment.js';
import './privacy_review_completion_fragment.js';
import './privacy_review_cookies_fragment.js';
import './privacy_review_history_sync_fragment.js';
import './privacy_review_msbb_fragment.js';
import './privacy_review_safe_browsing_fragment.js';
import './privacy_review_welcome_fragment.js';
import './step_indicator.js';
import {CrViewManagerElement} from 'chrome://resources/cr_elements/cr_view_manager/cr_view_manager.js';
import {assert} from 'chrome://resources/js/assert.m.js';
import {I18nMixin, I18nMixinInterface} from 'chrome://resources/js/i18n_mixin.js';
import {WebUIListenerMixin, WebUIListenerMixinInterface} from 'chrome://resources/js/web_ui_listener_mixin.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {HatsBrowserProxyImpl, TrustSafetyInteraction} from '../../hats_browser_proxy.js';
import {SyncBrowserProxy, SyncBrowserProxyImpl, SyncStatus} from '../../people_page/sync_browser_proxy.js';
import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
import {SafeBrowsingSetting} from '../../privacy_page/security_page.js';
import {routes} from '../../route.js';
import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
import {CookiePrimarySetting} from '../../site_settings/site_settings_prefs_browser_proxy.js';
import {PrivacyReviewStep} from './constants.js';
import {StepIndicatorModel} from './step_indicator.js';
interface PrivacyReviewStepComponents {
nextStep?: PrivacyReviewStep;
onForwardNavigation?(): void;
previousStep?: PrivacyReviewStep;
isAvailable(): boolean;
}
export interface SettingsPrivacyReviewPageElement {
$: {
viewManager: CrViewManagerElement,
},
}
const PrivacyReviewBase = RouteObserverMixin(WebUIListenerMixin(
I18nMixin(PrefsMixin(PolymerElement)))) as {
new (): PolymerElement & I18nMixinInterface & WebUIListenerMixinInterface &
RouteObserverMixinInterface & PrefsMixinInterface
};
export class SettingsPrivacyReviewPageElement extends PrivacyReviewBase {
static get is() {
return 'settings-privacy-review-page';
}
static get template() {
return html`{__html_template__}`;
}
static get properties() {
return {
/**
* Preferences state.
*/
prefs: {
type: Object,
notify: true,
},
/**
* Valid privacy review states.
*/
privacyReviewStepEnum_: {
type: Object,
value: PrivacyReviewStep,
},
/**
* The current step in the privacy review flow, or `undefined` if the flow
* has not yet been initialized from query parameters.
*/
privacyReviewStep_: {
type: String,
value: undefined,
},
/**
* Used by the 'step-indicator' element to display its dots.
*/
stepIndicatorModel_: {
type: Object,
computed:
'computeStepIndicatorModel(privacyReviewStep_, prefs.generated.cookie_primary_setting, prefs.generated.safe_browsing)',
},
};
}
static get observers() {
return [
`onPrefsChanged_(prefs.generated.cookie_primary_setting, prefs.generated.safe_browsing)`
];
}
private privacyReviewStep_: PrivacyReviewStep;
private stepIndicatorModel_: StepIndicatorModel;
private privacyReviewStepToComponentsMap_:
Map<PrivacyReviewStep, PrivacyReviewStepComponents>;
private syncBrowserProxy_: SyncBrowserProxy =
SyncBrowserProxyImpl.getInstance();
private syncStatus_: SyncStatus;
private animationsEnabled_: boolean = true;
constructor() {
super();
this.privacyReviewStepToComponentsMap_ =
this.computePrivacyReviewStepToComponentsMap_();
}
ready() {
super.ready();
this.addWebUIListener(
'sync-status-changed',
(syncStatus: SyncStatus) => this.onSyncStatusChange_(syncStatus));
this.syncBrowserProxy_.getSyncStatus().then(
(syncStatus: SyncStatus) => this.onSyncStatusChange_(syncStatus));
}
disableAnimationsForTesting() {
this.animationsEnabled_ = false;
}
/** RouteObserverBehavior */
currentRouteChanged(newRoute: Route) {
if (newRoute === routes.PRIVACY_REVIEW) {
// Set the pref that the user has viewed the Privacy guide.
this.setPrefValue('privacy_guide.viewed', true);
this.updateStateFromQueryParameters_();
}
}
/**
* @return the map of privacy review steps to their components.
*/
private computePrivacyReviewStepToComponentsMap_():
Map<PrivacyReviewStep, PrivacyReviewStepComponents> {
return new Map([
[
PrivacyReviewStep.WELCOME,
{
nextStep: PrivacyReviewStep.MSBB,
isAvailable: () => this.shouldShowWelcomeCard_(),
},
],
[
PrivacyReviewStep.COMPLETION,
{
onForwardNavigation: () => {
Router.getInstance().navigateToPreviousRoute();
},
previousStep: PrivacyReviewStep.COOKIES,
isAvailable: () => true,
},
],
[
PrivacyReviewStep.MSBB,
{
nextStep: PrivacyReviewStep.CLEAR_ON_EXIT,
previousStep: PrivacyReviewStep.WELCOME,
isAvailable: () => true,
},
],
[
PrivacyReviewStep.CLEAR_ON_EXIT,
{
nextStep: PrivacyReviewStep.HISTORY_SYNC,
previousStep: PrivacyReviewStep.MSBB,
// TODO(crbug/1215630): Enable the CoE step when it's ready.
isAvailable: () => false,
},
],
[
PrivacyReviewStep.HISTORY_SYNC,
{
nextStep: PrivacyReviewStep.SAFE_BROWSING,
previousStep: PrivacyReviewStep.CLEAR_ON_EXIT,
// Allow the history sync card to be shown while `syncStatus_` is
// unavailable. Otherwise we would skip it in
// `navigateForwardIfCurrentCardNoLongerAvailable` before
// `onSyncStatusChange_` is called asynchronously.
isAvailable: () => !this.syncStatus_ || this.isSyncOn_(),
},
],
[
PrivacyReviewStep.SAFE_BROWSING,
{
nextStep: PrivacyReviewStep.COOKIES,
previousStep: PrivacyReviewStep.HISTORY_SYNC,
isAvailable: () => this.shouldShowSafeBrowsingCard_(),
},
],
[
PrivacyReviewStep.COOKIES,
{
nextStep: PrivacyReviewStep.COMPLETION,
onForwardNavigation: () => {
HatsBrowserProxyImpl.getInstance().trustSafetyInteractionOccurred(
TrustSafetyInteraction.COMPLETED_PRIVACY_GUIDE);
},
previousStep: PrivacyReviewStep.SAFE_BROWSING,
isAvailable: () => this.shouldShowCookiesCard_(),
},
],
]);
}
/** Handler for when the sync state is pushed from the browser. */
private onSyncStatusChange_(syncStatus: SyncStatus) {
this.syncStatus_ = syncStatus;
this.navigateForwardIfCurrentCardNoLongerAvailable();
}
/** Update the privacy review state based on changed prefs. */
private onPrefsChanged_() {
// If this change resulted in the user no longer being in one of the
// available states for the given card, we need to skip it.
this.navigateForwardIfCurrentCardNoLongerAvailable();
}
private navigateForwardIfCurrentCardNoLongerAvailable() {
if (!this.privacyReviewStep_) {
// Not initialized.
return;
}
if (!this.privacyReviewStepToComponentsMap_.get(this.privacyReviewStep_)!
.isAvailable()) {
// This card is currently shown but is no longer available. Navigate to
// the next card in the flow.
this.navigateForward_(true);
}
}
/** Sets the privacy review step from the URL parameter. */
private updateStateFromQueryParameters_() {
assert(Router.getInstance().getCurrentRoute() === routes.PRIVACY_REVIEW);
const step = Router.getInstance().getQueryParameters().get('step');
// TODO(crbug/1215630): If the parameter is welcome but the user has opted
// to skip the welcome card in a previous flow, then navigate to the first
// settings card instead
if (Object.values(PrivacyReviewStep).includes(step as PrivacyReviewStep)) {
this.navigateToCard_(step as PrivacyReviewStep, false, true);
} else {
// If no step has been specified, then navigate to the welcome step.
this.navigateToCard_(PrivacyReviewStep.WELCOME, false, false);
}
}
private onNextButtonClick_() {
this.navigateForward_(true);
}
private navigateForward_(playAnimation: boolean) {
const components =
this.privacyReviewStepToComponentsMap_.get(this.privacyReviewStep_)!;
assert(components.onForwardNavigation || components.nextStep);
if (components.onForwardNavigation) {
components.onForwardNavigation();
}
if (components.nextStep) {
this.navigateToCard_(components.nextStep, false, playAnimation);
}
}
private onBackButtonClick_() {
this.navigateBackward_(true);
}
private navigateBackward_(playAnimation: boolean) {
this.navigateToCard_(
this.privacyReviewStepToComponentsMap_.get(this.privacyReviewStep_)!
.previousStep!,
true, playAnimation);
}
private navigateToCard_(
step: PrivacyReviewStep, isBackwardNavigation: boolean,
playAnimation: boolean) {
if (step === this.privacyReviewStep_) {
return;
}
this.privacyReviewStep_ = step;
if (!this.privacyReviewStepToComponentsMap_.get(step)!.isAvailable()) {
// This card is currently not available. Navigate to the next one, or
// the previous one if this was a back navigation.
if (isBackwardNavigation) {
this.navigateBackward_(playAnimation);
} else {
this.navigateForward_(playAnimation);
}
} else {
if (this.animationsEnabled_ && playAnimation) {
this.$.viewManager.switchView(this.privacyReviewStep_);
} else {
this.$.viewManager.switchView(
this.privacyReviewStep_, 'no-animation', 'no-animation');
}
Router.getInstance().updateRouteParams(
new URLSearchParams('step=' + step));
// TODO(crbug/1215630): Programmatically put the focus to the
// corresponding element.
}
}
private computeBackButtonClass_(): string {
if (!this.privacyReviewStep_) {
// Not initialized.
return '';
}
const components =
this.privacyReviewStepToComponentsMap_.get(this.privacyReviewStep_)!;
return (components.previousStep === undefined ? 'visibility-hidden' : '');
}
// TODO(rainhard): This is made public only because it is accessed by tests.
// Should change tests so that this method can be made private again.
computeStepIndicatorModel(): StepIndicatorModel {
let stepCount = 0;
let activeIndex = 0;
for (const step of Object.values(PrivacyReviewStep)) {
if (step === PrivacyReviewStep.WELCOME ||
step === PrivacyReviewStep.COMPLETION) {
// This card has no step in the step indicator.
continue;
}
if (this.privacyReviewStepToComponentsMap_.get(step)!.isAvailable()) {
if (step === this.privacyReviewStep_) {
activeIndex = stepCount;
}
++stepCount;
}
}
return {
active: activeIndex,
total: stepCount,
};
}
private isSyncOn_(): boolean {
assert(this.syncStatus_);
return !!this.syncStatus_.signedIn && !this.syncStatus_.hasError;
}
private shouldShowWelcomeCard_(): boolean {
return this.getPref('privacy_review.show_welcome_card').value;
}
private shouldShowCookiesCard_(): boolean {
const currentCookieSetting =
this.getPref('generated.cookie_primary_setting').value;
return currentCookieSetting === CookiePrimarySetting.BLOCK_THIRD_PARTY ||
currentCookieSetting ===
CookiePrimarySetting.BLOCK_THIRD_PARTY_INCOGNITO;
}
private shouldShowSafeBrowsingCard_(): boolean {
const currentSafeBrowsingSetting =
this.getPref('generated.safe_browsing').value;
return currentSafeBrowsingSetting === SafeBrowsingSetting.ENHANCED ||
currentSafeBrowsingSetting === SafeBrowsingSetting.STANDARD;
}
private showAnySettingFragment_(): boolean {
return this.privacyReviewStep_ !== PrivacyReviewStep.WELCOME &&
this.privacyReviewStep_ !== PrivacyReviewStep.COMPLETION;
}
}
declare global {
interface HTMLElementTagNameMap {
'settings-privacy-review-page': SettingsPrivacyReviewPageElement;
}
}
customElements.define(
SettingsPrivacyReviewPageElement.is, SettingsPrivacyReviewPageElement);