tor-browser

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

commit 113d24b27ae7e2932e195beb80c0a0d261325a22
parent dcbcf6b14a80ee09dd7a313bfba1fa43b59007dc
Author: Jeremy Swinarton <jswinarton@mozilla.com>
Date:   Thu, 18 Dec 2025 20:31:04 +0000

Bug 2003710: Add 'Update Note' tab context menu entries r=sthompson,fluent-reviewers,tabbrowser-reviewers,bolsson

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

Diffstat:
Mbrowser/base/content/main-popupset.inc.xhtml | 10+++++++++-
Mbrowser/base/content/main-popupset.js | 3+++
Mbrowser/components/tabbrowser/content/tabbrowser.js | 16++++++++++++----
Mbrowser/components/tabnotes/test/browser/browser_tab_notes_menu.js | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mbrowser/locales/en-US/browser/tabbrowser.ftl | 6++++++
5 files changed, 160 insertions(+), 11 deletions(-)

diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content/main-popupset.inc.xhtml @@ -70,7 +70,15 @@ <menuitem id="context_bookmarkTab" data-lazy-l10n-id="tab-context-bookmark-tab"/> <menuitem id="context_addNote" hidden="true" data-lazy-l10n-id="tab-context-add-note" /> - <menuitem id="context_editNote" hidden="true" data-lazy-l10n-id="tab-context-edit-note" /> + <menu id="context_updateNote" + hidden="true" + data-lazy-l10n-id="tab-context-update-note"> + <menupopup id="tabUpdateOptionsMenu"> + <menuitem id="context_editNote" data-lazy-l10n-id="tab-context-edit-note" /> + <menuitem id="context_deleteNote" data-lazy-l10n-id="tab-context-delete-note" /> + </menupopup> + </menu> + <menu id="context_moveTabOptions" data-lazy-l10n-id="tab-context-move-tabs" data-l10n-args='{"tabCount": 1}'> diff --git a/browser/base/content/main-popupset.js b/browser/base/content/main-popupset.js @@ -88,6 +88,9 @@ document.addEventListener( case "context_editNote": gBrowser.tabNoteMenu.openPanel(TabContextMenu.contextTab); break; + case "context_deleteNote": + TabContextMenu.deleteTabNotes(); + break; case "context_moveToStart": gBrowser.moveTabsToStart(TabContextMenu.contextTab); break; diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js @@ -9827,17 +9827,19 @@ var TabContextMenu = { } let contextAddNote = document.getElementById("context_addNote"); - let contextEditNote = document.getElementById("context_editNote"); + let contextUpdateNote = document.getElementById("context_updateNote"); if (gBrowser._tabNotesEnabled) { - contextAddNote.disabled = !this.TabNotes.isEligible(this.contextTab); + contextAddNote.disabled = + this.multiselected || !this.TabNotes.isEligible(this.contextTab); + contextUpdateNote.disabled = this.multiselected; this.TabNotes.has(this.contextTab).then(hasNote => { contextAddNote.hidden = hasNote; - contextEditNote.hidden = !hasNote; + contextUpdateNote.hidden = !hasNote; }); } else { contextAddNote.hidden = true; - contextEditNote.hidden = true; + contextUpdateNote.hidden = true; } // Split View @@ -10386,6 +10388,12 @@ var TabContextMenu = { ); }); }, + + deleteTabNotes() { + for (let tab of this.contextTabs) { + this.TabNotes.delete(tab); + } + }, }; ChromeUtils.defineESModuleGetters(TabContextMenu, { diff --git a/browser/components/tabnotes/test/browser/browser_tab_notes_menu.js b/browser/components/tabnotes/test/browser/browser_tab_notes_menu.js @@ -35,6 +35,58 @@ async function getContextMenu(triggerNode, contextMenuId) { return contextMenu; } +let activateTabContextMenuItem = async ( + selectedTab, + menuItemSelector, + submenuItemSelector +) => { + let submenuItem; + let submenuItemHiddenPromise; + + const win = selectedTab.ownerGlobal; + const tabContextMenu = win.document.getElementById("tabContextMenu"); + const contextMenuShown = BrowserTestUtils.waitForEvent( + tabContextMenu, + "popupshown", + false, + ev => ev.target == tabContextMenu + ); + EventUtils.synthesizeMouseAtCenter( + selectedTab, + { type: "contextmenu", button: 2 }, + win + ); + await contextMenuShown; + + if (submenuItemSelector) { + submenuItem = tabContextMenu.querySelector(submenuItemSelector); + + const submenuPopupPromise = BrowserTestUtils.waitForEvent( + submenuItem.menupopup, + "popupshown" + ); + submenuItem.openMenu(true); + await submenuPopupPromise; + + submenuItemHiddenPromise = BrowserTestUtils.waitForEvent( + submenuItem.menupopup, + "popuphidden" + ); + } + + const contextMenuHidden = BrowserTestUtils.waitForEvent( + tabContextMenu, + "popuphidden", + false, + ev => ev.target == tabContextMenu + ); + tabContextMenu.activateItem(tabContextMenu.querySelector(menuItemSelector)); + await contextMenuHidden; + if (submenuItemSelector) { + await submenuItemHiddenPromise; + } +}; + /** * @param {XULMenuElement|XULPopupElement} contextMenu * @returns {Promise<void>} @@ -45,11 +97,18 @@ async function closeContextMenu(contextMenu) { await menuHidden; } -async function openTabNoteMenu(tab) { - let tabContextMenu = await getContextMenu(tab, "tabContextMenu"); +async function openTabNoteMenuByAddNote(tab) { let tabNotePanel = document.getElementById("tabNotePanel"); let panelShown = BrowserTestUtils.waitForPopupEvent(tabNotePanel, "shown"); - tabContextMenu.activateItem(document.getElementById("context_addNote")); + activateTabContextMenuItem(tab, "#context_addNote"); + await panelShown; + return tabNotePanel; +} + +async function openTabNoteMenuByEditNote(tab) { + let tabNotePanel = document.getElementById("tabNotePanel"); + let panelShown = BrowserTestUtils.waitForPopupEvent(tabNotePanel, "shown"); + activateTabContextMenuItem(tab, "#context_editNote", "#context_updateNote"); await panelShown; return tabNotePanel; } @@ -116,7 +175,7 @@ add_task(async function test_dismissTabNotePanel() { // Dismiss panel by pressing Esc let tab = BrowserTestUtils.addTab(gBrowser, "https://www.example.com"); await BrowserTestUtils.browserLoaded(tab.linkedBrowser); - let tabNoteMenu = await openTabNoteMenu(tab); + let tabNoteMenu = await openTabNoteMenuByAddNote(tab); Assert.equal(tabNoteMenu.state, "open", "Tab note menu is open"); EventUtils.synthesizeKey("KEY_Escape"); await BrowserTestUtils.waitForPopupEvent(tabNoteMenu, "hidden"); @@ -127,7 +186,7 @@ add_task(async function test_dismissTabNotePanel() { ); // Dismiss panel by clicking Cancel - tabNoteMenu = await openTabNoteMenu(tab); + tabNoteMenu = await openTabNoteMenuByAddNote(tab); Assert.equal(tabNoteMenu.state, "open", "Tab note menu is open"); let menuHidden = BrowserTestUtils.waitForPopupEvent(tabNoteMenu, "hidden"); let cancelButton = document.getElementById("tab-note-editor-button-cancel"); @@ -148,7 +207,7 @@ add_task(async function test_saveTabNote() { }); let tab = BrowserTestUtils.addTab(gBrowser, "https://www.example.com"); await BrowserTestUtils.browserLoaded(tab.linkedBrowser); - let tabNoteMenu = await openTabNoteMenu(tab); + let tabNoteMenu = await openTabNoteMenuByAddNote(tab); tabNoteMenu.querySelector("textarea").value = "Lorem ipsum dolor"; let menuHidden = BrowserTestUtils.waitForPopupEvent(tabNoteMenu, "hidden"); let tabNoteCreated = BrowserTestUtils.waitForEvent(tab, "TabNote:Created"); @@ -167,3 +226,68 @@ add_task(async function test_saveTabNote() { await SpecialPowers.popPrefEnv(); }); + +add_task(async function test_editTabNote() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.tabs.notes.enabled", true]], + }); + + let initialNoteValue = "Lorem ipsum dolor"; + + let tab = BrowserTestUtils.addTab(gBrowser, "https://www.example.com"); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + let tabNoteCreated = BrowserTestUtils.waitForEvent(tab, "TabNote:Created"); + await TabNotes.set(tab, initialNoteValue); + await tabNoteCreated; + + let tabNoteMenu = await openTabNoteMenuByEditNote(tab); + Assert.equal( + tabNoteMenu.querySelector("textarea").value, + initialNoteValue, + "Tab note panel has initial note value in textarea" + ); + + let updatedNoteValue = initialNoteValue + " sit amet"; + + tabNoteMenu.querySelector("textarea").value = updatedNoteValue; + let menuHidden = BrowserTestUtils.waitForPopupEvent(tabNoteMenu, "hidden"); + let tabNoteEdited = BrowserTestUtils.waitForEvent(tab, "TabNote:Edited"); + tabNoteMenu.querySelector("#tab-note-editor-button-save").click(); + await Promise.all([menuHidden, tabNoteEdited]); + + const tabNote = await TabNotes.get(tab); + Assert.equal( + tabNote.text, + updatedNoteValue, + "The updated text entered into the textarea was saved as a note" + ); + + await TabNotes.delete(tab); + BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function test_deleteTabNote() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.tabs.notes.enabled", true]], + }); + + let initialNoteValue = "Lorem ipsum dolor"; + + let tab = BrowserTestUtils.addTab(gBrowser, "https://www.example.com"); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + let tabNoteCreated = BrowserTestUtils.waitForEvent(tab, "TabNote:Created"); + await TabNotes.set(tab, initialNoteValue); + await tabNoteCreated; + + let tabNoteRemoved = BrowserTestUtils.waitForEvent(tab, "TabNote:Removed"); + activateTabContextMenuItem(tab, "#context_deleteNote", "#context_updateNote"); + await tabNoteRemoved; + + let result = await TabNotes.has(tab); + + Assert.ok(!result, "Tab note was deleted"); + + BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); +}); diff --git a/browser/locales/en-US/browser/tabbrowser.ftl b/browser/locales/en-US/browser/tabbrowser.ftl @@ -386,9 +386,15 @@ tab-group-context-open-saved-group-in-new-window = tab-context-add-note = .label = Add Note .accesskey = A +tab-context-update-note = + .label = Update Note + .accesskey = U tab-context-edit-note = .label = Edit Note .accesskey = E +tab-context-delete-note = + .label = Delete Note + .accesskey = D tab-note-editor-title-create = Add note tab-note-editor-title-edit = Edit note tab-note-editor-text-field =