Skip to content

Commit bfd5a70

Browse files
committed
Bug 1783521 - add card preview when inactive tab hovered. r=settings-reviewers,desktop-theme-reviewers,tabbrowser-reviewers,fluent-reviewers,flod,dao,mconley
Differential Revision: https://phabricator.services.mozilla.com/D184260
1 parent c1fb133 commit bfd5a70

File tree

15 files changed

+497
-0
lines changed

15 files changed

+497
-0
lines changed

browser/app/profile/firefox.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,10 @@ pref("browser.tabs.tooltipsShowPidAndActiveness", true);
877877
pref("browser.tabs.tooltipsShowPidAndActiveness", false);
878878
#endif
879879

880+
pref("browser.tabs.cardPreview.enabled", false);
881+
pref("browser.tabs.cardPreview.delayMs", 1000);
882+
pref("browser.tabs.cardPreview.showThumbnails", true);
883+
880884
pref("browser.tabs.firefox-view", true);
881885
pref("browser.tabs.firefox-view-next", true);
882886
pref("browser.tabs.firefox-view-newIcon", true);

browser/base/content/browser.xhtml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
window.addEventListener("DOMContentLoaded",
138138
gBrowserInit.onDOMContentLoaded.bind(gBrowserInit), { once: true });
139139
</script>
140+
<script type="module" src="chrome://browser/content/tabpreview/tabpreview.mjs"></script>
140141
</head>
141142
<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
142143
# All sets except for popupsets (commands, keys, and stringbundles)

browser/base/content/main-popupset.inc.xhtml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,8 @@
417417
<hbox id="ctrlTab-showAll-container" pack="center"/>
418418
</panel>
419419

420+
<html:tab-preview id="tabbrowser-tab-preview" hidden="true" />
421+
420422
<html:template id="pageActionPanelTemplate">
421423
<panel id="pageActionPanel"
422424
class="cui-widget-panel panel-no-padding"

browser/base/content/tabbrowser-tab.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,8 @@
531531
tabToWarm = gBrowser._findTabToBlurTo(this);
532532
}
533533
gBrowser.warmupTab(tabToWarm);
534+
535+
this.dispatchEvent(new CustomEvent("TabHoverStart", { bubbles: true }));
534536
}
535537

536538
_mouseleave() {
@@ -542,6 +544,7 @@
542544
this.linkedBrowser.unselectedTabHover(false);
543545
this.cancelUnselectedTabHoverTimer();
544546
}
547+
this.dispatchEvent(new CustomEvent("TabHoverEnd", { bubbles: true }));
545548
}
546549

