blob: 966b005f13a6ab58694ed96b190df879f613215e [file] [log] [blame]
// 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);