From d685a58d743b9fc46743a3968132490311405abc Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Sun, 13 Aug 2017 21:49:57 +0100 Subject: [PATCH 1/2] Bootstrapify extension to work for Firefox Nightly 57 SDK removal. Fixes #725 --- bootstrap.js | 118 +++ index.js | 930 ------------------- install.rdf | 22 + package.json | 12 +- webextension/js/.eslintrc.js | 3 +- webextension/js/background/index.html | 2 - webextension/js/background/messageHandler.js | 3 - webextension/js/background/themeManager.js | 51 - webextension/js/popup.js | 9 +- webextension/manifest.json | 2 +- 10 files changed, 150 insertions(+), 1002 deletions(-) create mode 100644 bootstrap.js delete mode 100644 index.js create mode 100644 install.rdf delete mode 100644 webextension/js/background/themeManager.js diff --git a/bootstrap.js b/bootstrap.js new file mode 100644 index 00000000..9fcf037e --- /dev/null +++ b/bootstrap.js @@ -0,0 +1,118 @@ +"use strict"; + +const PREFS = [ + { + name: "privacy.userContext.enabled", + value: true, + type: "bool" + }, + { + name: "privacy.userContext.longPressBehavior", + value: 2, + type: "int" + }, + { + name: "privacy.userContext.ui.enabled", + value: true, // Post web ext we will be setting this true + type: "bool" + }, + { + name: "privacy.usercontext.about_newtab_segregation.enabled", + value: true, + type: "bool" + }, +]; +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +const { TextDecoder, TextEncoder } = Cu.import('resource://gre/modules/commonjs/toolkit/loader.js', {}); + +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); + +const JETPACK_DIR_BASENAME = "jetpack"; +const EXTENSION_ID = "@testpilot-containers"; + +function filename() { + let storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + storeFile.append(JETPACK_DIR_BASENAME); + storeFile.append(EXTENSION_ID); + storeFile.append("simple-storage"); + storeFile.append("store.json"); + return storeFile.path; +} + +async function getConfig() { + const bytes = await OS.File.read(filename()); + let raw = new TextDecoder().decode(bytes) || ""; + let savedConfig = {savedConfiguration: {}}; + if (raw) { + savedConfig = JSON.parse(raw); + } + + return savedConfig; +} + +async function initConfig() { + const savedConfig = await getConfig(); + savedConfig.savedConfiguration.version = 2; + if (!("prefs" in savedConfig.savedConfiguration)) { + savedConfig.savedConfiguration.prefs = {}; + PREFS.forEach((pref) => { + if ("int" === pref.type) { + savedConfig.savedConfiguration.prefs[pref.name] = Services.prefs.getIntPref(pref.name, pref.name); + } else { + savedConfig.savedConfiguration.prefs[pref.name] = Services.prefs.getBoolPref(pref.name, pref.value); + } + }); + } + const serialized = JSON.stringify(savedConfig); + let bytes = new TextEncoder().encode(serialized) || ""; + await OS.File.writeAtomic(filename(), bytes, { }); +} + +function setPrefs() { + PREFS.forEach((pref) => { + if ("int" === pref.type) { + Services.prefs.setIntPref(pref.name, pref.value); + } else { + Services.prefs.setBoolPref(pref.name, pref.value); + } + }); +} + +async function install() { + await initConfig(); + setPrefs(); +} + +async function uninstall(aData, aReason) { + if (aReason == ADDON_UNINSTALL + || aReason == ADDON_DISABLE) { + const config = await getConfig(); + const storedPrefs = config.savedConfiguration.prefs; + PREFS.forEach((pref) => { + if (pref.name in storedPrefs) { + if ("int" === pref.type) { + Services.prefs.setIntPref(pref.name, storedPrefs[pref.name]); + } else { + Services.prefs.setBoolPref(pref.name, storedPrefs[pref.name]); + } + } + }); + } +} + +function startup({webExtension}) { + // Reset prefs that may have changed, or are legacy + setPrefs(); + // Start the embedded webextension. + webExtension.startup().then(api => { + }); +} + +function shutdown(data) { +} + diff --git a/index.js b/index.js deleted file mode 100644 index 9b14addd..00000000 --- a/index.js +++ /dev/null @@ -1,930 +0,0 @@ -/* 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/. */ - -const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; - -const IDENTITY_COLORS = [ - { name: "blue", color: "#00a7e0" }, - { name: "turquoise", color: "#01bdad" }, - { name: "green", color: "#7dc14c" }, - { name: "yellow", color: "#ffcb00" }, - { name: "orange", color: "#f89c24" }, - { name: "red", color: "#d92215" }, - { name: "pink", color: "#ee5195" }, - { name: "purple", color: "#7a2f7a" }, -]; - -const IDENTITY_ICONS = [ - { name: "fingerprint", image: "chrome://browser/skin/usercontext/personal.svg" }, - { name: "briefcase", image: "chrome://browser/skin/usercontext/work.svg" }, - { name: "dollar", image: "chrome://browser/skin/usercontext/banking.svg" }, - { name: "cart", image: "chrome://browser/skin/usercontext/shopping.svg" }, - // All of these do not exist in gecko - { name: "gift", image: "gift" }, - { name: "vacation", image: "vacation" }, - { name: "food", image: "food" }, - { name: "fruit", image: "fruit" }, - { name: "pet", image: "pet" }, - { name: "tree", image: "tree" }, - { name: "chill", image: "chill" }, - { name: "circle", image: "circle" }, -]; - -const IDENTITY_COLORS_STANDARD = [ - "blue", "orange", "green", "pink", -]; - -const IDENTITY_ICONS_STANDARD = [ - "fingerprint", "briefcase", "dollar", "cart", -]; - -const PREFS = [ - [ "privacy.userContext.enabled", true ], - [ "privacy.userContext.longPressBehavior", 2 ], - [ "privacy.userContext.ui.enabled", false ], - [ "privacy.usercontext.about_newtab_segregation.enabled", true ], -]; - -const { attachTo, detachFrom } = require("sdk/content/mod"); -const { Cu } = require("chrome"); -const { ContextualIdentityService } = require("resource://gre/modules/ContextualIdentityService.jsm"); -const Metrics = require("./testpilot-metrics"); -const { modelFor } = require("sdk/model/core"); -const prefService = require("sdk/preferences/service"); -const self = require("sdk/self"); -const { Services } = require("resource://gre/modules/Services.jsm"); -const ss = require("sdk/simple-storage"); -const { study } = require("./study"); -const { Style } = require("sdk/stylesheet/style"); -const tabs = require("sdk/tabs"); -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"); - -Cu.import("resource:///modules/CustomizableUI.jsm"); -Cu.import("resource:///modules/CustomizableWidgets.jsm"); -Cu.import("resource:///modules/sessionstore/SessionStore.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - - -// ContextualIdentityProxy -const ContextualIdentityProxy = { - getIdentities() { - let response; - if ("getPublicIdentities" in ContextualIdentityService) { - response = ContextualIdentityService.getPublicIdentities(); - } else { - response = ContextualIdentityService.getIdentities(); - } - - return response.map((identity) => { - return this._convert(identity); - }); - }, - - getIdentityFromId(userContextId) { - let response; - if ("getPublicIdentityFromId" in ContextualIdentityService) { - response = ContextualIdentityService.getPublicIdentityFromId(userContextId); - } else { - response = ContextualIdentityService.getIdentityFromId(userContextId); - } - if (response) { - return this._convert(response); - } - return response; - }, - - _convert(identity) { - return { - name: ContextualIdentityService.getUserContextLabel(identity.userContextId), - icon: identity.icon, - color: identity.color, - userContextId: identity.userContextId, - }; - }, -}; - -// ---------------------------------------------------------------------------- -// ContainerService - -const ContainerService = { - _windowMap: new Map(), - _containerWasEnabled: false, - _onBackgroundConnectCallback: null, - - async init(installation, reason) { - // If we are just been installed, we must store some information for the - // uninstallation. This object contains also a version number, in case we - // need to implement a migration in the future. - // In 1.1.1 and less we deleted savedConfiguration on upgrade so we need to rebuild - if (!("savedConfiguration" in ss.storage) || - !("prefs" in ss.storage.savedConfiguration) || - (installation && reason !== "upgrade")) { - let preInstalledIdentities = []; // eslint-disable-line prefer-const - ContextualIdentityProxy.getIdentities().forEach(identity => { - preInstalledIdentities.push(identity.userContextId); - }); - - const object = { - version: 1, - prefs: {}, - metricsUUID: uuid.uuid().toString(), - preInstalledIdentities: preInstalledIdentities - }; - - PREFS.forEach(pref => { - object.prefs[pref[0]] = prefService.get(pref[0]); - }); - - ss.storage.savedConfiguration = object; - - if (prefService.get("privacy.userContext.enabled") !== true) { - // Maybe rename the Banking container. - const identity = ContextualIdentityProxy.getIdentityFromId(3); - if (identity && identity.l10nID === "userContextBanking.label") { - ContextualIdentityService.update(identity.userContextId, - "Finance", - identity.icon, - identity.color); - } - - // Let's create the default containers in case there are none. - if (ss.storage.savedConfiguration.preInstalledIdentities.length === 0) { - // Note: we have to create them in this way because there is no way to - // reuse the same ID and the localized strings. - ContextualIdentityService.create("Personal", "fingerprint", "blue"); - ContextualIdentityService.create("Work", "briefcase", "orange"); - ContextualIdentityService.create("Finance", "dollar", "green"); - ContextualIdentityService.create("Shopping", "cart", "pink"); - } - } - } - - // TOCHECK should this run on all code - ContextualIdentityProxy.getIdentities().forEach(identity => { - const newIcon = this._fromIconToName(identity.icon); - const newColor = this._fromColorToName(identity.color); - if (newIcon !== identity.icon || newColor !== identity.color) { - ContextualIdentityService.update(identity.userContextId, - ContextualIdentityService.getUserContextLabel(identity.userContextId), - newIcon, - newColor); - } - }); - - // Let's see if containers were enabled before this addon. - this._containerWasEnabled = - ss.storage.savedConfiguration.prefs["privacy.userContext.enabled"]; - - // Enabling preferences - - PREFS.forEach((pref) => { - prefService.set(pref[0], pref[1]); - }); - - this._metricsUUID = ss.storage.savedConfiguration.metricsUUID; - - // Disabling the customizable container panel. - CustomizableUI.destroyWidget("containers-panelmenu"); - - tabs.on("open", tab => { - this._restyleTab(tab); - }); - - tabs.on("activate", tab => { - this._restyleActiveTab(tab).catch(() => {}); - this._configureActiveWindows(); - }); - - // Modify CSS and other stuff for each window. - - this._configureWindows().catch(() => {}); - - windows.browserWindows.on("open", window => { - this._configureWindow(viewFor(window)).catch(() => {}); - }); - - windows.browserWindows.on("close", window => { - this.closeWindow(viewFor(window)); - }); - - // WebExtension startup - - try { - const api = await webExtension.startup(); - this.registerBackgroundConnection(api); - } catch (e) { - throw new Error("WebExtension startup failed. Unable to continue."); - } - - this._sendEvent = new Metrics({ - type: "sdk", - id: self.id, - version: self.version - }).sendEvent; - - // Begin-Of-Hack - ContextualIdentityService.workaroundForCookieManager = function(method, userContextId) { - let identity = method.call(ContextualIdentityService, userContextId); - if (!identity && userContextId) { - identity = { - userContextId, - icon: "", - color: "", - name: "Pending to be deleted", - public: true, - }; - } - - return identity; - }; - - if (!this._oldGetIdentityFromId) { - this._oldGetIdentityFromId = ContextualIdentityService.getIdentityFromId; - } - ContextualIdentityService.getIdentityFromId = function(userContextId) { - return this.workaroundForCookieManager(ContainerService._oldGetIdentityFromId, userContextId); - }; - - if ("getPublicIdentityFromId" in ContextualIdentityService) { - if (!this._oldGetPublicIdentityFromId) { - this._oldGetPublicIdentityFromId = ContextualIdentityService.getPublicIdentityFromId; - } - ContextualIdentityService.getPublicIdentityFromId = function(userContextId) { - return this.workaroundForCookieManager(ContainerService._oldGetPublicIdentityFromId, userContextId); - }; - } - // End-Of-Hack - - if (self.id === "@shield-study-containers") { - study.startup(reason); - this.shieldStudyVariation = study.variation; - } - }, - - registerBackgroundConnection(api) { - // This is only used for theme notifications and new tab - api.browser.runtime.onConnect.addListener((port) => { - this._onBackgroundConnectCallback = (message, topic) => { - port.postMessage({ - type: topic, - message - }); - }; - }); - }, - - triggerBackgroundCallback(message, topic) { - if (this._onBackgroundConnectCallback) { - this._onBackgroundConnectCallback(message, topic); - } - }, - - // In FF 50-51, the icon is the full path, in 52 and following - // releases, we have IDs to be used with a svg file. In this function - // we map URLs to svg IDs. - - // Helper methods for converting colors to names and names to colors. - - _fromNameToColor(name) { - return this._fromNameOrColor(name, "color"); - }, - - _fromColorToName(color) { - return this._fromNameOrColor(color, "name"); - }, - - _fromNameOrColor(what, attribute) { - for (let color of IDENTITY_COLORS) { // eslint-disable-line prefer-const - if (what === color.color || what === color.name) { - return color[attribute]; - } - } - return ""; - }, - - // Helper methods for converting icons to names and names to icons. - - _fromIconToName(icon) { - return this._fromNameOrIcon(icon, "name", "circle"); - }, - - _fromNameOrIcon(what, attribute, defaultValue) { - for (let icon of IDENTITY_ICONS) { // eslint-disable-line prefer-const - if (what === icon.image || what === icon.name) { - return icon[attribute]; - } - } - return defaultValue; - }, - - // Tab Helpers - - _getUserContextIdFromTab(tab) { - return parseInt(viewFor(tab).getAttribute("usercontextid") || 0, 10); - }, - - _matchTabsByContainer(userContextId) { - const matchedTabs = []; - for (const tab of tabs) { - if (userContextId === this._getUserContextIdFromTab(tab)) { - matchedTabs.push(tab); - } - } - return matchedTabs; - }, - - async _closeTabs(tabsToClose) { - // We create a new tab only if the current operation closes all the - // existing ones. - if (tabs.length === tabsToClose.length) { - await this.openTab({}); - } - - for (const tab of tabsToClose) { - // after .close() window is null. Let's take it now. - const window = viewFor(tab.window); - - tab.close(); - - // forget about this tab. 0 is the index of the forgotten tab and 0 - // means the last one. - try { - SessionStore.forgetClosedTab(window, 0); - } catch (e) {} // eslint-disable-line no-empty - } - }, - - _recentBrowserWindow() { - const browserWin = windowUtils.getMostRecentBrowserWindow(); - - // This should not really happen. - if (!browserWin || !browserWin.gBrowser) { - return Promise.resolve(null); - } - - return Promise.resolve(browserWin); - }, - - // Tabs management - - openTab(args) { - return this.triggerBackgroundCallback(args, "open-tab"); - }, - - // Identities management - - queryIdentities() { - return new Promise(resolve => { - const identities = ContextualIdentityProxy.getIdentities(); - resolve(identities); - }); - }, - - // Styling the window - - _configureWindows() { - const promises = []; - for (let window of windows.browserWindows) { // eslint-disable-line prefer-const - promises.push(this._configureWindow(viewFor(window))); - } - return Promise.all(promises); - }, - - _configureWindow(window) { - return this._getOrCreateContainerWindow(window).configure(); - }, - - _configureActiveWindows() { - const promises = []; - for (let window of windows.browserWindows) { // eslint-disable-line prefer-const - promises.push(this._configureActiveWindow(viewFor(window))); - } - return Promise.all(promises); - }, - - _configureActiveWindow(window) { - return this._getOrCreateContainerWindow(window).configureActive(); - }, - - closeWindow(window) { - this._windowMap.delete(window); - }, - - _getOrCreateContainerWindow(window) { - if (!(this._windowMap.has(window))) { - this._windowMap.set(window, new ContainerWindow(window)); - } - - return this._windowMap.get(window); - }, - - refreshNeeded() { - return this._configureWindows(); - }, - - _restyleActiveTab(tab) { - if (!tab) { - return Promise.resolve(null); - } - - const userContextId = ContainerService._getUserContextIdFromTab(tab); - const identity = ContextualIdentityProxy.getIdentityFromId(userContextId); - const hbox = viewFor(tab.window).document.getElementById("userContext-icons"); - - if (!identity) { - hbox.setAttribute("data-identity-color", ""); - return Promise.resolve(null); - } - - hbox.setAttribute("data-identity-color", identity.color); - - const label = viewFor(tab.window).document.getElementById("userContext-label"); - label.setAttribute("value", identity.name); - label.style.color = ContainerService._fromNameToColor(identity.color); - - const indicator = viewFor(tab.window).document.getElementById("userContext-indicator"); - indicator.setAttribute("data-identity-icon", identity.icon); - indicator.style.listStyleImage = ""; - - return this._restyleTab(tab); - }, - - _restyleTab(tab) { - if (!tab) { - return Promise.resolve(null); - } - const userContextId = ContainerService._getUserContextIdFromTab(tab); - const identity = ContextualIdentityProxy.getIdentityFromId(userContextId); - if (!identity) { - return Promise.resolve(null); - } - return Promise.resolve(viewFor(tab).setAttribute("data-identity-color", identity.color)); - }, - - // Uninstallation - uninstall(reason) { - const data = ss.storage.savedConfiguration; - if (!data) { - throw new DOMError("ERROR - No saved configuration!!"); - } - - if (data.version !== 1) { - throw new DOMError("ERROR - Unknown version!!"); - } - - if (reason !== "upgrade") { - PREFS.forEach(pref => { - if (pref[0] in data.prefs) { - prefService.set(pref[0], data.prefs[pref[0]]); - } - }); - } - - // Note: We cannot go back renaming the Finance identity back to Banking: - // the locale system doesn't work with renamed containers. - - // Restore the customizable container panel. - const widget = CustomizableWidgets.find(widget => widget.id === "containers-panelmenu"); - if (widget) { - CustomizableUI.createWidget(widget); - } - - for (let window of windows.browserWindows) { // eslint-disable-line prefer-const - // Let's close all the container tabs. - // Note: We cannot use _closeTabs() because at this point tab.window is - // null. - if (!this._containerWasEnabled && reason !== "upgrade") { - for (let tab of window.tabs) { // eslint-disable-line prefer-const - if (this._getUserContextIdFromTab(tab)) { - tab.close(); - try { - SessionStore.forgetClosedTab(viewFor(window), 0); - } catch(e) {} // eslint-disable-line no-empty - } - } - } - - this._getOrCreateContainerWindow(viewFor(window)).shutdown(); - } - - // all the configuration must go away now. - this._windowMap = new Map(); - - if (reason !== "upgrade") { - // Let's forget all the previous closed tabs. - this._forgetIdentity(); - - this._resetContainerToCentralIcons(); - - const preInstalledIdentities = data.preInstalledIdentities; - ContextualIdentityProxy.getIdentities().forEach(identity => { - if (!preInstalledIdentities.includes(identity.userContextId)) { - ContextualIdentityService.remove(identity.userContextId); - } else { - // Let's cleanup all the cookies for this container. - Services.obs.notifyObservers(null, "clear-origin-attributes-data", - JSON.stringify({ userContextId: identity.userContextId })); - } - }); - - // Let's delete the configuration. - delete ss.storage.savedConfiguration; - } - - // Begin-Of-Hack - if (this._oldGetIdentityFromId) { - ContextualIdentityService.getIdentityFromId = this._oldGetIdentityFromId; - } - - if (this._oldGetPublicIdentityFromId) { - ContextualIdentityService.getPublicIdentityFromId = this._oldGetPublicIdentityFromId; - } - // End-Of-Hack - }, - - forgetIdentityAndRefresh(args) { - this._forgetIdentity(args.userContextId); - return this.refreshNeeded(); - }, - - _forgetIdentity(userContextId = 0) { - for (let window of windows.browserWindows) { // eslint-disable-line prefer-const - window = viewFor(window); - const closedTabData = JSON.parse(SessionStore.getClosedTabData(window)); - for (let i = closedTabData.length - 1; i >= 0; --i) { - if (!closedTabData[i].state.userContextId) { - continue; - } - - if (userContextId === 0 || - closedTabData[i].state.userContextId === userContextId) { - try { - SessionStore.forgetClosedTab(window, i); - } catch(e) {} // eslint-disable-line no-empty - } - } - } - }, -}; - -// ---------------------------------------------------------------------------- -// ContainerWindow - -// This object is used to configure a single window. -function ContainerWindow(window) { - this._init(window); -} - -ContainerWindow.prototype = { - _window: null, - _style: null, - _panelElement: null, - _timeoutStore: new Map(), - _elementCache: new Map(), - _tooltipCache: new Map(), - _tabsElement: null, - - _init(window) { - this._window = window; - this._tabsElement = this._window.document.getElementById("tabbrowser-tabs"); - this._style = Style({ uri: self.data.url("usercontext.css") }); - this._plusButton = this._window.document.getAnonymousElementByAttribute(this._tabsElement, "anonid", "tabs-newtab-button"); - this._overflowPlusButton = this._window.document.getElementById("new-tab-button"); - - // Only hack the normal plus button as the alltabs is done elsewhere - this.attachMenuEvent("plus-button", this._plusButton); - - attachTo(this._style, this._window); - }, - - attachMenuEvent(source, button) { - const popup = button.querySelector(".new-tab-popup"); - popup.addEventListener("popupshown", () => { - popup.querySelector("menuseparator").remove(); - const popupMenuItems = [...popup.querySelectorAll("menuitem")]; - popupMenuItems.forEach((item) => { - const userContextId = item.getAttribute("data-usercontextid"); - if (!userContextId) { - item.remove(); - } - item.setAttribute("command", ""); - item.addEventListener("command", (e) => { - e.stopPropagation(); - e.preventDefault(); - ContainerService.openTab({ - userContextId: userContextId, - source: source - }); - }); - }); - }); - }, - - configure() { - return Promise.all([ - this._configureActiveTab(), - this._configureFileMenu(), - this._configureAllTabsMenu(), - this._configureTabStyle(), - this.configureActive(), - ]); - }, - - configureActive() { - return this._configureContextMenu(); - }, - - _configureTabStyle() { - const promises = []; - for (let tab of modelFor(this._window).tabs) { // eslint-disable-line prefer-const - promises.push(ContainerService._restyleTab(tab)); - } - return Promise.all(promises); - }, - - _configureActiveTab() { - const tab = modelFor(this._window).tabs.activeTab; - return ContainerService._restyleActiveTab(tab); - }, - - _configureFileMenu() { - return this._configureMenu("menu_newUserContext", null, e => { - const userContextId = parseInt(e.target.getAttribute("data-usercontextid"), 10); - ContainerService.openTab({ - userContextId: userContextId, - source: "file-menu" - }); - }); - }, - - _configureAllTabsMenu() { - return this._configureMenu("alltabs_containersTab", null, e => { - const userContextId = parseInt(e.target.getAttribute("data-usercontextid"), 10); - ContainerService.showTabs({ - userContextId, - nofocus: true, - window: this._window, - }).then(() => { - return ContainerService.openTab({ - userContextId, - source: "alltabs-menu" - }); - }).catch(() => {}); - }); - }, - - _configureContextMenu() { - return Promise.all([ - this._configureMenu("context-openlinkinusercontext-menu", - () => { - // This userContextId is what we want to exclude. - const tab = modelFor(this._window).tabs.activeTab; - return ContainerService._getUserContextIdFromTab(tab); - }, - e => { - // This is a super internal method. Hopefully it will be stable in the - // next FF releases. - this._window.gContextMenu.openLinkInTab(e); - - const userContextId = parseInt(e.target.getAttribute("data-usercontextid"), 10); - ContainerService.showTabs({ - userContextId, - nofocus: true, - window: this._window, - }); - } - ), - this._configureContextMenuOpenLink(), - ]); - }, - - _configureContextMenuOpenLink() { - return new Promise(resolve => { - const self = this; - this._window.gSetUserContextIdAndClick = function(event) { - const tab = modelFor(self._window).tabs.activeTab; - const userContextId = ContainerService._getUserContextIdFromTab(tab); - event.target.setAttribute("data-usercontextid", userContextId); - self._window.gContextMenu.openLinkInTab(event); - }; - - let item = this._window.document.getElementById("context-openlinkincontainertab"); - item.setAttribute("oncommand", "gSetUserContextIdAndClick(event)"); - - item = this._window.document.getElementById("context-openlinkintab"); - item.setAttribute("oncommand", "gSetUserContextIdAndClick(event)"); - - resolve(); - }); - }, - - // Generic menu configuration. - _configureMenu(menuId, excludedContainerCb, clickCb) { - const menu = this._window.document.getElementById(menuId); - if (!this._disableElement(menu)) { - // Delete stale menu that isn't native elements - while (menu.firstChild) { - menu.removeChild(menu.firstChild); - } - } - - const menupopup = this._window.document.createElementNS(XUL_NS, "menupopup"); - menu.appendChild(menupopup); - - menupopup.addEventListener("command", clickCb); - return this._createMenu(menupopup, excludedContainerCb); - }, - - _createMenu(target, excludedContainerCb) { - while (target.hasChildNodes()) { - target.removeChild(target.firstChild); - } - - return new Promise((resolve, reject) => { - ContainerService.queryIdentities().then(identities => { - const fragment = this._window.document.createDocumentFragment(); - - const excludedUserContextId = excludedContainerCb ? excludedContainerCb() : 0; - if (excludedUserContextId) { - const bundle = this._window.document.getElementById("bundle_browser"); - - const menuitem = this._window.document.createElementNS(XUL_NS, "menuitem"); - menuitem.setAttribute("data-usercontextid", "0"); - menuitem.setAttribute("label", bundle.getString("userContextNone.label")); - menuitem.setAttribute("accesskey", bundle.getString("userContextNone.accesskey")); - - fragment.appendChild(menuitem); - - const menuseparator = this._window.document.createElementNS(XUL_NS, "menuseparator"); - fragment.appendChild(menuseparator); - } - - identities.forEach(identity => { - if (identity.userContextId === excludedUserContextId) { - return; - } - - const menuitem = this._window.document.createElementNS(XUL_NS, "menuitem"); - menuitem.setAttribute("label", identity.name); - menuitem.classList.add("menuitem-iconic"); - menuitem.setAttribute("data-usercontextid", identity.userContextId); - menuitem.setAttribute("data-identity-color", identity.color); - menuitem.setAttribute("data-identity-icon", identity.icon); - fragment.appendChild(menuitem); - }); - - target.appendChild(fragment); - resolve(); - }).catch(() => {reject();}); - }); - }, - - // This timer is used to hide the panel auto-magically if it's not used in - // the following X seconds. This is need to avoid the leaking of the panel - // when the mouse goes out of of the 'plus' button. - _createTimeout(key, callback, timeoutTime) { - this._cleanTimeout(key); - this._timeoutStore.set(key, this._window.setTimeout(() => { - callback(); - this._timeoutStore.delete(key); - }, timeoutTime)); - }, - - _cleanAllTimeouts() { - for (let key of this._timeoutStore.keys()) { // eslint-disable-line prefer-const - this._cleanTimeout(key); - } - }, - - _cleanTimeout(key) { - if (this._timeoutStore.has(key)) { - this._window.clearTimeout(this._timeoutStore.get(key)); - this._timeoutStore.delete(key); - } - }, - - shutdown() { - // CSS must be removed. - detachFrom(this._style, this._window); - - this._shutdownFileMenu(); - this._shutdownAllTabsMenu(); - this._shutdownContextMenu(); - }, - - _shutDownPlusButtonMenuElement(buttonElement) { - if (buttonElement) { - this._shutdownElement(buttonElement); - buttonElement.setAttribute("tooltip", this._tooltipCache.get(buttonElement)); - - buttonElement.removeEventListener("mouseover", this); - buttonElement.removeEventListener("click", this); - buttonElement.removeEventListener("mouseout", this); - } - }, - - _shutdownFileMenu() { - this._shutdownMenu("menu_newUserContext"); - }, - - _shutdownAllTabsMenu() { - this._shutdownMenu("alltabs_containersTab"); - }, - - _shutdownContextMenu() { - this._shutdownMenu("context-openlinkinusercontext-menu"); - }, - - _shutdownMenu(menuId) { - const menu = this._window.document.getElementById(menuId); - this._shutdownElement(menu); - }, - - _shutdownElement(element) { - // Let's remove our elements. - while (element.firstChild) { - element.firstChild.remove(); - } - - const elementCache = this._elementCache.get(element); - if (elementCache) { - for (let e of elementCache) { // eslint-disable-line prefer-const - element.appendChild(e); - } - } - }, - - _disableElement(element) { - // Nothing to disable. - if (!element || this._elementCache.has(element)) { - return false; - } - const cacheArray = []; - - // Let's store the previous elements so that we can repopulate it in case - // the addon is uninstalled. - while (element.firstChild) { - cacheArray.push(element.removeChild(element.firstChild)); - } - - this._elementCache.set(element, cacheArray); - - return true; - }, - - _resetContainerToCentralIcons() { - ContextualIdentityProxy.getIdentities().forEach(identity => { - if (IDENTITY_ICONS_STANDARD.indexOf(identity.icon) !== -1 && - IDENTITY_COLORS_STANDARD.indexOf(identity.color) !== -1) { - return; - } - - if (IDENTITY_ICONS_STANDARD.indexOf(identity.icon) === -1) { - if (identity.userContextId <= IDENTITY_ICONS_STANDARD.length) { - identity.icon = IDENTITY_ICONS_STANDARD[identity.userContextId - 1]; - } else { - identity.icon = IDENTITY_ICONS_STANDARD[0]; - } - } - - if (IDENTITY_COLORS_STANDARD.indexOf(identity.color) === -1) { - if (identity.userContextId <= IDENTITY_COLORS_STANDARD.length) { - identity.color = IDENTITY_COLORS_STANDARD[identity.userContextId - 1]; - } else { - identity.color = IDENTITY_COLORS_STANDARD[0]; - } - } - - ContextualIdentityService.update(identity.userContextId, - identity.name, - identity.icon, - identity.color); - }); - } -}; - -// uninstall/install events --------------------------------------------------- - -exports.main = function (options) { - const installation = options.loadReason === "install" || - options.loadReason === "downgrade" || - options.loadReason === "enable" || - options.loadReason === "upgrade"; - - // Let's start :) - ContainerService.init(installation, options.loadReason); -}; - -exports.onUnload = function (reason) { - if (reason === "disable" || - reason === "downgrade" || - reason === "uninstall" || - reason === "upgrade") { - ContainerService.uninstall(reason); - } -}; diff --git a/install.rdf b/install.rdf new file mode 100644 index 00000000..a7434ecc --- /dev/null +++ b/install.rdf @@ -0,0 +1,22 @@ + + + + @testpilot-containers + 2 + true + true + true + Testpilot containers + + + {ec8030f7-c20a-464f-9b0e-13a3a9e97384} + 51.0a1 + * + + + 3.1.0 + false + + + diff --git a/package.json b/package.json index 85177f03..fd09b9f1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "testpilot-containers", "title": "Containers Experiment", "description": "Containers works by isolating cookie jars using separate origin-attributes defined visually by colored ‘Container Tabs’. This add-on is a modified version of the containers feature for Firefox Test Pilot.", - "version": "3.0.0", + "version": "3.1.0", "author": "Andrea Marchesini, Luke Crouch and Jonathan Kingston", "bugs": { "url": "https://github.com/mozilla/testpilot-containers/issues" @@ -24,17 +24,7 @@ "stylelint-order": "^0.3.0", "testpilot-metrics": "^2.1.0" }, - "engines": { - "firefox": ">=51.0" - }, - "permissions": { - "multiprocess": true - }, - "hasEmbeddedWebExtension": true, "homepage": "https://github.com/mozilla/testpilot-containers#readme", - "keywords": [ - "jetpack" - ], "license": "MPL-2.0", "main": "index.js", "repository": { diff --git a/webextension/js/.eslintrc.js b/webextension/js/.eslintrc.js index 620e9c63..801dad5a 100644 --- a/webextension/js/.eslintrc.js +++ b/webextension/js/.eslintrc.js @@ -8,7 +8,6 @@ module.exports = { "backgroundLogic": true, "identityState": true, "messageHandler": true, - "tabPageCounter": true, - "themeManager": true + "tabPageCounter": true } }; diff --git a/webextension/js/background/index.html b/webextension/js/background/index.html index 2a2924d3..cd0021e4 100644 --- a/webextension/js/background/index.html +++ b/webextension/js/background/index.html @@ -12,7 +12,6 @@ "js/background/identityState.js", "js/background/messageHandler.js", "js/background/tabPageCounter.js", - "js/background/themeManager.js", "js/backdround/init.js" ] --> @@ -22,7 +21,6 @@ - diff --git a/webextension/js/background/messageHandler.js b/webextension/js/background/messageHandler.js index 5a73d2b7..4f96891a 100644 --- a/webextension/js/background/messageHandler.js +++ b/webextension/js/background/messageHandler.js @@ -80,9 +80,6 @@ const messageHandler = { const port = browser.runtime.connect(); port.onMessage.addListener(m => { switch (m.type) { - case "lightweight-theme-changed": - themeManager.update(m.message); - break; case "open-tab": backgroundLogic.openTab(m.message); break; diff --git a/webextension/js/background/themeManager.js b/webextension/js/background/themeManager.js deleted file mode 100644 index b77ade63..00000000 --- a/webextension/js/background/themeManager.js +++ /dev/null @@ -1,51 +0,0 @@ -const THEME_BUILD_DATE = 20170630; -const themeManager = { - existingTheme: null, - disabled: false, - async init() { - const browserInfo = await browser.runtime.getBrowserInfo(); - if (Number(browserInfo.buildID.substring(0, 8)) >= THEME_BUILD_DATE) { - this.disabled = true; - } else { - this.check(); - } - }, - setPopupIcon(theme) { - if (this.disabled) { - return; - } - let icons = { - 16: "img/container-site-d-24.png", - 32: "img/container-site-d-48.png" - }; - if (theme === "firefox-compact-dark@mozilla.org") { - icons = { - 16: "img/container-site-w-24.png", - 32: "img/container-site-w-48.png" - }; - } - browser.browserAction.setIcon({ - path: icons - }); - }, - check() { - if (this.disabled) { - return; - } - browser.runtime.sendMessage({ - method: "getTheme" - }).then((theme) => { - this.update(theme); - }).catch(() => { - throw new Error("Unable to get theme"); - }); - }, - update(theme) { - if (this.existingTheme !== theme) { - this.setPopupIcon(theme); - this.existingTheme = theme; - } - } -}; - -themeManager.init(); diff --git a/webextension/js/popup.js b/webextension/js/popup.js index b658e8c6..a68f19a0 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -150,13 +150,18 @@ const Logic = { }, async identity(cookieStoreId) { - const identity = await browser.contextualIdentities.get(cookieStoreId); - return identity || { + const defaultContainer = { name: "Default", cookieStoreId, icon: "default-tab", color: "default-tab" }; + // Handle old style rejection with null and also Promise.reject new style + try { + return await browser.contextualIdentities.get(cookieStoreId) || defaultContainer; + } catch(e) { + return defaultContainer; + } }, addEnterHandler(element, handler) { diff --git a/webextension/manifest.json b/webextension/manifest.json index 489cd1f7..1c080b8c 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Containers Experiment", - "version": "3.0.0", + "version": "3.1.0", "description": "Containers works by isolating cookie jars using separate origin-attributes defined visually by colored ‘Container Tabs’. This add-on is a modified version of the containers feature for Firefox Test Pilot.", "icons": { From 57a31f7f97c699b35566c7a880e5febf758d497e Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Tue, 15 Aug 2017 17:30:27 +0100 Subject: [PATCH 2/2] Remove legacy telemetry code as non functional now --- .eslintignore | 2 - .eslintrc.js | 7 +- README.md | 22 +- bootstrap.js | 22 +- lib/shield/event-target.js | 55 --- lib/shield/index.js | 428 ------------------ lib/testpilot/experiment.js | 95 ---- package.json | 6 +- study.js | 40 -- testpilot-metrics.js | 336 -------------- webextension/js/background/assignManager.js | 12 - webextension/js/background/backgroundLogic.js | 65 --- webextension/js/background/messageHandler.js | 6 - webextension/js/background/tabPageCounter.js | 10 - webextension/js/confirm-page.js | 8 - webextension/js/popup.js | 24 +- 16 files changed, 32 insertions(+), 1106 deletions(-) delete mode 100644 lib/shield/event-target.js delete mode 100644 lib/shield/index.js delete mode 100644 lib/testpilot/experiment.js delete mode 100644 study.js delete mode 100644 testpilot-metrics.js diff --git a/.eslintignore b/.eslintignore index 4ced8a0d..9f1953cb 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1 @@ -testpilot-metrics.js -lib/shield/*.js lib/testpilot/*.js diff --git a/.eslintrc.js b/.eslintrc.js index d9f72703..1b0e9060 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -13,7 +13,12 @@ module.exports = { "CustomizableUI": true, "CustomizableWidgets": true, "SessionStore": true, - "Services": true + "Services": true, + "Components": true, + "XPCOMUtils": true, + "OS": true, + "ADDON_UNINSTALL": true, + "ADDON_DISABLE": true }, "plugins": [ "promise", diff --git a/README.md b/README.md index 027dc58f..8ae8c434 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,17 @@ Release & Beta channels do not allow un-signed add-ons, even with the DevPrefs. 5. Click the gear, and select "Install Add-on From File..." 6. Select the `.xpi` file +#### Correct prefs + +Whilst this is still using legacy code to test you will need the following in your profile: + + +Change the following prefs in about:config: + +- extensions.legacy.enabled = true +- xpinstall.signatures.required = false + + #### Run the TxP experiment with `jpm` 1. `git clone git@github.com:mozilla/testpilot-containers.git` @@ -49,23 +60,12 @@ Release & Beta channels do not allow un-signed add-ons, even with the DevPrefs. Check out the [Browser Toolbox](https://developer.mozilla.org/en-US/docs/Tools/Browser_Toolbox) for more information about debugging add-on code. -#### Run the shield study with `shield` - -1. `git clone git@github.com:mozilla/testpilot-containers.git` -2. `cd testpilot-containers` -3. `npm install` -4. `npm install -g shield-study-cli` -5. `shield run . -- --binary Nightly` - ### Building .xpi To build a local testpilot-containers.xpi, use the plain [`jpm xpi`](https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/jpm#jpm_xpi) command, or run `npm run build`. -#### Building a shield .xpi -To build a local shield-study-containers.xpi, run `npm run build-shield`. - ### Signing an .xpi To sign an .xpi, use [`jpm diff --git a/bootstrap.js b/bootstrap.js index 9fcf037e..64e89f02 100644 --- a/bootstrap.js +++ b/bootstrap.js @@ -22,12 +22,11 @@ const PREFS = [ type: "bool" }, ]; -const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -const { TextDecoder, TextEncoder } = Cu.import('resource://gre/modules/commonjs/toolkit/loader.js', {}); +const { TextDecoder, TextEncoder } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {}); XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); @@ -36,7 +35,7 @@ const JETPACK_DIR_BASENAME = "jetpack"; const EXTENSION_ID = "@testpilot-containers"; function filename() { - let storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + const storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile); storeFile.append(JETPACK_DIR_BASENAME); storeFile.append(EXTENSION_ID); storeFile.append("simple-storage"); @@ -46,7 +45,7 @@ function filename() { async function getConfig() { const bytes = await OS.File.read(filename()); - let raw = new TextDecoder().decode(bytes) || ""; + const raw = new TextDecoder().decode(bytes) || ""; let savedConfig = {savedConfiguration: {}}; if (raw) { savedConfig = JSON.parse(raw); @@ -69,7 +68,7 @@ async function initConfig() { }); } const serialized = JSON.stringify(savedConfig); - let bytes = new TextEncoder().encode(serialized) || ""; + const bytes = new TextEncoder().encode(serialized) || ""; await OS.File.writeAtomic(filename(), bytes, { }); } @@ -83,14 +82,16 @@ function setPrefs() { }); } +// eslint-disable-next-line no-unused-vars async function install() { await initConfig(); setPrefs(); } +// eslint-disable-next-line no-unused-vars async function uninstall(aData, aReason) { - if (aReason == ADDON_UNINSTALL - || aReason == ADDON_DISABLE) { + if (aReason === ADDON_UNINSTALL + || aReason === ADDON_DISABLE) { const config = await getConfig(); const storedPrefs = config.savedConfiguration.prefs; PREFS.forEach((pref) => { @@ -105,14 +106,15 @@ async function uninstall(aData, aReason) { } } +// eslint-disable-next-line no-unused-vars function startup({webExtension}) { // Reset prefs that may have changed, or are legacy setPrefs(); // Start the embedded webextension. - webExtension.startup().then(api => { - }); + webExtension.startup(); } -function shutdown(data) { +// eslint-disable-next-line no-unused-vars +function shutdown() { } diff --git a/lib/shield/event-target.js b/lib/shield/event-target.js deleted file mode 100644 index 4335d8c9..00000000 --- a/lib/shield/event-target.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Drop-in replacement for {@link external:sdk/event/target.EventTarget} for use - * with es6 classes. - * @module event-target - * @author Martin Giger - * @license MPL-2.0 - */ - /** - * An SDK class that add event reqistration methods - * @external sdk/event/target - * @requires sdk/event/target - */ -/** - * @class EventTarget - * @memberof external:sdk/event/target - * @see {@link https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/event_target#EventTarget} - */ - -// slightly modified from: https://raw.githubusercontent.com/freaktechnik/justintv-stream-notifications/master/lib/event-target.js - -"use strict"; - -const { on, once, off, setListeners } = require("sdk/event/core"); - -/* istanbul ignore next */ -/** - * @class - */ -class EventTarget { - constructor(options) { - setListeners(this, options); - } - - on(...args) { - on(this, ...args); - return this; - } - - once(...args) { - once(this, ...args); - return this; - } - - off(...args) { - off(this, ...args); - return this; - } - - removeListener(...args) { - off(this, ...args); - return this; - } -} - -exports.EventTarget = EventTarget; diff --git a/lib/shield/index.js b/lib/shield/index.js deleted file mode 100644 index 9d4ec95b..00000000 --- a/lib/shield/index.js +++ /dev/null @@ -1,428 +0,0 @@ -"use strict"; - -// Chrome privileged -const {Cu} = require("chrome"); -const { Services } = Cu.import("resource://gre/modules/Services.jsm"); -const { TelemetryController } = Cu.import("resource://gre/modules/TelemetryController.jsm"); -const CID = Cu.import("resource://gre/modules/ClientID.jsm"); - -// sdk -const { merge } = require("sdk/util/object"); -const querystring = require("sdk/querystring"); -const { prefs } = require("sdk/simple-prefs"); -const prefSvc = require("sdk/preferences/service"); -const { setInterval } = require("sdk/timers"); -const tabs = require("sdk/tabs"); -const { URL } = require("sdk/url"); - -const { EventTarget } = require("./event-target"); -const { emit } = require("sdk/event/core"); -const self = require("sdk/self"); - -const DAY = 86400*1000; - -// ongoing within-addon fuses / timers -let lastDailyPing = Date.now(); - -/* Functional, self-contained utils */ - -// equal probability choices from a list "choices" -function chooseVariation(choices,rng=Math.random()) { - let l = choices.length; - return choices[Math.floor(l*Math.random())]; -} - -function dateToUTC(date) { - return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds()); -} - -function generateTelemetryIdIfNeeded() { - let id = TelemetryController.clientID; - /* istanbul ignore next */ - if (id == undefined) { - return CID.ClientIDImpl._doLoadClientID() - } else { - return Promise.resolve(id) - } -} - -function userId () { - return prefSvc.get("toolkit.telemetry.cachedClientID","unknown"); -} - -var Reporter = new EventTarget().on("report", - (d) => prefSvc.get('shield.debug') && console.log("report",d) -); - -function report(data, src="addon", bucket="shield-study") { - data = merge({}, data , { - study_version: self.version, - about: { - _src: src, - _v: 2 - } - }); - if (prefSvc.get('shield.testing')) data.testing = true - - emit(Reporter, "report", data); - let telOptions = {addClientId: true, addEnvironment: true} - return TelemetryController.submitExternalPing(bucket, data, telOptions); -} - -function survey (url, queryArgs={}) { - if (! url) return - - let U = new URL(url); - let q = U.search; - if (q) { - url = U.href.split(q)[0]; - q = querystring.parse(querystring.unescape(q.slice(1))); - } else { - q = {}; - } - // get user info. - let newArgs = merge({}, - q, - queryArgs - ); - let searchstring = querystring.stringify(newArgs); - url = url + "?" + searchstring; - return url; -} - - -function setOrGetFirstrun () { - let firstrun = prefs["shield.firstrun"]; - if (firstrun === undefined) { - firstrun = prefs["shield.firstrun"] = String(dateToUTC(new Date())) // in utc, user set - } - return Number(firstrun) -} - -function reuseVariation (choices) { - return prefs["shield.variation"]; -} - -function setVariation (choice) { - prefs["shield.variation"] = choice - return choice -} - -function die (addonId=self.id) { - /* istanbul ignore else */ - if (prefSvc.get("shield.fakedie")) return; - /* istanbul ignore next */ - require("sdk/addon/installer").uninstall(addonId); -} - -// TODO: GRL vulnerable to clock time issues #1 -function expired (xconfig, now = Date.now() ) { - return ((now - Number(xconfig.firstrun))/ DAY) > xconfig.days; -} - -function resetShieldPrefs () { - delete prefs['shield.firstrun']; - delete prefs['shield.variation']; -} - -function cleanup () { - prefSvc.keys(`extensions.${self.preferencesBranch}`).forEach ( - (p) => { - delete prefs[p]; - }) -} - -function telemetrySubset (xconfig) { - return { - study_name: xconfig.name, - branch: xconfig.variation, - } -} - -class Study extends EventTarget { - constructor (config) { - super(); - this.config = merge({ - name: self.addonId, - variations: {'observe-only': () => {}}, - surveyUrls: {}, - days: 7 - },config); - - this.config.firstrun = setOrGetFirstrun(); - - let variation = reuseVariation(); - if (variation === undefined) { - variation = this.decideVariation(); - if (!(variation in this.config.variations)) { - // chaijs doesn't think this is an instanceof Error - // freaktechnik and gregglind debugged for a while. - // sdk errors might not be 'Errors' or chai is wack, who knows. - // https://dxr.mozilla.org/mozilla-central/search?q=regexp%3AError%5Cs%3F(%3A%7C%3D)+path%3Aaddon-sdk%2Fsource%2F&redirect=false would list - throw new Error("Study Error: chosen variation must be in config.variations") - } - setVariation(variation); - } - this.config.variation = variation; - - this.flags = { - ineligibleDie: undefined - }; - this.states = []; - // all these work, but could be cleaner. I hate the `bind` stuff. - this.on( - "change", (function (newstate) { - prefSvc.get('shield.debug') && console.log(newstate, this.states); - this.states.push(newstate); - emit(this, newstate); // could have checks here. - }).bind(this) - ) - this.on( - "starting", (function () { - this.changeState("modifying"); - }).bind(this) - ) - this.on( - "maybe-installing", (function () { - if (!this.isEligible()) { - this.changeState("ineligible-die"); - } else { - this.changeState("installed") - } - }).bind(this) - ) - this.on( - "ineligible-die", (function () { - try {this.whenIneligible()} catch (err) {/*ok*/} finally { /*ok*/ } - this.flags.ineligibleDie = true; - this.report(merge({}, telemetrySubset(this.config), {study_state: "ineligible"}), "shield"); - this.final(); - die(); - }).bind(this) - ) - this.on( - "installed", (function () { - try {this.whenInstalled()} catch (err) {/*ok*/} finally { /*ok*/ } - this.report(merge({}, telemetrySubset(this.config), {study_state: "install"}), "shield"); - this.changeState("modifying"); - }).bind(this) - ) - this.on( - "modifying", (function () { - var mybranchname = this.variation; - this.config.variations[mybranchname](); // do the effect - this.changeState("running"); - }).bind(this) - ) - this.on( // the one 'many' - "running", (function () { - // report success - this.report(merge({}, telemetrySubset(this.config), {study_state: "running"}), "shield"); - this.final(); - }).bind(this) - ) - this.on( - "normal-shutdown", (function () { - this.flags.dying = true; - this.report(merge({}, telemetrySubset(this.config), {study_state: "shutdown"}), "shield"); - this.final(); - }).bind(this) - ) - this.on( - "end-of-study", (function () { - if (this.flags.expired) { // safe to call multiple times - this.final(); - return; - } else { - // first time seen. - this.flags.expired = true; - try {this.whenComplete()} catch (err) { /*ok*/ } finally { /*ok*/ } - this.report(merge({}, telemetrySubset(this.config) ,{study_state: "end-of-study"}), "shield"); - // survey for end of study - let that = this; - generateTelemetryIdIfNeeded().then(()=>that.showSurvey("end-of-study")); - try {this.cleanup()} catch (err) {/*ok*/} finally { /*ok*/ } - this.final(); - die(); - } - }).bind(this) - ) - this.on( - "user-uninstall-disable", (function () { - if (this.flags.dying) { - this.final(); - return; - } - this.flags.dying = true; - this.report(merge({}, telemetrySubset(this.config), {study_state: "user-ended-study"}), "shield"); - let that = this; - generateTelemetryIdIfNeeded().then(()=>that.showSurvey("user-ended-study")); - try {this.cleanup()} catch (err) {/*ok*/} finally { /*ok*/ } - this.final(); - die(); - }).bind(this) - ) - } - - get state () { - let n = this.states.length; - return n ? this.states[n-1] : undefined - } - - get variation () { - return this.config.variation; - } - - get firstrun () { - return this.config.firstrun; - } - - dieIfExpired () { - let xconfig = this.config; - if (expired(xconfig)) { - emit(this, "change", "end-of-study"); - return true - } else { - return false - } - } - - alivenessPulse (last=lastDailyPing) { - // check for new day, phone home if true. - let t = Date.now(); - if ((t - last) >= DAY) { - lastDailyPing = t; - // phone home - emit(this,"change","running"); - } - // check expiration, and die with report if needed - return this.dieIfExpired(); - } - - changeState (newstate) { - emit(this,'change', newstate); - } - - final () { - emit(this,'final', {}); - } - - startup (reason) { - // https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Listening_for_load_and_unload - - // check expiry first, before anything, quit and die if so - - // check once, right away, short circuit both install and startup - // to prevent modifications from happening. - if (this.dieIfExpired()) return this - - switch (reason) { - case "install": - emit(this, "change", "maybe-installing"); - break; - - case "enable": - case "startup": - case "upgrade": - case "downgrade": - emit(this, "change", "starting"); - } - - if (! this._pulseTimer) this._pulseTimer = setInterval(this.alivenessPulse.bind(this), 5*60*1000 /*5 minutes */) - return this; - } - - shutdown (reason) { - // https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Listening_for_load_and_unload - if (this.flags.ineligibleDie || - this.flags.expired || - this.flags.dying - ) { return this } // special cases. - - switch (reason) { - case "uninstall": - case "disable": - emit(this, "change", "user-uninstall-disable"); - break; - - // 5. usual end of session. - case "shutdown": - case "upgrade": - case "downgrade": - emit(this, "change", "normal-shutdown") - break; - } - return this; - } - - cleanup () { - // do the simple prefs and simplestorage cleanup - // extend by extension - resetShieldPrefs(); - cleanup(); - } - - isEligible () { - return true; - } - - whenIneligible () { - // empty function unless overrided - } - - whenInstalled () { - // empty unless overrided - } - - whenComplete () { - // when the study expires - } - - /** - * equal choice from varations, by default. override to get unequal - */ - decideVariation (rng=Math.random()) { - return chooseVariation(Object.keys(this.config.variations), rng); - } - - get surveyQueryArgs () { - return { - variation: this.variation, - xname: this.config.name, - who: userId(), - updateChannel: Services.appinfo.defaultUpdateChannel, - fxVersion: Services.appinfo.version, - } - } - - showSurvey(reason) { - let partial = this.config.surveyUrls[reason]; - - let queryArgs = this.surveyQueryArgs; - queryArgs.reason = reason; - if (partial) { - let url = survey(partial, queryArgs); - tabs.open(url); - return url - } else { - return - } - } - - report () { // convenience only - return report.apply(null, arguments); - } -} - -module.exports = { - chooseVariation: chooseVariation, - die: die, - expired: expired, - generateTelemetryIdIfNeeded: generateTelemetryIdIfNeeded, - report: report, - Reporter: Reporter, - resetShieldPrefs: resetShieldPrefs, - Study: Study, - cleanup: cleanup, - survey: survey -} diff --git a/lib/testpilot/experiment.js b/lib/testpilot/experiment.js deleted file mode 100644 index 1b4e98f2..00000000 --- a/lib/testpilot/experiment.js +++ /dev/null @@ -1,95 +0,0 @@ -const { AddonManager } = require('resource://gre/modules/AddonManager.jsm'); -const { ClientID } = require('resource://gre/modules/ClientID.jsm'); -const Events = require('sdk/system/events'); -const { Services } = require('resource://gre/modules/Services.jsm'); -const { storage } = require('sdk/simple-storage'); -const { - TelemetryController -} = require('resource://gre/modules/TelemetryController.jsm'); -const { Request } = require('sdk/request'); - - -const EVENT_SEND_METRIC = 'testpilot::send-metric'; -const startTime = (Services.startup.getStartupInfo().process); - -function makeTimestamp(timestamp) { - return Math.round((timestamp - startTime) / 1000); -} - -function experimentPing(event) { - const timestamp = new Date(); - const { subject, data } = event; - let parsed; - try { - parsed = JSON.parse(data); - } catch (err) { - // eslint-disable-next-line no-console - return console.error(`Dropping bad metrics packet: ${err}`); - } - - AddonManager.getAddonByID(subject, addon => { - const payload = { - test: subject, - version: addon.version, - timestamp: makeTimestamp(timestamp), - variants: storage.experimentVariants && - subject in storage.experimentVariants - ? storage.experimentVariants[subject] - : null, - payload: parsed - }; - TelemetryController.submitExternalPing('testpilottest', payload, { - addClientId: true, - addEnvironment: true - }); - - // TODO: DRY up this ping centre code here and in lib/Telemetry. - const pcPing = TelemetryController.getCurrentPingData(); - pcPing.type = 'testpilot'; - pcPing.payload = payload; - const pcPayload = { - // 'method' is used by testpilot-metrics library. - // 'event' was used before that library existed. - event_type: parsed.event || parsed.method, - client_time: makeTimestamp(parsed.timestamp || timestamp), - addon_id: subject, - addon_version: addon.version, - firefox_version: pcPing.environment.build.version, - os_name: pcPing.environment.system.os.name, - os_version: pcPing.environment.system.os.version, - locale: pcPing.environment.settings.locale, - // Note: these two keys are normally inserted by the ping-centre client. - client_id: ClientID.getCachedClientID(), - topic: 'testpilot' - }; - // Add any other extra top-level keys = require(the payload, possibly including - // 'object' or 'category', among others. - Object.keys(parsed).forEach(f => { - // Ignore the keys we've already added to `pcPayload`. - const ignored = ['event', 'method', 'timestamp']; - if (!ignored.includes(f)) { - pcPayload[f] = parsed[f]; - } - }); - - const req = new Request({ - url: 'https://tiles.services.mozilla.com/v3/links/ping-centre', - contentType: 'application/json', - content: JSON.stringify(pcPayload) - }); - req.post(); - }); -} - -function Experiment() { - // If the user has @testpilot-addon, it already bound - // experimentPing to testpilot::send-metric, - // so we don't need to bind this one - AddonManager.getAddonByID('@testpilot-addon', addon => { - if (!addon) { - Events.on(EVENT_SEND_METRIC, experimentPing); - } - }); -} - -module.exports = Experiment; diff --git a/package.json b/package.json index fd09b9f1..e50ec0dc 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,9 @@ "jpm": "^1.2.2", "json": "^9.0.6", "npm-run-all": "^4.0.0", - "shield-studies-addon-utils": "^2.0.0", "stylelint": "^7.9.0", "stylelint-config-standard": "^16.0.0", - "stylelint-order": "^0.3.0", - "testpilot-metrics": "^2.1.0" + "stylelint-order": "^0.3.0" }, "homepage": "https://github.com/mozilla/testpilot-containers#readme", "license": "MPL-2.0", @@ -33,7 +31,6 @@ }, "scripts": { "build": "npm test && jpm xpi", - "build-shield": "npm test && npm run package-shield", "deploy": "deploy-txp", "lint": "npm-run-all lint:*", "lint:addon": "addons-linter webextension --self-hosted", @@ -41,7 +38,6 @@ "lint:html": "htmllint webextension/*.html", "lint:js": "eslint .", "package": "npm run build && mv testpilot-containers.xpi addon.xpi", - "package-shield": "./node_modules/.bin/json -I -f package.json -e 'this.name=\"shield-study-containers\"' && jpm xpi && ./node_modules/.bin/json -I -f package.json -e 'this.name=\"testpilot-containers\"'", "test": "npm run lint" }, "updateURL": "https://testpilot.firefox.com/files/@testpilot-containers/updates.json" diff --git a/study.js b/study.js deleted file mode 100644 index df595555..00000000 --- a/study.js +++ /dev/null @@ -1,40 +0,0 @@ -/* 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/. */ - -const self = require("sdk/self"); -const { when: unload } = require("sdk/system/unload"); - -const shield = require("./lib/shield/index"); - -const surveyUrl = "https://www.surveygizmo.com/s3/3621810/shield-txp-containers"; - -const studyConfig = { - name: self.addonId, - days: 28, - surveyUrls: { - "end-of-study": surveyUrl, - "user-ended-study": surveyUrl, - ineligible: null, - }, - variations: { - "control": () => {}, - "securityOnboarding": () => {} - } -}; - -class ContainersStudy extends shield.Study { - isEligible () { - // If the user already has testpilot-containers extension, they are in the - // Test Pilot experiment, so exclude them. - return super.isEligible(); - } -} - -const thisStudy = new ContainersStudy(studyConfig); - -if (self.id === "@shield-study-containers") { - unload((reason) => thisStudy.shutdown(reason)); -} - -exports.study = thisStudy; diff --git a/testpilot-metrics.js b/testpilot-metrics.js deleted file mode 100644 index 29148843..00000000 --- a/testpilot-metrics.js +++ /dev/null @@ -1,336 +0,0 @@ -// 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/. -const Experiment = require('./lib/testpilot/experiment'); - -const experiment = new Experiment(); - -/** - * 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 diff --git a/webextension/js/background/assignManager.js b/webextension/js/background/assignManager.js index e356c6dc..72d1dc28 100644 --- a/webextension/js/background/assignManager.js +++ b/webextension/js/background/assignManager.js @@ -234,10 +234,6 @@ const assignManager = { browser.tabs.sendMessage(tabId, { text: `Successfully ${actionName} site to always open in this container` }); - backgroundLogic.sendTelemetryPayload({ - event: `${actionName}-container-assignment`, - userContextId: userContextId, - }); const tab = await browser.tabs.get(tabId); this.calculateContextMenu(tab); }, @@ -298,15 +294,7 @@ const assignManager = { // If the user has explicitly checked "Never Ask Again" on the warning page we will send them straight there if (neverAsk) { browser.tabs.create({url, cookieStoreId, index}); - backgroundLogic.sendTelemetryPayload({ - event: "auto-reload-page-in-container", - userContextId: userContextId, - }); } else { - backgroundLogic.sendTelemetryPayload({ - event: "prompt-to-reload-page-in-container", - userContextId: userContextId, - }); let confirmUrl = `${loadPage}?url=${encodeURIComponent(url)}&cookieStoreId=${cookieStoreId}`; let currentCookieStoreId; if (currentUserContextId) { diff --git a/webextension/js/background/backgroundLogic.js b/webextension/js/background/backgroundLogic.js index 42ee3828..b0d03553 100644 --- a/webextension/js/background/backgroundLogic.js +++ b/webextension/js/background/backgroundLogic.js @@ -26,11 +26,6 @@ const backgroundLogic = { }, async deleteContainer(userContextId) { - this.sendTelemetryPayload({ - event: "delete-container", - userContextId - }); - await this._closeTabs(userContextId); await browser.contextualIdentities.remove(this.cookieStoreId(userContextId)); assignManager.deleteContainer(userContextId); @@ -47,15 +42,8 @@ const backgroundLogic = { this.cookieStoreId(options.userContextId), options.params ); - this.sendTelemetryPayload({ - event: "edit-container", - userContextId: options.userContextId - }); } else { donePromise = browser.contextualIdentities.create(options.params); - this.sendTelemetryPayload({ - event: "add-container" - }); } await donePromise; browser.runtime.sendMessage({ @@ -67,18 +55,8 @@ const backgroundLogic = { let url = options.url || undefined; const userContextId = ("userContextId" in options) ? options.userContextId : 0; const active = ("nofocus" in options) ? options.nofocus : true; - const source = ("source" in options) ? options.source : null; const cookieStoreId = backgroundLogic.cookieStoreId(userContextId); - // Only send telemetry for tabs opened by UI - i.e., not via showTabs - if (source && userContextId) { - this.sendTelemetryPayload({ - "event": "open-tab", - "eventSource": source, - "userContextId": userContextId, - "clickedContainerTabCount": await identityState.containerTabCount(cookieStoreId) - }); - } // Autofocus url bar will happen in 54: https://bugzilla.mozilla.org/show_bug.cgi?id=1295072 // We can't open new tab pages, so open a blank tab. Used in tab un-hide @@ -131,12 +109,6 @@ const backgroundLogic = { return null; } - this.sendTelemetryPayload({ - "event": "move-tabs-to-window", - "userContextId": userContextId, - "clickedContainerTabCount": identityState.containerTabCount(userContextId), - }); - const list = await identityState._matchTabsByContainer(options.cookieStoreId); const containerState = await identityState.storageArea.get(options.cookieStoreId); @@ -199,13 +171,6 @@ const backgroundLogic = { }, async sortTabs() { - const containersCounts = identityState.containersCounts(); - this.sendTelemetryPayload({ - "event": "sort-tabs", - "shownContainersCount": containersCounts.shown, - "totalContainerTabsCount": await identityState.totalContainerTabsCount(), - "totalNonContainerTabsCount": await identityState.totalNonContainerTabsCount() - }); const windows = await browser.windows.getAll(); for (let window of windows) { // eslint-disable-line prefer-const // First the pinned tabs, then the normal ones. @@ -267,16 +232,6 @@ const backgroundLogic = { return null; } - const containersCounts = identityState.containersCounts(); - this.sendTelemetryPayload({ - "event": "hide-tabs", - "userContextId": userContextId, - "clickedContainerTabCount": identityState.containerTabCount(userContextId), - "shownContainersCount": containersCounts.shown, - "hiddenContainersCount": containersCounts.hidden, - "totalContainersCount": containersCounts.total - }); - const containerState = await identityState.storeHidden(options.cookieStoreId); await this._closeTabs(userContextId); return containerState; @@ -293,16 +248,6 @@ const backgroundLogic = { return null; } - const containersCounts = identityState.containersCounts(); - this.sendTelemetryPayload({ - "event": "show-tabs", - "userContextId": userContextId, - "clickedContainerTabCount": await identityState.containerTabCount(options.cookieStoreId), - "shownContainersCount": containersCounts.shown, - "hiddenContainersCount": containersCounts.hidden, - "totalContainersCount": containersCounts.total - }); - const promises = []; const containerState = await identityState.storageArea.get(options.cookieStoreId); @@ -322,16 +267,6 @@ const backgroundLogic = { return await identityState.storageArea.set(options.cookieStoreId, containerState); }, - - sendTelemetryPayload(message = {}) { - if (!message.event) { - throw new Error("Missing event name for telemetry"); - } - message.method = "sendTelemetryPayload"; - //TODO decide where this goes - // browser.runtime.sendMessage(message); - }, - cookieStoreId(userContextId) { return `firefox-container-${userContextId}`; }, diff --git a/webextension/js/background/messageHandler.js b/webextension/js/background/messageHandler.js index 4f96891a..d675b19f 100644 --- a/webextension/js/background/messageHandler.js +++ b/webextension/js/background/messageHandler.js @@ -38,9 +38,6 @@ const messageHandler = { return assignManager._setOrRemoveAssignment(tab.id, m.url, m.userContextId, m.value); }); break; - case "sendTelemetryPayload": - // TODO - break; case "sortTabs": backgroundLogic.sortTabs(); break; @@ -53,9 +50,6 @@ const messageHandler = { case "checkIncompatibleAddons": // TODO break; - case "getShieldStudyVariation": - // TODO - break; case "moveTabsToWindow": response = backgroundLogic.moveTabsToWindow({ cookieStoreId: m.cookieStoreId diff --git a/webextension/js/background/tabPageCounter.js b/webextension/js/background/tabPageCounter.js index b0b62729..19d95913 100644 --- a/webextension/js/background/tabPageCounter.js +++ b/webextension/js/background/tabPageCounter.js @@ -34,20 +34,10 @@ const tabPageCounter = { return; } if (why === "user-closed-tab" && this.counters[tabId].tab) { - backgroundLogic.sendTelemetryPayload({ - event: "page-requests-completed-per-tab", - userContextId: this.counters[tabId].tab.cookieStoreId, - pageRequestCount: this.counters[tabId].tab.pageRequests - }); // When we send the ping because the user closed the tab, // delete both the 'tab' and 'activity' counters delete this.counters[tabId]; } else if (why === "user-went-idle" && this.counters[tabId].activity) { - backgroundLogic.sendTelemetryPayload({ - event: "page-requests-completed-per-activity", - userContextId: this.counters[tabId].activity.cookieStoreId, - pageRequestCount: this.counters[tabId].activity.pageRequests - }); // When we send the ping because the user went idle, // only reset the 'activity' counter this.counters[tabId].activity = { diff --git a/webextension/js/confirm-page.js b/webextension/js/confirm-page.js index aa8c9a28..fef08d48 100644 --- a/webextension/js/confirm-page.js +++ b/webextension/js/confirm-page.js @@ -49,10 +49,6 @@ function confirmSubmit(redirectUrl, cookieStoreId) { pageUrl: redirectUrl }); } - browser.runtime.sendMessage({ - method: "sendTelemetryPayload", - event: "click-to-reload-page-in-container", - }); openInContainer(redirectUrl, cookieStoreId); } @@ -70,10 +66,6 @@ async function denySubmit(redirectUrl) { tabId: tab[0].id, pageUrl: redirectUrl }); - browser.runtime.sendMessage({ - method: "sendTelemetryPayload", - event: "click-to-reload-page-in-same-container", - }); document.location.replace(redirectUrl); } diff --git a/webextension/js/popup.js b/webextension/js/popup.js index a68f19a0..82f96585 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -81,11 +81,9 @@ const Logic = { // Retrieve the list of identities. const identitiesPromise = this.refreshIdentities(); - // Get the onboarding variation - const variationPromise = this.getShieldStudyVariation(); try { - await Promise.all([identitiesPromise, variationPromise]); + await identitiesPromise; } catch(e) { throw new Error("Failed to retrieve the identities or variation. We cannot continue. ", e.message); } @@ -158,7 +156,7 @@ const Logic = { }; // Handle old style rejection with null and also Promise.reject new style try { - return await browser.contextualIdentities.get(cookieStoreId) || defaultContainer; + return await browser.contextualIdentities.get(cookieStoreId) || defaultContainer; } catch(e) { return defaultContainer; } @@ -274,14 +272,6 @@ const Logic = { return identity.cookieStoreId; }, - sendTelemetryPayload(message = {}) { - if (!message.event) { - throw new Error("Missing event name for telemetry"); - } - message.method = "sendTelemetryPayload"; - browser.runtime.sendMessage(message); - }, - removeIdentity(userContextId) { if (!userContextId) { return Promise.reject("removeIdentity must be called with userContextId argument."); @@ -317,13 +307,6 @@ const Logic = { }); }, - async getShieldStudyVariation() { - const variation = await browser.runtime.sendMessage({ - method: "getShieldStudyVariation" - }); - this._onboardingVariation = variation; - }, - generateIdentityName() { const defaultName = "Container #"; const ids = []; @@ -474,9 +457,6 @@ Logic.registerPanel(P_CONTAINERS_LIST, { }); Logic.addEnterHandler(document.querySelector("#edit-containers-link"), () => { - Logic.sendTelemetryPayload({ - event: "edit-containers" - }); Logic.showPanel(P_CONTAINERS_EDIT); });