"use strict"; const { TelemetryArchive } = ChromeUtils.importESModule( "resource://gre/modules/TelemetryArchive.sys.mjs" ); const { TelemetryUtils } = ChromeUtils.importESModule( "resource://gre/modules/TelemetryUtils.sys.mjs" ); const { TelemetryTestUtils } = ChromeUtils.importESModule( "resource://testing-common/TelemetryTestUtils.sys.mjs" ); const { TelemetryArchiveTesting } = ChromeUtils.importESModule( "resource://testing-common/TelemetryArchiveTesting.sys.mjs" ); const { TestUtils } = ChromeUtils.importESModule( "resource://testing-common/TestUtils.sys.mjs" ); // All tests run privileged unless otherwise specified not to. function createExtension( backgroundScript, permissions, isPrivileged = true, telemetry ) { let extensionData = { background: backgroundScript, manifest: { permissions, telemetry }, isPrivileged, }; return ExtensionTestUtils.loadExtension(extensionData); } async function run(test) { let extension = createExtension( test.backgroundScript, test.permissions || ["telemetry"], test.isPrivileged, test.telemetry ); await extension.startup(); await extension.awaitFinish(test.doneSignal); await extension.unload(); } // Currently unsupported on Android: blocked on 1220177. // See 1280234 c67 for discussion. if (AppConstants.MOZ_BUILD_APP === "browser") { add_task(async function test_telemetry_without_telemetry_permission() { await run({ backgroundScript: () => { browser.test.assertTrue( !browser.telemetry, "'telemetry' permission is required" ); browser.test.notifyPass("telemetry_permission"); }, permissions: [], doneSignal: "telemetry_permission", isPrivileged: false, }); }); add_task( async function test_telemetry_without_telemetry_permission_privileged() { await run({ backgroundScript: () => { browser.test.assertTrue( !browser.telemetry, "'telemetry' permission is required" ); browser.test.notifyPass("telemetry_permission"); }, permissions: [], doneSignal: "telemetry_permission", }); } ); add_task(async function test_telemetry_scalar_add() { Services.telemetry.clearScalars(); await run({ backgroundScript: async () => { await browser.telemetry.scalarAdd( "telemetry.test.unsigned_int_kind", 1 ); browser.test.notifyPass("scalar_add"); }, doneSignal: "scalar_add", }); TelemetryTestUtils.assertScalar( TelemetryTestUtils.getProcessScalars("parent", false, true), "telemetry.test.unsigned_int_kind", 1 ); }); add_task(async function test_telemetry_scalar_add_unknown_name() { let { messages } = await promiseConsoleOutput(async () => { await run({ backgroundScript: async () => { await browser.telemetry.scalarAdd("telemetry.test.does_not_exist", 1); browser.test.notifyPass("scalar_add_unknown_name"); }, doneSignal: "scalar_add_unknown_name", }); }); Assert.ok( messages.find(({ message }) => message.includes("Unknown scalar")), "Telemetry should warn if an unknown scalar is incremented" ); }); add_task(async function test_telemetry_scalar_add_illegal_value() { await run({ backgroundScript: () => { browser.test.assertThrows( () => browser.telemetry.scalarAdd("telemetry.test.unsigned_int_kind", {}), /Incorrect argument types for telemetry.scalarAdd/, "The second 'value' argument to scalarAdd must be an integer, string, or boolean" ); browser.test.notifyPass("scalar_add_illegal_value"); }, doneSignal: "scalar_add_illegal_value", }); }); add_task(async function test_telemetry_scalar_add_invalid_keyed_scalar() { let { messages } = await promiseConsoleOutput(async function () { await run({ backgroundScript: async () => { await browser.telemetry.scalarAdd( "telemetry.test.keyed_unsigned_int", 1 ); browser.test.notifyPass("scalar_add_invalid_keyed_scalar"); }, doneSignal: "scalar_add_invalid_keyed_scalar", }); }); Assert.ok( messages.find(({ message }) => message.includes("Attempting to manage a keyed scalar as a scalar") ), "Telemetry should warn if a scalarAdd is called for a keyed scalar" ); }); add_task(async function test_telemetry_scalar_set_bool_true() { Services.telemetry.clearScalars(); await run({ backgroundScript: async () => { await browser.telemetry.scalarSet("telemetry.test.boolean_kind", true); browser.test.notifyPass("scalar_set_bool_true"); }, doneSignal: "scalar_set_bool_true", }); TelemetryTestUtils.assertScalar( TelemetryTestUtils.getProcessScalars("parent", false, true), "telemetry.test.boolean_kind", true ); }); add_task(async function test_telemetry_scalar_set_bool_false() { Services.telemetry.clearScalars(); await run({ backgroundScript: async () => { await browser.telemetry.scalarSet("telemetry.test.boolean_kind", false); browser.test.notifyPass("scalar_set_bool_false"); }, doneSignal: "scalar_set_bool_false", }); TelemetryTestUtils.assertScalar( TelemetryTestUtils.getProcessScalars("parent", false, true), "telemetry.test.boolean_kind", false ); }); add_task(async function test_telemetry_scalar_unset_bool() { Services.telemetry.clearScalars(); TelemetryTestUtils.assertScalarUnset( TelemetryTestUtils.getProcessScalars("parent", false, true), "telemetry.test.boolean_kind" ); }); add_task(async function test_telemetry_scalar_set_unknown_name() { let { messages } = await promiseConsoleOutput(async function () { await run({ backgroundScript: async () => { await browser.telemetry.scalarSet( "telemetry.test.does_not_exist", true ); browser.test.notifyPass("scalar_set_unknown_name"); }, doneSignal: "scalar_set_unknown_name", }); }); Assert.ok( messages.find(({ message }) => message.includes("Unknown scalar")), "Telemetry should warn if an unknown scalar is set" ); }); add_task(async function test_telemetry_scalar_set_zero() { Services.telemetry.clearScalars(); await run({ backgroundScript: async () => { await browser.telemetry.scalarSet( "telemetry.test.unsigned_int_kind", 0 ); browser.test.notifyPass("scalar_set_zero"); }, doneSignal: "scalar_set_zero", }); TelemetryTestUtils.assertScalar( TelemetryTestUtils.getProcessScalars("parent", false, true), "telemetry.test.unsigned_int_kind", 0 ); }); add_task(async function test_telemetry_scalar_set_maximum() { Services.telemetry.clearScalars(); await run({ backgroundScript: async () => { await browser.telemetry.scalarSetMaximum( "telemetry.test.unsigned_int_kind", 123 ); browser.test.notifyPass("scalar_set_maximum"); }, doneSignal: "scalar_set_maximum", }); TelemetryTestUtils.assertScalar( TelemetryTestUtils.getProcessScalars("parent", false, true), "telemetry.test.unsigned_int_kind", 123 ); }); add_task(async function test_telemetry_scalar_set_maximum_unknown_name() { let { messages } = await promiseConsoleOutput(async function () { await run({ backgroundScript: async () => { await browser.telemetry.scalarSetMaximum( "telemetry.test.does_not_exist", 1 ); browser.test.notifyPass("scalar_set_maximum_unknown_name"); }, doneSignal: "scalar_set_maximum_unknown_name", }); }); Assert.ok( messages.find(({ message }) => message.includes("Unknown scalar")), "Telemetry should warn if an unknown scalar is set" ); }); add_task(async function test_telemetry_scalar_set_maximum_illegal_value() { await run({ backgroundScript: () => { browser.test.assertThrows( () => browser.telemetry.scalarSetMaximum( "telemetry.test.unsigned_int_kind", "string" ), /Incorrect argument types for telemetry.scalarSetMaximum/, "The second 'value' argument to scalarSetMaximum must be a scalar" ); browser.test.notifyPass("scalar_set_maximum_illegal_value"); }, doneSignal: "scalar_set_maximum_illegal_value", }); }); add_task(async function test_telemetry_keyed_scalar_add() { Services.telemetry.clearScalars(); await run({ backgroundScript: async () => { await browser.telemetry.keyedScalarAdd( "telemetry.test.keyed_unsigned_int", "foo", 1 ); browser.test.notifyPass("keyed_scalar_add"); }, doneSignal: "keyed_scalar_add", }); TelemetryTestUtils.assertKeyedScalar( TelemetryTestUtils.getProcessScalars("parent", true, true), "telemetry.test.keyed_unsigned_int", "foo", 1 ); }); add_task(async function test_telemetry_keyed_scalar_add_unknown_name() { let { messages } = await promiseConsoleOutput(async () => { await run({ backgroundScript: async () => { await browser.telemetry.keyedScalarAdd( "telemetry.test.does_not_exist", "foo", 1 ); browser.test.notifyPass("keyed_scalar_add_unknown_name"); }, doneSignal: "keyed_scalar_add_unknown_name", }); }); Assert.ok( messages.find(({ message }) => message.includes("Unknown scalar")), "Telemetry should warn if an unknown keyed scalar is incremented" ); }); add_task(async function test_telemetry_keyed_scalar_add_illegal_value() { await run({ backgroundScript: () => { browser.test.assertThrows( () => browser.telemetry.keyedScalarAdd( "telemetry.test.keyed_unsigned_int", "foo", {} ), /Incorrect argument types for telemetry.keyedScalarAdd/, "The second 'value' argument to keyedScalarAdd must be an integer, string, or boolean" ); browser.test.notifyPass("keyed_scalar_add_illegal_value"); }, doneSignal: "keyed_scalar_add_illegal_value", }); }); add_task(async function test_telemetry_keyed_scalar_add_invalid_scalar() { let { messages } = await promiseConsoleOutput(async function () { await run({ backgroundScript: async () => { await browser.telemetry.keyedScalarAdd( "telemetry.test.unsigned_int_kind", "foo", 1 ); browser.test.notifyPass("keyed_scalar_add_invalid_scalar"); }, doneSignal: "keyed_scalar_add_invalid_scalar", }); }); Assert.ok( messages.find(({ message }) => message.includes( "Attempting to manage a keyed scalar as a scalar (or vice-versa)" ) ), "Telemetry should warn if a scalar is incremented as a keyed scalar" ); }); add_task(async function test_telemetry_keyed_scalar_add_long_key() { let { messages } = await promiseConsoleOutput(async () => { await run({ backgroundScript: async () => { await browser.telemetry.keyedScalarAdd( "telemetry.test.keyed_unsigned_int", "X".repeat(73), 1 ); browser.test.notifyPass("keyed_scalar_add_long_key"); }, doneSignal: "keyed_scalar_add_long_key", }); }); Assert.ok( messages.find(({ message }) => message.includes("The key length must be limited to 72 characters.") ), "Telemetry should warn if keyed scalar's key is too long" ); }); add_task(async function test_telemetry_keyed_scalar_set() { Services.telemetry.clearScalars(); await run({ backgroundScript: async () => { await browser.telemetry.keyedScalarSet( "telemetry.test.keyed_boolean_kind", "foo", true ); browser.test.notifyPass("keyed_scalar_set"); }, doneSignal: "keyed_scalar_set", }); TelemetryTestUtils.assertKeyedScalar( TelemetryTestUtils.getProcessScalars("parent", true, true), "telemetry.test.keyed_boolean_kind", "foo", true ); }); add_task(async function test_telemetry_keyed_scalar_set_unknown_name() { let { messages } = await promiseConsoleOutput(async function () { await run({ backgroundScript: async () => { await browser.telemetry.keyedScalarSet( "telemetry.test.does_not_exist", "foo", true ); browser.test.notifyPass("keyed_scalar_set_unknown_name"); }, doneSignal: "keyed_scalar_set_unknown_name", }); }); Assert.ok( messages.find(({ message }) => message.includes("Unknown scalar")), "Telemetry should warn if an unknown keyed scalar is incremented" ); }); add_task(async function test_telemetry_keyed_scalar_set_long_key() { let { messages } = await promiseConsoleOutput(async () => { await run({ backgroundScript: async () => { await browser.telemetry.keyedScalarSet( "telemetry.test.keyed_unsigned_int", "X".repeat(73), 1 ); browser.test.notifyPass("keyed_scalar_set_long_key"); }, doneSignal: "keyed_scalar_set_long_key", }); }); Assert.ok( messages.find(({ message }) => message.includes("The key length must be limited to 72 characters") ), "Telemetry should warn if keyed scalar's key is too long" ); }); add_task(async function test_telemetry_keyed_scalar_set_maximum() { Services.telemetry.clearScalars(); await run({ backgroundScript: async () => { await browser.telemetry.keyedScalarSetMaximum( "telemetry.test.keyed_unsigned_int", "foo", 123 ); browser.test.notifyPass("keyed_scalar_set_maximum"); }, doneSignal: "keyed_scalar_set_maximum", }); TelemetryTestUtils.assertKeyedScalar( TelemetryTestUtils.getProcessScalars("parent", true, true), "telemetry.test.keyed_unsigned_int", "foo", 123 ); }); add_task( async function test_telemetry_keyed_scalar_set_maximum_unknown_name() { let { messages } = await promiseConsoleOutput(async function () { await run({ backgroundScript: async () => { await browser.telemetry.keyedScalarSetMaximum( "telemetry.test.does_not_exist", "foo", 1 ); browser.test.notifyPass("keyed_scalar_set_maximum_unknown_name"); }, doneSignal: "keyed_scalar_set_maximum_unknown_name", }); }); Assert.ok( messages.find(({ message }) => message.includes("Unknown scalar")), "Telemetry should warn if an unknown keyed scalar is set" ); } ); add_task( async function test_telemetry_keyed_scalar_set_maximum_illegal_value() { await run({ backgroundScript: () => { browser.test.assertThrows( () => browser.telemetry.keyedScalarSetMaximum( "telemetry.test.keyed_unsigned_int", "foo", "string" ), /Incorrect argument types for telemetry.keyedScalarSetMaximum/, "The third 'value' argument to keyedScalarSetMaximum must be a scalar" ); browser.test.notifyPass("keyed_scalar_set_maximum_illegal_value"); }, doneSignal: "keyed_scalar_set_maximum_illegal_value", }); } ); add_task(async function test_telemetry_keyed_scalar_set_maximum_long_key() { let { messages } = await promiseConsoleOutput(async () => { await run({ backgroundScript: async () => { await browser.telemetry.keyedScalarSetMaximum( "telemetry.test.keyed_unsigned_int", "X".repeat(73), 1 ); browser.test.notifyPass("keyed_scalar_set_maximum_long_key"); }, doneSignal: "keyed_scalar_set_maximum_long_key", }); }); Assert.ok( messages.find(({ message }) => message.includes("The key length must be limited to 72 characters") ), "Telemetry should warn if keyed scalar's key is too long" ); }); add_task(async function test_telemetry_record_event() { Services.telemetry.clearEvents(); Services.telemetry.setEventRecordingEnabled("telemetry.test", true); await run({ backgroundScript: async () => { await browser.telemetry.recordEvent( "telemetry.test", "test1", "object1" ); browser.test.notifyPass("record_event_ok"); }, doneSignal: "record_event_ok", }); TelemetryTestUtils.assertEvents( [ { category: "telemetry.test", method: "test1", object: "object1", }, ], { category: "telemetry.test" } ); Services.telemetry.setEventRecordingEnabled("telemetry.test", false); Services.telemetry.clearEvents(); }); // Bug 1536877 add_task(async function test_telemetry_record_event_value_must_be_string() { Services.telemetry.clearEvents(); Services.telemetry.setEventRecordingEnabled("telemetry.test", true); await run({ backgroundScript: async () => { try { await browser.telemetry.recordEvent( "telemetry.test", "test1", "object1", "value1" ); browser.test.notifyPass("record_event_string_value"); } catch (ex) { browser.test.fail( `Unexpected exception raised during record_event_value_must_be_string: ${ex}` ); browser.test.notifyPass("record_event_string_value"); throw ex; } }, doneSignal: "record_event_string_value", }); TelemetryTestUtils.assertEvents( [ { category: "telemetry.test", method: "test1", object: "object1", value: "value1", }, ], { category: "telemetry.test" } ); Services.telemetry.setEventRecordingEnabled("telemetry.test", false); Services.telemetry.clearEvents(); }); add_task(async function test_telemetry_register_scalars_string() { Services.telemetry.clearScalars(); await run({ backgroundScript: async () => { await browser.telemetry.registerScalars("telemetry.test.dynamic", { webext_string: { kind: browser.telemetry.ScalarType.STRING, keyed: false, record_on_release: true, }, }); await browser.telemetry.scalarSet( "telemetry.test.dynamic.webext_string", "hello" ); browser.test.notifyPass("register_scalars_string"); }, doneSignal: "register_scalars_string", }); TelemetryTestUtils.assertScalar( TelemetryTestUtils.getProcessScalars("dynamic", false, true), "telemetry.test.dynamic.webext_string", "hello" ); }); add_task(async function test_telemetry_register_scalars_multiple() { Services.telemetry.clearScalars(); await run({ backgroundScript: async () => { await browser.telemetry.registerScalars("telemetry.test.dynamic", { webext_string: { kind: browser.telemetry.ScalarType.STRING, keyed: false, record_on_release: true, }, webext_string_too: { kind: browser.telemetry.ScalarType.STRING, keyed: false, record_on_release: true, }, }); await browser.telemetry.scalarSet( "telemetry.test.dynamic.webext_string", "hello" ); await browser.telemetry.scalarSet( "telemetry.test.dynamic.webext_string_too", "world" ); browser.test.notifyPass("register_scalars_multiple"); }, doneSignal: "register_scalars_multiple", }); const scalars = TelemetryTestUtils.getProcessScalars( "dynamic", false, true ); TelemetryTestUtils.assertScalar( scalars, "telemetry.test.dynamic.webext_string", "hello" ); TelemetryTestUtils.assertScalar( scalars, "telemetry.test.dynamic.webext_string_too", "world" ); }); add_task(async function test_telemetry_register_scalars_boolean() { Services.telemetry.clearScalars(); await run({ backgroundScript: async () => { await browser.telemetry.registerScalars("telemetry.test.dynamic", { webext_boolean: { kind: browser.telemetry.ScalarType.BOOLEAN, keyed: false, record_on_release: true, }, }); await browser.telemetry.scalarSet( "telemetry.test.dynamic.webext_boolean", true ); browser.test.notifyPass("register_scalars_boolean"); }, doneSignal: "register_scalars_boolean", }); TelemetryTestUtils.assertScalar( TelemetryTestUtils.getProcessScalars("dynamic", false, true), "telemetry.test.dynamic.webext_boolean", true ); }); add_task(async function test_telemetry_register_scalars_count() { Services.telemetry.clearScalars(); await run({ backgroundScript: async () => { await browser.telemetry.registerScalars("telemetry.test.dynamic", { webext_count: { kind: browser.telemetry.ScalarType.COUNT, keyed: false, record_on_release: true, }, }); await browser.telemetry.scalarSet( "telemetry.test.dynamic.webext_count", 123 ); browser.test.notifyPass("register_scalars_count"); }, doneSignal: "register_scalars_count", }); TelemetryTestUtils.assertScalar( TelemetryTestUtils.getProcessScalars("dynamic", false, true), "telemetry.test.dynamic.webext_count", 123 ); }); add_task(async function test_telemetry_register_events() { Services.telemetry.clearEvents(); await run({ backgroundScript: async () => { await browser.telemetry.registerEvents("telemetry.test.dynamic", { test1: { methods: ["test1"], objects: ["object1"], extra_keys: [], }, }); await browser.telemetry.recordEvent( "telemetry.test.dynamic", "test1", "object1" ); browser.test.notifyPass("register_events"); }, doneSignal: "register_events", }); TelemetryTestUtils.assertEvents( [ { category: "telemetry.test.dynamic", method: "test1", object: "object1", }, ], { category: "telemetry.test.dynamic" }, { process: "dynamic" } ); }); add_task(async function test_telemetry_submit_ping() { let archiveTester = new TelemetryArchiveTesting.Checker(); await archiveTester.promiseInit(); await run({ backgroundScript: async () => { await browser.telemetry.submitPing("webext-test", {}, {}); browser.test.notifyPass("submit_ping"); }, doneSignal: "submit_ping", }); await TestUtils.waitForCondition( () => archiveTester.promiseFindPing("webext-test", []), "Failed to find the webext-test ping" ); }); add_task(async function test_telemetry_submit_encrypted_ping() { await run({ backgroundScript: async () => { try { await browser.telemetry.submitEncryptedPing( { payload: "encrypted-webext-test" }, { schemaName: "schema-name", schemaVersion: 123, } ); browser.test.fail( "Expected exception without required manifest entries set." ); } catch (e) { browser.test.assertTrue( e, /Encrypted telemetry pings require ping_type and public_key to be set in manifest./ ); browser.test.notifyPass("submit_encrypted_ping_fail"); } }, doneSignal: "submit_encrypted_ping_fail", }); const telemetryManifestEntries = { ping_type: "encrypted-webext-ping", schemaNamespace: "schema-namespace", public_key: { id: "pioneer-dev-20200423", key: { crv: "P-256", kty: "EC", x: "Qqihp7EryDN2-qQ-zuDPDpy5mJD5soFBDZmzPWTmjwk", y: "PiEQVUlywi2bEsA3_5D0VFrCHClCyUlLW52ajYs-5uc", }, }, }; await run({ backgroundScript: async () => { await browser.telemetry.submitEncryptedPing( { payload: "encrypted-webext-test", }, { schemaName: "schema-name", schemaVersion: 123, } ); browser.test.notifyPass("submit_encrypted_ping_pass"); }, permissions: ["telemetry"], doneSignal: "submit_encrypted_ping_pass", isPrivileged: true, telemetry: telemetryManifestEntries, }); telemetryManifestEntries.pioneer_id = true; telemetryManifestEntries.study_name = "test123"; Services.prefs.setStringPref("toolkit.telemetry.pioneerId", "test123"); await run({ backgroundScript: async () => { await browser.telemetry.submitEncryptedPing( { payload: "encrypted-webext-test" }, { schemaName: "schema-name", schemaVersion: 123, } ); browser.test.notifyPass("submit_encrypted_ping_pass"); }, permissions: ["telemetry"], doneSignal: "submit_encrypted_ping_pass", isPrivileged: true, telemetry: telemetryManifestEntries, }); let pings; await TestUtils.waitForCondition(async function () { pings = await TelemetryArchive.promiseArchivedPingList(); return pings.length >= 3; }, "Wait until we have at least 3 pings in the telemetry archive"); equal(pings.length, 3); equal(pings[1].type, "encrypted-webext-ping"); equal(pings[2].type, "encrypted-webext-ping"); }); add_task(async function test_telemetry_can_upload_enabled() { Services.prefs.setBoolPref( TelemetryUtils.Preferences.FhrUploadEnabled, true ); await run({ backgroundScript: async () => { const result = await browser.telemetry.canUpload(); browser.test.assertTrue(result); browser.test.notifyPass("can_upload_enabled"); }, doneSignal: "can_upload_enabled", }); Services.prefs.clearUserPref(TelemetryUtils.Preferences.FhrUploadEnabled); }); add_task(async function test_telemetry_can_upload_disabled() { Services.prefs.setBoolPref( TelemetryUtils.Preferences.FhrUploadEnabled, false ); await run({ backgroundScript: async () => { const result = await browser.telemetry.canUpload(); browser.test.assertFalse(result); browser.test.notifyPass("can_upload_disabled"); }, doneSignal: "can_upload_disabled", }); Services.prefs.clearUserPref(TelemetryUtils.Preferences.FhrUploadEnabled); }); }