547550
setSecondaryTabTooltipLabel(l10nID, l10nArgs) {

browser/base/content/tabbrowser-tabs.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
// This is loaded into all browser windows. Wrap in a block to prevent
1010
// leaking to window scope.
1111
{
12+
const TAB_PREVIEW_PREF = "browser.tabs.cardPreview.enabled";
13+
1214
class MozTabbrowserTabs extends MozElements.TabsBase {
1315
constructor() {
1416
super();
@@ -20,6 +22,8 @@
2022
this.addEventListener("TabShow", this);
2123
this.addEventListener("TabPinned", this);
2224
this.addEventListener("TabUnpinned", this);
25+
this.addEventListener("TabHoverStart", this);
26+
this.addEventListener("TabHoverEnd", this);
2327
this.addEventListener("transitionend", this);
2428
this.addEventListener("dblclick", this);
2529
this.addEventListener("click", this);
@@ -118,6 +122,24 @@
118122
if (gMultiProcessBrowser) {
119123
this.tabbox.tabpanels.setAttribute("async", "true");
120124
}
125+
126+
this.configureTooltip = () => {
127+
// fall back to original tooltip behavior if pref is not set
128+
this.tooltip = this._showCardPreviews ? null : "tabbrowser-tab-tooltip";
129+
130+
// activate new tooltip behavior if pref is set
131+
document
132+
.getElementById("tabbrowser-tab-preview")
133+
.toggleAttribute("hidden", !this._showCardPreviews);
134+
};
135+
XPCOMUtils.defineLazyPreferenceGetter(
136+
this,
137+
"_showCardPreviews",
138+
TAB_PREVIEW_PREF,
139+
false,
140+
() => this.configureTooltip()
141+
);
142+
this.configureTooltip();
121143
}
122144

123145
on_TabSelect(event) {
@@ -1791,6 +1813,22 @@
17911813

17921814
handleEvent(aEvent) {
17931815
switch (aEvent.type) {
1816+
case "TabHoverStart":
1817+
if (this._showCardPreviews) {
1818+
const previewContainer = document.getElementById(
1819+
"tabbrowser-tab-preview"
1820+
);
1821+
previewContainer.tab = aEvent.target;
1822+
}
1823+
break;
1824+
case "TabHoverEnd":
1825+
if (this._showCardPreviews) {
1826+
const previewContainer = document.getElementById(
1827+
"tabbrowser-tab-preview"
1828+
);
1829+
previewContainer.tab = null;
1830+
}
1831+
break;
17941832
case "mouseout":
17951833
// If the "related target" (the node to which the pointer went) is not
17961834
// a child of the current document, the mouse just left the window.

browser/base/content/test/tabs/browser.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ skip-if =
202202
[browser_tab_manager_visibility.js]
203203
[browser_tab_move_to_new_window_reload.js]
204204
[browser_tab_play.js]
205+
[browser_tab_preview.js]
205206
[browser_tab_tooltips.js]
206207
[browser_tabswitch_contextmenu.js]
207208
[browser_tabswitch_select.js]
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
"use strict";
6+
7+
async function openPreview(tab) {
8+
const previewShown = BrowserTestUtils.waitForEvent(
9+
document.getElementById("tabbrowser-tab-preview"),
10+
"previewshown",
11+
false,
12+
e => {
13+
return e.detail.tab === tab;
14+
}
15+
);
16+
EventUtils.synthesizeMouseAtCenter(tab, { type: "mouseover" });
17+
return previewShown;
18+
}
19+
20+
async function closePreviews() {
21+
const tabs = document.getElementById("tabbrowser-tabs");
22+
const previewHidden = BrowserTestUtils.waitForEvent(
23+
document.getElementById("tabbrowser-tab-preview"),
24+
"previewhidden"
25+
);
26+
EventUtils.synthesizeMouse(tabs, 0, tabs.outerHeight + 1, {
27+
type: "mouseout",
28+
});
29+
return previewHidden;
30+
}
31+
32+
add_setup(async function () {
33+
await SpecialPowers.pushPrefEnv({
34+
set: [
35+
["browser.tabs.cardPreview.enabled", true],
36+
["browser.tabs.cardPreview.showThumbnails", false],
37+
["browser.tabs.cardPreview.delayMs", 0],
38+
],
39+
});
40+
});
41+
42+
/**
43+
* Verify the following:
44+
*
45+
* 1. Tab preview card appears when the mouse hovers over a tab
46+
* 2. Tab preview card shows the correct preview for the tab being hovered
47+
* 3. Tab preview card is dismissed when the mouse leaves the tab bar
48+
*/
49+
add_task(async () => {
50+
const tabUrl1 =
51+
"data:text/html,<html><head><title>First New Tab</title></head><body>Hello</body></html>";
52+
const tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, tabUrl1);
53+
const tabUrl2 =
54+
"data:text/html,<html><head><title>Second New Tab</title></head><body>Hello</body></html>";
55+
const tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, tabUrl2);
56+
const previewContainer = document.getElementById("tabbrowser-tab-preview");
57+
58+
await openPreview(tab1);
59+
Assert.ok(
60+
["open", "showing"].includes(previewContainer.panel.state),
61+
"tab1 preview shown"
62+
);
63+
Assert.equal(
64+
previewContainer.renderRoot.querySelector(".tab-preview-title").innerText,
65+
"First New Tab",
66+
"Preview of tab1 shows correct title"
67+
);
68+
69+
await openPreview(tab2);
70+
Assert.ok(
71+
["open", "showing"].includes(previewContainer.panel.state),
72+
"tab2 preview shown"
73+
);
74+
Assert.equal(
75+
previewContainer.renderRoot.querySelector(".tab-preview-title").innerText,
76+
"Second New Tab",
77+
"Preview of tab2 shows correct title"
78+
);
79+
80+
await closePreviews();
81+
Assert.ok(
82+
["closed", "hiding"].includes(previewContainer.panel.state),
83+
"preview container is now hidden"
84+
);
85+
86+
BrowserTestUtils.removeTab(tab1);
87+
BrowserTestUtils.removeTab(tab2);
88+
});
89+
90+
/**
91+
* Verify that non-selected tabs display a thumbnail in their preview
92+
* when browser.tabs.cardPreview.showThumbnails is set to true,
93+
* while the currently selected tab never displays a thumbnail in its preview.
94+
*/
95+
add_task(async () => {
96+
await SpecialPowers.pushPrefEnv({
97+
set: [["browser.tabs.cardPreview.showThumbnails", true]],
98+
});
99+
const tabUrl1 = "about:blank";
100+
const tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, tabUrl1);
101+
const tabUrl2 = "about:blank";
102+
const tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, tabUrl2);
103+
const previewContainer = document.getElementById("tabbrowser-tab-preview");
104+
105+
const thumbnailUpdated = BrowserTestUtils.waitForEvent(
106+
previewContainer,
107+
"previewThumbnailUpdated"
108+
);
109+
await openPreview(tab1);
110+
await thumbnailUpdated;
111+
Assert.ok(
112+
previewContainer.renderRoot.querySelectorAll("img,canvas").length,
113+
"Tab1 preview contains thumbnail"
114+
);
115+
116+
await openPreview(tab2);
117+
Assert.equal(
118+
previewContainer.renderRoot.querySelectorAll("img,canvas").length,
119+
0,
120+
"Tab2 (selected) does not contain thumbnail"
121+
);
122+
123+
BrowserTestUtils.removeTab(tab1);
124+
BrowserTestUtils.removeTab(tab2);
125+
await SpecialPowers.popPrefEnv();
126+
});

browser/components/moz.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ DIRS += [
5656
"sessionstore",
5757
"shell",
5858
"syncedtabs",
59+
"tabpreview",
5960
"tabunloader",
6061
"textrecognition",
6162
"translations",

browser/components/preferences/main.inc.xhtml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@
9696
preference="browser.taskbar.previews.enable"/>
9797
#endif
9898

99+
<checkbox id="tabPreviewShowThumbnails" data-l10n-id="settings-tabs-show-image-in-preview"
100+
preference="browser.tabs.cardPreview.showThumbnails" hidden="true"/>
101+
99102
<vbox id="browserContainersbox" hidden="true">
100103
<hbox id="browserContainersExtensionContent"
101104
align="center" class="extension-controlled info-box-container">

browser/components/preferences/main.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ Preferences.addAll([
8484
{ id: "browser.warnOnQuitShortcut", type: "bool" },
8585
{ id: "browser.tabs.warnOnOpen", type: "bool" },
8686
{ id: "browser.ctrlTab.sortByRecentlyUsed", type: "bool" },
87+
{ id: "browser.tabs.cardPreview.enabled", type: "bool" },
88+
{ id: "browser.tabs.cardPreview.showThumbnails", type: "bool" },
8789

8890
// CFR
8991
{
@@ -351,6 +353,15 @@ var gMainPane = {
351353
} catch (ex) {}
352354
}
353355

356+
let thumbsCheckbox = document.getElementById("tabPreviewShowThumbnails");
357+
let cardPreviewEnabledPref = Preferences.get(
358+
"browser.tabs.cardPreview.enabled"
359+
);
360+
let maybeShowThumbsCheckbox = () =>
361+
(thumbsCheckbox.hidden = !cardPreviewEnabledPref.value);
362+
cardPreviewEnabledPref.on("change", maybeShowThumbsCheckbox);
363+
maybeShowThumbsCheckbox();
364+
354365
// The "opening multiple tabs might slow down Firefox" warning provides
355366
// an option for not showing this warning again. When the user disables it,
356367
// we provide checkboxes to re-enable the warning.

browser/components/tabpreview/jar.mn

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
browser.jar:
6+
content/browser/tabpreview/tabpreview.mjs (tabpreview.mjs)
7+
content/browser/tabpreview/tabpreview.css (tabpreview.css)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
JAR_MANIFESTS += ["jar.mn"]
6+
7+
with Files("**"):
8+
BUG_COMPONENT = ("Firefox", "Tabbed Browser")
9+
10+
SPHINX_TREES["docs"] = "docs"
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
.tab-preview-container {
6+
background-color: #ffffff;
7+
color: #15141a;
8+
border-radius: 9px;
9+
display: inline-block;
10+
width: 280px;
11+
overflow: hidden;
12+
line-height: 1.5;
13+
}
14+
15+
.tab-preview-title {
16+
max-height: 3em;
17+
overflow: hidden;
18+
font-weight: 600;
19+
}
20+
21+
.tab-preview-uri {
22+
color: #4a4b49;
23+
max-height: 1.5em;
24+
overflow: hidden;
25+
white-space: nowrap;
26+
text-overflow: ellipsis;
27+
}
28+
29+
.tab-preview-text-container {
30+
padding: 8px;
31+
}
32+
33+
.tab-preview-thumbnail-container img,
34+
.tab-preview-thumbnail-container canvas {
35+
display: block;
36+
width: 100%;
37+
}
38+
39+
@media (prefers-color-scheme: dark) {
40+
.tab-preview-container {
41+
background-color: #42414d;
42+
color: #fbfbfe;
43+
}
44+
.tab-preview-uri {
45+
color: #cfcfd8;
46+
}
47+
}
48+
49+
@media (prefers-contrast) {
50+
.tab-preview-container {
51+
background-color: Canvas;
52+
color: CanvasText;
53+
}
54+
.tab-preview-uri {
55+
color: CanvasText;
56+
}
57+
}
58+
59+
@media (max-width: 640px) {
60+
.tab-preview-thumbnail-container {
61+
display: none;
62+
}
63+
}

0 commit comments

Comments
 (0)