tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit b2bada9642675e69b7b8c4dca8d1c472c9d73696
parent a1b1d695856317c4bb08cb8695c45a88a9669316
Author: Stephen Thompson <sthompson@mozilla.com>
Date:   Mon,  5 Jan 2026 17:37:54 +0000

Bug 2000070 - "add note" button on tab hover preview r=dwalker,jswinarton,desktop-theme-reviewers,tabbrowser-reviewers

When hovering over a tab with a loaded web page, the tab hover preview panel will now display an "Add note" button if the tab does not yet have a tab note. Clicking the "Add note" button will open the "Add note" tab note panel and close the tab hover preview panel.

Differential Revision: https://phabricator.services.mozilla.com/D275678

Diffstat:
Mbrowser/base/content/main-popupset.inc.xhtml | 5+++++
Mbrowser/components/tabbrowser/content/tab-hover-preview.mjs | 41+++++++++++++++++++++++++++++++++++++----
Mbrowser/components/tabbrowser/test/browser/tabs/browser_tab_preview.js | 47+++++++++++++++++++++++++++++++++++++++++++----
Mbrowser/themes/shared/tabbrowser/tab-hover-preview.css | 10++++++++++
Mtoolkit/themes/shared/desktop-jar.inc.mn | 1+
Atoolkit/themes/shared/icons/tab-notes.svg | 4++++
6 files changed, 100 insertions(+), 8 deletions(-)

diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content/main-popupset.inc.xhtml @@ -571,6 +571,11 @@ </html:div> <html:div class="tab-preview-thumbnail-container"></html:div> <html:div class="tab-note-text-container"></html:div> + <html:moz-box-button + class="tab-preview-add-note" + data-l10n-id="tab-context-add-note" + iconsrc="chrome://global/skin/icons/tab-notes.svg" + /> </panel> <panel id="tabgroup-preview-panel" diff --git a/browser/components/tabbrowser/content/tab-hover-preview.mjs b/browser/components/tabbrowser/content/tab-hover-preview.mjs @@ -274,8 +274,15 @@ class TabPanel extends HoverPanel { this.#tab = null; this.#thumbnailElement = null; + + this.panelElement + .querySelector(".tab-preview-add-note") + .addEventListener("click", () => this.#openTabNotePanel()); } + /** + * @param {Event} e + */ handleEvent(e) { switch (e.type) { case "popupshowing": @@ -334,6 +341,11 @@ class TabPanel extends HoverPanel { } } + /** + * @param {MozTabbrowserTab} [leavingTab] + * @param {object} [options] + * @param {boolean} [options.force=false] + */ deactivate(leavingTab = null, { force = false } = {}) { if (!this._prefUseTabNotes) { force = true; @@ -475,6 +487,16 @@ class TabPanel extends HoverPanel { : ""; } + /** + * Opens the tab note menu in the context of the current tab. Since only + * one panel should be open at a time, this also closes the tab hover preview + * panel. + */ + #openTabNotePanel() { + this.win.gBrowser.tabNoteMenu.openPanel(this.#tab); + this.deactivate(this.#tab, { force: true }); + } + #updatePreview(tab = null) { if (tab) { this.#tab = tab; @@ -496,10 +518,21 @@ class TabPanel extends HoverPanel { ""; } - lazy.TabNotes.get(this.#tab).then(note => { - this.panelElement.querySelector(".tab-note-text-container").textContent = - note?.text || ""; - }); + const noteTextContainer = this.panelElement.querySelector( + ".tab-note-text-container" + ); + const addNoteButton = this.panelElement.querySelector( + ".tab-preview-add-note" + ); + if (this._prefUseTabNotes && lazy.TabNotes.isEligible(this.#tab)) { + lazy.TabNotes.get(this.#tab).then(note => { + noteTextContainer.textContent = note?.text || ""; + addNoteButton.toggleAttribute("hidden", !!note); + }); + } else { + noteTextContainer.textContent = ""; + addNoteButton.setAttribute("hidden", ""); + } let thumbnailContainer = this.panelElement.querySelector( ".tab-preview-thumbnail-container" diff --git a/browser/components/tabbrowser/test/browser/tabs/browser_tab_preview.js b/browser/components/tabbrowser/test/browser/tabs/browser_tab_preview.js @@ -555,7 +555,8 @@ add_task(async function tabContentChangeTests() { }); /** - * Test that if a note is set on a tab, the note appears in the preview panel + * Test that tab notes and their UI elements appear correctly in the tab + * hover preview panel. */ add_task(async function tabNotesTests() { if (!Services.prefs.getBoolPref("browser.tabs.notes.enabled", false)) { @@ -571,18 +572,39 @@ add_task(async function tabNotesTests() { const tab = await addTabTo(gBrowser, "https://example.com/"); + info("validate the presentation of an eligible tab with no note"); await openTabPreview(tab); Assert.equal( previewPanel.querySelector(".tab-note-text-container").innerText, "", "Preview panel contains no tab note" ); - await closeTabPreviews(); + let addNoteButton = previewPanel.querySelector(".tab-preview-add-note"); + Assert.ok( + !addNoteButton.hasAttribute("hidden"), + "add note button should be visible on an eligible tab without a tab note" + ); + info("choose to add a note from the tab hover preview panel"); + let tabNotePanel = document.getElementById("tabNotePanel"); + let panelShown = BrowserTestUtils.waitForPopupEvent(tabNotePanel, "shown"); + const previewHidden = BrowserTestUtils.waitForPopupEvent( + previewPanel, + "hidden" + ); + addNoteButton.click(); + await Promise.all([panelShown, previewHidden]); + + info("save a new tab note"); + tabNotePanel.querySelector("textarea").value = noteText; + let menuHidden = BrowserTestUtils.waitForPopupEvent(tabNotePanel, "hidden"); const tabNoteCreated = BrowserTestUtils.waitForEvent(tab, "TabNote:Created"); - TabNotes.set(tab, noteText); - await tabNoteCreated; + tabNotePanel.querySelector("#tab-note-editor-button-save").click(); + await Promise.all([menuHidden, tabNoteCreated]); + + await closeTabPreviews(); + info("validate the presentation of an eligible tab with a tab note"); await openTabPreview(tab); Assert.equal( @@ -590,22 +612,39 @@ add_task(async function tabNotesTests() { noteText, "New tab note is visible in preview panel" ); + addNoteButton = previewPanel.querySelector(".tab-preview-add-note"); + Assert.ok( + addNoteButton.hasAttribute("hidden"), + "add note button should be hidden on an eligible tab with a tab note" + ); await closeTabPreviews(); + info( + "delete the tab note to return the tab hover preview to the state with no tab note" + ); const tabNoteRemoved = BrowserTestUtils.waitForEvent(tab, "TabNote:Removed"); TabNotes.delete(tab); await tabNoteRemoved; + info( + "validate the presentation of an eligible tab after its note has been deleted" + ); await openTabPreview(tab); Assert.equal( previewPanel.querySelector(".tab-note-text-container").innerText, "", "Preview panel contains no tab note after delete" ); + addNoteButton = previewPanel.querySelector(".tab-preview-add-note"); + Assert.ok( + !addNoteButton.hasAttribute("hidden"), + "add note button should be visible on an eligible tab without a tab note after delete" + ); await closeTabPreviews(); BrowserTestUtils.removeTab(tab); await resetState(); + await TabNotes.reset(); }); /* diff --git a/browser/themes/shared/tabbrowser/tab-hover-preview.css b/browser/themes/shared/tabbrowser/tab-hover-preview.css @@ -64,6 +64,16 @@ -webkit-line-clamp: 10; } +.tab-preview-add-note { + --box-border-color: transparent; + --box-padding: var(--space-medium); + margin: 0 var(--space-small) var(--space-small); + + &[hidden] { + display: none; + } +} + @keyframes tab-hover-preview-fadein { from { opacity: 0; diff --git a/toolkit/themes/shared/desktop-jar.inc.mn b/toolkit/themes/shared/desktop-jar.inc.mn @@ -116,6 +116,7 @@ skin/classic/global/icons/sort-arrow.svg (../../shared/icons/sort-arrow.svg) skin/classic/global/icons/trending.svg (../../shared/icons/trending.svg) skin/classic/global/icons/trophy.svg (../../shared/icons/trophy.svg) + skin/classic/global/icons/tab-notes.svg (../../shared/icons/tab-notes.svg) skin/classic/global/icons/tab-notes-12.svg (../../shared/icons/tab-notes-12.svg) skin/classic/global/icons/update-icon.svg (../../shared/icons/update-icon.svg) skin/classic/global/icons/arrow-counterclockwise-16.svg (../../shared/icons/arrow-counterclockwise-16.svg) diff --git a/toolkit/themes/shared/icons/tab-notes.svg b/toolkit/themes/shared/icons/tab-notes.svg @@ -0,0 +1,4 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<svg width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity" xmlns="http://www.w3.org/2000/svg"><path d="m14.78 10.78-4 4a.747.747 0 0 1-.53.22H3a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v7.25c0 .199-.079.39-.22.53zm-1.28-.841V3c0-.275-.225-.5-.5-.5H3c-.275 0-.5.225-.5.5v10c0 .275.225.5.5.5h6.939l.061-.061V10.5a.5.5 0 0 1 .5-.5h2.939l.061-.061z"/></svg>