Merge pull request #163 from mozilla/testpilot-metrics-117
for #117: start telemetry with testpilot-metrics
This commit is contained in:
commit
fe1decef4c
6 changed files with 481 additions and 8 deletions
1
.eslintignore
Normal file
1
.eslintignore
Normal file
|
@ -0,0 +1 @@
|
|||
testpilot-metrics.js
|
|
@ -114,7 +114,6 @@ of a `testpilottest` telemetry ping for each scenario.
|
|||
```js
|
||||
{
|
||||
"uuid": <uuid>,
|
||||
"userContextId": <userContextId>,
|
||||
"event": "add-container"
|
||||
}
|
||||
```
|
||||
|
|
146
index.js
146
index.js
|
@ -35,6 +35,7 @@ const PREFS = [
|
|||
const { attachTo, detachFrom } = require("sdk/content/mod");
|
||||
const { ContextualIdentityService } = require("resource://gre/modules/ContextualIdentityService.jsm");
|
||||
const { getFavicon } = require("sdk/places/favicon");
|
||||
const Metrics = require("./testpilot-metrics");
|
||||
const { modelFor } = require("sdk/model/core");
|
||||
const prefService = require("sdk/preferences/service");
|
||||
const self = require("sdk/self");
|
||||
|
@ -42,11 +43,13 @@ const ss = require("sdk/simple-storage");
|
|||
const { Style } = require("sdk/stylesheet/style");
|
||||
const tabs = require("sdk/tabs");
|
||||
const tabsUtils = require("sdk/tabs/utils");
|
||||
const uuid = require("sdk/util/uuid");
|
||||
const { viewFor } = require("sdk/view/core");
|
||||
const webExtension = require("sdk/webextension");
|
||||
const windows = require("sdk/windows");
|
||||
const windowUtils = require("sdk/window/utils");
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ContainerService
|
||||
|
||||
|
@ -61,7 +64,8 @@ const ContainerService = {
|
|||
if (installation) {
|
||||
const object = {
|
||||
version: 1,
|
||||
prefs: {}
|
||||
prefs: {},
|
||||
metricsUUID: uuid.uuid().toString(),
|
||||
};
|
||||
|
||||
PREFS.forEach(pref => {
|
||||
|
@ -77,6 +81,8 @@ const ContainerService = {
|
|||
prefService.set(pref[0], pref[1]);
|
||||
});
|
||||
|
||||
this._metricsUUID = ss.storage.savedConfiguration.metricsUUID;
|
||||
|
||||
// Message routing
|
||||
|
||||
// only these methods are allowed. We have a 1:1 mapping between messages
|
||||
|
@ -95,6 +101,7 @@ const ContainerService = {
|
|||
"removeIdentity",
|
||||
"updateIdentity",
|
||||
"getPreference",
|
||||
"sendTelemetryPayload"
|
||||
];
|
||||
|
||||
// Map of identities.
|
||||
|
@ -159,10 +166,65 @@ const ContainerService = {
|
|||
}).catch(() => {
|
||||
throw new Error("WebExtension startup failed. Unable to continue.");
|
||||
});
|
||||
|
||||
this._sendEvent = new Metrics({
|
||||
type: "sdk",
|
||||
id: self.id,
|
||||
version: self.version
|
||||
}).sendEvent;
|
||||
|
||||
},
|
||||
|
||||
// utility methods
|
||||
|
||||
_containerTabCount(userContextId) {
|
||||
// Returns the total of open and hidden tabs with this userContextId
|
||||
let containerTabsCount = 0;
|
||||
containerTabsCount += this._identitiesState[userContextId].openTabs;
|
||||
containerTabsCount += this._identitiesState[userContextId].hiddenTabs.length;
|
||||
return containerTabsCount;
|
||||
},
|
||||
|
||||
_totalContainerTabsCount() {
|
||||
// Returns the number of total open tabs across ALL containers
|
||||
let totalContainerTabsCount = 0;
|
||||
for (const userContextId in this._identitiesState) {
|
||||
totalContainerTabsCount += this._identitiesState[userContextId].openTabs;
|
||||
}
|
||||
return totalContainerTabsCount;
|
||||
},
|
||||
|
||||
_totalNonContainerTabsCount() {
|
||||
// Returns the number of open tabs NOT IN a container
|
||||
let totalNonContainerTabsCount = 0;
|
||||
for (const tab of tabs) {
|
||||
if (this._getUserContextIdFromTab(tab) === 0) {
|
||||
++totalNonContainerTabsCount;
|
||||
}
|
||||
}
|
||||
return totalNonContainerTabsCount;
|
||||
},
|
||||
|
||||
_containersCounts() {
|
||||
let containersCounts = { // eslint-disable-line prefer-const
|
||||
"shown": 0,
|
||||
"hidden": 0,
|
||||
"total": 0
|
||||
};
|
||||
for (const userContextId in this._identitiesState) {
|
||||
if (this._identitiesState[userContextId].openTabs > 0) {
|
||||
++containersCounts.shown;
|
||||
++containersCounts.total;
|
||||
continue;
|
||||
} else if (this._identitiesState[userContextId].hiddenTabs.length > 0) {
|
||||
++containersCounts.hidden;
|
||||
++containersCounts.total;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return containersCounts;
|
||||
},
|
||||
|
||||
_convert(identity) {
|
||||
// Let's convert the known colors to their color names.
|
||||
return {
|
||||
|
@ -291,6 +353,17 @@ const ContainerService = {
|
|||
ss.storage.identitiesData = this._identitiesState;
|
||||
},
|
||||
|
||||
sendTelemetryPayload(args = {}) {
|
||||
// when pings come from popup, delete "method" prop
|
||||
delete args.method;
|
||||
let payload = { // eslint-disable-line prefer-const
|
||||
"uuid": this._metricsUUID
|
||||
};
|
||||
Object.assign(payload, args);
|
||||
|
||||
this._sendEvent(payload);
|
||||
},
|
||||
|
||||
// Tabs management
|
||||
|
||||
hideTabs(args) {
|
||||
|
@ -303,6 +376,16 @@ const ContainerService = {
|
|||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const containersCounts = this._containersCounts();
|
||||
this.sendTelemetryPayload({
|
||||
"event": "hide-tabs",
|
||||
"userContextId": args.userContextId,
|
||||
"clickedContainerTabCount": this._containerTabCount(args.userContextId),
|
||||
"shownContainersCount": containersCounts.shown,
|
||||
"hiddenContainersCount": containersCounts.hidden,
|
||||
"totalContainersCount": containersCounts.total
|
||||
});
|
||||
|
||||
const tabsToClose = [];
|
||||
|
||||
this._containerTabIterator(args.userContextId, tab => {
|
||||
|
@ -337,6 +420,16 @@ const ContainerService = {
|
|||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const containersCounts = this._containersCounts();
|
||||
this.sendTelemetryPayload({
|
||||
"event": "show-tabs",
|
||||
"userContextId": args.userContextId,
|
||||
"clickedContainerTabCount": this._containerTabCount(args.userContextId),
|
||||
"shownContainersCount": containersCounts.shown,
|
||||
"hiddenContainersCount": containersCounts.hidden,
|
||||
"totalContainersCount": containersCounts.total
|
||||
});
|
||||
|
||||
const promises = [];
|
||||
|
||||
for (let object of this._identitiesState[args.userContextId].hiddenTabs) { // eslint-disable-line prefer-const
|
||||
|
@ -351,6 +444,13 @@ const ContainerService = {
|
|||
},
|
||||
|
||||
sortTabs() {
|
||||
const containersCounts = this._containersCounts();
|
||||
this.sendTelemetryPayload({
|
||||
"event": "sort-tabs",
|
||||
"shownContainersCount": containersCounts.shown,
|
||||
"totalContainerTabsCount": this._totalContainerTabsCount(),
|
||||
"totalNonContainerTabsCount": this._totalNonContainerTabsCount()
|
||||
});
|
||||
return new Promise(resolve => {
|
||||
for (let window of windows.browserWindows) { // eslint-disable-line prefer-const
|
||||
// First the pinned tabs, then the normal ones.
|
||||
|
@ -460,6 +560,12 @@ const ContainerService = {
|
|||
return;
|
||||
}
|
||||
|
||||
this.sendTelemetryPayload({
|
||||
"event": "move-tabs-to-window",
|
||||
"userContextId": args.userContextId,
|
||||
"clickedContainerTabCount": this._containerTabCount(args.userContextId),
|
||||
});
|
||||
|
||||
// Let's create a list of the tabs.
|
||||
const list = [];
|
||||
this._containerTabIterator(args.userContextId, tab => {
|
||||
|
@ -500,9 +606,17 @@ const ContainerService = {
|
|||
|
||||
openTab(args) {
|
||||
return this._recentBrowserWindow().then(browserWin => {
|
||||
let userContextId = 0;
|
||||
if ("userContextId" in args) {
|
||||
userContextId = args.userContextId;
|
||||
const userContextId = ("userContextId" in args) ? args.userContextId : 0;
|
||||
const source = ("source" in args) ? args.source : null;
|
||||
|
||||
// Only send telemetry for tabs opened by UI - i.e., not via showTabs
|
||||
if (source) {
|
||||
this.sendTelemetryPayload({
|
||||
"event": "open-tab",
|
||||
"eventSource": source,
|
||||
"userContextId": userContextId,
|
||||
"clickedContainerTabCount": this._containerTabCount(userContextId)
|
||||
});
|
||||
}
|
||||
|
||||
const tab = browserWin.gBrowser.addTab(args.url || DEFAULT_TAB, { userContextId });
|
||||
|
@ -538,6 +652,10 @@ const ContainerService = {
|
|||
},
|
||||
|
||||
createIdentity(args) {
|
||||
this.sendTelemetryPayload({
|
||||
"event": "add-container",
|
||||
});
|
||||
|
||||
for (let arg of [ "name", "color", "icon"]) { // eslint-disable-line prefer-const
|
||||
if (!(arg in args)) {
|
||||
return Promise.reject("createIdentity must be called with " + arg + " argument.");
|
||||
|
@ -563,6 +681,11 @@ const ContainerService = {
|
|||
return Promise.reject("updateIdentity must be called with userContextId argument.");
|
||||
}
|
||||
|
||||
this.sendTelemetryPayload({
|
||||
"event": "edit-container",
|
||||
"userContextId": args.userContextId
|
||||
});
|
||||
|
||||
const identity = ContextualIdentityService.getIdentityFromId(args.userContextId);
|
||||
for (let arg of [ "name", "color", "icon"]) { // eslint-disable-line prefer-const
|
||||
if ((arg in args)) {
|
||||
|
@ -589,6 +712,11 @@ const ContainerService = {
|
|||
return Promise.reject("removeIdentity must be called with userContextId argument.");
|
||||
}
|
||||
|
||||
this.sendTelemetryPayload({
|
||||
"event": "delete-container",
|
||||
"userContextId": args.userContextId
|
||||
});
|
||||
|
||||
const tabsToClose = [];
|
||||
this._containerTabIterator(args.userContextId, tab => {
|
||||
tabsToClose.push(tab);
|
||||
|
@ -837,7 +965,10 @@ ContainerWindow.prototype = {
|
|||
menuItemElement.setAttribute("data-identity-color", identity.color);
|
||||
|
||||
menuItemElement.addEventListener("command", (e) => {
|
||||
ContainerService.openTab({userContextId: identity.userContextId});
|
||||
ContainerService.openTab({
|
||||
userContextId: identity.userContextId,
|
||||
source: "tab-bar"
|
||||
});
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
|
@ -870,7 +1001,10 @@ ContainerWindow.prototype = {
|
|||
_configureFileMenu() {
|
||||
return this._configureMenu("menu_newUserContext", null, e => {
|
||||
const userContextId = parseInt(e.target.getAttribute("data-usercontextid"), 10);
|
||||
ContainerService.openTab({ userContextId });
|
||||
ContainerService.openTab({
|
||||
userContextId: userContextId,
|
||||
source: "file-menu"
|
||||
});
|
||||
}, "_fileMenuElements");
|
||||
},
|
||||
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
"jpm": "^1.2.2",
|
||||
"npm-run-all": "^4.0.0",
|
||||
"stylelint": "^7.7.1",
|
||||
"stylelint-config-standard": "^15.0.1"
|
||||
"stylelint-config-standard": "^15.0.1",
|
||||
"testpilot-metrics": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"firefox": ">=50.0"
|
||||
|
|
333
testpilot-metrics.js
Normal file
333
testpilot-metrics.js
Normal file
|
@ -0,0 +1,333 @@
|
|||
// 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/.
|
||||
|
||||
/**
|
||||
* Class that represents a metrics event broker. Events are sent to Google
|
||||
* Analytics if the `tid` parameter is set. Events are sent to Mozilla's
|
||||
* data pipeline via the Test Pilot add-on. No metrics code changes are
|
||||
* needed when the experiment is added to or removed from Test Pilot.
|
||||
* @constructor
|
||||
* @param {string} $0.id - addon ID, e.g. '@testpilot-addon'. See https://mdn.io/add_on_id.
|
||||
* @param {string} $0.version - addon version, e.g. '1.0.2'.
|
||||
* @param {string} [$0.uid] - unique identifier for a specific instance of an addon.
|
||||
* Optional, but required to send events to Google Analytics. Sent to Google Analytics
|
||||
* but not Mozilla services.
|
||||
* @param {string} [$0.tid] - Google Analytics tracking ID. Optional, but required
|
||||
* to send events to Google Analytics.
|
||||
* @param {string} [$0.type=webextension] - addon type. one of: 'webextension',
|
||||
* 'sdk', 'bootstrapped'.
|
||||
* @param {boolean} [$0.debug=false] - if true, enables logging. Note that this
|
||||
* value can be changed on a running instance, by modifying its `debug` property.
|
||||
* @throws {SyntaxError} If the required properties are missing, or if the
|
||||
* 'type' property is unrecognized.
|
||||
* @throws {Error} if initializing the transports fails.
|
||||
*/
|
||||
function Metrics({id, version, uid, tid = null, type = 'webextension', debug = false}) {
|
||||
if (!id) {
|
||||
throw new SyntaxError(`'id' property is required.`);
|
||||
} else if (!version) {
|
||||
throw new SyntaxError(`'version' property is required.`);
|
||||
} else if (tid && !uid) {
|
||||
throw new SyntaxError(`'uid' property is required to send events to Google Analytics.`);
|
||||
}
|
||||
|
||||
if (!['webextension', 'sdk', 'bootstrapped'].includes(type)) {
|
||||
throw new SyntaxError(`'type' property must be one of: 'webextension', 'sdk', or 'bootstrapped'`);
|
||||
}
|
||||
Object.assign(this, {id, uid, version, tid, type, debug});
|
||||
|
||||
// The test pilot add-on uses its own nsIObserverService topic for sending
|
||||
// pings to Telemetry. Otherwise, the topic is based on add-on type.
|
||||
if (id === '@testpilot-addon') {
|
||||
this.topic = 'testpilot';
|
||||
} else if (type === 'webextension') {
|
||||
this.topic = 'testpilot-telemetry';
|
||||
} else {
|
||||
this.topic = 'testpilottest';
|
||||
}
|
||||
|
||||
// NOTE: order is important here. _initTransports uses console.log, which may
|
||||
// not be available before _initConsole has run.
|
||||
this._initConsole();
|
||||
this._initTransports();
|
||||
|
||||
this.sendEvent = this.sendEvent.bind(this);
|
||||
|
||||
this._log(`Initialized topic to ${this.topic}`);
|
||||
if (!tid) {
|
||||
this._log(`Google Analytics disabled: 'tid' value not passed to constructor.`);
|
||||
} else {
|
||||
this._log(`Google Analytics enabled for Tracking ID ${tid}.`);
|
||||
}
|
||||
this._log('Constructor finished successfully.');
|
||||
}
|
||||
Metrics.prototype = {
|
||||
/**
|
||||
* Sends an event to the Mozilla data pipeline (and Google Analytics, if
|
||||
* a `tid` was passed to the constructor). Note: to avoid breaking callers,
|
||||
* if sending the event fails, no Errors will be thrown. Instead, the message
|
||||
* will be silently dropped, and, if debug mode is enabled, an error will be
|
||||
* logged to the Browser Console.
|
||||
*
|
||||
* If you want to pass extra fields to GA, or use a GA hit type other than
|
||||
* `Event`, you can transform the output data object yourself using the
|
||||
* `transform` parameter. You will need to add Custom Dimensions to GA for any
|
||||
* extra fields: https://support.google.com/analytics/answer/2709828. Note
|
||||
* that, by convention, the `variant` argument is mapped to the first Custom
|
||||
* Dimension (`cd1`) when constructing the GA Event hit.
|
||||
*
|
||||
* Note: the data object format is currently different for each experiment,
|
||||
* and should be defined based on the result of conversations with the Mozilla
|
||||
* data team.
|
||||
*
|
||||
* A suggested default format is:
|
||||
* @param {string} [$0.method] - What is happening? e.g. `click`
|
||||
* @param {string} [$0.object] - What is being affected? e.g. `home-button-1`
|
||||
* @param {string} [$0.category=interactions] - If you want to add a category
|
||||
* for easy reporting later. e.g. `mainmenu`
|
||||
* @param {string} [$0.variant=null] - An identifying string if you're running
|
||||
* different variants. e.g. `cohort-A`
|
||||
* @param {function} [transform] - Transform function used to alter the
|
||||
* parameters sent to GA. The `transform` function signature is
|
||||
* `transform(input, output)`, where `input` is the object passed to
|
||||
* `sendEvent` (excluding `transform`), and `output` is the default GA
|
||||
* object generated by the `_gaTransform` method. The `transform` function
|
||||
* should return an object whose keys are GA Measurement Protocol parameters.
|
||||
* The returned object will be form encoded and sent to GA.
|
||||
*/
|
||||
sendEvent: function(params = {}, transform) {
|
||||
const args = this._clone(params);
|
||||
args.object = params.object || null;
|
||||
args.category = params.category || 'interactions';
|
||||
args.variant = params.variant || null;
|
||||
|
||||
this._log(`sendEvent called with method = ${args.method}, object = ${args.object}, category = ${args.category}, variant = ${args.variant}.`);
|
||||
|
||||
const clientData = this._clone(args);
|
||||
const gaData = this._clone(args);
|
||||
if (!clientData) {
|
||||
this._error(`Unable to process data object. Dropping packet.`);
|
||||
return;
|
||||
}
|
||||
this._sendToClient(clientData);
|
||||
|
||||
if (this.tid && this.uid) {
|
||||
const defaultEvent = this._gaTransform(gaData);
|
||||
|
||||
let userEvent;
|
||||
if (transform) {
|
||||
userEvent = transform.call(null, gaData, defaultEvent);
|
||||
}
|
||||
|
||||
this._gaSend(userEvent || defaultEvent);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clone a data object by serializing / deserializing it.
|
||||
* @private
|
||||
* @param {object} o - Object to be cloned.
|
||||
* @returns A clone of the object, or `null` if cloning failed.
|
||||
*/
|
||||
_clone: function(o) {
|
||||
let cloned;
|
||||
try {
|
||||
cloned = JSON.parse(JSON.stringify(o));
|
||||
} catch (ex) {
|
||||
this._error(`Unable to clone object: ${ex}.`);
|
||||
return null;
|
||||
}
|
||||
return cloned;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends an event to the Mozilla data pipeline via the Test Pilot add-on.
|
||||
* Uses BroadcastChannel for WebExtensions, and nsIObserverService for other
|
||||
* add-on types.
|
||||
* @private
|
||||
* @param {object} params - Entire object sent to `sendEvent`.
|
||||
*/
|
||||
_sendToClient: function(params) {
|
||||
if (this.type === 'webextension') {
|
||||
this._channel.postMessage(params);
|
||||
this._log(`Sent client message via postMessage: ${params}`);
|
||||
} else {
|
||||
let stringified;
|
||||
|
||||
try {
|
||||
stringified = JSON.stringify(params);
|
||||
} catch(ex) {
|
||||
this._error(`Unable to serialize metrics event: ${ex}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const subject = {
|
||||
wrappedJSObject: {
|
||||
observersModuleSubjectWrapper: true,
|
||||
object: this.id
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
Services.obs.notifyObservers(subject, 'testpilot::send-metric', stringified);
|
||||
this._log(`Sent client message via nsIObserverService: ${stringified}`);
|
||||
} catch (ex) {
|
||||
this._error(`Failed to send nsIObserver client ping: ${ex}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Transforms `sendEvent()` arguments into a Google Analytics `Event` hit.
|
||||
* @private
|
||||
* @param {string} method - see `sendEvent` docs
|
||||
* @param {string} [object] - see `sendEvent` docs
|
||||
* @param {string} category - see `sendEvent` docs. Note that `category` is
|
||||
* required here, assuming the default value was filled in by `sendEvent()`.
|
||||
* @param {string} variant - see `sendEvent` docs. Note that `variant` is
|
||||
* required here, assuming the default value was filled in by `sendEvent()`.
|
||||
*/
|
||||
_gaTransform: function({method, object, category, variant}) {
|
||||
const data = {
|
||||
v: 1,
|
||||
an: this.id,
|
||||
av: this.version,
|
||||
tid: this.tid,
|
||||
uid: this.uid,
|
||||
t: 'event',
|
||||
ec: category,
|
||||
ea: method
|
||||
};
|
||||
if (object) {
|
||||
data.el = object;
|
||||
}
|
||||
if (variant) {
|
||||
data.cd1 = variant;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Encodes and sends an event message to Google Analytics.
|
||||
* @private
|
||||
* @param {object} msg - An object whose keys correspond to parameters in the
|
||||
* Google Analytics Measurement Protocol.
|
||||
*/
|
||||
_gaSend: function(msg) {
|
||||
const encoded = this._formEncode(msg);
|
||||
const GA_URL = 'https://ssl.google-analytics.com/collect';
|
||||
if (this.type === 'webextension') {
|
||||
navigator.sendBeacon(GA_URL, encoded);
|
||||
} else {
|
||||
// SDK and bootstrapped types might not have a window reference, so get
|
||||
// the sendBeacon DOM API from the hidden window.
|
||||
Services.appShell.hiddenDOMWindow.navigator.sendBeacon(GA_URL, encoded);
|
||||
}
|
||||
this._log(`Sent GA message: ${encoded}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* URL encodes an object. Encodes spaces as '%20', not '+', following the
|
||||
* GA docs.
|
||||
*
|
||||
* @example
|
||||
* // returns 'a=b&foo=b%20ar'
|
||||
* metrics._formEncode({a: 'b', foo: 'b ar'});
|
||||
* @private
|
||||
* @param {Object} obj - Any JS object
|
||||
* @returns {string}
|
||||
*/
|
||||
_formEncode: function(obj) {
|
||||
const params = [];
|
||||
if (!obj) { return ''; }
|
||||
Object.keys(obj).forEach(item => {
|
||||
const encoded = encodeURIComponent(item) + '=' + encodeURIComponent(obj[item]);
|
||||
params.push(encoded);
|
||||
});
|
||||
return params.join('&');
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes transports used for sending messages. For WebExtensions,
|
||||
* creates a `BroadcastChannel` (transport for client pings). WebExtensions
|
||||
* use navigator.sendBeacon for GA transport, and they always have access
|
||||
* to DOM APIs, so there's no setup work required. For other types, loads
|
||||
* `Services.jsm`, which exposes the nsIObserverService (transport for client
|
||||
* pings), and exposes the navigator.sendBeacon API (GA transport) via the
|
||||
* appShell service's hidden window.
|
||||
* @private
|
||||
* @throws {Error} if transport setup unexpectedly fails
|
||||
*/
|
||||
_initTransports: function() {
|
||||
if (this.type === 'webextension') {
|
||||
try {
|
||||
this._channel = new BroadcastChannel(this.topic);
|
||||
} catch(ex) {
|
||||
throw new Error(`Unable to create BroadcastChannel: ${ex}`);
|
||||
}
|
||||
} else if (this.type === 'sdk') {
|
||||
try {
|
||||
const { Cu } = require('chrome');
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
} catch(ex) {
|
||||
throw new Error(`Unable to load Services.jsm: ${ex}`);
|
||||
}
|
||||
} else { /* this.type === 'bootstrapped' */
|
||||
try {
|
||||
Components.utils.import('resource://gre/modules/Services.jsm');
|
||||
} catch(ex) {
|
||||
throw new Error(`Unable to load Services.jsm: ${ex}`);
|
||||
}
|
||||
}
|
||||
this._log('Successfully initialized transports.');
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes a console for 'bootstrapped' add-ons.
|
||||
* @private
|
||||
*/
|
||||
_initConsole: function() {
|
||||
if (this.type === 'bootstrapped') {
|
||||
try {
|
||||
Components.utils.import('resource://gre/modules/Console.jsm');
|
||||
this._log('Successfully initialized console.');
|
||||
} catch(ex) {
|
||||
throw new Error(`Unable to initialize console: ${ex}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Logs messages to the console. Only enabled if `this.debug` is truthy.
|
||||
* @private
|
||||
* @param {string} msg - A message
|
||||
*/
|
||||
_log: function(msg) {
|
||||
if (this.debug) {
|
||||
console.log(msg); // eslint-disable-line no-console
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Logs errors to the console. Only enabled if `this.debug` is truthy.
|
||||
* @private
|
||||
* @param {string} msg - An error message
|
||||
*/
|
||||
_error: function(msg) {
|
||||
if (this.debug) {
|
||||
console.error(msg); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// WebExtensions don't support CommonJS module style, so 'module' might not be
|
||||
// defined.
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = Metrics;
|
||||
}
|
||||
|
||||
// Export the Metrics constructor in Gecko JSM style, for legacy addons
|
||||
// that use the JSM loader. See also: https://mdn.io/jsm/using
|
||||
const EXPORTED_SYMBOLS = ['Metrics']; // eslint-disable-line no-unused-vars
|
|
@ -203,6 +203,10 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
|
|||
});
|
||||
|
||||
document.querySelector("#edit-containers-link").addEventListener("click", () => {
|
||||
browser.runtime.sendMessage({
|
||||
method: "sendTelemetryPayload",
|
||||
event: "edit-containers"
|
||||
});
|
||||
Logic.showPanel(P_CONTAINERS_EDIT);
|
||||
});
|
||||
|
||||
|
@ -246,6 +250,7 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
|
|||
return browser.runtime.sendMessage({
|
||||
method: "openTab",
|
||||
userContextId: identity.userContextId,
|
||||
source: "pop-up"
|
||||
});
|
||||
}).then(() => {
|
||||
window.close();
|
||||
|
|
Loading…
Add table
Reference in a new issue