Bug 1857005 - Add indicators to open tabs in Fx View r=desktop-theme-reviewers,fxview-reviewers,fluent-reviewers,sfoster,bolsson
Differential Revision: https://phabricator.services.mozilla.com/D199119
This commit is contained in:
@@ -15,7 +15,14 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
|
||||
});
|
||||
|
||||
const TAB_ATTRS_TO_WATCH = Object.freeze(["image", "label"]);
|
||||
const TAB_ATTRS_TO_WATCH = Object.freeze([
|
||||
"attention",
|
||||
"image",
|
||||
"label",
|
||||
"muted",
|
||||
"soundplaying",
|
||||
"titlechanged",
|
||||
]);
|
||||
const TAB_CHANGE_EVENTS = Object.freeze([
|
||||
"TabAttrModified",
|
||||
"TabClose",
|
||||
@@ -30,6 +37,7 @@ const TAB_RECENCY_CHANGE_EVENTS = Object.freeze([
|
||||
"TabClose",
|
||||
"TabOpen",
|
||||
"TabSelect",
|
||||
"TabAttrModified",
|
||||
]);
|
||||
|
||||
// Debounce tab/tab recency changes and dispatch max once per frame at 60fps
|
||||
|
||||
@@ -4,21 +4,21 @@
|
||||
|
||||
.fxview-tab-list {
|
||||
display: grid;
|
||||
grid-template-columns: min-content 3fr 2fr 1fr 1fr min-content;
|
||||
grid-template-columns: min-content 3fr min-content 2fr 1fr 1fr min-content min-content;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
:host([compactRows]) .fxview-tab-list {
|
||||
grid-template-columns: min-content 1fr min-content;
|
||||
grid-template-columns: min-content 1fr min-content min-content min-content;
|
||||
}
|
||||
|
||||
virtual-list {
|
||||
display: grid;
|
||||
grid-column: span 7;
|
||||
grid-column: span 9;
|
||||
grid-template-columns: subgrid;
|
||||
|
||||
.top-padding,
|
||||
.bottom-padding {
|
||||
grid-column: span 7;
|
||||
grid-column: span 9;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import {
|
||||
classMap,
|
||||
html,
|
||||
ifDefined,
|
||||
repeat,
|
||||
@@ -185,7 +186,19 @@ export default class FxviewTabList extends MozLitElement {
|
||||
// set this.currentActiveElementId to that element's ID
|
||||
e.preventDefault();
|
||||
if (document.dir == "rtl") {
|
||||
this.currentActiveElementId = fxviewTabRow.focusLink();
|
||||
if (
|
||||
(fxviewTabRow.soundPlaying || fxviewTabRow.muted) &&
|
||||
this.currentActiveElementId === "fxview-tab-row-secondary-button"
|
||||
) {
|
||||
this.currentActiveElementId = fxviewTabRow.focusMediaButton();
|
||||
} else {
|
||||
this.currentActiveElementId = fxviewTabRow.focusLink();
|
||||
}
|
||||
} else if (
|
||||
(fxviewTabRow.soundPlaying || fxviewTabRow.muted) &&
|
||||
this.currentActiveElementId === "fxview-tab-row-main"
|
||||
) {
|
||||
this.currentActiveElementId = fxviewTabRow.focusMediaButton();
|
||||
} else {
|
||||
this.currentActiveElementId = fxviewTabRow.focusButton();
|
||||
}
|
||||
@@ -194,7 +207,19 @@ export default class FxviewTabList extends MozLitElement {
|
||||
// set this.currentActiveElementId to that element's ID
|
||||
e.preventDefault();
|
||||
if (document.dir == "rtl") {
|
||||
this.currentActiveElementId = fxviewTabRow.focusButton();
|
||||
if (
|
||||
(fxviewTabRow.soundPlaying || fxviewTabRow.muted) &&
|
||||
this.currentActiveElementId === "fxview-tab-row-main"
|
||||
) {
|
||||
this.currentActiveElementId = fxviewTabRow.focusMediaButton();
|
||||
} else {
|
||||
this.currentActiveElementId = fxviewTabRow.focusButton();
|
||||
}
|
||||
} else if (
|
||||
(fxviewTabRow.soundPlaying || fxviewTabRow.muted) &&
|
||||
this.currentActiveElementId === "fxview-tab-row-secondary-button"
|
||||
) {
|
||||
this.currentActiveElementId = fxviewTabRow.focusMediaButton();
|
||||
} else {
|
||||
this.currentActiveElementId = fxviewTabRow.focusLink();
|
||||
}
|
||||
@@ -264,14 +289,20 @@ export default class FxviewTabList extends MozLitElement {
|
||||
?active=${i == this.activeIndex}
|
||||
?compact=${this.compactRows}
|
||||
.hasPopup=${this.hasPopup}
|
||||
.containerObj=${tabItem.containerObj}
|
||||
.currentActiveElementId=${this.currentActiveElementId}
|
||||
.dateTimeFormat=${this.dateTimeFormat}
|
||||
.favicon=${tabItem.icon}
|
||||
.isBookmark=${ifDefined(tabItem.isBookmark)}
|
||||
.muted=${ifDefined(tabItem.muted)}
|
||||
.pinned=${ifDefined(tabItem.pinned)}
|
||||
.primaryL10nId=${tabItem.primaryL10nId}
|
||||
.primaryL10nArgs=${ifDefined(tabItem.primaryL10nArgs)}
|
||||
role="listitem"
|
||||
.secondaryL10nId=${tabItem.secondaryL10nId}
|
||||
.secondaryL10nArgs=${ifDefined(tabItem.secondaryL10nArgs)}
|
||||
.attention=${ifDefined(tabItem.attention)}
|
||||
.soundPlaying=${ifDefined(tabItem.soundPlaying)}
|
||||
.sourceClosedId=${ifDefined(tabItem.sourceClosedId)}
|
||||
.sourceWindowId=${ifDefined(tabItem.sourceWindowId)}
|
||||
.closedId=${ifDefined(tabItem.closedId || tabItem.closedId)}
|
||||
@@ -280,6 +311,7 @@ export default class FxviewTabList extends MozLitElement {
|
||||
.time=${ifDefined(time)}
|
||||
.timeMsPref=${ifDefined(this.timeMsPref)}
|
||||
.title=${tabItem.title}
|
||||
.titleChanged=${ifDefined(tabItem.titleChanged)}
|
||||
.url=${tabItem.url}
|
||||
></fxview-tab-row>
|
||||
`;
|
||||
@@ -338,20 +370,27 @@ customElements.define("fxview-tab-list", FxviewTabList);
|
||||
*
|
||||
* @property {boolean} active - Should current item have focus on keydown
|
||||
* @property {boolean} compact - Whether to hide the URL and date/time for this tab.
|
||||
* @property {object} containerObj - Info about an open tab's container if within one
|
||||
* @property {string} currentActiveElementId - ID of currently focused element within each tab item
|
||||
* @property {string} dateTimeFormat - Expected format for date and/or time
|
||||
* @property {string} hasPopup - The aria-haspopup attribute for the secondary action, if required
|
||||
* @property {boolean} isBookmark - Whether an open tab is bookmarked
|
||||
* @property {number} closedId - The tab ID for when the tab item was closed.
|
||||
* @property {number} sourceClosedId - The closedId of the closed window its from if applicable
|
||||
* @property {number} sourceWindowId - The sessionstore id of the window its from if applicable
|
||||
* @property {string} favicon - The favicon for the tab item.
|
||||
* @property {boolean} muted - Whether an open tab is muted
|
||||
* @property {boolean} pinned - Whether an open tab is pinned
|
||||
* @property {string} primaryL10nId - The l10n id used for the primary action element
|
||||
* @property {string} primaryL10nArgs - The l10n args used for the primary action element
|
||||
* @property {string} secondaryL10nId - The l10n id used for the secondary action button
|
||||
* @property {string} secondaryL10nArgs - The l10n args used for the secondary action element
|
||||
* @property {boolean} attention - Whether to show a notification dot
|
||||
* @property {boolean} soundPlaying - Whether an open tab has soundPlaying
|
||||
* @property {object} tabElement - The MozTabbrowserTab element for the tab item.
|
||||
* @property {number} time - The timestamp for when the tab was last accessed.
|
||||
* @property {string} title - The title for the tab item.
|
||||
* @property {boolean} titleChanged - Whether the title has changed for an open tab
|
||||
* @property {string} url - The url for the tab item.
|
||||
* @property {number} timeMsPref - The frequency in milliseconds of updates to relative time
|
||||
* @property {string} searchQuery - The query string to highlight, if provided.
|
||||
@@ -366,20 +405,27 @@ export class FxviewTabRow extends MozLitElement {
|
||||
static properties = {
|
||||
active: { type: Boolean },
|
||||
compact: { type: Boolean },
|
||||
containerObj: { type: Object },
|
||||
currentActiveElementId: { type: String },
|
||||
dateTimeFormat: { type: String },
|
||||
favicon: { type: String },
|
||||
hasPopup: { type: String },
|
||||
isBookmark: { type: Boolean },
|
||||
muted: { type: Boolean },
|
||||
pinned: { type: Boolean },
|
||||
primaryL10nId: { type: String },
|
||||
primaryL10nArgs: { type: String },
|
||||
secondaryL10nId: { type: String },
|
||||
secondaryL10nArgs: { type: String },
|
||||
soundPlaying: { type: Boolean },
|
||||
closedId: { type: Number },
|
||||
sourceClosedId: { type: Number },
|
||||
sourceWindowId: { type: String },
|
||||
tabElement: { type: Object },
|
||||
time: { type: Number },
|
||||
title: { type: String },
|
||||
titleChanged: { type: Boolean },
|
||||
attention: { type: Boolean },
|
||||
timeMsPref: { type: Number },
|
||||
url: { type: String },
|
||||
searchQuery: { type: String },
|
||||
@@ -387,11 +433,16 @@ export class FxviewTabRow extends MozLitElement {
|
||||
|
||||
static queries = {
|
||||
mainEl: ".fxview-tab-row-main",
|
||||
buttonEl: ".fxview-tab-row-button:not([hidden])",
|
||||
buttonEl: "#fxview-tab-row-secondary-button:not([hidden])",
|
||||
mediaButtonEl: "#fxview-tab-row-media-button",
|
||||
};
|
||||
|
||||
get currentFocusable() {
|
||||
return this.renderRoot.getElementById(this.currentActiveElementId);
|
||||
let focusItem = this.renderRoot.getElementById(this.currentActiveElementId);
|
||||
if (!focusItem) {
|
||||
focusItem = this.renderRoot.getElementById("fxview-tab-row-main");
|
||||
}
|
||||
return focusItem;
|
||||
}
|
||||
|
||||
focus() {
|
||||
@@ -403,6 +454,11 @@ export class FxviewTabRow extends MozLitElement {
|
||||
return this.buttonEl.id;
|
||||
}
|
||||
|
||||
focusMediaButton() {
|
||||
this.mediaButtonEl.focus();
|
||||
return this.mediaButtonEl.id;
|
||||
}
|
||||
|
||||
focusLink() {
|
||||
this.mainEl.focus();
|
||||
return this.mainEl.id;
|
||||
@@ -475,6 +531,16 @@ export class FxviewTabRow extends MozLitElement {
|
||||
return icon;
|
||||
}
|
||||
|
||||
getContainerClasses() {
|
||||
let containerClasses = ["fxview-tab-row-container-indicator", "icon"];
|
||||
if (this.containerObj) {
|
||||
let { icon, color } = this.containerObj;
|
||||
containerClasses.push(`identity-icon-${icon}`);
|
||||
containerClasses.push(`identity-color-${color}`);
|
||||
}
|
||||
return containerClasses;
|
||||
}
|
||||
|
||||
primaryActionHandler(event) {
|
||||
if (
|
||||
(event.type == "click" && !event.altKey) ||
|
||||
@@ -511,6 +577,11 @@ export class FxviewTabRow extends MozLitElement {
|
||||
}
|
||||
}
|
||||
|
||||
muteOrUnmuteTab() {
|
||||
this.tabElement.toggleMuteAudio();
|
||||
this.muted = !this.muted;
|
||||
}
|
||||
|
||||
render() {
|
||||
const title = this.title;
|
||||
const relativeString = this.relativeTime(
|
||||
@@ -528,6 +599,15 @@ export class FxviewTabRow extends MozLitElement {
|
||||
const time = this.time;
|
||||
const timeArgs = JSON.stringify({ time });
|
||||
return html`
|
||||
${when(
|
||||
this.containerObj,
|
||||
() => html`
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/usercontext/usercontext.css"
|
||||
/>
|
||||
`
|
||||
)}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://global/skin/in-content/common.css"
|
||||
@@ -550,12 +630,27 @@ export class FxviewTabRow extends MozLitElement {
|
||||
@keydown=${this.primaryActionHandler}
|
||||
>
|
||||
<span
|
||||
class="fxview-tab-row-favicon icon"
|
||||
id="fxview-tab-row-favicon"
|
||||
style=${styleMap({
|
||||
backgroundImage: `url(${this.getImageUrl(this.favicon, this.url)})`,
|
||||
})}
|
||||
></span>
|
||||
class="${classMap({
|
||||
"fxview-tab-row-favicon-wrapper": true,
|
||||
bookmark: this.isBookmark && !this.attention,
|
||||
notification: this.pinned
|
||||
? this.attention || this.titleChanged
|
||||
: this.attention,
|
||||
soundplaying: this.soundPlaying && !this.muted && this.pinned,
|
||||
muted: this.muted && this.pinned,
|
||||
})}"
|
||||
>
|
||||
<span
|
||||
class="fxview-tab-row-favicon icon"
|
||||
id="fxview-tab-row-favicon"
|
||||
style=${styleMap({
|
||||
backgroundImage: `url(${this.getImageUrl(
|
||||
this.favicon,
|
||||
this.url
|
||||
)})`,
|
||||
})}
|
||||
></span>
|
||||
</span>
|
||||
<span
|
||||
class="fxview-tab-row-title text-truncated-ellipsis"
|
||||
id="fxview-tab-row-title"
|
||||
@@ -567,6 +662,7 @@ export class FxviewTabRow extends MozLitElement {
|
||||
() => title
|
||||
)}
|
||||
</span>
|
||||
<span class=${this.getContainerClasses().join(" ")}></span>
|
||||
<span
|
||||
class="fxview-tab-row-url text-truncated-ellipsis"
|
||||
id="fxview-tab-row-url"
|
||||
@@ -604,6 +700,29 @@ export class FxviewTabRow extends MozLitElement {
|
||||
>
|
||||
</span>
|
||||
</a>
|
||||
${when(
|
||||
(this.soundPlaying || this.muted) && !this.pinned,
|
||||
() => html`<button
|
||||
class=fxview-tab-row-button ghost-button icon-button semi-transparent"
|
||||
id="fxview-tab-row-media-button"
|
||||
data-l10n-id=${
|
||||
this.muted
|
||||
? "fxviewtabrow-unmute-tab-button"
|
||||
: "fxviewtabrow-mute-tab-button"
|
||||
}
|
||||
data-l10n-args=${JSON.stringify({ tabTitle: title })}
|
||||
muted=${ifDefined(this.muted)}
|
||||
soundplaying=${this.soundPlaying && !this.muted}
|
||||
@click=${this.muteOrUnmuteTab}
|
||||
tabindex="${
|
||||
this.active &&
|
||||
this.currentActiveElementId === "fxview-tab-row-media-button"
|
||||
? "0"
|
||||
: "-1"
|
||||
}"
|
||||
></button>`,
|
||||
() => html`<span></span>`
|
||||
)}
|
||||
${when(
|
||||
this.secondaryL10nId && this.secondaryActionHandler,
|
||||
() => html`<button
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
--fxviewtabrow-element-background-active: color-mix(in srgb, currentColor 21%, transparent);
|
||||
display: grid;
|
||||
grid-template-columns: subgrid;
|
||||
grid-column: span 6;
|
||||
grid-column: span 9;
|
||||
align-items: stretch;
|
||||
border-radius: 4px;
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
.fxview-tab-row-main {
|
||||
display: grid;
|
||||
grid-template-columns: subgrid;
|
||||
grid-column: span 5;
|
||||
grid-column: span 6;
|
||||
gap: 16px;
|
||||
border-radius: 4px;
|
||||
align-items: center;
|
||||
@@ -66,6 +66,56 @@
|
||||
}
|
||||
}
|
||||
|
||||
.fxview-tab-row-favicon-wrapper {
|
||||
height: 16px;
|
||||
|
||||
.fxview-tab-row-favicon::after {
|
||||
display: block;
|
||||
content: "";
|
||||
background-size: 12px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
position: absolute;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
-moz-context-properties: fill, stroke;
|
||||
fill: currentColor;
|
||||
stroke: var(--fxview-background-color-secondary);
|
||||
}
|
||||
|
||||
&.bookmark .fxview-tab-row-favicon::after {
|
||||
background-image: url("chrome://browser/skin/bookmark-12.svg");
|
||||
inset-block-start: 9px;
|
||||
inset-inline-end: -6px;
|
||||
fill: var(--fxview-primary-action-background);
|
||||
}
|
||||
|
||||
&.notification .fxview-tab-row-favicon::after {
|
||||
background-image: radial-gradient(circle, light-dark(rgb(42, 195, 162), rgb(84, 255, 189)), light-dark(rgb(42, 195, 162), rgb(84, 255, 189)) 2px, transparent 2px);
|
||||
height: 4px;
|
||||
width: 100%;
|
||||
inset-block-start: 20px;
|
||||
}
|
||||
|
||||
&.soundplaying .fxview-tab-row-favicon::after {
|
||||
background-image: url("chrome://global/skin/media/audio.svg");
|
||||
inset-block-start: -5px;
|
||||
inset-inline-end: -7px;
|
||||
border-radius: 100%;
|
||||
background-color: var(--fxview-background-color-secondary);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
&.muted .fxview-tab-row-favicon::after {
|
||||
background-image: url("chrome://global/skin/media/audio-muted.svg");
|
||||
inset-block-start: -5px;
|
||||
inset-inline-end: -7px;
|
||||
border-radius: 100%;
|
||||
background-color: var(--fxview-background-color-secondary);
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.fxview-tab-row-favicon {
|
||||
background-size: cover;
|
||||
-moz-context-properties: fill;
|
||||
@@ -80,6 +130,15 @@
|
||||
text-align: match-parent;
|
||||
}
|
||||
|
||||
.fxview-tab-row-container-indicator {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-image: var(--identity-icon);
|
||||
background-size: cover;
|
||||
-moz-context-properties: fill;
|
||||
fill: var(--identity-icon-color);
|
||||
}
|
||||
|
||||
.fxview-tab-row-url {
|
||||
color: var(--text-color-deemphasized);
|
||||
text-decoration-line: underline;
|
||||
@@ -101,6 +160,25 @@
|
||||
.fxview-tab-row-button {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
min-width: 0;
|
||||
background-color: transparent;
|
||||
|
||||
&[muted="true"],
|
||||
&[soundplaying="true"] {
|
||||
background-size: 16px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
&[muted="true"] {
|
||||
background-image: url("chrome://global/skin/media/audio-muted.svg");
|
||||
}
|
||||
|
||||
&[soundplaying="true"] {
|
||||
background-image: url("chrome://global/skin/media/audio.svg");
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-contrast) {
|
||||
|
||||
@@ -21,6 +21,8 @@ import { ViewPage, ViewPageContent } from "./viewpage.mjs";
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ContextualIdentityService:
|
||||
"resource://gre/modules/ContextualIdentityService.sys.mjs",
|
||||
NonPrivateTabs: "resource:///modules/OpenTabs.sys.mjs",
|
||||
getTabsTargetForWindow: "resource:///modules/OpenTabs.sys.mjs",
|
||||
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
|
||||
@@ -476,6 +478,7 @@ class OpenTabsInViewCard extends ViewPageContent {
|
||||
.maxTabsLength=${this.getMaxTabsLength()}
|
||||
.tabItems=${this.searchResults || getTabListItems(this.tabs)}
|
||||
.searchQuery=${this.searchQuery}
|
||||
.showTabIndicators=${true}
|
||||
><view-opentabs-contextmenu slot="menu"></view-opentabs-contextmenu>
|
||||
</fxview-tab-list>
|
||||
</div>
|
||||
@@ -715,6 +718,24 @@ class OpenTabsContextMenu extends MozLitElement {
|
||||
}
|
||||
customElements.define("view-opentabs-contextmenu", OpenTabsContextMenu);
|
||||
|
||||
/**
|
||||
* Checks if a given tab is within a container (contextual identity)
|
||||
*
|
||||
* @param {MozTabbrowserTab[]} tab
|
||||
* Tab to fetch container info on.
|
||||
* @returns {object[]}
|
||||
* Container object.
|
||||
*/
|
||||
function getContainerObj(tab) {
|
||||
let userContextId = tab.getAttribute("usercontextid");
|
||||
let containerObj = null;
|
||||
if (userContextId) {
|
||||
containerObj =
|
||||
lazy.ContextualIdentityService.getPublicIdentityFromId(userContextId);
|
||||
}
|
||||
return containerObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list of tabs into the format expected by the fxview-tab-list
|
||||
* component.
|
||||
@@ -725,19 +746,27 @@ customElements.define("view-opentabs-contextmenu", OpenTabsContextMenu);
|
||||
* Formatted objects.
|
||||
*/
|
||||
function getTabListItems(tabs) {
|
||||
return tabs
|
||||
?.filter(tab => !tab.closing && !tab.hidden && !tab.pinned)
|
||||
.map(tab => ({
|
||||
icon: tab.getAttribute("image"),
|
||||
primaryL10nId: "firefoxview-opentabs-tab-row",
|
||||
primaryL10nArgs: JSON.stringify({
|
||||
url: tab.linkedBrowser?.currentURI?.spec,
|
||||
}),
|
||||
secondaryL10nId: "fxviewtabrow-options-menu-button",
|
||||
secondaryL10nArgs: JSON.stringify({ tabTitle: tab.label }),
|
||||
tabElement: tab,
|
||||
time: tab.lastAccessed,
|
||||
title: tab.label,
|
||||
let filtered = tabs?.filter(
|
||||
tab => !tab.closing && !tab.hidden && !tab.pinned
|
||||
);
|
||||
|
||||
return filtered.map(tab => ({
|
||||
attention: tab.hasAttribute("attention"),
|
||||
containerObj: getContainerObj(tab),
|
||||
icon: tab.getAttribute("image"),
|
||||
muted: tab.hasAttribute("muted"),
|
||||
pinned: tab.pinned,
|
||||
primaryL10nId: "firefoxview-opentabs-tab-row",
|
||||
primaryL10nArgs: JSON.stringify({
|
||||
url: tab.linkedBrowser?.currentURI?.spec,
|
||||
}));
|
||||
}),
|
||||
secondaryL10nId: "fxviewtabrow-options-menu-button",
|
||||
secondaryL10nArgs: JSON.stringify({ tabTitle: tab.label }),
|
||||
soundPlaying: tab.hasAttribute("soundplaying"),
|
||||
tabElement: tab,
|
||||
time: tab.lastAccessed,
|
||||
title: tab.label,
|
||||
titleChanged: tab.hasAttribute("titlechanged"),
|
||||
url: tab.linkedBrowser?.currentURI?.spec,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ skip-if = [
|
||||
"os == 'mac' && verify"
|
||||
] # macos times out, see bug 1857293, skipped for windows, see bug 1858460
|
||||
|
||||
["browser_opentabs_tab_indicators.js"]
|
||||
|
||||
["browser_recentlyclosed_firefoxview.js"]
|
||||
fail-if = ["a11y_checks"] # Bug 1854625 clicked button.fxview-tab-row-secondary-button and a.fxview-tab-row-main may not be focusable
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const { NonPrivateTabs } = ChromeUtils.importESModule(
|
||||
"resource:///modules/OpenTabs.sys.mjs"
|
||||
);
|
||||
|
||||
let pageWithAlert =
|
||||
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
|
||||
"http://example.com/browser/browser/base/content/test/tabPrompts/openPromptOffTimeout.html";
|
||||
let pageWithSound =
|
||||
"http://mochi.test:8888/browser/dom/base/test/file_audioLoop.html";
|
||||
|
||||
function cleanup() {
|
||||
// Cleanup
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
BrowserTestUtils.removeTab(gBrowser.tabs[0]);
|
||||
}
|
||||
}
|
||||
|
||||
add_task(async function test_notification_dot_indicator() {
|
||||
await withFirefoxView({}, async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
let win = browser.ownerGlobal;
|
||||
await navigateToCategoryAndWait(document, "opentabs");
|
||||
// load page that opens prompt when page is hidden
|
||||
let openedTab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
pageWithAlert,
|
||||
true
|
||||
);
|
||||
let openedTabGotAttentionPromise = BrowserTestUtils.waitForAttribute(
|
||||
"attention",
|
||||
openedTab
|
||||
);
|
||||
|
||||
let tabChangeRaised = BrowserTestUtils.waitForEvent(
|
||||
NonPrivateTabs,
|
||||
"TabChange"
|
||||
);
|
||||
|
||||
await switchToFxViewTab();
|
||||
|
||||
let openTabs = document.querySelector("view-opentabs[name=opentabs]");
|
||||
|
||||
await openedTabGotAttentionPromise;
|
||||
await tabChangeRaised;
|
||||
await openTabs.updateComplete;
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => openTabs.viewCards[0].tabList.rowEls[1].attention,
|
||||
"The opened tab doesn't have the attention property, so no notification dot is shown."
|
||||
);
|
||||
|
||||
info("The newly opened tab has a notification dot.");
|
||||
|
||||
// Switch back to other tab to close prompt before cleanup
|
||||
await BrowserTestUtils.switchTab(gBrowser, openedTab);
|
||||
EventUtils.synthesizeKey("KEY_Enter", {}, win);
|
||||
|
||||
cleanup();
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_container_indicator() {
|
||||
await withFirefoxView({}, async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
let win = browser.ownerGlobal;
|
||||
|
||||
// Load a page in a container tab
|
||||
let userContextId = 1;
|
||||
let containerTab = BrowserTestUtils.addTab(win.gBrowser, URLs[0], {
|
||||
userContextId,
|
||||
});
|
||||
|
||||
await BrowserTestUtils.browserLoaded(
|
||||
containerTab.linkedBrowser,
|
||||
false,
|
||||
URLs[0]
|
||||
);
|
||||
|
||||
await navigateToCategoryAndWait(document, "opentabs");
|
||||
|
||||
let openTabs = document.querySelector("view-opentabs[name=opentabs]");
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() =>
|
||||
containerTab.getAttribute("usercontextid") === userContextId.toString(),
|
||||
"The container tab doesn't have the usercontextid attribute."
|
||||
);
|
||||
await openTabs.updateComplete;
|
||||
await TestUtils.waitForCondition(
|
||||
() => openTabs.viewCards[0].tabList?.rowEls.length,
|
||||
"The tab list hasn't rendered."
|
||||
);
|
||||
info("openTabs component has finished updating.");
|
||||
|
||||
let containerTabElem = openTabs.viewCards[0].tabList.rowEls[1];
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => containerTabElem.containerObj,
|
||||
"The container tab element isn't marked in Fx View."
|
||||
);
|
||||
|
||||
ok(
|
||||
containerTabElem.shadowRoot
|
||||
.querySelector(".fxview-tab-row-container-indicator")
|
||||
.classList.contains("identity-color-blue"),
|
||||
"The container color is blue."
|
||||
);
|
||||
|
||||
info("The newly opened tab is marked as a container tab.");
|
||||
|
||||
cleanup();
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_sound_playing_muted_indicator() {
|
||||
await withFirefoxView({}, async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
await navigateToCategoryAndWait(document, "opentabs");
|
||||
|
||||
// Load a page in a container tab
|
||||
let soundTab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
pageWithSound,
|
||||
true
|
||||
);
|
||||
|
||||
let tabChangeRaised = BrowserTestUtils.waitForEvent(
|
||||
NonPrivateTabs,
|
||||
"TabChange"
|
||||
);
|
||||
|
||||
await switchToFxViewTab();
|
||||
|
||||
let openTabs = document.querySelector("view-opentabs[name=opentabs]");
|
||||
|
||||
await TestUtils.waitForCondition(() =>
|
||||
soundTab.hasAttribute("soundplaying")
|
||||
);
|
||||
await tabChangeRaised;
|
||||
await openTabs.updateComplete;
|
||||
await TestUtils.waitForCondition(
|
||||
() => openTabs.viewCards[0].tabList?.rowEls.length,
|
||||
"The tab list hasn't rendered."
|
||||
);
|
||||
|
||||
let soundPlayingTabElem = openTabs.viewCards[0].tabList.rowEls[1];
|
||||
|
||||
await TestUtils.waitForCondition(() => soundPlayingTabElem.soundPlaying);
|
||||
|
||||
ok(
|
||||
soundPlayingTabElem.mediaButtonEl,
|
||||
"The tab has the mute button showing."
|
||||
);
|
||||
|
||||
tabChangeRaised = BrowserTestUtils.waitForEvent(
|
||||
NonPrivateTabs,
|
||||
"TabChange"
|
||||
);
|
||||
|
||||
// Mute the tab
|
||||
EventUtils.synthesizeMouseAtCenter(
|
||||
soundPlayingTabElem.mediaButtonEl,
|
||||
{},
|
||||
content
|
||||
);
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => soundTab.hasAttribute("soundplaying"),
|
||||
"The tab doesn't have the soundplaying attribute."
|
||||
);
|
||||
await tabChangeRaised;
|
||||
await openTabs.updateComplete;
|
||||
|
||||
await TestUtils.waitForCondition(() => soundPlayingTabElem.muted);
|
||||
|
||||
ok(
|
||||
soundPlayingTabElem.mediaButtonEl,
|
||||
"The tab has the unmute button showing."
|
||||
);
|
||||
|
||||
// Mute and unmute the tab and make sure the element in Fx View updates
|
||||
soundTab.toggleMuteAudio();
|
||||
await tabChangeRaised;
|
||||
await openTabs.updateComplete;
|
||||
await TestUtils.waitForCondition(() => soundPlayingTabElem.soundPlaying);
|
||||
|
||||
ok(
|
||||
soundPlayingTabElem.mediaButtonEl,
|
||||
"The tab has the mute button showing."
|
||||
);
|
||||
|
||||
soundTab.toggleMuteAudio();
|
||||
await tabChangeRaised;
|
||||
await openTabs.updateComplete;
|
||||
await TestUtils.waitForCondition(() => soundPlayingTabElem.muted);
|
||||
|
||||
ok(
|
||||
soundPlayingTabElem.mediaButtonEl,
|
||||
"The tab has the unmute button showing."
|
||||
);
|
||||
|
||||
cleanup();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user