| // Copyright 2019 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. |
| |
| import './strings.m.js'; |
| |
| import {CustomElement} from 'chrome://resources/js/custom_element.js'; |
| import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; |
| |
| import {TabAlertState} from './tabs_api_proxy.js'; |
| |
| /** @const {string} */ |
| const MAX_WIDTH = '16px'; |
| |
| /** |
| * @param {!TabAlertState} alertState |
| * @return {string} |
| */ |
| function getAriaLabel(alertState) { |
| // The existing labels for alert states currently expects to format itself |
| // using the title of the tab (eg. "Website - Audio is playing"). The WebUI |
| // tab strip will provide the title of the tab elsewhere outside of this |
| // element, so just provide an empty string as the title here. This also |
| // allows for multiple labels for the same title (eg. "Website - Audio is |
| // playing - VR is presenting"). |
| switch (alertState) { |
| case TabAlertState.MEDIA_RECORDING: |
| return loadTimeData.getStringF('mediaRecording', ''); |
| case TabAlertState.TAB_CAPTURING: |
| return loadTimeData.getStringF('tabCapturing', ''); |
| case TabAlertState.AUDIO_PLAYING: |
| return loadTimeData.getStringF('audioPlaying', ''); |
| case TabAlertState.AUDIO_MUTING: |
| return loadTimeData.getStringF('audioMuting', ''); |
| case TabAlertState.BLUETOOTH_CONNECTED: |
| return loadTimeData.getStringF('bluetoothConnected', ''); |
| case TabAlertState.USB_CONNECTED: |
| return loadTimeData.getStringF('usbConnected', ''); |
| case TabAlertState.HID_CONNECTED: |
| return loadTimeData.getStringF('hidConnected', ''); |
| case TabAlertState.SERIAL_CONNECTED: |
| return loadTimeData.getStringF('serialConnected', ''); |
| case TabAlertState.PIP_PLAYING: |
| return loadTimeData.getStringF('pipPlaying', ''); |
| case TabAlertState.DESKTOP_CAPTURING: |
| return loadTimeData.getStringF('desktopCapturing', ''); |
| case TabAlertState.VR_PRESENTING_IN_HEADSET: |
| return loadTimeData.getStringF('vrPresenting', ''); |
| default: |
| return ''; |
| } |
| } |
| |
| export class AlertIndicatorElement extends CustomElement { |
| static get template() { |
| return `{__html_template__}`; |
| } |
| |
| constructor() { |
| super(); |
| |
| /** @private {!TabAlertState} */ |
| this.alertState_; |
| |
| /** @private {number} */ |
| this.fadeDurationMs_ = 125; |
| |
| /** |
| * An animation that is currently in-flight to fade the element in. |
| * @private {?Animation} |
| */ |
| this.fadeInAnimation_ = null; |
| |
| /** |
| * An animation that is currently in-flight to fade the element out. |
| * @private {?Animation} |
| */ |
| this.fadeOutAnimation_ = null; |
| |
| /** |
| * A promise that resolves when the fade out animation finishes or rejects |
| * if a fade out animation is canceled. |
| * @private {?Promise} |
| */ |
| this.fadeOutAnimationPromise_ = null; |
| } |
| |
| /** @return {!TabAlertState} */ |
| get alertState() { |
| return this.alertState_; |
| } |
| |
| /** @param {!TabAlertState} alertState */ |
| set alertState(alertState) { |
| this.setAttribute('alert-state_', alertState); |
| this.setAttribute('aria-label', getAriaLabel(alertState)); |
| this.alertState_ = alertState; |
| } |
| |
| /** @param {number} duration */ |
| overrideFadeDurationForTesting(duration) { |
| this.fadeDurationMs_ = duration; |
| } |
| |
| show() { |
| if (this.fadeOutAnimation_) { |
| // Cancel any fade out animations to prevent the element from fading out |
| // and being removed. At this point, the tab's alertStates have changed |
| // to a state in which this indicator should be visible. |
| this.fadeOutAnimation_.cancel(); |
| this.fadeOutAnimation_ = null; |
| this.fadeOutAnimationPromise_ = null; |
| } |
| |
| if (this.fadeInAnimation_) { |
| // If the element was already faded in, don't fade it in again |
| return; |
| } |
| |
| |
| if (this.alertState_ === TabAlertState.MEDIA_RECORDING || |
| this.alertState_ === TabAlertState.TAB_CAPTURING || |
| this.alertState_ === TabAlertState.DESKTOP_CAPTURING) { |
| // Fade in and out 2 times and then fade in |
| const totalDuration = 2600; |
| this.fadeInAnimation_ = this.animate( |
| [ |
| {opacity: 0, maxWidth: 0, offset: 0}, |
| {opacity: 1, maxWidth: MAX_WIDTH, offset: 200 / totalDuration}, |
| {opacity: 0, maxWidth: MAX_WIDTH, offset: 1200 / totalDuration}, |
| {opacity: 1, maxWidth: MAX_WIDTH, offset: 1400 / totalDuration}, |
| {opacity: 0, maxWidth: MAX_WIDTH, offset: 2400 / totalDuration}, |
| {opacity: 1, maxWidth: MAX_WIDTH, offset: 1}, |
| ], |
| { |
| duration: totalDuration, |
| easing: 'linear', |
| fill: 'forwards', |
| }); |
| } else { |
| this.fadeInAnimation_ = this.animate( |
| [ |
| {opacity: 0, maxWidth: 0}, |
| {opacity: 1, maxWidth: MAX_WIDTH}, |
| ], |
| { |
| duration: this.fadeDurationMs_, |
| fill: 'forwards', |
| }); |
| } |
| } |
| |
| /** @return {!Promise} */ |
| hide() { |
| if (this.fadeInAnimation_) { |
| // Cancel any fade in animations to prevent the element from fading in. At |
| // this point, the tab's alertStates have changed to a state in which this |
| // indicator should not be visible. |
| this.fadeInAnimation_.cancel(); |
| this.fadeInAnimation_ = null; |
| } |
| |
| if (this.fadeOutAnimationPromise_) { |
| return this.fadeOutAnimationPromise_; |
| } |
| |
| this.fadeOutAnimationPromise_ = new Promise((resolve, reject) => { |
| this.fadeOutAnimation_ = this.animate( |
| [ |
| {opacity: 1, maxWidth: MAX_WIDTH}, |
| {opacity: 0, maxWidth: 0}, |
| ], |
| { |
| duration: this.fadeDurationMs_, |
| fill: 'forwards', |
| }); |
| this.fadeOutAnimation_.addEventListener('cancel', () => { |
| reject(); |
| }); |
| this.fadeOutAnimation_.addEventListener('finish', () => { |
| this.remove(); |
| this.fadeOutAnimation_ = null; |
| this.fadeOutAnimationPromise_ = null; |
| resolve(); |
| }); |
| }); |
| |
| return this.fadeOutAnimationPromise_; |
| } |
| } |
| |
| customElements.define('tabstrip-alert-indicator', AlertIndicatorElement); |