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:
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() {