--- a/toolkit/components/extensions/ext-toolkit.json
+++ b/toolkit/components/extensions/ext-toolkit.json
@@ -155,16 +155,24 @@
"storage": {
"url": "chrome://extensions/content/parent/ext-storage.js",
"schema": "chrome://extensions/content/schemas/storage.json",
"scopes": ["addon_parent", "content_parent", "devtools_parent"],
"paths": [
["storage"]
]
},
+ "telemetry": {
+ "url": "chrome://extensions/content/parent/ext-telemetry.js",
+ "schema": "chrome://extensions/content/schemas/telemetry.json",
+ "scopes": ["addon_parent"],
+ "paths": [
+ ["telemetry"]
+ ]
+ },
"test": {
"schema": "chrome://extensions/content/schemas/test.json",
"scopes": ["content_child"]
},
"theme": {
"url": "chrome://extensions/content/parent/ext-theme.js",
"schema": "chrome://extensions/content/schemas/theme.json",
"scopes": ["addon_parent"],
--- a/toolkit/components/extensions/jar.mn
+++ b/toolkit/components/extensions/jar.mn
@@ -26,16 +26,17 @@ toolkit.jar:
content/extensions/parent/ext-notifications.js (parent/ext-notifications.js)
content/extensions/parent/ext-permissions.js (parent/ext-permissions.js)
content/extensions/parent/ext-privacy.js (parent/ext-privacy.js)
content/extensions/parent/ext-protocolHandlers.js (parent/ext-protocolHandlers.js)
content/extensions/parent/ext-proxy.js (parent/ext-proxy.js)
content/extensions/parent/ext-runtime.js (parent/ext-runtime.js)
content/extensions/parent/ext-storage.js (parent/ext-storage.js)
content/extensions/parent/ext-tabs-base.js (parent/ext-tabs-base.js)
+ content/extensions/parent/ext-telemetry.js (parent/ext-telemetry.js)
content/extensions/parent/ext-theme.js (parent/ext-theme.js)
content/extensions/parent/ext-toolkit.js (parent/ext-toolkit.js)
content/extensions/parent/ext-topSites.js (parent/ext-topSites.js)
content/extensions/parent/ext-webRequest.js (parent/ext-webRequest.js)
content/extensions/parent/ext-webNavigation.js (parent/ext-webNavigation.js)
content/extensions/child/ext-backgroundPage.js (child/ext-backgroundPage.js)
content/extensions/child/ext-contentScripts.js (child/ext-contentScripts.js)
content/extensions/child/ext-extension.js (child/ext-extension.js)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/parent/ext-telemetry.js
@@ -0,0 +1,115 @@
+"use strict";
+
+ChromeUtils.defineModuleGetter(this, "TelemetryController",
+ "resource://gre/modules/TelemetryController.jsm");
+ChromeUtils.defineModuleGetter(this, "TelemetryUtils",
+ "resource://gre/modules/TelemetryUtils.jsm");
+ChromeUtils.defineModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+ChromeUtils.defineModuleGetter(this, "ExtensionUtils",
+ "resource://gre/modules/ExtensionUtils.jsm");
+
+const SCALAR_TYPES = {
+ count: Ci.nsITelemetry.SCALAR_TYPE_COUNT,
+ string: Ci.nsITelemetry.SCALAR_TYPE_STRING,
+ boolean: Ci.nsITelemetry.SCALAR_TYPE_BOOLEAN,
+};
+
+// Currently unsupported on Android: blocked on 1220177.
+// See 1280234 c67 for discussion.
+function desktopCheck() {
+ if (AppConstants.MOZ_BUILD_APP !== "browser") {
+ throw new ExtensionUtils.ExtensionError("This API is only supported on desktop");
+ }
+}
+
+this.telemetry = class extends ExtensionAPI {
+ getAPI(context) {
+ return {
+ telemetry: {
+ submitPing(type, payload, options) {
+ desktopCheck();
+ try {
+ TelemetryController.submitExternalPing(type, payload, options);
+ } catch (ex) {
+ throw new ExtensionUtils.ExtensionError(ex);
+ }
+ },
+ canUpload() {
+ desktopCheck();
+ // Note: remove the ternary and direct pref check when
+ // TelemetryController.canUpload() is implemented (bug 1440089).
+ try {
+ const result = ("canUpload" in TelemetryController) ?
+ TelemetryController.canUpload() :
+ Services.prefs.getBoolPref(TelemetryUtils.Preferences.FhrUploadEnabled, false);
+ return result;
+ } catch (ex) {
+ throw new ExtensionUtils.ExtensionError(ex);
+ }
+ },
+ scalarAdd(name, value) {
+ desktopCheck();
+ try {
+ Services.telemetry.scalarAdd(name, value);
+ } catch (ex) {
+ throw new ExtensionUtils.ExtensionError(ex);
+ }
+ },
+ scalarSet(name, value) {
+ desktopCheck();
+ try {
+ Services.telemetry.scalarSet(name, value);
+ } catch (ex) {
+ throw new ExtensionUtils.ExtensionError(ex);
+ }
+ },
+ scalarSetMaximum(name, value) {
+ desktopCheck();
+ try {
+ Services.telemetry.scalarSetMaximum(name, value);
+ } catch (ex) {
+ throw new ExtensionUtils.ExtensionError(ex);
+ }
+ },
+ recordEvent(category, method, object, value, extra) {
+ desktopCheck();
+ try {
+ Services.telemetry.recordEvent(category, method, object, value, extra);
+ } catch (ex) {
+ throw new ExtensionUtils.ExtensionError(ex);
+ }
+ },
+ registerScalars(category, data) {
+ desktopCheck();
+ try {
+ // For each scalar in `data`, replace scalar.kind with
+ // the appropriate nsITelemetry constant.
+ Object.keys(data).forEach(scalar => {
+ data[scalar].kind = SCALAR_TYPES[data[scalar].kind];
+ });
+ Services.telemetry.registerScalars(category, data);
+ } catch (ex) {
+ throw new ExtensionUtils.ExtensionError(ex);
+ }
+ },
+ setEventRecordingEnabled(category, enabled) {
+ desktopCheck();
+ try {
+ Services.telemetry.setEventRecordingEnabled(category, enabled);
+ } catch (ex) {
+ throw new ExtensionUtils.ExtensionError(ex);
+ }
+ },
+ registerEvents(category, data) {
+ desktopCheck();
+ try {
+ Services.telemetry.registerEvents(category, data);
+ } catch (ex) {
+ throw new ExtensionUtils.ExtensionError(ex);
+ }
+ },
+ },
+ };
+ }
+};
--- a/toolkit/components/extensions/schemas/jar.mn
+++ b/toolkit/components/extensions/schemas/jar.mn
@@ -26,14 +26,15 @@ toolkit.jar:
content/extensions/schemas/manifest.json
content/extensions/schemas/native_manifest.json
content/extensions/schemas/notifications.json
content/extensions/schemas/permissions.json
content/extensions/schemas/proxy.json
content/extensions/schemas/privacy.json
content/extensions/schemas/runtime.json
content/extensions/schemas/storage.json
+ content/extensions/schemas/telemetry.json
content/extensions/schemas/test.json
content/extensions/schemas/theme.json
content/extensions/schemas/top_sites.json
content/extensions/schemas/types.json
content/extensions/schemas/web_navigation.json
content/extensions/schemas/web_request.json
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/schemas/telemetry.json
@@ -0,0 +1,301 @@
+[
+ {
+ "namespace": "manifest",
+ "types": [{
+ "$extend": "Permission",
+ "choices": [{
+ "type": "string",
+ "enum": [
+ "telemetry"
+ ]
+ }]
+ }]
+ },
+ {
+ "namespace": "telemetry",
+ "description": "Use the <code>browser.telemetry</code> API to send telemetry data to the Mozilla Telemetry service. Restricted to Mozilla privileged webextensions.",
+ "types": [{
+ "id": "ScalarType",
+ "type": "string",
+ "enum": ["count", "string", "boolean"],
+ "description": "Type of scalar: 'count' for numeric values, 'string' for string values, 'boolean' for boolean values. Maps to <code>nsITelemetry.SCALAR_TYPE_*</code>."
+ }, {
+ "id": "ScalarData",
+ "type": "object",
+ "description": "Represents registration data for a Telemetry scalar.",
+ "properties": {
+ "kind": {
+ "$ref": "ScalarType"
+ },
+ "keyed": {
+ "type": "boolean",
+ "optional": true,
+ "default": false,
+ "description": "True if this is a keyed scalar."
+ },
+ "record_on_release": {
+ "type": "boolean",
+ "optional": true,
+ "default": false,
+ "description": "True if this data should be recorded on release."
+ },
+ "expired": {
+ "type": "boolean",
+ "optional": true,
+ "default": false,
+ "description": "True if this scalar entry is expired. This allows recording it without error, but it will be discarded."
+ }
+ }
+ }, {
+ "id": "EventData",
+ "type": "object",
+ "description": "Represents registration data for a Telemetry event.",
+ "properties": {
+ "methods": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "List of methods for this event entry."
+ },
+ "objects": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "List of objects for this event entry."
+ },
+ "extra_keys": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "List of allowed extra keys for this event entry."
+ },
+ "record_on_release": {
+ "type": "boolean",
+ "optional": true,
+ "default": false,
+ "description": "True if this data should be recorded on release."
+ },
+ "expired": {
+ "type": "boolean",
+ "optional": true,
+ "default": false,
+ "description": "True if this event entry is expired. This allows recording it without error, but it will be discarded."
+ }
+ }
+ }],
+ "permissions": [
+ "telemetry",
+ "mozillaAddons"
+ ],
+ "functions": [{
+ "name": "submitPing",
+ "type": "function",
+ "description": "Submits a custom ping to the Telemetry back-end. See <code>submitExternalPing</code> inside TelemetryController.jsm for more details.",
+ "async": true,
+ "parameters": [
+ {
+ "name": "type",
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9-]+[a-z0-9]$",
+ "description": "The type of the ping."
+ },
+ {
+ "name": "message",
+ "type": "object",
+ "additionalProperties": { "type": "any" },
+ "description": "The data payload for the ping."
+ },
+ {
+ "description": "Options object.",
+ "name": "options",
+ "type": "object",
+ "properties": {
+ "addClientId": {
+ "type": "boolean",
+ "optional": true,
+ "default": false,
+ "description": "True if the ping should contain the client id."
+ },
+ "addEnvironment": {
+ "type": "boolean",
+ "optional": true,
+ "default": false,
+ "description": "True if the ping should contain the environment data."
+ },
+ "overrideEnvironment": {
+ "type": "object",
+ "additionalProperties": { "type": "any" },
+ "optional": true,
+ "default": false,
+ "description": "Set to override the environment data."
+ },
+ "usePingSender": {
+ "type": "boolean",
+ "optional": true,
+ "default": false,
+ "description": "If true, send the ping using the PingSender."
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "canUpload",
+ "type": "function",
+ "description": "Checks if Telemetry is enabled.",
+ "parameters": [],
+ "async": true
+ },
+ {
+ "name": "scalarAdd",
+ "type": "function",
+ "description": "Adds the value to the given scalar.",
+ "async": true,
+ "parameters": [
+ {
+ "name": "name",
+ "type": "string",
+ "description": "The scalar name."
+ },
+ {
+ "name": "value",
+ "type": "integer",
+ "minimum": 1,
+ "description": "The numeric value to add to the scalar. Only unsigned integers supported."
+ }
+ ]
+ },
+ {
+ "name": "scalarSet",
+ "type": "function",
+ "description": "Sets the named scalar to the given value. Throws if the value type doesn't match the scalar type.",
+ "async": true,
+ "parameters": [
+ {
+ "name": "name",
+ "type": "string",
+ "description": "The scalar name"
+ },
+ {
+ "name": "value",
+ "description": "The value to set the scalar to",
+ "choices": [
+ { "type": "string" },
+ { "type": "boolean" },
+ { "type": "integer" },
+ { "type": "object", "additionalProperties": { "type": "any" } }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "scalarSetMaximum",
+ "type": "function",
+ "description": "Sets the scalar to the maximum of the current and the passed value",
+ "async": true,
+ "parameters": [
+ {
+ "name": "name",
+ "type": "string",
+ "description": "The scalar name."
+ },
+ {
+ "name": "value",
+ "type": "integer",
+ "minimum": 0,
+ "description": "The numeric value to set the scalar to. Only unsigned integers supported."
+ }
+ ]
+ },
+ {
+ "name": "recordEvent",
+ "type": "function",
+ "description": "Record an event in Telemetry. Throws when trying to record an unknown event.",
+ "async": true,
+ "parameters": [
+ {
+ "name": "category",
+ "type": "string",
+ "description": "The category name."
+ },
+ {
+ "name": "method",
+ "type": "string",
+ "description": "The method name."
+ },
+ {
+ "name": "object",
+ "type": "string",
+ "description": "The object name."
+ },
+ {
+ "name": "value",
+ "type": "integer",
+ "optional": true,
+ "description": "An optional string value to record."
+ },
+ {
+ "name": "extra",
+ "type": "object",
+ "optional": true,
+ "description": "An optional object of the form (string -> string). It should only contain registered extra keys.",
+ "additionalProperties": { "type": "string" }
+ }
+ ]
+ },
+
+ {
+ "name": "registerScalars",
+ "type": "function",
+ "description": "Register new scalars to record them from addons. See nsITelemetry.idl for more details.",
+ "async": true,
+ "parameters": [
+ {
+ "name": "category",
+ "type": "string",
+ "description": "The unique category the scalars are registered in."
+ },
+ {
+ "name": "data",
+ "type": "object",
+ "additionalProperties": { "$ref": "ScalarData" },
+ "description": "An object that contains registration data for multiple scalars. Each property name is the scalar name, and the corresponding property value is an object of ScalarData type."
+ }
+ ]
+ },
+ {
+ "name": "registerEvents",
+ "type": "function",
+ "description": "Register new events to record them from addons. See nsITelemetry.idl for more details.",
+ "async": true,
+ "parameters": [
+ {
+ "name": "category",
+ "type": "string",
+ "description": "The unique category the events are registered in."
+ },
+ {
+ "name": "data",
+ "type": "object",
+ "additionalProperties": { "$ref": "EventData" },
+ "description": "An object that contains registration data for 1+ events. Each property name is the category name, and the corresponding property value is an object of EventData type."
+ }
+ ]
+ },
+ {
+ "name": "setEventRecordingEnabled",
+ "type": "function",
+ "description": "Enable recording of events in a category. Events default to recording disabled. This allows to toggle recording for all events in the specified category.",
+ "async": true,
+ "parameters": [
+ {
+ "name": "category",
+ "type": "string",
+ "description": "The category name."
+ },
+ {
+ "name": "enabled",
+ "type": "boolean",
+ "description": "Whether recording is enabled for events in that category."
+ }
+ ]
+ }]
+ }
+]
--- a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
@@ -407,16 +407,17 @@ const GRANTED_WITHOUT_USER_PROMPT = [
"cookies",
"geckoProfiler",
"identity",
"idle",
"menus",
"mozillaAddons",
"search",
"storage",
+ "telemetry",
"theme",
"webRequest",
"webRequestBlocking",
];
add_task(function test_permissions_have_localization_strings() {
const ns = Schemas.getNamespace("manifest");
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_telemetry.js
@@ -0,0 +1,365 @@
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/TelemetryArchive.jsm", this);
+ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this);
+
+function createExtension(backgroundScript, permissions) {
+ let extensionData = {
+ background: backgroundScript,
+ manifest: {permissions},
+ };
+ return ExtensionTestUtils.loadExtension(extensionData);
+}
+
+async function run(test) {
+ let extension = createExtension(test.backgroundScript, test.permissions || ["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",
+ });
+ });
+
+ 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",
+ });
+
+ const scalars = Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+ equal(scalars.parent["telemetry.test.unsigned_int_kind"], 1);
+
+ Services.telemetry.clearScalars();
+ });
+
+ 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",
+ });
+ });
+
+ messages = messages.filter(msg => /telemetry.test.does_not_exist - Unknown scalar./);
+ equal(messages.length, 1, "Telemetry should throw 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",
+ });
+ });
+
+ messages = messages.filter(msg => /Attempting to manage a keyed scalar as a scalar/);
+ equal(messages.length, 1, "Telemetry should throw if a keyed scalar is incremented");
+ });
+
+ add_task(async function test_telemetry_scalar_set() {
+ Services.telemetry.clearScalars();
+
+ await run({
+ backgroundScript: async () => {
+ await browser.telemetry.scalarSet("telemetry.test.boolean_kind", true);
+ browser.test.notifyPass("scalar_set");
+ },
+ doneSignal: "scalar_set",
+ });
+
+ const scalars = Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+ equal(scalars.parent["telemetry.test.boolean_kind"], true);
+
+ Services.telemetry.clearScalars();
+ });
+
+ 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",
+ });
+ });
+
+ messages = messages.filter(msg => /Unknown scalar/);
+ equal(messages.length, 1, "Telemetry should throw if an unknown scalar is set");
+ });
+
+ 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",
+ });
+
+ const scalars = Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+ equal(scalars.parent["telemetry.test.unsigned_int_kind"], 123);
+
+ Services.telemetry.clearScalars();
+ });
+
+ 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",
+ });
+ });
+
+ messages = messages.filter(msg => /Unknown scalar/);
+ equal(messages.length, 1, "Telemetry should throw 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_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",
+ });
+
+ let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
+ equal(events.parent.length, 1);
+ equal(events.parent[0][1], "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",
+ });
+
+ const scalars = Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+ equal(scalars.dynamic["telemetry.test.dynamic.webext_string"], "hello");
+
+ Services.telemetry.clearScalars();
+ });
+
+ 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 = Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+ equal(scalars.dynamic["telemetry.test.dynamic.webext_string"], "hello");
+ equal(scalars.dynamic["telemetry.test.dynamic.webext_string_too"], "world");
+
+ Services.telemetry.clearScalars();
+ });
+
+ 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",
+ });
+
+ const scalars = Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+ equal(scalars.dynamic["telemetry.test.dynamic.webext_boolean"], true);
+
+ Services.telemetry.clearScalars();
+ });
+
+ 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",
+ });
+
+ const scalars = Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+ equal(scalars.dynamic["telemetry.test.dynamic.webext_count"], 123);
+
+ Services.telemetry.clearScalars();
+ });
+
+ 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",
+ });
+
+ let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+ let expected = [["telemetry.test.dynamic", "test1", "object1"]];
+ equal(events.dynamic.length, expected.length);
+ deepEqual(events.dynamic.map(e => e.slice(1)), expected);
+
+ Services.telemetry.clearEvents();
+ });
+
+ add_task(async function test_telemetry_submit_ping() {
+ await run({
+ backgroundScript: async () => {
+ await browser.telemetry.submitPing("webext-test", {}, {});
+ browser.test.notifyPass("submit_ping");
+ },
+ doneSignal: "submit_ping",
+ });
+
+ let pings = await TelemetryArchive.promiseArchivedPingList();
+ equal(pings.length, 1);
+ equal(pings[0].type, "webext-test");
+ });
+
+ 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);
+ });
+}
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -112,16 +112,17 @@ head = head.js head_sync.js
skip-if = appname == "thunderbird" || os == "android"
[test_ext_storage_sync_crypto.js]
skip-if = appname == "thunderbird" || os == "android"
[test_ext_storage_tab.js]
[test_ext_storage_telemetry.js]
skip-if = os == "android" # checking for telemetry needs to be updated: 1384923
[test_ext_tab_teardown.js]
skip-if = os == 'android' # Bug 1258975 on android.
+[test_ext_telemetry.js]
[test_ext_trustworthy_origin.js]
[test_ext_topSites.js]
skip-if = os == "android"
[test_ext_unload_frame.js]
skip-if = true # Too frequent intermittent failures
[test_ext_webRequest_auth.js]
[test_ext_webRequest_filterResponseData.js]
[test_ext_webRequest_permission.js]