| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview 'downloads-dangerous-download-interstitial' is the interstitial |
| * that allows bypassing a download warning (keeping a file flagged as |
| * dangerous). A 'success' indicates the warning interstitial was confirmed and |
| * the dangerous file was downloaded. |
| */ |
| import 'chrome://resources/cr_elements/cr_button/cr_button.js'; |
| import 'chrome://resources/cr_elements/cr_icon/cr_icon.js'; |
| import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.js'; |
| import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js'; |
| import 'chrome://resources/cr_elements/cr_shared_vars.css.js'; |
| |
| import {getInstance as getAnnouncerInstance} from 'chrome://resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js'; |
| import type {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js'; |
| import {assert} from 'chrome://resources/js/assert.js'; |
| import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; |
| import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js'; |
| |
| import {BrowserProxy} from './browser_proxy.js'; |
| import {getCss} from './dangerous_download_interstitial.css.js'; |
| import {getHtml} from './dangerous_download_interstitial.html.js'; |
| import type {PageHandlerInterface} from './downloads.mojom-webui.js'; |
| import {DangerousDownloadInterstitialSurveyOptions as surveyOptions} from './downloads.mojom-webui.js'; |
| |
| export interface DownloadsDangerousDownloadInterstitialElement { |
| $: { |
| dialog: HTMLDialogElement, |
| continueAnywayButton: CrButtonElement, |
| backToSafetyButton: CrButtonElement, |
| }; |
| } |
| |
| export class DownloadsDangerousDownloadInterstitialElement extends |
| CrLitElement { |
| static get is() { |
| return 'downloads-dangerous-download-interstitial'; |
| } |
| |
| static override get styles() { |
| return getCss(); |
| } |
| |
| override render() { |
| return getHtml.bind(this)(); |
| } |
| |
| static override get properties() { |
| return { |
| bypassPromptItemId: {type: String}, |
| hideSurveyAndDownloadButton_: {type: Boolean}, |
| selectedRadioOption_: {type: String}, |
| trustSiteLine: {type: String}, |
| trustSiteLineAccessibleText: {type: String}, |
| }; |
| } |
| |
| bypassPromptItemId: string = ''; |
| trustSiteLine: string = ''; |
| trustSiteLineAccessibleText: string = ''; |
| |
| private boundKeydown_: ((e: KeyboardEvent) => void)|null = null; |
| protected hideSurveyAndDownloadButton_: boolean = true; |
| protected selectedRadioOption_?: string; |
| |
| private mojoHandler_: PageHandlerInterface|null = null; |
| |
| override firstUpdated() { |
| this.mojoHandler_ = BrowserProxy.getInstance().handler; |
| } |
| |
| override connectedCallback() { |
| super.connectedCallback(); |
| this.$.dialog.showModal(); |
| this.$.dialog.focus(); |
| this.disableEscapeKey_(); |
| } |
| |
| override disconnectedCallback() { |
| super.disconnectedCallback(); |
| this.removeKeydownListener_(); |
| } |
| |
| getSurveyResponse(): surveyOptions { |
| const surveyResponse = this.$.dialog.returnValue; |
| switch (surveyResponse) { |
| case 'CreatedFile': |
| return surveyOptions.kCreatedFile; |
| case 'TrustSite': |
| return surveyOptions.kTrustSite; |
| case 'AcceptRisk': |
| return surveyOptions.kAcceptRisk; |
| default: |
| return surveyOptions.kNoResponse; |
| } |
| } |
| |
| private disableEscapeKey_() { |
| this.boundKeydown_ = this.boundKeydown_ || this.onKeydown_.bind(this); |
| this.addEventListener('keydown', this.boundKeydown_); |
| // Sometimes <body> is key event's target and in that case the event |
| // will bypass dialog. We should consume those events too in order to |
| // modally. This prevents cancelling the interstitial via keyboard events. |
| document.body.addEventListener('keydown', this.boundKeydown_); |
| } |
| |
| protected onBackToSafetyClick_() { |
| this.$.dialog.close(); |
| assert(!this.$.dialog.open); |
| this.dispatchEvent( |
| new CustomEvent('cancel', {bubbles: true, composed: true})); |
| } |
| |
| protected onContinueAnywayClick_() { |
| const continueAnywayButton = this.$.continueAnywayButton; |
| assert(!!continueAnywayButton); |
| continueAnywayButton.setAttribute('disabled', 'true'); |
| |
| const backToSafetyButton = this.$.backToSafetyButton; |
| assert(!!backToSafetyButton); |
| backToSafetyButton.focus(); |
| this.hideSurveyAndDownloadButton_ = false; |
| |
| assert(this.bypassPromptItemId !== ''); |
| assert(!!this.mojoHandler_); |
| this.mojoHandler_.recordOpenSurveyOnDangerousInterstitial( |
| this.bypassPromptItemId); |
| } |
| |
| protected onDownloadClick_() { |
| getAnnouncerInstance().announce( |
| loadTimeData.getString('screenreaderSavedDangerous')); |
| |
| this.$.dialog.close(this.selectedRadioOption_ || ''); |
| assert(!this.$.dialog.open); |
| this.dispatchEvent( |
| new CustomEvent('close', {bubbles: true, composed: true})); |
| } |
| |
| private onKeydown_(e: KeyboardEvent) { |
| if (e.key === 'Escape') { |
| e.preventDefault(); |
| } |
| } |
| |
| private removeKeydownListener_() { |
| if (!this.boundKeydown_) { |
| return; |
| } |
| |
| this.removeEventListener('keydown', this.boundKeydown_); |
| document.body.removeEventListener('keydown', this.boundKeydown_); |
| this.boundKeydown_ = null; |
| } |
| |
| protected onSelectedRadioOptionChanged_(e: CustomEvent<{value: string}>) { |
| this.selectedRadioOption_ = e.detail.value; |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'downloads-dangerous-download-interstitial': |
| DownloadsDangerousDownloadInterstitialElement; |
| } |
| } |
| |
| customElements.define( |
| DownloadsDangerousDownloadInterstitialElement.is, |
| DownloadsDangerousDownloadInterstitialElement); |