Skip to content

Commit 2c25cf5

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 5846827 commit 2c25cf5

File tree

15 files changed

+511
-0
lines changed

15 files changed

+511
-0
lines changed

browser/app/profile/firefox.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,13 @@ pref("browser.tabs.tooltipsShowPidAndActiveness", true);
904904
pref("browser.tabs.tooltipsShowPidAndActiveness", false);
905905
#endif
906906

907+
pref("browser.tabs.cardPreview.enabled", false);
908+
pref("browser.tabs.cardPreview.delayMs", 1000);
909+
pref("browser.tabs.cardPreview.showThumbnails", true);
910+
911+
pref("browser.tabs.firefox-view", true);
912+
pref("browser.tabs.firefox-view-next", true);
913+
pref("browser.tabs.firefox-view-newIcon", true);
907914
pref("browser.tabs.firefox-view.logLevel", "Warn");
908915
pref("browser.tabs.firefox-view.notify-for-tabs", false);
909916

browser/base/content/browser.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2336,6 +2336,13 @@ var gBrowserInit = {
23362336
readSetting();
23372337
});
23382338

2339+
scheduleIdleTask(() => {
2340+
// load the tab preview component
2341+
import("chrome://browser/content/tabpreview/tabpreview.mjs").catch(
2342+
console.error
2343+
);
2344+
});
2345+
23392346
scheduleIdleTask(() => {
23402347
// setup simple gestures support
23412348
gGestureSupport.init(true);

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

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

415+
<html:tab-preview id="tabbrowser-tab-preview" hidden="true" />
416+
415417
<html:template id="pageActionPanelTemplate">
416418
<panel id="pageActionPanel"
417419
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
@@ -550,6 +550,8 @@
550550
tabToWarm = gBrowser._findTabToBlurTo(this);
551551
}
552552
gBrowser.warmupTab(tabToWarm);
553+
554+
this.dispatchEvent(new CustomEvent("TabHoverStart", { bubbles: true }));
553555
}
554556

555557
_mouseleave() {
@@ -561,6 +563,7 @@
561563
this.linkedBrowser.unselectedTabHover(false);
562564
this.cancelUnselectedTabHoverTimer();
563565
}
566+
this.dispatchEvent(new CustomEvent("TabHoverEnd", { bubbles: true }));
564567
}
565568

566569
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.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ skip-if = ["true"] # Bug 1616418 Bug 1549985
318318

319319
["browser_tab_play.js"]
320320

321+
["browser_tab_preview.js"]
322+
321323
["browser_tab_tooltips.js"]
322324

323325
["browser_tabswitch_contextmenu.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
@@ -61,6 +61,7 @@ DIRS += [
6161
"shell",
6262
"shopping",
6363
"syncedtabs",
64+
"tabpreview",
6465
"tabunloader",
6566
"textrecognition",
6667
"translations",

browser/components/preferences/main.inc.xhtml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@
110110
preference="browser.taskbar.previews.enable"/>
111111
#endif
112112

113+
<checkbox id="tabPreviewShowThumbnails" data-l10n-id="settings-tabs-show-image-in-preview"
114+
preference="browser.tabs.cardPreview.showThumbnails" hidden="true"/>
115+
113116
<vbox id="browserContainersbox" hidden="true">
114117
<hbox id="browserContainersExtensionContent"
115118
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
@@ -86,6 +86,8 @@ Preferences.addAll([
8686
{ id: "browser.warnOnQuitShortcut", type: "bool" },
8787
{ id: "browser.tabs.warnOnOpen", type: "bool" },
8888
{ id: "browser.ctrlTab.sortByRecentlyUsed", type: "bool" },
89+
{ id: "browser.tabs.cardPreview.enabled", type: "bool" },
90+
{ id: "browser.tabs.cardPreview.showThumbnails", type: "bool" },
8991

9092
// CFR
9193
{
@@ -350,6 +352,15 @@ var gMainPane = {
350352
} catch (ex) {}
351353
}
352354

355+
let thumbsCheckbox = document.getElementById("tabPreviewShowThumbnails");
356+
let cardPreviewEnabledPref = Preferences.get(
357+
"browser.tabs.cardPreview.enabled"
358+
);
359+
let maybeShowThumbsCheckbox = () =>
360+
(thumbsCheckbox.hidden = !cardPreviewEnabledPref.value);
361+
cardPreviewEnabledPref.on("change", maybeShowThumbsCheckbox);
362+
maybeShowThumbsCheckbox();
363+
353364
// The "opening multiple tabs might slow down Firefox" warning provides
354365
// an option for not showing this warning again. When the user disables it,
355366
// 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)