summaryrefslogtreecommitdiffstats
path: root/browser/modules
diff options
context:
space:
mode:
Diffstat (limited to 'browser/modules')
-rw-r--r--browser/modules/BrowserUsageTelemetry.sys.mjs35
-rw-r--r--browser/modules/ExtensionsUI.sys.mjs12
-rw-r--r--browser/modules/FirefoxBridgeExtensionUtils.sys.mjs121
-rw-r--r--browser/modules/metrics.yaml46
-rw-r--r--browser/modules/pings.yaml22
-rw-r--r--browser/modules/test/browser/browser.toml4
-rw-r--r--browser/modules/test/browser/browser_ProcessHangNotifications.js2
-rw-r--r--browser/modules/test/browser/browser_UnsubmittedCrashHandler.js6
-rw-r--r--browser/modules/test/browser/browser_UsageTelemetry_interaction.js206
-rw-r--r--browser/modules/test/unit/test_FirefoxBridgeExtensionUtils.js461
-rw-r--r--browser/modules/test/unit/test_FirefoxBridgeExtensionUtilsNativeManifest.js115
11 files changed, 713 insertions, 317 deletions
diff --git a/browser/modules/BrowserUsageTelemetry.sys.mjs b/browser/modules/BrowserUsageTelemetry.sys.mjs
index 955a3338ec..0635d17bed 100644
--- a/browser/modules/BrowserUsageTelemetry.sys.mjs
+++ b/browser/modules/BrowserUsageTelemetry.sys.mjs
@@ -160,6 +160,11 @@ const PLACES_OPEN_COMMANDS = [
"placesCmd_open:tab",
];
+// How long of a delay between events means the start of a new flow?
+// Used by Browser UI Interaction event instrumentation.
+// Default: 5min.
+const FLOW_IDLE_TIME = 5 * 60 * 1000;
+
function telemetryId(widgetId, obscureAddons = true) {
// Add-on IDs need to be obscured.
function addonId(id) {
@@ -872,6 +877,7 @@ export let BrowserUsageTelemetry = {
let source = this._getWidgetContainer(node);
if (item && source) {
+ this.recordInteractionEvent(item, source);
let scalar = `browser.ui.interaction.${source.replace(/-/g, "_")}`;
Services.telemetry.keyedScalarAdd(scalar, telemetryId(item), 1);
if (SET_USAGECOUNT_PREF_BUTTONS.includes(item)) {
@@ -889,6 +895,7 @@ export let BrowserUsageTelemetry = {
node.closest("menupopup")?.triggerNode
);
if (triggerContainer) {
+ this.recordInteractionEvent(item, contextMenu);
let scalar = `browser.ui.interaction.${contextMenu.replace(/-/g, "_")}`;
Services.telemetry.keyedScalarAdd(
scalar,
@@ -899,6 +906,34 @@ export let BrowserUsageTelemetry = {
}
},
+ _flowId: null,
+ _flowIdTS: 0,
+
+ recordInteractionEvent(widgetId, source) {
+ // A note on clocks. Cu.now() is monotonic, but its behaviour across
+ // computer sleeps is different per platform.
+ // We're okay with this for flows because we're looking at idle times
+ // on the order of minutes and within the same machine, so the weirdest
+ // thing we may expect is a flow that accidentally continues across a
+ // sleep. Until we have evidence that this is common, we're in the clear.
+ if (!this._flowId || this._flowIdTS + FLOW_IDLE_TIME < Cu.now()) {
+ // We submit the ping full o' events on every new flow,
+ // including at startup.
+ GleanPings.prototypeNoCodeEvents.submit();
+ // We use a GUID here because we need to identify events in a flow
+ // out of all events from all flows across all clients.
+ this._flowId = Services.uuid.generateUUID();
+ }
+ this._flowIdTS = Cu.now();
+
+ const extra = {
+ source,
+ widgetId: telemetryId(widgetId),
+ flowId: this._flowId,
+ };
+ Glean.browserUsage.interaction.record(extra);
+ },
+
/**
* Listens for UI interactions in the window.
*/
diff --git a/browser/modules/ExtensionsUI.sys.mjs b/browser/modules/ExtensionsUI.sys.mjs
index 6b2781670a..f6cbcfcd88 100644
--- a/browser/modules/ExtensionsUI.sys.mjs
+++ b/browser/modules/ExtensionsUI.sys.mjs
@@ -119,12 +119,12 @@ export var ExtensionsUI = {
showAddonsManager(tabbrowser, strings, icon) {
let global = tabbrowser.selectedBrowser.ownerGlobal;
- return global
- .BrowserOpenAddonsMgr("addons://list/extension")
- .then(aomWin => {
+ return global.BrowserAddonUI.openAddonsMgr("addons://list/extension").then(
+ aomWin => {
let aomBrowser = aomWin.docShell.chromeEventHandler;
return this.showPermissionsPrompt(aomBrowser, strings, icon);
- });
+ }
+ );
},
showSideloaded(tabbrowser, addon) {
@@ -134,7 +134,7 @@ export var ExtensionsUI = {
let strings = this._buildStrings({
addon,
- permissions: addon.userPermissions,
+ permissions: addon.installPermissions,
type: "sideload",
});
@@ -339,8 +339,6 @@ export var ExtensionsUI = {
async showPermissionsPrompt(target, strings, icon) {
let { browser, window } = getTabBrowser(target);
- await window.ensureCustomElements("moz-support-link");
-
// Wait for any pending prompts to complete before showing the next one.
let pending;
while ((pending = this.pendingNotifications.get(browser))) {
diff --git a/browser/modules/FirefoxBridgeExtensionUtils.sys.mjs b/browser/modules/FirefoxBridgeExtensionUtils.sys.mjs
index 7b0094205d..e1222db6e0 100644
--- a/browser/modules/FirefoxBridgeExtensionUtils.sys.mjs
+++ b/browser/modules/FirefoxBridgeExtensionUtils.sys.mjs
@@ -56,6 +56,10 @@ export const FirefoxBridgeExtensionUtils = {
* In Firefox 122, we enabled the firefox and firefox-private protocols.
* We switched over to using firefox-bridge and firefox-private-bridge,
*
+ * In Firefox 126, we deleted the above firefox-bridge and
+ * firefox-private-bridge protocols in favor of using native
+ * messaging so we are only keeping the deletion code.
+ *
* but we want to clean up the use of the other protocols.
*
* deleteBridgeProtocolRegistryEntryHelper handles everything outside of the logic needed for
@@ -66,7 +70,15 @@ export const FirefoxBridgeExtensionUtils = {
* them with. If the entries are changed in any way, it is assumed that the user
* mucked with them manually and knows what they are doing.
*/
+
+ PUBLIC_PROTOCOL: "firefox-bridge",
+ PRIVATE_PROTOCOL: "firefox-private-bridge",
+ OLD_PUBLIC_PROTOCOL: "firefox",
+ OLD_PRIVATE_PROTOCOL: "firefox-private",
+
maybeDeleteBridgeProtocolRegistryEntries(
+ publicProtocol = this.PUBLIC_PROTOCOL,
+ privateProtocol = this.PRIVATE_PROTOCOL,
deleteBridgeProtocolRegistryEntryHelper = new DeleteBridgeProtocolRegistryEntryHelperImplementation()
) {
try {
@@ -110,9 +122,9 @@ export const FirefoxBridgeExtensionUtils = {
}
};
- maybeDeleteRegistryKey("firefox", `\"${path}\" -osint -url \"%1\"`);
+ maybeDeleteRegistryKey(publicProtocol, `\"${path}\" -osint -url \"%1\"`);
maybeDeleteRegistryKey(
- "firefox-private",
+ privateProtocol,
`\"${path}\" -osint -private-window \"%1\"`
);
} catch (err) {
@@ -122,111 +134,6 @@ export const FirefoxBridgeExtensionUtils = {
}
},
- /**
- * Registers the firefox-bridge and firefox-private-bridge protocols
- * on the Windows platform.
- */
- maybeRegisterFirefoxBridgeProtocols() {
- const FIREFOX_BRIDGE_HANDLER_NAME = "firefox-bridge";
- const FIREFOX_PRIVATE_BRIDGE_HANDLER_NAME = "firefox-private-bridge";
- const path = Services.dirsvc.get("XREExeF", Ci.nsIFile).path;
- let wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
- Ci.nsIWindowsRegKey
- );
- try {
- wrk.open(wrk.ROOT_KEY_CLASSES_ROOT, "", wrk.ACCESS_READ);
- let FxSet = wrk.hasChild(FIREFOX_BRIDGE_HANDLER_NAME);
- let FxPrivateSet = wrk.hasChild(FIREFOX_PRIVATE_BRIDGE_HANDLER_NAME);
- wrk.close();
- if (FxSet && FxPrivateSet) {
- return;
- }
- wrk.open(wrk.ROOT_KEY_CURRENT_USER, "Software\\Classes", wrk.ACCESS_ALL);
- const maybeUpdateRegistry = (isSetAlready, handler, protocolName) => {
- if (isSetAlready) {
- return;
- }
- let FxKey = wrk.createChild(handler, wrk.ACCESS_ALL);
- try {
- // Write URL protocol key
- FxKey.writeStringValue("", protocolName);
- FxKey.writeStringValue("URL Protocol", "");
- FxKey.close();
- // Write defaultIcon key
- FxKey.create(
- FxKey.ROOT_KEY_CURRENT_USER,
- "Software\\Classes\\" + handler + "\\DefaultIcon",
- FxKey.ACCESS_ALL
- );
- FxKey.open(
- FxKey.ROOT_KEY_CURRENT_USER,
- "Software\\Classes\\" + handler + "\\DefaultIcon",
- FxKey.ACCESS_ALL
- );
- FxKey.writeStringValue("", `\"${path}\",1`);
- FxKey.close();
- // Write shell\\open\\command key
- FxKey.create(
- FxKey.ROOT_KEY_CURRENT_USER,
- "Software\\Classes\\" + handler + "\\shell",
- FxKey.ACCESS_ALL
- );
- FxKey.create(
- FxKey.ROOT_KEY_CURRENT_USER,
- "Software\\Classes\\" + handler + "\\shell\\open",
- FxKey.ACCESS_ALL
- );
- FxKey.create(
- FxKey.ROOT_KEY_CURRENT_USER,
- "Software\\Classes\\" + handler + "\\shell\\open\\command",
- FxKey.ACCESS_ALL
- );
- FxKey.open(
- FxKey.ROOT_KEY_CURRENT_USER,
- "Software\\Classes\\" + handler + "\\shell\\open\\command",
- FxKey.ACCESS_ALL
- );
- if (handler == FIREFOX_PRIVATE_BRIDGE_HANDLER_NAME) {
- FxKey.writeStringValue(
- "",
- `\"${path}\" -osint -private-window \"%1\"`
- );
- } else {
- FxKey.writeStringValue("", `\"${path}\" -osint -url \"%1\"`);
- }
- } catch (ex) {
- console.error(ex);
- } finally {
- FxKey.close();
- }
- };
-
- try {
- maybeUpdateRegistry(
- FxSet,
- FIREFOX_BRIDGE_HANDLER_NAME,
- "URL:Firefox Bridge Protocol"
- );
- } catch (ex) {
- console.error(ex);
- }
-
- try {
- maybeUpdateRegistry(
- FxPrivateSet,
- FIREFOX_PRIVATE_BRIDGE_HANDLER_NAME,
- "URL:Firefox Private Bridge Protocol"
- );
- } catch (ex) {
- console.error(ex);
- }
- } catch (ex) {
- console.error(ex);
- } finally {
- wrk.close();
- }
- },
-
getNativeMessagingHostId() {
let nativeMessagingHostId = "org.mozilla.firefox_bridge_nmh";
if (AppConstants.NIGHTLY_BUILD) {
diff --git a/browser/modules/metrics.yaml b/browser/modules/metrics.yaml
index ac4e0c6ef9..a4fdba875d 100644
--- a/browser/modules/metrics.yaml
+++ b/browser/modules/metrics.yaml
@@ -118,3 +118,49 @@ performance.interaction:
- mconley@mozilla.com
- perf-telemetry-alerts@mozilla.com
expires: never
+
+browser.usage:
+ interaction:
+ type: event
+ description: >
+ The user interacted with something in the Firefox Desktop frontend.
+ Could be via mouse or keyboard, could be a command or a UI element.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1889111
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1889111
+ expires: 132
+ data_sensitivity: [interaction]
+ notification_emails:
+ - chutten@mozilla.com
+ extra_keys:
+ flow_id:
+ type: string
+ description: >
+ An UUIDv4 used to group interaction events together under the
+ assumption that they're part of the same user activity.
+ See BrowserUsageTelemetry's FLOW_IDLE_TIME for details.
+ source:
+ type: string
+ description: >
+ The source of the interaction. Usually a UI section
+ (like `bookmarks_bar` or `content_context`), but can also be an input
+ method (like `keyboard`).
+ The full list of supported `source`s can be found in
+ `BrowserUsageTelemetry`'s `BROWSER_UI_CONTAINER_IDS. Plus `keyboard`
+ and panes from `about:preferences` listed in `PREFERENCES_PANES`.
+ See `_getWidgetContainer` for details.
+ widget_id:
+ type: string
+ description: >
+ The item interacted with.
+ Usually the `id` of the DOM Node that the user used,
+ sometimes the `id` of the parent or ancestor Node instead.
+ This node is then conjugated by obscuring any addon id in it
+ (turning it to the string `addonX` where `X` is a number stable
+ within a browsing session) and then replacing any underscore with a
+ hyphen.
+ See `BrowserUsageTelemetry#_getWidgetID` and `telemetryId`.
+ e.g. "Browser:Reload", "key-newNavigatorTab", "PanelUI-Bookmarks".
+ send_in_pings:
+ - prototype-no-code-events
diff --git a/browser/modules/pings.yaml b/browser/modules/pings.yaml
new file mode 100644
index 0000000000..0bc4d2227f
--- /dev/null
+++ b/browser/modules/pings.yaml
@@ -0,0 +1,22 @@
+# 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/.
+
+---
+$schema: moz://mozilla.org/schemas/glean/pings/2-0-0
+
+prototype-no-code-events:
+ description: |
+ **Prototype-only ping not for general use!**
+ Transport for no-code Firefox Desktop frontend instrumentation,
+ should mostly contain no-code events in browser.ui.* categories.
+ Submitted whenever the next flow of events begins (including startup).
+ include_client_id: true
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1889111
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1889111
+ notification_emails:
+ - chutten@mozilla.com
+ - tlong@mozilla.com
+ enabled: false # To be enabled by Server Knobs for selected populations.
diff --git a/browser/modules/test/browser/browser.toml b/browser/modules/test/browser/browser.toml
index 21b3cdf18c..82611ed4b2 100644
--- a/browser/modules/test/browser/browser.toml
+++ b/browser/modules/test/browser/browser.toml
@@ -36,6 +36,10 @@ support-files = [
"../../../base/content/test/tabs/file_mediaPlayback.html",
"../../../base/content/test/general/audio.ogg",
]
+skip-if = [
+ "os == 'linux' && os_version == '18.04' && asan", # Bug 1781868
+ "os == 'linux' && os_version == '18.04' && tsan", # Bug 1781868
+]
["browser_Telemetry_numberOfSiteOrigins.js"]
support-files = ["contain_iframe.html"]
diff --git a/browser/modules/test/browser/browser_ProcessHangNotifications.js b/browser/modules/test/browser/browser_ProcessHangNotifications.js
index 9150c36d4c..963dc2d4b4 100644
--- a/browser/modules/test/browser/browser_ProcessHangNotifications.js
+++ b/browser/modules/test/browser/browser_ProcessHangNotifications.js
@@ -184,7 +184,7 @@ add_task(async function waitForScriptTest() {
});
// Click the "Close" button this time, we shouldn't get a callback at all.
- notification.currentNotification.closeButtonEl.click();
+ notification.currentNotification.closeButton.click();
// send another hang pulse, we should not get a notification here
Services.obs.notifyObservers(hangReport, "process-hang-report");
diff --git a/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js b/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js
index 6300bd17ba..d105e8374e 100644
--- a/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js
+++ b/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js
@@ -292,7 +292,7 @@ add_task(async function test_other_ignored() {
Assert.ok(notification, "There should be a notification");
// Dismiss notification, creating the .dmp.ignore file
- notification.closeButtonEl.click();
+ notification.closeButton.click();
gNotificationBox.removeNotification(notification, true);
await waitForIgnoredReports(toIgnore);
@@ -525,7 +525,7 @@ add_task(async function test_can_ignore() {
Assert.ok(notification, "There should be a notification");
// Dismiss the notification by clicking on the "X" button.
- notification.closeButtonEl.click();
+ notification.closeButton.click();
// We'll not wait for the notification to finish its transition -
// we'll just remove it right away.
gNotificationBox.removeNotification(notification, true);
@@ -599,7 +599,7 @@ add_task(async function test_shutdown_while_not_showing() {
Assert.ok(notification, "There should be a notification");
// Dismiss the notification by clicking on the "X" button.
- notification.closeButtonEl.click();
+ notification.closeButton.click();
// We'll not wait for the notification to finish its transition -
// we'll just remove it right away.
gNotificationBox.removeNotification(notification, true);
diff --git a/browser/modules/test/browser/browser_UsageTelemetry_interaction.js b/browser/modules/test/browser/browser_UsageTelemetry_interaction.js
index 5fa436a349..56a7f530ad 100644
--- a/browser/modules/test/browser/browser_UsageTelemetry_interaction.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_interaction.js
@@ -33,6 +33,8 @@ const AREAS = [
// keys in the scalars. Also runs keyed scalar checks against non-area types
// passed in through expectedOther.
function assertInteractionScalars(expectedAreas, expectedOther = {}) {
+ // Every time this checks Scalars, it clears them. So clear FOG too.
+ Services.fog.testResetFOG();
let processScalars =
Services.telemetry.getSnapshotForKeyedScalars("main", true)?.parent ?? {};
@@ -83,6 +85,7 @@ add_task(async function toolbarButtons() {
});
Services.telemetry.getSnapshotForKeyedScalars("main", true);
+ Services.fog.testResetFOG();
let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
let tabClose = BrowserTestUtils.waitForTabClosing(newTab);
@@ -164,6 +167,22 @@ add_task(async function toolbarButtons() {
click(customButton);
+ let events = Glean.browserUsage.interaction
+ .testGetValue()
+ .map(e => [e.extra.source, e.extra.widget_id]);
+ Assert.deepEqual(
+ [
+ ["nav-bar", "stop-reload-button"],
+ ["nav-bar", "back-button"],
+ ["nav-bar", "back-button"],
+ ["all-tabs-panel-entrypoint", "alltabs-button"],
+ ["tabs-bar", "alltabs-button"],
+ ["tabs-bar", "tab-close-button"],
+ ["bookmarks-bar", "bookmark-item"],
+ ["nav-bar", "12foo"],
+ ],
+ events
+ );
assertInteractionScalars(
{
nav_bar: {
@@ -192,6 +211,7 @@ add_task(async function toolbarButtons() {
add_task(async function contextMenu() {
await BrowserTestUtils.withNewTab("https://example.com", async browser => {
Services.telemetry.getSnapshotForKeyedScalars("main", true);
+ Services.fog.testResetFOG();
let tab = gBrowser.getTabForBrowser(browser);
let context = elem("tabContextMenu");
@@ -207,6 +227,16 @@ add_task(async function contextMenu() {
context.activateItem(document.getElementById("context_toggleMuteTab"));
await hidden;
+ let events = Glean.browserUsage.interaction
+ .testGetValue()
+ .map(e => [e.extra.source, e.extra.widget_id]);
+ Assert.deepEqual(
+ [
+ ["tabs-context", "context-toggleMuteTab"],
+ ["tabs-context-entrypoint", "context-toggleMuteTab"],
+ ],
+ events
+ );
assertInteractionScalars({
tabs_context: {
"context-toggleMuteTab": 1,
@@ -233,6 +263,16 @@ add_task(async function contextMenu() {
);
await hidden;
+ events = Glean.browserUsage.interaction
+ .testGetValue()
+ .map(e => [e.extra.source, e.extra.widget_id]);
+ Assert.deepEqual(
+ [
+ ["tabs-context", "toolbar-context-selectAllTabs"],
+ ["tabs-context-entrypoint", "toolbar-context-selectAllTabs"],
+ ],
+ events
+ );
assertInteractionScalars({
tabs_context: {
"toolbar-context-selectAllTabs": 1,
@@ -318,6 +358,7 @@ add_task(async function contextMenu_entrypoints() {
add_task(async function appMenu() {
await BrowserTestUtils.withNewTab("https://example.com", async () => {
Services.telemetry.getSnapshotForKeyedScalars("main", true);
+ Services.fog.testResetFOG();
let shown = BrowserTestUtils.waitForEvent(
elem("appMenu-popup"),
@@ -339,9 +380,21 @@ add_task(async function appMenu() {
nav_bar: {
"PanelUI-menu-button": 1,
},
- app_menu: {},
+ app_menu: {
+ [findButtonID]: 1,
+ },
};
- expectedScalars.app_menu[findButtonID] = 1;
+
+ let events = Glean.browserUsage.interaction
+ .testGetValue()
+ .map(e => [e.extra.source, e.extra.widget_id]);
+ Assert.deepEqual(
+ [
+ ["nav-bar", "PanelUI-menu-button"],
+ ["app-menu", findButtonID],
+ ],
+ events
+ );
assertInteractionScalars(expectedScalars);
});
@@ -350,6 +403,7 @@ add_task(async function appMenu() {
add_task(async function devtools() {
await BrowserTestUtils.withNewTab("https://example.com", async () => {
Services.telemetry.getSnapshotForKeyedScalars("main", true);
+ Services.fog.testResetFOG();
let shown = BrowserTestUtils.waitForEvent(
elem("appMenu-popup"),
@@ -381,6 +435,17 @@ add_task(async function devtools() {
BrowserTestUtils.removeTab(tab);
// Note that item ID's have '_' converted to '-'.
+ let events = Glean.browserUsage.interaction
+ .testGetValue()
+ .map(e => [e.extra.source, e.extra.widget_id]);
+ Assert.deepEqual(
+ [
+ ["nav-bar", "PanelUI-menu-button"],
+ ["app-menu", "appMenu-more-button2"],
+ ["app-menu", "key-viewSource"],
+ ],
+ events
+ );
assertInteractionScalars({
nav_bar: {
"PanelUI-menu-button": 1,
@@ -398,6 +463,7 @@ add_task(async function webextension() {
await BrowserTestUtils.withNewTab("https://example.com", async browser => {
Services.telemetry.getSnapshotForKeyedScalars("main", true);
+ Services.fog.testResetFOG();
function background() {
browser.commands.onCommand.addListener(() => {
@@ -470,6 +536,11 @@ add_task(async function webextension() {
// As the first add-on interacted with this should show up as `addon0`.
click("random_addon_example_com-browser-action");
+ let events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [["nav-bar", "addon0"]],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
nav_bar: {
addon0: 1,
@@ -482,6 +553,11 @@ add_task(async function webextension() {
);
click("pageAction-urlbar-random_addon_example_com");
+ events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [["pageaction-urlbar", "addon0"]],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
pageaction_urlbar: {
addon0: 1,
@@ -490,6 +566,11 @@ add_task(async function webextension() {
EventUtils.synthesizeKey("j", { altKey: true, shiftKey: true });
await extension.awaitMessage("oncommand");
+ events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [["keyboard", "addon0"]],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
keyboard: {
addon0: 1,
@@ -498,6 +579,11 @@ add_task(async function webextension() {
EventUtils.synthesizeKey("q", { altKey: true, shiftKey: true });
await extension.awaitMessage("sidebar-opened");
+ events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [["keyboard", "addon0"]],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
keyboard: {
addon0: 1,
@@ -537,6 +623,11 @@ add_task(async function webextension() {
// A second extension should be `addon1`.
click("random_addon2_example_com-browser-action");
+ events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [["nav-bar", "addon1"]],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
nav_bar: {
addon1: 1,
@@ -549,6 +640,11 @@ add_task(async function webextension() {
);
click("pageAction-urlbar-random_addon2_example_com");
+ events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [["pageaction-urlbar", "addon1"]],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
pageaction_urlbar: {
addon1: 1,
@@ -557,6 +653,11 @@ add_task(async function webextension() {
EventUtils.synthesizeKey("9", { altKey: true, shiftKey: true });
await extension2.awaitMessage("oncommand");
+ events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [["keyboard", "addon1"]],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
keyboard: {
addon1: 1,
@@ -565,6 +666,11 @@ add_task(async function webextension() {
// The first should have retained its ID.
click("random_addon_example_com-browser-action");
+ events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [["nav-bar", "addon0"]],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
nav_bar: {
addon0: 1,
@@ -573,6 +679,11 @@ add_task(async function webextension() {
EventUtils.synthesizeKey("j", { altKey: true, shiftKey: true });
await extension.awaitMessage("oncommand");
+ events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [["keyboard", "addon0"]],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
keyboard: {
addon0: 1,
@@ -580,6 +691,11 @@ add_task(async function webextension() {
});
click("pageAction-urlbar-random_addon_example_com");
+ events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [["pageaction-urlbar", "addon0"]],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
pageaction_urlbar: {
addon0: 1,
@@ -590,11 +706,19 @@ add_task(async function webextension() {
// Clear the last opened ID so if this test runs again the sidebar won't
// automatically open when the extension is installed.
- window.SidebarUI.lastOpenedId = null;
+ window.SidebarController.lastOpenedId = null;
// The second should retain its ID.
click("random_addon2_example_com-browser-action");
click("random_addon2_example_com-browser-action");
+ events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [
+ ["nav-bar", "addon1"],
+ ["nav-bar", "addon1"],
+ ],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
nav_bar: {
addon1: 2,
@@ -602,6 +726,11 @@ add_task(async function webextension() {
});
click("pageAction-urlbar-random_addon2_example_com");
+ events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [["pageaction-urlbar", "addon1"]],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
pageaction_urlbar: {
addon1: 1,
@@ -610,6 +739,11 @@ add_task(async function webextension() {
EventUtils.synthesizeKey("9", { altKey: true, shiftKey: true });
await extension2.awaitMessage("oncommand");
+ events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [["keyboard", "addon1"]],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
keyboard: {
addon1: 1,
@@ -643,6 +777,11 @@ add_task(async function webextension() {
await shown;
click("random_addon3_example_com-browser-action");
+ events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [["unified-extensions-area", "addon2"]],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
unified_extensions_area: {
addon2: 1,
@@ -669,6 +808,7 @@ add_task(async function mainMenu() {
await BrowserTestUtils.withNewTab("https://example.com", async () => {
Services.telemetry.getSnapshotForKeyedScalars("main", true);
+ Services.fog.testResetFOG();
CustomizableUI.setToolbarVisibility("toolbar-menubar", true);
@@ -686,6 +826,11 @@ add_task(async function mainMenu() {
click("menu_selectAll");
await hidden;
+ let events = Glean.browserUsage.interaction.testGetValue();
+ Assert.deepEqual(
+ [["menu-bar", "menu-selectAll"]],
+ events.map(e => [e.extra.source, e.extra.widget_id])
+ );
assertInteractionScalars({
menu_bar: {
// Note that the _ is replaced with - for telemetry identifiers.
@@ -706,6 +851,7 @@ add_task(async function preferences() {
await finalPrefPaneLoaded;
Services.telemetry.getSnapshotForKeyedScalars("main", true);
+ Services.fog.testResetFOG();
await BrowserTestUtils.synthesizeMouseAtCenter(
"#browserRestoreSession",
@@ -742,6 +888,16 @@ add_task(async function preferences() {
await onLearnMoreOpened;
gBrowser.removeCurrentTab();
+ let events = Glean.browserUsage.interaction
+ .testGetValue()
+ .map(e => [e.extra.source, e.extra.widget_id]);
+ Assert.deepEqual(
+ [
+ ["preferences_paneGeneral", "browserRestoreSession"],
+ ["preferences_panePrivacy", "contentBlockingLearnMore"],
+ ],
+ events
+ );
assertInteractionScalars({
preferences_paneGeneral: {
browserRestoreSession: 1,
@@ -806,6 +962,17 @@ async function history_appMenu(useContextClick) {
app_menu: { "history-item": 1, "appMenu-history-button": 1 },
};
+ let events = Glean.browserUsage.interaction
+ .testGetValue()
+ .map(e => [e.extra.source, e.extra.widget_id]);
+ Assert.deepEqual(
+ [
+ ["nav-bar", "PanelUI-menu-button"],
+ ["app-menu", "appMenu-history-button"],
+ ["app-menu", "history-item"],
+ ],
+ events
+ );
assertInteractionScalars(expectedScalars);
});
}
@@ -852,6 +1019,17 @@ async function bookmarks_appMenu(useContextClick) {
app_menu: { "bookmark-item": 1, "appMenu-bookmarks-button": 1 },
};
+ let events = Glean.browserUsage.interaction
+ .testGetValue()
+ .map(e => [e.extra.source, e.extra.widget_id]);
+ Assert.deepEqual(
+ [
+ ["nav-bar", "PanelUI-menu-button"],
+ ["app-menu", "appMenu-bookmarks-button"],
+ ["app-menu", "bookmark-item"],
+ ],
+ events
+ );
assertInteractionScalars(expectedScalars);
});
}
@@ -893,6 +1071,17 @@ async function bookmarks_library_navbar(useContextClick) {
"appMenu-library-bookmarks-button": 1,
},
};
+ let events = Glean.browserUsage.interaction
+ .testGetValue()
+ .map(e => [e.extra.source, e.extra.widget_id]);
+ Assert.deepEqual(
+ [
+ ["nav-bar", "library-button"],
+ ["nav-bar", "appMenu-library-bookmarks-button"],
+ ["nav-bar", "bookmark-item"],
+ ],
+ events
+ );
assertInteractionScalars(expectedScalars);
});
@@ -940,6 +1129,17 @@ async function history_library_navbar(useContextClick) {
"appMenu-library-history-button": 1,
},
};
+ let events = Glean.browserUsage.interaction
+ .testGetValue()
+ .map(e => [e.extra.source, e.extra.widget_id]);
+ Assert.deepEqual(
+ [
+ ["nav-bar", "library-button"],
+ ["nav-bar", "appMenu-library-history-button"],
+ ["nav-bar", "history-item"],
+ ],
+ events
+ );
assertInteractionScalars(expectedScalars);
});
diff --git a/browser/modules/test/unit/test_FirefoxBridgeExtensionUtils.js b/browser/modules/test/unit/test_FirefoxBridgeExtensionUtils.js
index 77db0d8286..d4dcd4fad6 100644
--- a/browser/modules/test/unit/test_FirefoxBridgeExtensionUtils.js
+++ b/browser/modules/test/unit/test_FirefoxBridgeExtensionUtils.js
@@ -7,9 +7,10 @@ const { FirefoxBridgeExtensionUtils } = ChromeUtils.importESModule(
"resource:///modules/FirefoxBridgeExtensionUtils.sys.mjs"
);
-const FIREFOX_SHELL_OPEN_COMMAND_PATH = "firefox\\shell\\open\\command";
-const FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH =
- "firefox-private\\shell\\open\\command";
+const OLD_FIREFOX_SHELL_OPEN_COMMAND_PATH = `${FirefoxBridgeExtensionUtils.OLD_PUBLIC_PROTOCOL}\\shell\\open\\command`;
+const OLD_FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH = `${FirefoxBridgeExtensionUtils.OLD_PRIVATE_PROTOCOL}\\shell\\open\\command`;
+const FIREFOX_SHELL_OPEN_COMMAND_PATH = `${FirefoxBridgeExtensionUtils.PUBLIC_PROTOCOL}\\shell\\open\\command`;
+const FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH = `${FirefoxBridgeExtensionUtils.PRIVATE_PROTOCOL}\\shell\\open\\command`;
class StubbedRegistryKey {
#children;
@@ -145,206 +146,274 @@ class StubbedDeleteBridgeProtocolRegistryEntryHelper {
}
add_task(async function test_DeleteWhenSameFirefoxInstall() {
- const applicationPath = "testPath";
-
- const firefoxEntries = new Map();
- firefoxEntries.set("", `\"${applicationPath}\" -osint -url \"%1\"`);
-
- const firefoxProtocolRegKey = new StubbedRegistryKey(
- new Map(),
- firefoxEntries
- );
-
- const firefoxPrivateEntries = new Map();
- firefoxPrivateEntries.set(
- "",
- `\"${applicationPath}\" -osint -private-window \"%1\"`
- );
- const firefoxPrivateProtocolRegKey = new StubbedRegistryKey(
- new Map(),
- firefoxPrivateEntries
- );
-
- const children = new Map();
- children.set(FIREFOX_SHELL_OPEN_COMMAND_PATH, firefoxProtocolRegKey);
- children.set(
- FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH,
- firefoxPrivateProtocolRegKey
- );
-
- const registryRootKey = new StubbedRegistryKey(children, new Map());
-
- const stubbedDeleteBridgeProtocolRegistryHelper =
- new StubbedDeleteBridgeProtocolRegistryEntryHelper({
- applicationPath,
- registryRootKey,
- });
-
- FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries(
- stubbedDeleteBridgeProtocolRegistryHelper
- );
-
- ok(registryRootKey.wasCloseCalled, "Root key closed");
-
- ok(firefoxProtocolRegKey.wasOpenedForRead, "Firefox key opened");
- ok(firefoxProtocolRegKey.wasCloseCalled, "Firefox key closed");
- ok(
- registryRootKey.isChildDeleted("firefox"),
- "Firefox protocol registry entry deleted"
- );
-
- ok(
- firefoxPrivateProtocolRegKey.wasOpenedForRead,
- "Firefox private key opened"
- );
- ok(firefoxPrivateProtocolRegKey.wasCloseCalled, "Firefox private key closed");
- ok(
- registryRootKey.isChildDeleted("firefox-private"),
- "Firefox private protocol registry entry deleted"
- );
+ for (let protocols of [
+ [
+ FirefoxBridgeExtensionUtils.OLD_PUBLIC_PROTOCOL,
+ FirefoxBridgeExtensionUtils.OLD_PRIVATE_PROTOCOL,
+ OLD_FIREFOX_SHELL_OPEN_COMMAND_PATH,
+ OLD_FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH,
+ ],
+ [
+ FirefoxBridgeExtensionUtils.PUBLIC_PROTOCOL,
+ FirefoxBridgeExtensionUtils.PRIVATE_PROTOCOL,
+ FIREFOX_SHELL_OPEN_COMMAND_PATH,
+ FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH,
+ ],
+ ]) {
+ let [publicProtocol, privateProtocol, publicPath, privatePath] = protocols;
+ const applicationPath = "testPath";
+
+ const firefoxEntries = new Map();
+ firefoxEntries.set("", `\"${applicationPath}\" -osint -url \"%1\"`);
+
+ const firefoxProtocolRegKey = new StubbedRegistryKey(
+ new Map(),
+ firefoxEntries
+ );
+
+ const firefoxPrivateEntries = new Map();
+ firefoxPrivateEntries.set(
+ "",
+ `\"${applicationPath}\" -osint -private-window \"%1\"`
+ );
+ const firefoxPrivateProtocolRegKey = new StubbedRegistryKey(
+ new Map(),
+ firefoxPrivateEntries
+ );
+
+ const children = new Map();
+ children.set(publicPath, firefoxProtocolRegKey);
+ children.set(privatePath, firefoxPrivateProtocolRegKey);
+
+ const registryRootKey = new StubbedRegistryKey(children, new Map());
+
+ const stubbedDeleteBridgeProtocolRegistryHelper =
+ new StubbedDeleteBridgeProtocolRegistryEntryHelper({
+ applicationPath,
+ registryRootKey,
+ });
+
+ FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries(
+ publicProtocol,
+ privateProtocol,
+ stubbedDeleteBridgeProtocolRegistryHelper
+ );
+
+ ok(registryRootKey.wasCloseCalled, "Root key closed");
+
+ ok(firefoxProtocolRegKey.wasOpenedForRead, "Firefox key opened");
+ ok(firefoxProtocolRegKey.wasCloseCalled, "Firefox key closed");
+ ok(
+ registryRootKey.isChildDeleted(publicProtocol),
+ "Firefox protocol registry entry deleted"
+ );
+
+ ok(
+ firefoxPrivateProtocolRegKey.wasOpenedForRead,
+ "Firefox private key opened"
+ );
+ ok(
+ firefoxPrivateProtocolRegKey.wasCloseCalled,
+ "Firefox private key closed"
+ );
+ ok(
+ registryRootKey.isChildDeleted(privateProtocol),
+ "Firefox private protocol registry entry deleted"
+ );
+ }
});
add_task(async function test_DeleteWhenDifferentFirefoxInstall() {
- const applicationPath = "testPath";
- const badApplicationPath = "testPath2";
-
- const firefoxEntries = new Map();
- firefoxEntries.set("", `\"${badApplicationPath}\" -osint -url \"%1\"`);
-
- const firefoxProtocolRegKey = new StubbedRegistryKey(
- new Map(),
- firefoxEntries
- );
-
- const firefoxPrivateEntries = new Map();
- firefoxPrivateEntries.set(
- "",
- `\"${badApplicationPath}\" -osint -private-window \"%1\"`
- );
- const firefoxPrivateProtocolRegKey = new StubbedRegistryKey(
- new Map(),
- firefoxPrivateEntries
- );
-
- const children = new Map();
- children.set(FIREFOX_SHELL_OPEN_COMMAND_PATH, firefoxProtocolRegKey);
- children.set(
- FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH,
- firefoxPrivateProtocolRegKey
- );
-
- const registryRootKey = new StubbedRegistryKey(children, new Map());
-
- const stubbedDeleteBridgeProtocolRegistryHelper =
- new StubbedDeleteBridgeProtocolRegistryEntryHelper({
- applicationPath,
- registryRootKey,
- });
-
- FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries(
- stubbedDeleteBridgeProtocolRegistryHelper
- );
-
- ok(registryRootKey.wasCloseCalled, "Root key closed");
-
- ok(firefoxProtocolRegKey.wasOpenedForRead, "Firefox key opened");
- ok(firefoxProtocolRegKey.wasCloseCalled, "Firefox key closed");
- ok(
- !registryRootKey.isChildDeleted("firefox"),
- "Firefox protocol registry entry not deleted"
- );
-
- ok(
- firefoxPrivateProtocolRegKey.wasOpenedForRead,
- "Firefox private key opened"
- );
- ok(firefoxPrivateProtocolRegKey.wasCloseCalled, "Firefox private key closed");
- ok(
- !registryRootKey.isChildDeleted("firefox-private"),
- "Firefox private protocol registry entry not deleted"
- );
+ for (let protocols of [
+ [
+ FirefoxBridgeExtensionUtils.OLD_PUBLIC_PROTOCOL,
+ FirefoxBridgeExtensionUtils.OLD_PRIVATE_PROTOCOL,
+ OLD_FIREFOX_SHELL_OPEN_COMMAND_PATH,
+ OLD_FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH,
+ ],
+ [
+ FirefoxBridgeExtensionUtils.PUBLIC_PROTOCOL,
+ FirefoxBridgeExtensionUtils.PRIVATE_PROTOCOL,
+ FIREFOX_SHELL_OPEN_COMMAND_PATH,
+ FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH,
+ ],
+ ]) {
+ let [publicProtocol, privateProtocol, publicPath, privatePath] = protocols;
+ const applicationPath = "testPath";
+ const badApplicationPath = "testPath2";
+
+ const firefoxEntries = new Map();
+ firefoxEntries.set("", `\"${badApplicationPath}\" -osint -url \"%1\"`);
+
+ const firefoxProtocolRegKey = new StubbedRegistryKey(
+ new Map(),
+ firefoxEntries
+ );
+
+ const firefoxPrivateEntries = new Map();
+ firefoxPrivateEntries.set(
+ "",
+ `\"${badApplicationPath}\" -osint -private-window \"%1\"`
+ );
+ const firefoxPrivateProtocolRegKey = new StubbedRegistryKey(
+ new Map(),
+ firefoxPrivateEntries
+ );
+
+ const children = new Map();
+ children.set(publicPath, firefoxProtocolRegKey);
+ children.set(privatePath, firefoxPrivateProtocolRegKey);
+
+ const registryRootKey = new StubbedRegistryKey(children, new Map());
+
+ const stubbedDeleteBridgeProtocolRegistryHelper =
+ new StubbedDeleteBridgeProtocolRegistryEntryHelper({
+ applicationPath,
+ registryRootKey,
+ });
+
+ FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries(
+ publicProtocol,
+ privateProtocol,
+ stubbedDeleteBridgeProtocolRegistryHelper
+ );
+
+ ok(registryRootKey.wasCloseCalled, "Root key closed");
+
+ ok(firefoxProtocolRegKey.wasOpenedForRead, "Firefox key opened");
+ ok(firefoxProtocolRegKey.wasCloseCalled, "Firefox key closed");
+ ok(
+ !registryRootKey.isChildDeleted(publicProtocol),
+ "Firefox protocol registry entry not deleted"
+ );
+
+ ok(
+ firefoxPrivateProtocolRegKey.wasOpenedForRead,
+ "Firefox private key opened"
+ );
+ ok(
+ firefoxPrivateProtocolRegKey.wasCloseCalled,
+ "Firefox private key closed"
+ );
+ ok(
+ !registryRootKey.isChildDeleted(privateProtocol),
+ "Firefox private protocol registry entry not deleted"
+ );
+ }
});
add_task(async function test_DeleteWhenNoRegistryEntries() {
- const applicationPath = "testPath";
-
- const firefoxPrivateEntries = new Map();
- const firefoxPrivateProtocolRegKey = new StubbedRegistryKey(
- new Map(),
- firefoxPrivateEntries
- );
-
- const children = new Map();
- children.set(
- FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH,
- firefoxPrivateProtocolRegKey
- );
-
- const registryRootKey = new StubbedRegistryKey(children, new Map());
-
- const stubbedDeleteBridgeProtocolRegistryHelper =
- new StubbedDeleteBridgeProtocolRegistryEntryHelper({
- applicationPath,
- registryRootKey,
- });
-
- FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries(
- stubbedDeleteBridgeProtocolRegistryHelper
- );
-
- ok(registryRootKey.wasCloseCalled, "Root key closed");
-
- ok(
- firefoxPrivateProtocolRegKey.wasOpenedForRead,
- "Firefox private key opened"
- );
- ok(firefoxPrivateProtocolRegKey.wasCloseCalled, "Firefox private key closed");
- ok(
- !registryRootKey.isChildDeleted("firefox"),
- "Firefox protocol registry entry deleted when it shouldn't be"
- );
- ok(
- !registryRootKey.isChildDeleted("firefox-private"),
- "Firefox private protocol registry deleted when it shouldn't be"
- );
+ for (let protocols of [
+ [
+ FirefoxBridgeExtensionUtils.OLD_PUBLIC_PROTOCOL,
+ FirefoxBridgeExtensionUtils.OLD_PRIVATE_PROTOCOL,
+ OLD_FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH,
+ ],
+ [
+ FirefoxBridgeExtensionUtils.PUBLIC_PROTOCOL,
+ FirefoxBridgeExtensionUtils.PRIVATE_PROTOCOL,
+ FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH,
+ ],
+ ]) {
+ let [publicProtocol, privateProtocol, privatePath] = protocols;
+ const applicationPath = "testPath";
+
+ const firefoxPrivateEntries = new Map();
+ const firefoxPrivateProtocolRegKey = new StubbedRegistryKey(
+ new Map(),
+ firefoxPrivateEntries
+ );
+
+ const children = new Map();
+ children.set(privatePath, firefoxPrivateProtocolRegKey);
+
+ const registryRootKey = new StubbedRegistryKey(children, new Map());
+
+ const stubbedDeleteBridgeProtocolRegistryHelper =
+ new StubbedDeleteBridgeProtocolRegistryEntryHelper({
+ applicationPath,
+ registryRootKey,
+ });
+
+ FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries(
+ publicProtocol,
+ privateProtocol,
+ stubbedDeleteBridgeProtocolRegistryHelper
+ );
+
+ ok(registryRootKey.wasCloseCalled, "Root key closed");
+
+ ok(
+ firefoxPrivateProtocolRegKey.wasOpenedForRead,
+ "Firefox private key opened"
+ );
+ ok(
+ firefoxPrivateProtocolRegKey.wasCloseCalled,
+ "Firefox private key closed"
+ );
+ ok(
+ !registryRootKey.isChildDeleted(publicProtocol),
+ "Firefox protocol registry entry deleted when it shouldn't be"
+ );
+ ok(
+ !registryRootKey.isChildDeleted(privateProtocol),
+ "Firefox private protocol registry deleted when it shouldn't be"
+ );
+ }
});
add_task(async function test_DeleteWhenUnexpectedRegistryEntries() {
- const applicationPath = "testPath";
-
- const firefoxEntries = new Map();
- firefoxEntries.set("", `\"${applicationPath}\" -osint -url \"%1\"`);
- firefoxEntries.set("extraEntry", "extraValue");
- const firefoxProtocolRegKey = new StubbedRegistryKey(
- new Map(),
- firefoxEntries
- );
-
- const children = new Map();
- children.set(FIREFOX_SHELL_OPEN_COMMAND_PATH, firefoxProtocolRegKey);
-
- const registryRootKey = new StubbedRegistryKey(children, new Map());
-
- const stubbedDeleteBridgeProtocolRegistryHelper =
- new StubbedDeleteBridgeProtocolRegistryEntryHelper({
- applicationPath,
- registryRootKey,
- });
-
- FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries(
- stubbedDeleteBridgeProtocolRegistryHelper
- );
-
- ok(registryRootKey.wasCloseCalled, "Root key closed");
-
- ok(firefoxProtocolRegKey.wasOpenedForRead, "Firefox key opened");
- ok(firefoxProtocolRegKey.wasCloseCalled, "Firefox key closed");
- ok(
- !registryRootKey.isChildDeleted("firefox"),
- "Firefox protocol registry entry deleted when it shouldn't be"
- );
- ok(
- !registryRootKey.isChildDeleted("firefox-private"),
- "Firefox private protocol registry deleted when it shouldn't be"
- );
+ for (let protocols of [
+ [
+ FirefoxBridgeExtensionUtils.OLD_PUBLIC_PROTOCOL,
+ FirefoxBridgeExtensionUtils.OLD_PRIVATE_PROTOCOL,
+ OLD_FIREFOX_SHELL_OPEN_COMMAND_PATH,
+ ],
+ [
+ FirefoxBridgeExtensionUtils.PUBLIC_PROTOCOL,
+ FirefoxBridgeExtensionUtils.PRIVATE_PROTOCOL,
+ FIREFOX_SHELL_OPEN_COMMAND_PATH,
+ ],
+ ]) {
+ let [publicProtocol, privateProtocol, publicPath] = protocols;
+ const applicationPath = "testPath";
+
+ const firefoxEntries = new Map();
+ firefoxEntries.set("", `\"${applicationPath}\" -osint -url \"%1\"`);
+ firefoxEntries.set("extraEntry", "extraValue");
+ const firefoxProtocolRegKey = new StubbedRegistryKey(
+ new Map(),
+ firefoxEntries
+ );
+
+ const children = new Map();
+ children.set(publicPath, firefoxProtocolRegKey);
+
+ const registryRootKey = new StubbedRegistryKey(children, new Map());
+
+ const stubbedDeleteBridgeProtocolRegistryHelper =
+ new StubbedDeleteBridgeProtocolRegistryEntryHelper({
+ applicationPath,
+ registryRootKey,
+ });
+
+ FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries(
+ publicProtocol,
+ privateProtocol,
+ stubbedDeleteBridgeProtocolRegistryHelper
+ );
+
+ ok(registryRootKey.wasCloseCalled, "Root key closed");
+
+ ok(firefoxProtocolRegKey.wasOpenedForRead, "Firefox key opened");
+ ok(firefoxProtocolRegKey.wasCloseCalled, "Firefox key closed");
+ ok(
+ !registryRootKey.isChildDeleted(publicProtocol),
+ "Firefox protocol registry entry deleted when it shouldn't be"
+ );
+ ok(
+ !registryRootKey.isChildDeleted(privateProtocol),
+ "Firefox private protocol registry deleted when it shouldn't be"
+ );
+ }
});
diff --git a/browser/modules/test/unit/test_FirefoxBridgeExtensionUtilsNativeManifest.js b/browser/modules/test/unit/test_FirefoxBridgeExtensionUtilsNativeManifest.js
index cef550d705..8686871255 100644
--- a/browser/modules/test/unit/test_FirefoxBridgeExtensionUtilsNativeManifest.js
+++ b/browser/modules/test/unit/test_FirefoxBridgeExtensionUtilsNativeManifest.js
@@ -16,6 +16,30 @@ const { FirefoxBridgeExtensionUtils } = ChromeUtils.importESModule(
const DUAL_BROWSER_EXTENSION_ORIGIN = ["chrome-extension://fake-origin/"];
const NATIVE_MESSAGING_HOST_ID = "org.mozilla.firefox_bridge_test";
+if (AppConstants.platform == "win") {
+ var { MockRegistry } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistry.sys.mjs"
+ );
+}
+
+let registry = null;
+add_setup(() => {
+ if (AppConstants.platform == "win") {
+ registry = new MockRegistry();
+ registerCleanupFunction(() => {
+ registry.shutdown();
+ });
+ }
+});
+
+function resetMockRegistry() {
+ if (AppConstants.platform != "win") {
+ return;
+ }
+ registry.shutdown();
+ registry = new MockRegistry();
+}
+
let dir = FileUtils.getDir("TmpD", ["NativeMessagingHostsTest"]);
dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
@@ -75,6 +99,35 @@ function getExpectedOutput() {
};
}
+function DumpWindowsRegistry() {
+ let key = "";
+ let pathBuffer = [];
+
+ if (AppConstants.platform == "win") {
+ function bufferPrint(line) {
+ let regPath = line.trimStart();
+ if (regPath.includes(":")) {
+ // After trimming white space, keys are formatted as
+ // ": <key> (<value_type>)". We can assume it's only ever
+ // going to be of type REG_SZ for this test.
+ key = regPath.slice(2, regPath.length - " (REG_SZ)".length);
+ } else {
+ pathBuffer.push(regPath);
+ }
+ }
+
+ MockRegistry.dump(
+ MockRegistry.getRoot(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER),
+ "",
+ bufferPrint
+ );
+ } else {
+ Assert.ok(false, "Only windows has a registry!");
+ }
+
+ return [pathBuffer, key];
+}
+
add_task(async function test_maybeWriteManifestFiles() {
await FirefoxBridgeExtensionUtils.maybeWriteManifestFiles(
USER_TEST_PATH,
@@ -196,6 +249,7 @@ add_task(async function test_ensureRegistered() {
appDir.path,
"Mozilla\\Firefox"
);
+ resetMockRegistry();
} else {
throw new Error("Unsupported platform");
}
@@ -219,4 +273,65 @@ add_task(async function test_ensureRegistered() {
let JSONContent = await IOUtils.readUTF8(expectedJSONPath);
await IOUtils.remove(expectedJSONPath);
Assert.equal(JSONContent, expectedOutput);
+
+ // Test that the registry key is written for Windows only
+ if (AppConstants.platform == "win") {
+ let [pathBuffer, key] = DumpWindowsRegistry();
+ Assert.equal(
+ pathBuffer.toString(),
+ [
+ "Software",
+ "Google",
+ "Chrome",
+ "NativeMessagingHosts",
+ nativeHostId,
+ ].toString()
+ );
+ Assert.equal(key, expectedJSONPath);
+ }
+});
+
+add_task(async function test_maybeWriteNativeMessagingRegKeys() {
+ if (AppConstants.platform != "win") {
+ return;
+ }
+ resetMockRegistry();
+ FirefoxBridgeExtensionUtils.maybeWriteNativeMessagingRegKeys(
+ "Test\\Path\\For\\Reg\\Key",
+ binFile.parent.path,
+ NATIVE_MESSAGING_HOST_ID
+ );
+ let [pathBuffer, key] = DumpWindowsRegistry();
+ registry.shutdown();
+ Assert.equal(
+ pathBuffer.toString(),
+ ["Test", "Path", "For", "Reg", "Key", NATIVE_MESSAGING_HOST_ID].toString()
+ );
+ console.log("The key is: " + key);
+ Assert.equal(key, `${binFile.parent.path}\\${NATIVE_MESSAGING_HOST_ID}.json`);
+});
+
+add_task(async function test_maybeWriteNativeMessagingRegKeysIncorrectValue() {
+ if (AppConstants.platform != "win") {
+ return;
+ }
+ resetMockRegistry();
+ registry.setValue(
+ Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ `Test\\Path\\For\\Reg\\Key\\${NATIVE_MESSAGING_HOST_ID}`,
+ "",
+ "IncorrectValue"
+ );
+ FirefoxBridgeExtensionUtils.maybeWriteNativeMessagingRegKeys(
+ "Test\\Path\\For\\Reg\\Key",
+ binFile.parent.path,
+ NATIVE_MESSAGING_HOST_ID
+ );
+ let [pathBuffer, key] = DumpWindowsRegistry();
+ registry.shutdown();
+ Assert.equal(
+ pathBuffer.toString(),
+ ["Test", "Path", "For", "Reg", "Key", NATIVE_MESSAGING_HOST_ID].toString()
+ );
+ Assert.equal(key, `${binFile.parent.path}\\${NATIVE_MESSAGING_HOST_ID}.json`);
});