tor-browser

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

commit be77b2595a99898b209a1a14eabffe5efe00aae7
parent 934c10f8994dfa91037f2fb9d3072ca6155b95ea
Author: Jeremy Swinarton <jswinarton@mozilla.com>
Date:   Wed,  7 Jan 2026 13:07:01 +0000

Bug 2003705: Telemetry event when tab note is deleted r=sthompson,tabbrowser-reviewers

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

Diffstat:
Mbrowser/components/tabbrowser/content/tabbrowser.js | 4+++-
Mbrowser/components/tabnotes/TabNotes.sys.mjs | 3++-
Mbrowser/components/tabnotes/TabNotesController.sys.mjs | 12++++++++++++
Mbrowser/components/tabnotes/metrics.yaml | 27+++++++++++++++++++++++++++
Mbrowser/components/tabnotes/test/browser/browser_tab_notes_menu.js | 38++++++++++++++++++++++++++++++++++++++
5 files changed, 82 insertions(+), 2 deletions(-)

diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js @@ -10419,7 +10419,9 @@ var TabContextMenu = { deleteTabNotes() { for (let tab of this.contextTabs) { - this.TabNotes.delete(tab); + this.TabNotes.delete(tab, { + telemetrySource: this.TabNotes.TELEMETRY_SOURCE.TAB_CONTEXT_MENU, + }); } }, }; diff --git a/browser/components/tabnotes/TabNotes.sys.mjs b/browser/components/tabnotes/TabNotes.sys.mjs @@ -89,8 +89,9 @@ export class TabNotesStorage { */ init(options) { const basePath = options?.basePath ?? PathUtils.profileDir; + this.dbPath = PathUtils.join(basePath, this.DATABASE_FILE_NAME); return Sqlite.openConnection({ - path: PathUtils.join(basePath, this.DATABASE_FILE_NAME), + path: this.dbPath, }).then(async connection => { this.#connection = connection; await this.#connection.execute("PRAGMA journal_mode = WAL"); diff --git a/browser/components/tabnotes/TabNotesController.sys.mjs b/browser/components/tabnotes/TabNotesController.sys.mjs @@ -148,6 +148,18 @@ class TabNotesControllerClass { break; case "TabNote:Removed": { + const { telemetrySource, note } = event.detail; + const now = Temporal.Now.instant(); + const noteAgeHours = Math.round( + now.since(note.created).total("hours") + ); + if (telemetrySource) { + Glean.tabNotes.deleted.record({ + source: telemetrySource, + note_age_hours: noteAgeHours, + }); + } + // A new tab note was removed from a specific canonical URL. Ensure that // all tabs with the same canonical URL also indicate that there is no // longer a tab note. diff --git a/browser/components/tabnotes/metrics.yaml b/browser/components/tabnotes/metrics.yaml @@ -32,3 +32,30 @@ tab_notes: - `hover_menu` # Tab hover preview panel's "Add Note" button type: string expires: never + deleted: + type: event + description: > + Recorded when a user deletes an existing note for a tab. + notification_emails: + - sthompson@mozilla.com + - jswinarton@mozilla.com + bugs: + - https://bugzil.la/2003705 + data_reviews: + - https://bugzil.la/2003705 + data_sensitivity: + - interaction + extra_keys: + source: + description: > + Identifies the user interface entry point that resulted in this tab + note being deleted. Expected values: + - `context_menu` # Tab context menu's "Delete Note" menu item + - `hover_menu` # "Delete Note" button in the edit note UI via tab hover preview + type: string + note_age_hours: + description: > + Number of hours that elapsed since the tab note was first created, + expressed as an integer (rounded to the nearest full hour). + type: quantity + expires: never diff --git a/browser/components/tabnotes/test/browser/browser_tab_notes_menu.js b/browser/components/tabnotes/test/browser/browser_tab_notes_menu.js @@ -4,6 +4,10 @@ "use strict"; +const { Sqlite } = ChromeUtils.importESModule( + "resource://gre/modules/Sqlite.sys.mjs" +); + registerCleanupFunction(async () => { await TabNotes.reset(); }); @@ -274,15 +278,49 @@ add_task(async function test_deleteTabNote() { await TabNotes.set(tab, initialNoteValue); await tabNoteCreated; + // Modify the created time of the note so we can test the max age recorded by + // Glean + const dbConn = await Sqlite.openConnection({ + path: TabNotes.dbPath, + }); + dbConn.executeCached( + 'UPDATE tabnotes SET created = unixepoch("now", "-12 hours") WHERE canonical_url = :url', + { + url: tab.canonicalUrl, + } + ); + let tabNoteRemoved = BrowserTestUtils.waitForEvent(tab, "TabNote:Removed"); activateTabContextMenuItem(tab, "#context_deleteNote", "#context_updateNote"); await tabNoteRemoved; + await BrowserTestUtils.waitForCondition( + () => Glean.tabNotes.deleted.testGetValue()?.length, + "wait for event to be recorded" + ); + let result = await TabNotes.has(tab); Assert.ok(!result, "Tab note was deleted"); + const [deletedMetric] = Glean.tabNotes.deleted.testGetValue(); + Assert.equal( + deletedMetric.extra.source, + "context_menu", + "deleted event extra data should say the tab note was deleted from the context menu" + ); + Assert.equal( + deletedMetric.extra.note_age_hours, + 12, + "note_age_hours should show that note was created 12 hours ago" + ); + BrowserTestUtils.removeTab(tab); + await dbConn.close(); + + // Reset Glean metrics + await Services.fog.testFlushAllChildren(); + Services.fog.testResetFOG(); }); add_task(async function test_tabNoteOverflow() {