tor-browser

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

commit 32de7fcb43cf6614e23873240cfe3cb55e85a784
parent c9fd191de6dfbd1f87e2e3b565cb6df777534a14
Author: Stephen Thompson <sthompson@mozilla.com>
Date:   Tue,  6 Jan 2026 01:31:32 +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 | 15++++++++++++++-
Mbrowser/components/tabbrowser/content/tab-hover-preview.mjs | 61++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mbrowser/components/tabbrowser/test/browser/tabs/browser_tab_preview.js | 54++++++++++++++++++++++++++++++++++++++++++++++++++----
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, 133 insertions(+), 12 deletions(-)

diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content/main-popupset.inc.xhtml @@ -553,7 +553,7 @@ <hbox id="ctrlTab-showAll-container" pack="center"/> </panel> - <!-- TODO: create lazily? --> + <!-- TODO Bug 2008627: create lazily? --> <panel id="tab-preview-panel" type="arrow" orient="vertical" @@ -573,6 +573,19 @@ <html:div class="tab-note-text-container"></html:div> </panel> + <!-- + TODO Bug 2008627: put all of #tab-preview-panel in here, not just this button. + This is being lazy-loaded because otherwise it will eagerly load multiple svg + images on startup (see browser_startup_images.js). + --> + <html:template id="tabPreviewPanelTemplate"> + <html:moz-box-button + class="tab-preview-add-note" + data-l10n-id="tab-context-add-note" + iconsrc="chrome://global/skin/icons/tab-notes.svg" + /> + </html:template> + <panel id="tabgroup-preview-panel" role="menu" data-l10n-id="tab-group-preview-name" diff --git a/browser/components/tabbrowser/content/tab-hover-preview.mjs b/browser/components/tabbrowser/content/tab-hover-preview.mjs @@ -42,6 +42,9 @@ export default class TabHoverPanelSet { /** @type {HoverPanel|null} */ #activePanel; + /** + * @param {Window} win + */ constructor(win) { XPCOMUtils.defineLazyPreferenceGetter( this, @@ -59,10 +62,21 @@ export default class TabHoverPanelSet { this.#win ); - this.tabPanel = new TabPanel( - this.#win.document.getElementById("tab-preview-panel"), - this + /** @type {HTMLTemplateElement} */ + const tabPreviewTemplate = win.document.getElementById( + "tabPreviewPanelTemplate" ); + const importedFragment = win.document.importNode( + tabPreviewTemplate.content, + true + ); + // #tabPreviewPanelTemplate is currently just the .tab-preview-add-note + // button element, so append it to the tab preview panel body. + const addNoteButton = importedFragment.firstElementChild; + const tabPreviewPanel = + this.#win.document.getElementById("tab-preview-panel"); + tabPreviewPanel.append(addNoteButton); + this.tabPanel = new TabPanel(tabPreviewPanel, this); this.tabGroupPanel = new TabGroupPanel( this.#win.document.getElementById("tabgroup-preview-panel"), this @@ -274,8 +288,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 +355,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 +501,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 +532,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,46 @@ 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"); + Assert.equal( + document.activeElement, + tabNotePanel.querySelector("textarea"), + "tab note textarea should be focused" + ); + const input = BrowserTestUtils.waitForEvent(document.activeElement, "input"); + EventUtils.sendString(noteText, window); + await input; + 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 +619,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>