fune/browser/components/tests/browser/browser_to_handle_telemetry.js
Nick Alexander 969b4ce42e Bug 1243603 - Telemetry for when Windows invokes Firefox to handle a registered file type or protocol. r=Gijs
The `-osint` flag is used as the signal that Windows is invoking
Firefox to handle a file type or protocol.  The `-osint` flag was
introduced in order to mitigate security breaches due to poor argument
quoting (by consumers invoking Firefox); to use it for this new
purpose, it must be preserved for downstream consumers to react to.
Alternately, some marker of the flag could be maintained.  Since the
flag needs to transit through the launcher process, I've elected to
simply not strip it as we validate command lines, and to accommodate
it further downstream.  (It looks like Thunderbird already
accommodates `-osint`: see
https://searchfox.org/comm-central/rev/3e8f926de9ea09945b237177eb6d489c70318f0e/mail/components/MessengerContentHandler.jsm#568.)

The telemetry in this patch achieves two purposes.  The first is to
count the number of times Firefox is invoked to handle a registered
file type or protocol: for this, a new keyed uint scalar was added.
File types start with a ".", just like on Windows; protocols
(equivalently, the schemes used to identify them) do not start with a
".".

The second is to identify times when Firefox is launched (i.e., it was
not already running) to handle a registered file type or protocol.

This generalizes the existing `os.environment.launch_method`,
introducing `os.environment.launched_to_handle` and
`os.environment.invoked_to_handle` string scalars, which record the
file type or protocol.

The command line state `STATE_INITIAL_LAUNCH` is used to discriminate
launching from invoking.

Differential Revision: https://phabricator.services.mozilla.com/D132288
2021-12-11 00:00:55 +00:00

199 lines
5.3 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
async function handleCommandLine(args, state) {
let newWinPromise;
let target = args[args.length - 1];
const EXISTING_FILE = Cc["@mozilla.org/file/local;1"].createInstance(
Ci.nsIFile
);
EXISTING_FILE.initWithPath(getTestFilePath("dummy.pdf"));
if (!target.includes("://")) {
// For simplicity, we handle only absolute paths. We could resolve relative
// paths, but that would itself require the functionality of the
// `nsICommandLine` instance we produce using this input.
const file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(target);
target = Services.io.newFileURI(file).spec;
}
if (state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
newWinPromise = BrowserTestUtils.waitForNewWindow({
url: target, // N.b.: trailing slashes matter when matching.
});
}
let cmdLineHandler = Cc["@mozilla.org/browser/final-clh;1"].getService(
Ci.nsICommandLineHandler
);
let fakeCmdLine = Cu.createCommandLine(args, EXISTING_FILE.parent, state);
cmdLineHandler.handle(fakeCmdLine);
if (newWinPromise) {
let newWin = await newWinPromise;
await BrowserTestUtils.closeWindow(newWin);
} else {
BrowserTestUtils.removeTab(gBrowser.selectedTab);
}
}
function assertToHandleTelemetry(assertions) {
const scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
const { invoked, launched, ...unknown } = assertions;
if (Object.keys(unknown).length) {
throw Error(
`Unknown keys given to assertToHandleTelemetry: ${JSON.stringify(
unknown
)}`
);
}
if (invoked === undefined && launched === undefined) {
throw Error("No known keys given to assertToHandleTelemetry");
}
for (let scalar of ["invoked", "launched"]) {
if (scalar in assertions) {
const { handled, not_handled } = assertions[scalar] || {};
if (handled) {
TelemetryTestUtils.assertKeyedScalar(
scalars,
`os.environment.${scalar}_to_handle`,
handled,
1,
`${scalar} to handle '${handled}' 1 times`
);
// Intentionally nested.
if (not_handled) {
Assert.equal(
not_handled in scalars[`os.environment.${scalar}_to_handle`],
false,
`${scalar} to handle '${not_handled}' 0 times`
);
}
} else {
TelemetryTestUtils.assertScalarUnset(
scalars,
`os.environment.${scalar}_to_handle`
);
if (not_handled) {
throw new Error(
`In ${scalar}, 'not_handled' is only valid with 'handled'`
);
}
}
}
}
}
add_task(async function test_invoked_to_handle_registered_file_type() {
await handleCommandLine(
[
"-osint",
"-url",
getTestFilePath("../../../../dom/security/test/csp/dummy.pdf"),
],
Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
);
assertToHandleTelemetry({
invoked: { handled: ".pdf", not_handled: ".html" },
launched: null,
});
});
add_task(async function test_invoked_to_handle_unregistered_file_type() {
await handleCommandLine(
["-osint", "-url", getTestFilePath("browser.ini")],
Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
);
assertToHandleTelemetry({
invoked: { handled: ".<other extension>", not_handled: ".ini" },
launched: null,
});
});
add_task(async function test_invoked_to_handle_registered_protocol() {
await handleCommandLine(
["-osint", "-url", "https://example.com/"],
Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
);
assertToHandleTelemetry({
invoked: { handled: "https", not_handled: "mailto" },
launched: null,
});
});
add_task(async function test_invoked_to_handle_unregistered_protocol() {
// Truly unknown protocols get "URI fixed up" to search provider queries.
// `ftp` does not get fixed up.
await handleCommandLine(
["-osint", "-url", "ftp://example.com/"],
Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
);
assertToHandleTelemetry({
invoked: { handled: "<other protocol>", not_handled: "ftp" },
launched: null,
});
});
add_task(async function test_launched_to_handle_registered_protocol() {
await handleCommandLine(
["-osint", "-url", "https://example.com/"],
Ci.nsICommandLine.STATE_INITIAL_LAUNCH
);
assertToHandleTelemetry({
invoked: null,
launched: { handled: "https", not_handled: "mailto" },
});
});
add_task(async function test_launched_to_handle_registered_file_type() {
await handleCommandLine(
[
"-osint",
"-url",
getTestFilePath("../../../../dom/security/test/csp/dummy.pdf"),
],
Ci.nsICommandLine.STATE_INITIAL_LAUNCH
);
assertToHandleTelemetry({
invoked: null,
launched: { handled: ".pdf", not_handled: ".html" },
});
});
add_task(async function test_invoked_no_osint() {
await handleCommandLine(
["-url", "https://example.com/"],
Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
);
assertToHandleTelemetry({
invoked: null,
launched: null,
});
});
add_task(async function test_launched_no_osint() {
await handleCommandLine(
["-url", "https://example.com/"],
Ci.nsICommandLine.STATE_INITIAL_LAUNCH
);
assertToHandleTelemetry({
invoked: null,
launched: null,
});
});