From cd03ea7a5931c0c7412a23c590cbda1a975b8fbb Mon Sep 17 00:00:00 2001 From: groovecoder Date: Mon, 1 May 2017 10:32:51 -0500 Subject: [PATCH 01/69] start study.js --- index.js | 1 + package.json | 1 + study.js | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 study.js diff --git a/index.js b/index.js index bf70a1c..07eb8e7 100644 --- a/index.js +++ b/index.js @@ -60,6 +60,7 @@ 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 tabsUtils = require("sdk/tabs/utils"); diff --git a/package.json b/package.json index 30d562f..8a6db61 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "htmllint-cli": "^0.0.5", "jpm": "^1.2.2", "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", diff --git a/study.js b/study.js new file mode 100644 index 0000000..5b1bf9f --- /dev/null +++ b/study.js @@ -0,0 +1,35 @@ +const self = require("sdk/self"); +const { shield } = require("./node_modules/shield-studies-addon-utils/lib/index"); +const { when: unload } = require("sdk/system/unload"); + +const studyConfig = { + name: self.addonId, + days: 28, + surveyUrls: { + }, + variations: { + "control": () => {}, + "privacyOnboarding": () => {}, + "onlineAccountsOnboarding": () => {}, + "tabManagementOnboarding": () => {} + } +}; + +class ContainersStudy extends shield.Study { + isEligible () { + } + + whenEligible () { + } + + whenInstalled () { + } + + cleanup(reason) { + console.log(reason); + } +} + +const thisStudy = new ContainersStudy(studyConfig); + +unload((reason) => thisStudy.shutdown(reason)); From e499ff5711a8e75a440396b4002c683496087f58 Mon Sep 17 00:00:00 2001 From: groovecoder Date: Tue, 2 May 2017 09:52:56 -0500 Subject: [PATCH 02/69] include lib/shield to make it work --- index.js | 2 +- lib/shield/event-target.js | 55 +++++ lib/shield/index.js | 428 +++++++++++++++++++++++++++++++++++++ study.js | 2 +- 4 files changed, 485 insertions(+), 2 deletions(-) create mode 100644 lib/shield/event-target.js create mode 100644 lib/shield/index.js diff --git a/index.js b/index.js index 07eb8e7..76beafb 100644 --- a/index.js +++ b/index.js @@ -60,7 +60,7 @@ 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 study = require("./study"); const { Style } = require("sdk/stylesheet/style"); const tabs = require("sdk/tabs"); const tabsUtils = require("sdk/tabs/utils"); diff --git a/lib/shield/event-target.js b/lib/shield/event-target.js new file mode 100644 index 0000000..4335d8c --- /dev/null +++ b/lib/shield/event-target.js @@ -0,0 +1,55 @@ +/** + * 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 new file mode 100644 index 0000000..9d4ec95 --- /dev/null +++ b/lib/shield/index.js @@ -0,0 +1,428 @@ +"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/study.js b/study.js index 5b1bf9f..b8a7e90 100644 --- a/study.js +++ b/study.js @@ -1,5 +1,5 @@ const self = require("sdk/self"); -const { shield } = require("./node_modules/shield-studies-addon-utils/lib/index"); +const shield = require("./lib/shield/index"); const { when: unload } = require("sdk/system/unload"); const studyConfig = { From 54c598e22e8a78a37909349d71d7c515c8d5dc22 Mon Sep 17 00:00:00 2001 From: groovecoder Date: Tue, 2 May 2017 11:04:30 -0500 Subject: [PATCH 03/69] startup the study --- study.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/study.js b/study.js index b8a7e90..fc5323c 100644 --- a/study.js +++ b/study.js @@ -1,6 +1,8 @@ const self = require("sdk/self"); -const shield = require("./lib/shield/index"); const { when: unload } = require("sdk/system/unload"); +const tabs = require("sdk/tabs"); + +const shield = require("./lib/shield/index"); const studyConfig = { name: self.addonId, @@ -17,15 +19,21 @@ const studyConfig = { class ContainersStudy extends shield.Study { isEligible () { + console.log("ContainersStudy.isEligible()"); } whenEligible () { + console.log("ContainersStudy.whenEligible()"); } whenInstalled () { + console.log("ContainersStudy.whenInstalled()"); + console.log("shield variation: ", this.variation); + tabs.open(`data:text/html, Thank you for helping us study Containers in Firefox. You are in the ${this.variation} variation.`); } cleanup(reason) { + console.log("ContainersStudy.cleanup()"); console.log(reason); } } @@ -33,3 +41,5 @@ class ContainersStudy extends shield.Study { const thisStudy = new ContainersStudy(studyConfig); unload((reason) => thisStudy.shutdown(reason)); + +thisStudy.startup(self.loadReason); From b0c53063d2ba2c31fb04d9611b0529ead5fee043 Mon Sep 17 00:00:00 2001 From: groovecoder Date: Tue, 2 May 2017 11:42:14 -0500 Subject: [PATCH 04/69] start shield study AFTER SDK starts the webext --- index.js | 2 +- study.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 76beafb..cb1a893 100644 --- a/index.js +++ b/index.js @@ -60,7 +60,7 @@ 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 { study } = require("./study"); const { Style } = require("sdk/stylesheet/style"); const tabs = require("sdk/tabs"); const tabsUtils = require("sdk/tabs/utils"); diff --git a/study.js b/study.js index fc5323c..335d3e0 100644 --- a/study.js +++ b/study.js @@ -19,7 +19,9 @@ const studyConfig = { class ContainersStudy extends shield.Study { isEligible () { - console.log("ContainersStudy.isEligible()"); + // If the user already has testpilot-containers extension, they are in the + // Test Pilot experiment, so exclude them. + return super.isEligible(); } whenEligible () { @@ -42,4 +44,4 @@ const thisStudy = new ContainersStudy(studyConfig); unload((reason) => thisStudy.shutdown(reason)); -thisStudy.startup(self.loadReason); +exports.study = thisStudy; From 84dd73bff54c18a710f15b3c9087b4ce12a41b7d Mon Sep 17 00:00:00 2001 From: groovecoder Date: Tue, 2 May 2017 12:01:34 -0500 Subject: [PATCH 05/69] update README with shield run instructions --- README.md | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 3fead42..8a18e78 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ -# Containers: Test Pilot Experiment +# Containers Add-on [![Available on Test Pilot](https://img.shields.io/badge/available_on-Test_Pilot-0996F8.svg)](https://testpilot.firefox.com/experiments/containers) -[Embedded Web Extension](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Embedded_WebExtensions) to experiment with [Containers](https://blog.mozilla.org/tanvi/2016/06/16/contextual-identities-on-the-web/) in [Firefox Test Pilot](https://testpilot.firefox.com/) to learn: +[Embedded Web Extension](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Embedded_WebExtensions) to build [Containers](https://blog.mozilla.org/tanvi/2016/06/16/contextual-identities-on-the-web/) as a Firefox [Test Pilot](https://testpilot.firefox.com/) Experiment and [Shield Study](https://wiki.mozilla.org/Firefox/Shield/Shield_Studies) to learn: * Will a general Firefox audience understand the Containers feature? * Is the UI as currently implemented in Nightly clear or discoverable? -See [the Product Hypothesis Document for more -details](https://docs.google.com/document/d/1WQdHTVXROk7dYkSFluc6_hS44tqZjIrG9I-uPyzevE8/edit?ts=5824ba12#). +For more info, see: + +* [Test Pilot Product Hypothesis Document](https://docs.google.com/document/d/1WQdHTVXROk7dYkSFluc6_hS44tqZjIrG9I-uPyzevE8/edit#) +* [Shield Product Hypothesis Document](https://docs.google.com/document/d/1vMD-fH_5hGDDqNvpRZk12_RhCN2WAe4_yaBamaNdtik/edit#) ## Requirements @@ -17,38 +19,28 @@ details](https://docs.google.com/document/d/1WQdHTVXROk7dYkSFluc6_hS44tqZjIrG9I- * Firefox 51+ -## Run it - -See Development - - ## Development ### Development Environment Add-on development is better with [a particular environment](https://developer.mozilla.org/en-US/Add-ons/Setting_up_extension_development_environment). One simple way to get that environment set up is to install the [DevPrefs add-on](https://addons.mozilla.org/en-US/firefox/addon/devprefs/). You can make a custom Firefox profile that includes the DevPrefs add-on, and use that profile when you run the code in this repository. - 1. Make a new profile by running `/path/to/firefox -P`, which launches the profile editor. "Create Profile" -- name it whatever you wish (e.g. 'addon_dev') and store it in the default location. It's probably best to deselect the option to "Use without asking," since you probably don't want to use this as your default profile. 2. Once you've created your profile, click "Start Firefox". A new instance of Firefox should launch. Go to Tools->Add-ons and search for "DevPrefs". Install it. Quit Firefox. 3. Now you have a new, vanilla Firefox profile with the DevPrefs add-on installed. You can use your new profile with the code in _this_ repository like so: -**Beta building** +#### Run the `.xpi` file in an unbranded build +Release & Beta channels do not allow un-signed add-ons, even with the DevPrefs. So, you must run the add-on in an [unbranded build](https://wiki.mozilla.org/Add-ons/Extension_Signing#Unbranded_Builds): -To build this for 51 beta just using the downloaded version of beta will not work as XPI signature checking is disabled fully. +1. Download and install an un-branded build of Firefox +2. Download the latest `.xpi` from this repository's releases +3. Run the un-branded build of Firefox with your DevPrefs profile +4. Go to `about:addons` +5. Click the gear, and select "Install Add-on From File..." +6. Select the `.xpi` file -The only way to run the experiment is using an [unbranded version build](https://wiki.mozilla.org/Add-ons/Extension_Signing#Unbranded_Builds) or to build beta yourself: - -1. [Download the mozilla-beta repo](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Source_Code/Mercurial#mozilla-beta_(prerelease_development_tree)) -2. [Create a mozconfig file](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Configuring_Build_Options) - probably optional -3. `cd ` -3. `./mach bootstrap` -4. `./mach build` -5. Follow the above instructions by creating the new profile via: `~//obj-x86_64-pc-linux-gnu/dist/bin/firefox -P` (Where "obj-x86_64-pc-linux-gnu" may be different depending on platform obj-...) - - -### Run with jpm +#### Run the TxP experiment with `jpm` 1. `git clone git@github.com:mozilla/testpilot-containers.git` 2. `cd testpilot-containers` @@ -57,6 +49,13 @@ The only way to run the experiment is using an [unbranded version build](https:/ 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 shield-study-cli` +4. `./node_modules/.bin/shield run . -- --binary Nightly` + ### Building .xpi From 93b6378b2209fecc3be77e1afa106bc470c2f656 Mon Sep 17 00:00:00 2001 From: groovecoder Date: Tue, 2 May 2017 12:07:20 -0500 Subject: [PATCH 06/69] fix npm test/lint failures --- .eslintignore | 1 + study.js | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.eslintignore b/.eslintignore index 9b27377..b126955 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ testpilot-metrics.js +lib/shield/*.js diff --git a/study.js b/study.js index 335d3e0..672e5f6 100644 --- a/study.js +++ b/study.js @@ -1,3 +1,7 @@ +/* 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 tabs = require("sdk/tabs"); @@ -25,18 +29,13 @@ class ContainersStudy extends shield.Study { } whenEligible () { - console.log("ContainersStudy.whenEligible()"); } whenInstalled () { - console.log("ContainersStudy.whenInstalled()"); - console.log("shield variation: ", this.variation); tabs.open(`data:text/html, Thank you for helping us study Containers in Firefox. You are in the ${this.variation} variation.`); } - cleanup(reason) { - console.log("ContainersStudy.cleanup()"); - console.log(reason); + cleanup() { } } From 099d07bf1fbe33a5b7424d937dfd8a4e43f48c4f Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 11 May 2017 16:52:37 +0100 Subject: [PATCH 07/69] Update shield install path --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8a18e78..9c3e51f 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,8 @@ Check out the [Browser Toolbox](https://developer.mozilla.org/en-US/docs/Tools/B 1. `git clone git@github.com:mozilla/testpilot-containers.git` 2. `cd testpilot-containers` -3. `npm install shield-study-cli` -4. `./node_modules/.bin/shield run . -- --binary Nightly` +3. `npm install -g shield-study-cli` +4. `shield run . -- --binary Nightly` ### Building .xpi From dad3214986817d5fde6502d9838700c1915fa471 Mon Sep 17 00:00:00 2001 From: groovecoder Date: Wed, 17 May 2017 14:41:04 -0500 Subject: [PATCH 08/69] actually start the study --- index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.js b/index.js index cb1a893..e9f57be 100644 --- a/index.js +++ b/index.js @@ -321,6 +321,8 @@ const ContainerService = { // End-Of-Hack Services.obs.addObserver(this, "lightweight-theme-changed", false); + + study.startup(reason); }, registerBackgroundConnection(api) { From 3700e6f461217cf57e2d6cfe752ea3f5b3a0392b Mon Sep 17 00:00:00 2001 From: groovecoder Date: Thu, 18 May 2017 09:05:50 -0500 Subject: [PATCH 09/69] experiment.js from testpilot addon Remove variants functionality and javascript-flow .eslintignore experiment.js --- .eslintignore | 1 + lib/testpilot/experiment.js | 102 ++++++++++++++++++++++++++++++++++++ testpilot-metrics.js | 4 ++ 3 files changed, 107 insertions(+) create mode 100644 lib/testpilot/experiment.js diff --git a/.eslintignore b/.eslintignore index b126955..4ced8a0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ testpilot-metrics.js lib/shield/*.js +lib/testpilot/*.js diff --git a/lib/testpilot/experiment.js b/lib/testpilot/experiment.js new file mode 100644 index 0000000..329bd69 --- /dev/null +++ b/lib/testpilot/experiment.js @@ -0,0 +1,102 @@ +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() { + Events.on(EVENT_SEND_METRIC, experimentPing); +} + +Experiment.prototype = { + constructor: function() { + Events.on(EVENT_SEND_METRIC, experimentPing); + }, + + ping: function(event) { + experimentPing(event); + }, + + teardown: function() { + Events.off(EVENT_SEND_METRIC, experimentPing); + } +}; + +module.exports = Experiment; diff --git a/testpilot-metrics.js b/testpilot-metrics.js index f68aae3..2aad4bd 100644 --- a/testpilot-metrics.js +++ b/testpilot-metrics.js @@ -1,6 +1,9 @@ // 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 @@ -170,6 +173,7 @@ Metrics.prototype = { }; try { + console.log("notifying observerser of testpilot::send-metric; subject: ", subject, " stringified: ", stringified); Services.obs.notifyObservers(subject, 'testpilot::send-metric', stringified); this._log(`Sent client message via nsIObserverService: ${stringified}`); } catch (ex) { From 5916bd287198b296007c962583d309c995b499ed Mon Sep 17 00:00:00 2001 From: groovecoder Date: Fri, 19 May 2017 13:38:04 -0500 Subject: [PATCH 10/69] only use our experimentPing outside of Test Pilot --- lib/testpilot/experiment.js | 23 ++++++++--------------- testpilot-metrics.js | 1 - 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/lib/testpilot/experiment.js b/lib/testpilot/experiment.js index 329bd69..1b4e98f 100644 --- a/lib/testpilot/experiment.js +++ b/lib/testpilot/experiment.js @@ -82,21 +82,14 @@ function experimentPing(event) { } function Experiment() { - Events.on(EVENT_SEND_METRIC, experimentPing); + // 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); + } + }); } -Experiment.prototype = { - constructor: function() { - Events.on(EVENT_SEND_METRIC, experimentPing); - }, - - ping: function(event) { - experimentPing(event); - }, - - teardown: function() { - Events.off(EVENT_SEND_METRIC, experimentPing); - } -}; - module.exports = Experiment; diff --git a/testpilot-metrics.js b/testpilot-metrics.js index 2aad4bd..2914884 100644 --- a/testpilot-metrics.js +++ b/testpilot-metrics.js @@ -173,7 +173,6 @@ Metrics.prototype = { }; try { - console.log("notifying observerser of testpilot::send-metric; subject: ", subject, " stringified: ", stringified); Services.obs.notifyObservers(subject, 'testpilot::send-metric', stringified); this._log(`Sent client message via nsIObserverService: ${stringified}`); } catch (ex) { From 08ba094748c07152774407e142fe8b4840d685b2 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 11 May 2017 16:52:37 +0100 Subject: [PATCH 11/69] add `npm run build-shield` command --- README.md | 8 ++++++-- package.json | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9c3e51f..dd1db0d 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,12 @@ Check out the [Browser Toolbox](https://developer.mozilla.org/en-US/docs/Tools/B ### Building .xpi -To build a local .xpi, use the plain [`jpm -xpi`](https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/jpm#jpm_xpi) command. +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 diff --git a/package.json b/package.json index 8a6db61..56c84a5 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "eslint-plugin-promise": "^3.4.0", "htmllint-cli": "^0.0.5", "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", @@ -42,6 +43,7 @@ }, "scripts": { "build": "npm test && jpm xpi", + "build-shield": "npm test && json -I -f package.json -e 'this.name=\"shield-study-containers\"' && jpm xpi && json -I -f package.json -e 'this.name=\"testpilot-containers\"'", "deploy": "deploy-txp", "lint": "npm-run-all lint:*", "lint:addon": "addons-linter webextension --self-hosted", From 69d497bacd052e25ff87ef0245f3e7ebb7e95150 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 19 May 2017 16:43:43 +0100 Subject: [PATCH 12/69] Adding container assignment exemption on confirm prompt. Fixes #500 --- .stylelintrc | 2 +- webextension/background.js | 45 +++++++++++++---- webextension/confirm-page.html | 11 ++--- webextension/css/confirm-page.css | 40 +++++++++++++-- webextension/js/confirm-page.js | 82 +++++++++++++++++++++++++------ 5 files changed, 146 insertions(+), 34 deletions(-) diff --git a/.stylelintrc b/.stylelintrc index 656873f..cf506c8 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -11,7 +11,7 @@ "declaration-block-no-duplicate-properties": true, "order/declaration-block-properties-alphabetical-order": true, "property-blacklist": [ - "/height/", + "/(min[-]|max[-])height/", "/width/", "/top/", "/bottom/", diff --git a/webextension/background.js b/webextension/background.js index 8f4278c..979c164 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -70,6 +70,18 @@ const assignManager = { } }, + // We return here so the confirm page can load the tab when exempted + async _exemptTab(m) { + const pageUrl = m.pageUrl; + // If we have existing data and for some reason it hasn't been deleted etc lets update it + const siteSettings = await this.storageArea.get(pageUrl); + if (siteSettings) { + siteSettings.exempted.push(m.tabId); + await this.storageArea.set(pageUrl, siteSettings); + } + return true; + }, + init() { browser.contextMenus.onClicked.addListener((info, tab) => { const userContextId = this.getUserContextIdFromCookieStore(tab); @@ -81,7 +93,8 @@ const assignManager = { actionName = "added"; storageAction = this.storageArea.set(info.pageUrl, { userContextId, - neverAsk: false + neverAsk: false, + exempted: [] }); } else { actionName = "removed"; @@ -117,11 +130,12 @@ const assignManager = { const userContextId = this.getUserContextIdFromCookieStore(tab); if (!siteSettings || userContextId === siteSettings.userContextId - || tab.incognito) { + || tab.incognito + || siteSettings.exempted.includes(tab.id)) { return {}; } - this.reloadPageInContainer(options.url, siteSettings.userContextId, tab.index + 1, siteSettings.neverAsk); + this.reloadPageInContainer(options.url, userContextId, siteSettings.userContextId, tab.index + 1, siteSettings.neverAsk); this.calculateContextMenu(tab); /* Removal of existing tabs: @@ -209,11 +223,12 @@ const assignManager = { } }, - reloadPageInContainer(url, userContextId, index, neverAsk = false) { + reloadPageInContainer(url, currentUserContextId, userContextId, index, neverAsk = false) { + const cookieStoreId = backgroundLogic.cookieStoreId(userContextId); const loadPage = browser.extension.getURL("confirm-page.html"); // 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: backgroundLogic.cookieStoreId(userContextId), index}); + browser.tabs.create({url, cookieStoreId, index}); backgroundLogic.sendTelemetryPayload({ event: "auto-reload-page-in-container", userContextId: userContextId, @@ -223,8 +238,17 @@ const assignManager = { event: "prompt-to-reload-page-in-container", userContextId: userContextId, }); - const confirmUrl = `${loadPage}?url=${url}`; - browser.tabs.create({url: confirmUrl, cookieStoreId: backgroundLogic.cookieStoreId(userContextId), index}).then(() => { + let confirmUrl = `${loadPage}?url=${encodeURIComponent(url)}&cookieStoreId=${cookieStoreId}`; + let currentCookieStoreId; + if (currentUserContextId) { + currentCookieStoreId = backgroundLogic.cookieStoreId(currentUserContextId); + confirmUrl += `¤tCookieStoreId=${currentCookieStoreId}`; + } + browser.tabs.create({ + url: confirmUrl, + cookieStoreId: currentCookieStoreId, + index + }).then(() => { // We don't want to sync this URL ever nor clutter the users history browser.history.deleteUrl({url: confirmUrl}); }).catch((e) => { @@ -354,7 +378,7 @@ const messageHandler = { LAST_CREATED_TAB_TIMER: 2000, init() { - // Handles messages from webextension/js/popup.js + // Handles messages from webextension code browser.runtime.onMessage.addListener((m) => { let response; @@ -372,11 +396,14 @@ const messageHandler = { case "neverAsk": assignManager._neverAsk(m); break; + case "exemptContainerAssignment": + response = assignManager._exemptTab(m); + break; } return response; }); - // Handles messages from index.js + // Handles messages from sdk code const port = browser.runtime.connect(); port.onMessage.addListener(m => { switch (m.type) { diff --git a/webextension/confirm-page.html b/webextension/confirm-page.html index 48e12f9..09d4852 100644 --- a/webextension/confirm-page.html +++ b/webextension/confirm-page.html @@ -8,22 +8,21 @@
-

Should we open this in your container?

+

Open this site in your assigned container?

- Looks like you requested: + You asked Firefox to always open for this site:

-

- You asked Firefox to always open in this type of container. Would you like to proceed?
-

+

Would you still like to open in this current container?




- + +
diff --git a/webextension/css/confirm-page.css b/webextension/css/confirm-page.css index a595e4c..24baa7c 100644 --- a/webextension/css/confirm-page.css +++ b/webextension/css/confirm-page.css @@ -4,11 +4,21 @@ } main { - background: url(/img/onboarding-1.png) no-repeat; + background: url(/img/onboarding-4.png) no-repeat; background-position: -10px -15px; - background-size: 285px; - margin-inline-start: -285px; - padding-inline-start: 285px; + background-size: 300px; + margin-inline-start: -350px; + padding-inline-start: 350px; +} + +.container-name { + font-weight: bold; +} + +button .container-name, +#current-container-name { + font-weight: bold; + text-transform: capitalize; } @media only screen and (max-width: 1300px) { @@ -36,6 +46,28 @@ html { word-break: break-all; } +#redirect-url { + background: #efefef; + border-radius: 2px; + line-height: 1.5; + padding-block-end: 0.5rem; + padding-block-start: 0.5rem; + padding-inline-end: 0.5rem; + padding-inline-start: 0.5rem; +} + +#redirect-url img { + block-size: 16px; + inline-size: 16px; + margin-inline-end: 6px; + offset-block-start: 3px; + position: relative; +} + dfn { font-style: normal; } + +.button-container > button { + min-inline-size: 240px; +} diff --git a/webextension/js/confirm-page.js b/webextension/js/confirm-page.js index d54dd06..5c5038b 100644 --- a/webextension/js/confirm-page.js +++ b/webextension/js/confirm-page.js @@ -1,10 +1,46 @@ -const redirectUrl = new URL(window.location).searchParams.get("url"); -document.getElementById("redirect-url").textContent = redirectUrl; -const redirectSite = new URL(redirectUrl).hostname; -document.getElementById("redirect-site").textContent = redirectSite; +async function load() { + const searchParams = new URL(window.location).searchParams; + const redirectUrl = decodeURIComponent(searchParams.get("url")); + const cookieStoreId = searchParams.get("cookieStoreId"); + const currentCookieStoreId = searchParams.get("currentCookieStoreId"); + const redirectUrlElement = document.getElementById("redirect-url"); + redirectUrlElement.textContent = redirectUrl; + createFavicon(redirectUrl, redirectUrlElement); -document.getElementById("redirect-form").addEventListener("submit", (e) => { - e.preventDefault(); + const container = await browser.contextualIdentities.get(cookieStoreId); + [...document.querySelectorAll(".container-name")].forEach((containerNameElement) => { + containerNameElement.textContent = container.name; + }); + + // If default container, button will default to normal HTML content + if (currentCookieStoreId) { + const currentContainer = await browser.contextualIdentities.get(currentCookieStoreId); + document.getElementById("current-container-name").textContent = currentContainer.name; + } + + document.getElementById("redirect-form").addEventListener("submit", (e) => { + e.preventDefault(); + const buttonTarget = e.explicitOriginalTarget; + switch (buttonTarget.id) { + case "confirm": + confirmSubmit(redirectUrl, cookieStoreId); + break; + case "deny": + denySubmit(redirectUrl); + break; + } + }); +} + +function createFavicon(pageUrl, redirectUrlElement) { + const origin = new URL(pageUrl).origin; + const imageElement = document.createElement("img"); + imageElement.src = `${origin}/favicon.ico`; + + redirectUrlElement.prepend(imageElement); +} + +function confirmSubmit(redirectUrl, cookieStoreId) { const neverAsk = document.getElementById("never-ask").checked; // Sending neverAsk message to background to store for next time we see this process if (neverAsk) { @@ -12,20 +48,38 @@ document.getElementById("redirect-form").addEventListener("submit", (e) => { method: "neverAsk", neverAsk: true, pageUrl: redirectUrl - }).then(() => { - redirect(); - }).catch(() => { - // Can't really do much here user will have to click it again }); } browser.runtime.sendMessage({ method: "sendTelemetryPayload", event: "click-to-reload-page-in-container", }); - redirect(); -}); + openInContainer(redirectUrl, cookieStoreId); +} -function redirect() { - const redirectUrl = document.getElementById("redirect-url").textContent; +async function denySubmit(redirectUrl) { + const tab = await browser.tabs.query({active: true}); + await browser.runtime.sendMessage({ + method: "exemptContainerAssignment", + tabId: tab[0].id, + pageUrl: redirectUrl + }); document.location.replace(redirectUrl); } + +load(); + +async function openInContainer(redirectUrl, cookieStoreId) { + const tabs = await browser.tabs.query({active: true}); + browser.runtime.sendMessage({ + method: "sendTelemetryPayload", + event: "click-to-reload-page-in-same-container", + }); + await browser.tabs.create({ + cookieStoreId, + url: redirectUrl + }); + if (tabs.length > 0) { + browser.tabs.remove(tabs[0].id); + } +} From 4f6e91336f0442a4cac4c64bc44eddb97d24061a Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Tue, 23 May 2017 09:41:45 +0100 Subject: [PATCH 13/69] WIP assignment controls. Fixes #499 --- .gitignore | 1 + webextension/background.js | 135 ++++++++++++++++++++++--------------- webextension/css/popup.css | 103 ++++++++++++++++++++++++++-- webextension/js/popup.js | 127 +++++++++++++++++++++++++++++++--- webextension/popup.html | 10 ++- 5 files changed, 309 insertions(+), 67 deletions(-) diff --git a/.gitignore b/.gitignore index f5198a3..6ff00a9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ node_modules README.html *.xpi *.swp +*.swo .vimrc .env addon.env diff --git a/webextension/background.js b/webextension/background.js index 979c164..a1a3817 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -84,38 +84,7 @@ const assignManager = { init() { browser.contextMenus.onClicked.addListener((info, tab) => { - const userContextId = this.getUserContextIdFromCookieStore(tab); - // Mapping ${URL(info.pageUrl).hostname} to ${userContextId} - if (userContextId) { - let actionName; - let storageAction; - if (info.menuItemId === this.MENU_ASSIGN_ID) { - actionName = "added"; - storageAction = this.storageArea.set(info.pageUrl, { - userContextId, - neverAsk: false, - exempted: [] - }); - } else { - actionName = "removed"; - storageAction = this.storageArea.remove(info.pageUrl); - } - storageAction.then(() => { - browser.notifications.create({ - type: "basic", - title: "Containers", - message: `Successfully ${actionName} site to always open in this container`, - iconUrl: browser.extension.getURL("/img/onboarding-1.png") - }); - backgroundLogic.sendTelemetryPayload({ - event: `${actionName}-container-assignment`, - userContextId: userContextId, - }); - this.calculateContextMenu(tab); - }).catch((e) => { - throw e; - }); - } + this._onClickedHandler(info, tab); }); // Before a request is handled by the browser we decide if we should route through a different container @@ -163,6 +132,26 @@ const assignManager = { },{urls: [""], types: ["main_frame"]}, ["blocking"]); }, + async _onClickedHandler(info, tab) { + const userContextId = this.getUserContextIdFromCookieStore(tab); + // Mapping ${URL(info.pageUrl).hostname} to ${userContextId} + if (userContextId) { + // let actionName; + let remove; + if (info.menuItemId === this.MENU_ASSIGN_ID) { + //actionName = "added"; + // storageAction = this._setAssignment(info.pageUrl, userContextId, setOrRemove); + remove = false; + } else { + // actionName = "removed"; + //storageAction = this.storageArea.remove(info.pageUrl); + remove = true; + } + await this._setOrRemoveAssignment(info.pageUrl, userContextId, remove); + this.calculateContextMenu(tab); + } + }, + deleteContainer(userContextId) { this.storageArea.deleteContainer(userContextId); @@ -191,36 +180,63 @@ const assignManager = { return true; }, - calculateContextMenu(tab) { + async _setOrRemoveAssignment(pageUrl, userContextId, remove) { + let storageAction; + if (!remove) { + await this.storageArea.set(pageUrl, { + userContextId, + neverAsk: false, + exempted: [] + }); + actionName = "added"; + } else { + await this.storageArea.remove(pageUrl); + actionName = "removed"; + } + browser.notifications.create({ + type: "basic", + title: "Containers", + message: `Successfully ${actionName} site to always open in this container`, + iconUrl: browser.extension.getURL("/img/onboarding-1.png") + }); + backgroundLogic.sendTelemetryPayload({ + event: `${actionName}-container-assignment`, + userContextId: userContextId, + }); + }, + + async _getAssignment(tab) { + const cookieStore = this.getUserContextIdFromCookieStore(tab); + // Ensure we have a cookieStore to assign to + if (cookieStore + && this.isTabPermittedAssign(tab)) { + return await this.storageArea.get(tab.url); + } + return false; + }, + + async calculateContextMenu(tab) { // There is a focus issue in this menu where if you change window with a context menu click // you get the wrong menu display because of async // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1215376#c16 // We also can't change for always private mode // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1352102 - const cookieStore = this.getUserContextIdFromCookieStore(tab); browser.contextMenus.remove(this.MENU_ASSIGN_ID); browser.contextMenus.remove(this.MENU_REMOVE_ID); - // Ensure we have a cookieStore to assign to - if (cookieStore - && this.isTabPermittedAssign(tab)) { - this.storageArea.get(tab.url).then((siteSettings) => { - // ✓ This is to mitigate https://bugzilla.mozilla.org/show_bug.cgi?id=1351418 - let prefix = " "; // Alignment of non breaking space, unknown why this requires so many spaces to align with the tick - let menuId = this.MENU_ASSIGN_ID; - if (siteSettings) { - prefix = "✓"; - menuId = this.MENU_REMOVE_ID; - } - browser.contextMenus.create({ - id: menuId, - title: `${prefix} Always Open in This Container`, - checked: true, - contexts: ["all"], - }); - }).catch((e) => { - throw e; - }); + const siteSettings = await this._getAssignment(tab); + // ✓ This is to mitigate https://bugzilla.mozilla.org/show_bug.cgi?id=1351418 + let prefix = " "; // Alignment of non breaking space, unknown why this requires so many spaces to align with the tick + let menuId = this.MENU_ASSIGN_ID; + if (siteSettings) { + prefix = "✓"; + menuId = this.MENU_REMOVE_ID; } + browser.contextMenus.create({ + id: menuId, + title: `${prefix} Always Open in This Container`, + checked: true, + contexts: ["all"], + }); }, reloadPageInContainer(url, currentUserContextId, userContextId, index, neverAsk = false) { @@ -396,6 +412,17 @@ const messageHandler = { case "neverAsk": assignManager._neverAsk(m); break; + case "getAssignment": + response = browser.tabs.get(m.tabId).then((tab) => { + return assignManager._getAssignment(tab); + }); + break; + case "setOrRemoveAssignment": + response = browser.tabs.get(m.tabId).then((tab) => { + const userContextId = assignManager.getUserContextIdFromCookieStore(tab); + return assignManager._setOrRemoveAssignment(tab.url, userContextId, m.value); + }); + break; case "exemptContainerAssignment": response = assignManager._exemptTab(m); break; diff --git a/webextension/css/popup.css b/webextension/css/popup.css index d053693..c01a194 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -8,6 +8,11 @@ html { box-sizing: border-box; } +:root { + --font-size-heading: 16px; + --primary-action-color: #248aeb; +} + *, *::before, *::after { @@ -35,6 +40,10 @@ table { overflow: auto; } +.offpage { + opacity: 0; +} + /* Color and icon helpers */ [data-identity-color="blue"] { --identity-tab-color: #37adff; @@ -140,6 +149,18 @@ table { background-color: rgba(0, 0, 0, 0.05); } +/* Text links with actions */ + +.action-link:link { + color: var(--primary-action-color); + text-decoration: none; +} + +.action-link:active, +.action-link:hover { + text-decoration: underline; +} + /* Panels keep everything togethert */ .panel { display: flex; @@ -223,7 +244,7 @@ table { .onboarding-title { color: #43484e; - font-size: 16px; + font-size: var(--font-size-heading); margin-block-end: 0; margin-block-start: 0; margin-inline-end: 0; @@ -312,7 +333,7 @@ manage things like container crud */ .panel-header-text { color: #4a4a4a; flex: 1; - font-size: 16px; + font-size: var(--font-size-heading); font-weight: normal; margin-block-end: 0; margin-block-start: 0; @@ -324,6 +345,24 @@ manage things like container crud */ padding-inline-start: 16px; } +#container-panel .panel-header { + block-size: 26px; + background-color: #efefef; + font-size: 14px; +} + +#container-panel .panel-header-text { + font-size: 14px; + text-transform: uppercase; + color: #727272; + padding-block-end: 0; + padding-block-start: 0; +} + +#container-panel .sort-containers-link { + margin-inline-end: 16px; +} + span ~ .panel-header-text { padding-block-end: 0; padding-block-start: 0; @@ -331,6 +370,62 @@ span ~ .panel-header-text { padding-inline-start: 0; } +#current-tab { + max-inline-size: 100%; + min-block-size: 94px; + padding-block-end: 16px; + padding-block-start: 16px; + padding-inline-end: 16px; + padding-inline-start: 16px; +} + +#current-tab > h3 { + color: #4a4a4a; + font-size: var(--font-size-heading); + font-weight: normal; + margin-block-end: 0; + margin-block-start: 0; +} + +#current-page { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#current-page > img { + block-size: 16px; + inline-size: 16px; +} + +#current-tab > label { + align-items: center; + display: flex; + margin-inline-start: 17px; +} + +#current-tab > label > input { + display: inline; +} +#current-tab > label > img { + block-size: 12px; + display: inline-block; + inline-size: 12px; +} + +#current-container { + display: contents; + text-transform: lowercase; +} + +#current-container > .usercontext-icon { + block-size: 16px; + display: block; + flex: 0 0 20px; + inline-size: 20px; + background-size: 16px; +} + /* Rows used when iterating over panels */ .container-panel-row { align-items: center; @@ -501,7 +596,7 @@ span ~ .panel-header-text { .edit-containers-exit-text { align-items: center; - background: #248aeb; + background: var(--primary-action-color); block-size: 100%; color: #fff; display: flex; @@ -528,7 +623,7 @@ span ~ .panel-header-text { .delete-container-confirm-title { color: #000; - font-size: 16px; + font-size: var(--font-size-heading); } /* Form info */ diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 26afa59..70188e6 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -18,6 +18,7 @@ const P_CONTAINERS_EDIT = "containersEdit"; const P_CONTAINER_INFO = "containerInfo"; const P_CONTAINER_EDIT = "containerEdit"; const P_CONTAINER_DELETE = "containerDelete"; +const DEFAULT_FAVICON = "moz-icon://goat?size=16"; /** * Escapes any occurances of &, ", <, > or / with XML entities. @@ -107,6 +108,16 @@ const Logic = { browser.storage.local.set({browserActionBadgesClicked: storage.browserActionBadgesClicked}); }, + async identity(cookieStoreId) { + const identity = await browser.contextualIdentities.get(cookieStoreId); + return identity || { + name: "Default", + cookieStoreId, + icon: "circle", + color: "black" + }; + }, + addEnterHandler(element, handler) { element.addEventListener("click", handler); element.addEventListener("keydown", (e) => { @@ -121,6 +132,14 @@ const Logic = { return (userContextId !== cookieStoreId) ? Number(userContextId) : false; }, + async currentTab() { + const activeTabs = await browser.tabs.query({active: true}); + if (activeTabs.length > 0) { + return activeTabs[0] + } + return false; + }, + refreshIdentities() { return Promise.all([ browser.contextualIdentities.query({}), @@ -139,7 +158,7 @@ const Logic = { }).catch((e) => {throw e;}); }, - showPanel(panel, currentIdentity = null) { + async showPanel(panel, currentIdentity = null) { // Invalid panel... ?!? if (!(panel in this._panels)) { throw new Error("Something really bad happened. Unknown panel: " + panel); @@ -151,15 +170,18 @@ const Logic = { this._currentIdentity = currentIdentity; // Initialize the panel before showing it. - this._panels[panel].prepare().then(() => { - for (let panelElement of document.querySelectorAll(".panel")) { // eslint-disable-line prefer-const + await this._panels[panel].prepare(); + Object.keys(this._panels).forEach((panelKey) => { + const panelItem = this._panels[panelKey]; + const panelElement = document.querySelector(panelItem.panelSelector); + if (!panelElement.classList.contains("hide")) { panelElement.classList.add("hide"); + if ("unregister" in panelItem) { + panelItem.unregister(); + } } - document.querySelector(this._panels[panel].panelSelector).classList.remove("hide"); - }) - .catch(() => { - throw new Error("Failed to show panel " + panel); }); + document.querySelector(this._panels[panel].panelSelector).classList.remove("hide"); }, showPreviousPanel() { @@ -205,6 +227,21 @@ const Logic = { }); }, + getAssignment(tab) { + return browser.runtime.sendMessage({ + method: "getAssignment", + tabId: tab.id + }); + }, + + setOrRemoveAssignment(tab, value) { + return browser.runtime.sendMessage({ + method: "setOrRemoveAssignment", + tabId: tab.id, + value + }); + }, + generateIdentityName() { const defaultName = "Container #"; const ids = []; @@ -364,12 +401,86 @@ Logic.registerPanel(P_CONTAINERS_LIST, { break; } }); + + // When the popup is open sometimes the tab will still be updating it's state + this.tabUpdateHandler = (tabId, changeInfo) => { + const propertiesToUpdate = ["title", "favIconUrl"]; + const hasChanged = Object.keys(changeInfo).find((changeInfoKey) => { + if (propertiesToUpdate.includes(changeInfoKey)) { + return true; + } + }); + if (hasChanged) { + this.prepareCurrentTabHeader(); + } + }; + browser.tabs.onUpdated.addListener(this.tabUpdateHandler); + }, + + unregister() { + browser.tabs.onUpdated.removeListener(this.tabUpdateHandler); + }, + + setupAssignmentCheckbox(siteSettings) { + const assignmentCheckboxElement = document.getElementById("container-page-assigned"); + // Cater for null and false + assignmentCheckboxElement.checked = !!siteSettings; + let disabled = false; + if (siteSettings === false) { + disabled = true; + } + assignmentCheckboxElement.disabled = disabled; + }, + + async prepareCurrentTabHeader() { + const currentTab = await Logic.currentTab(); + const currentTabElement = document.getElementById("current-tab"); + const assignmentCheckboxElement = document.getElementById("container-page-assigned"); + assignmentCheckboxElement.addEventListener("change", () => { + Logic.setOrRemoveAssignment(currentTab, !assignmentCheckboxElement.checked); + }); + currentTabElement.hidden = !currentTab; + this.setupAssignmentCheckbox(false); + if (currentTab) { + const identity = await Logic.identity(currentTab.cookieStoreId); + const siteSettings = await Logic.getAssignment(currentTab); + this.setupAssignmentCheckbox(siteSettings); + const currentPage = document.getElementById("current-page"); + const favIconUrl = currentTab.favIconUrl || ""; + currentPage.innerHTML = escaped` + ${currentTab.title} + `; + + const imageElement = currentPage.querySelector("img"); + const loadListener = (e) => { + e.target.classList.remove("offpage"); + e.target.removeEventListener("load", loadListener); + e.target.removeEventListener("error", errorListener); + }; + const errorListener = (e) => { + e.target.src = DEFAULT_FAVICON; + }; + imageElement.addEventListener("error", errorListener); + imageElement.addEventListener("load", loadListener); + + const currentContainer = document.getElementById("current-container"); + currentContainer.innerHTML = escaped` +
+
+ ${identity.name} + `; + } }, // This method is called when the panel is shown. - prepare() { + async prepare() { const fragment = document.createDocumentFragment(); + this.prepareCurrentTabHeader(); + Logic.identities().forEach(identity => { const hasTabs = (identity.hasHiddenTabs || identity.hasOpenTabs); const tr = document.createElement("tr"); diff --git a/webextension/popup.html b/webextension/popup.html index 794c9b0..82ea19e 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -38,9 +38,17 @@
+
+

Current Tab

+
+ +

Containers

- Sort Containers + Sort Tabs
From bd72b4e7597ce657287a4222dd816acee76d0c88 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 25 May 2017 17:00:54 +0100 Subject: [PATCH 14/69] Adding new exemption pings to metrics.md --- docs/metrics.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/metrics.md b/docs/metrics.md index 135ed97..af1ae8d 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -220,7 +220,7 @@ of a `testpilottest` telemetry ping for each scenario. } ``` -* The user clicks "Take me there" to reload a site into a container after the user picked "Always Open in this Container". +* The user clicks "Open in *assigned* container" to reload a site into a container after the user picked "Always Open in this Container". ```js { @@ -229,6 +229,15 @@ of a `testpilottest` telemetry ping for each scenario. } ``` +* The user clicks "Open in *Current* container" to reload a site into a container after the user picked "Always Open in this Container". + +```js + { + "uuid": , + "event": "click-to-reload-page-in-same-container" + } +``` + * Firefox automatically reloads a site into a container after the user picked "Always Open in this Container". ```js From a29fae08931c16d94afbf15eb2b0448a3f1bc586 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 25 May 2017 17:01:54 +0100 Subject: [PATCH 15/69] Fixing exemption to be stored in memory rather than storage (prevents exemption from being remembered on restart). --- webextension/background.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/webextension/background.js b/webextension/background.js index a1a3817..c8d1174 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -6,6 +6,7 @@ const assignManager = { MENU_REMOVE_ID: "remove-open-in-this-container", storageArea: { area: browser.storage.local, + exemptedTabs: {}, getSiteStoreKey(pageUrl) { const url = new window.URL(pageUrl); @@ -13,6 +14,22 @@ const assignManager = { return `${storagePrefix}${url.hostname}`; }, + setExempted(pageUrl, tabId) { + if (!(tabId in this.exemptedTabs)) { + this.exemptedTabs[tabId] = []; + } + const siteStoreKey = this.getSiteStoreKey(pageUrl); + this.exemptedTabs[tabId].push(siteStoreKey); + }, + + isExempted(pageUrl, tabId) { + if (!(tabId in this.exemptedTabs)) { + return false; + } + const siteStoreKey = this.getSiteStoreKey(pageUrl); + return this.exemptedTabs[tabId].includes(siteStoreKey); + }, + get(pageUrl) { const siteStoreKey = this.getSiteStoreKey(pageUrl); return new Promise((resolve, reject) => { @@ -73,12 +90,7 @@ const assignManager = { // We return here so the confirm page can load the tab when exempted async _exemptTab(m) { const pageUrl = m.pageUrl; - // If we have existing data and for some reason it hasn't been deleted etc lets update it - const siteSettings = await this.storageArea.get(pageUrl); - if (siteSettings) { - siteSettings.exempted.push(m.tabId); - await this.storageArea.set(pageUrl, siteSettings); - } + this.storageArea.setExempted(pageUrl, m.tabId); return true; }, @@ -100,7 +112,7 @@ const assignManager = { if (!siteSettings || userContextId === siteSettings.userContextId || tab.incognito - || siteSettings.exempted.includes(tab.id)) { + || this.storageArea.isExempted(options.url, tab.id)) { return {}; } From fb845cce12166c2e06265ed41d01eaaeb83d4b33 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 25 May 2017 17:12:53 +0100 Subject: [PATCH 16/69] Fixing linting errors --- webextension/background.js | 2 +- webextension/css/popup.css | 11 ++++++----- webextension/js/popup.js | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/webextension/background.js b/webextension/background.js index c8d1174..ca01d89 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -193,7 +193,7 @@ const assignManager = { }, async _setOrRemoveAssignment(pageUrl, userContextId, remove) { - let storageAction; + let actionName; if (!remove) { await this.storageArea.set(pageUrl, { userContextId, diff --git a/webextension/css/popup.css b/webextension/css/popup.css index c01a194..b336b34 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -346,17 +346,17 @@ manage things like container crud */ } #container-panel .panel-header { - block-size: 26px; background-color: #efefef; + block-size: 26px; font-size: 14px; } #container-panel .panel-header-text { - font-size: 14px; - text-transform: uppercase; color: #727272; + font-size: 14px; padding-block-end: 0; padding-block-start: 0; + text-transform: uppercase; } #container-panel .sort-containers-link { @@ -388,9 +388,9 @@ span ~ .panel-header-text { } #current-page { - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + white-space: nowrap; } #current-page > img { @@ -407,6 +407,7 @@ span ~ .panel-header-text { #current-tab > label > input { display: inline; } + #current-tab > label > img { block-size: 12px; display: inline-block; @@ -419,11 +420,11 @@ span ~ .panel-header-text { } #current-container > .usercontext-icon { + background-size: 16px; block-size: 16px; display: block; flex: 0 0 20px; inline-size: 20px; - background-size: 16px; } /* Rows used when iterating over panels */ diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 70188e6..e14dc63 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -135,7 +135,7 @@ const Logic = { async currentTab() { const activeTabs = await browser.tabs.query({active: true}); if (activeTabs.length > 0) { - return activeTabs[0] + return activeTabs[0]; } return false; }, From 5cd2ac01878617e0f722c557ee8515e5749c6615 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 26 May 2017 11:41:14 +0100 Subject: [PATCH 17/69] Cleaning up layout issues for current tab panel. --- webextension/css/popup.css | 7 ++++++- webextension/img/blank-tab.svg | 3 +++ webextension/js/popup.js | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 webextension/img/blank-tab.svg diff --git a/webextension/css/popup.css b/webextension/css/popup.css index b336b34..2ffaf67 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -133,6 +133,11 @@ table { --identity-icon: url("/img/usercontext.svg#chill"); } +#current-tab [data-identity-icon="default-tab"] { + background: center center no-repeat url("/img/blank-tab.svg"); + fill: currentColor; +} + /* Buttons */ .button.primary { background-color: #0996f8; @@ -359,7 +364,7 @@ manage things like container crud */ text-transform: uppercase; } -#container-panel .sort-containers-link { +#container-panel #sort-containers-link { margin-inline-end: 16px; } diff --git a/webextension/img/blank-tab.svg b/webextension/img/blank-tab.svg new file mode 100644 index 0000000..351945b --- /dev/null +++ b/webextension/img/blank-tab.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/webextension/js/popup.js b/webextension/js/popup.js index e14dc63..76c3e66 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -113,8 +113,8 @@ const Logic = { return identity || { name: "Default", cookieStoreId, - icon: "circle", - color: "black" + icon: "default-tab", + color: "default-tab" }; }, From ab2b9a48c732c2a1866d9a7d8c363fbd7d1695e0 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Tue, 30 May 2017 12:43:21 +0100 Subject: [PATCH 18/69] Changing current tab truncation to prevent container overflowing. Fixes #552 --- webextension/css/popup.css | 44 +++++++++++++++----------------------- webextension/js/popup.js | 19 +++++++--------- webextension/popup.html | 8 ++++--- 3 files changed, 30 insertions(+), 41 deletions(-) diff --git a/webextension/css/popup.css b/webextension/css/popup.css index 2ffaf67..ad2966e 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -44,6 +44,13 @@ table { opacity: 0; } +/* Effect borrowed from tabs in Firefox, ensure that the element flexes to the full width */ +.truncate-text { + mask-image: linear-gradient(to left, transparent, black 1em); + overflow: hidden; + white-space: nowrap; +} + /* Color and icon helpers */ [data-identity-color="blue"] { --identity-tab-color: #37adff; @@ -377,9 +384,9 @@ span ~ .panel-header-text { #current-tab { max-inline-size: 100%; - min-block-size: 94px; - padding-block-end: 16px; - padding-block-start: 16px; + min-block-size: 91px; + padding-block-end: 13px; + padding-block-start: 13px; padding-inline-end: 16px; padding-inline-start: 16px; } @@ -388,16 +395,10 @@ span ~ .panel-header-text { color: #4a4a4a; font-size: var(--font-size-heading); font-weight: normal; - margin-block-end: 0; + margin-block-end: 3px; margin-block-start: 0; } -#current-page { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - #current-page > img { block-size: 16px; inline-size: 16px; @@ -407,29 +408,26 @@ span ~ .panel-header-text { align-items: center; display: flex; margin-inline-start: 17px; + white-space: nowrap; } #current-tab > label > input { display: inline; } -#current-tab > label > img { - block-size: 12px; - display: inline-block; - inline-size: 12px; -} - #current-container { - display: contents; + flex: 1; text-transform: lowercase; } -#current-container > .usercontext-icon { +#current-tab > label > .usercontext-icon { background-size: 16px; block-size: 16px; display: block; flex: 0 0 20px; inline-size: 20px; + margin-inline-end: 3px; + margin-inline-start: 3px; } /* Rows used when iterating over panels */ @@ -444,12 +442,10 @@ span ~ .panel-header-text { } .container-panel-row .container-name { + flex: 1; max-inline-size: 160px; - overflow: hidden; padding-inline-end: 4px; padding-inline-start: 4px; - text-overflow: ellipsis; - white-space: nowrap; } .edit-containers-panel .userContext-wrapper { @@ -531,9 +527,6 @@ span ~ .panel-header-text { /* Container info list */ #container-info-name { margin-inline-end: 0.5rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } #container-info-hideorshow { @@ -578,9 +571,6 @@ span ~ .panel-header-text { .container-info-tab-row td { max-inline-size: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } .container-info-list { diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 76c3e66..12ca299 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -464,14 +464,11 @@ Logic.registerPanel(P_CONTAINERS_LIST, { imageElement.addEventListener("load", loadListener); const currentContainer = document.getElementById("current-container"); - currentContainer.innerHTML = escaped` -
-
- ${identity.name} - `; + currentContainer.innerText = identity.name; + + const currentContainerIcon = document.getElementById("current-container-icon"); + currentContainerIcon.setAttribute("data-identity-icon", identity.icon); + currentContainerIcon.setAttribute("data-identity-color", identity.color); } }, @@ -500,7 +497,7 @@ Logic.registerPanel(P_CONTAINERS_LIST, { data-identity-color="${identity.color}"> -
`; +
`; context.querySelector(".container-name").textContent = identity.name; manage.innerHTML = ""; @@ -649,7 +646,7 @@ Logic.registerPanel(P_CONTAINER_INFO, { tr.classList.add("container-info-tab-row"); tr.innerHTML = escaped`
- `; + `; // On click, we activate this tab. But only if this tab is active. if (tab.active) { @@ -699,7 +696,7 @@ Logic.registerPanel(P_CONTAINERS_EDIT, { data-identity-color="${identity.color}"> -
+
+ `; + tr.querySelector("td").appendChild(Utils.createFavIconElement(tab.favicon)); // On click, we activate this tab. But only if this tab is active. if (tab.active) { diff --git a/webextension/js/utils.js b/webextension/js/utils.js new file mode 100644 index 0000000..5d5046b --- /dev/null +++ b/webextension/js/utils.js @@ -0,0 +1,23 @@ +const DEFAULT_FAVICON = "moz-icon://goat?size=16"; + +// TODO use export here instead of globals +window.Utils = { + + createFavIconElement(url) { + const imageElement = document.createElement("img"); + imageElement.classList.add("icon", "offpage"); + imageElement.src = url; + const loadListener = (e) => { + e.target.classList.remove("offpage"); + e.target.removeEventListener("load", loadListener); + e.target.removeEventListener("error", errorListener); + }; + const errorListener = (e) => { + e.target.src = DEFAULT_FAVICON; + }; + imageElement.addEventListener("error", errorListener); + imageElement.addEventListener("load", loadListener); + return imageElement; + } + +}; diff --git a/webextension/popup.html b/webextension/popup.html index 25a7d0d..a524f7e 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -148,7 +148,7 @@ - + From 9903e811c2a0361eaa6f0fa1e823cd626c0f81cf Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 31 May 2017 15:36:45 +0100 Subject: [PATCH 23/69] Fix first focus issue on opening browser action. Fixes #564 --- webextension/js/popup.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 7c43e5d..cac57b6 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -523,8 +523,14 @@ Logic.registerPanel(P_CONTAINERS_LIST, { /* Not sure why extensions require a focus for the doorhanger, however it allows us to have a tabindex before the first selected item */ - document.addEventListener("focus", () => { + const focusHandler = () => { list.querySelector("tr").focus(); + document.removeEventListener("focus", focusHandler); + }; + document.addEventListener("focus", focusHandler); + /* If the user mousedown's first then remove the focus handler */ + document.addEventListener("mousedown", () => { + document.removeEventListener("focus", focusHandler); }); return Promise.resolve(); From 49e8afaf9a62c92dce3f6e9e51c49425d3df6713 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 31 May 2017 16:12:07 +0100 Subject: [PATCH 24/69] Fixing truncation for info screen tabs. --- webextension/css/popup.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webextension/css/popup.css b/webextension/css/popup.css index ad2966e..bd524a1 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -529,6 +529,10 @@ span ~ .panel-header-text { margin-inline-end: 0.5rem; } +.container-info-tab-title { + flex: 1; +} + #container-info-hideorshow { margin-block-start: 4px; } From 06d35e65ce7f6c59897b5eee8b8c9540d855d3db Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 1 Jun 2017 03:28:02 +0100 Subject: [PATCH 25/69] Adding in content notification to look more browser like. --- webextension/background.js | 13 +++----- webextension/css/content.css | 22 +++++++++++++ webextension/js/content-script.js | 53 +++++++++++++++++++++++++++++++ webextension/manifest.json | 15 +++++++-- 4 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 webextension/css/content.css create mode 100644 webextension/js/content-script.js diff --git a/webextension/background.js b/webextension/background.js index ca01d89..6448a99 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -159,7 +159,7 @@ const assignManager = { //storageAction = this.storageArea.remove(info.pageUrl); remove = true; } - await this._setOrRemoveAssignment(info.pageUrl, userContextId, remove); + await this._setOrRemoveAssignment(tab.id, info.pageUrl, userContextId, remove); this.calculateContextMenu(tab); } }, @@ -192,7 +192,7 @@ const assignManager = { return true; }, - async _setOrRemoveAssignment(pageUrl, userContextId, remove) { + async _setOrRemoveAssignment(tabId, pageUrl, userContextId, remove) { let actionName; if (!remove) { await this.storageArea.set(pageUrl, { @@ -205,11 +205,8 @@ const assignManager = { await this.storageArea.remove(pageUrl); actionName = "removed"; } - browser.notifications.create({ - type: "basic", - title: "Containers", - message: `Successfully ${actionName} site to always open in this container`, - iconUrl: browser.extension.getURL("/img/onboarding-1.png") + browser.tabs.sendMessage(tabId, { + text: `Successfully ${actionName} site to always open in this container` }); backgroundLogic.sendTelemetryPayload({ event: `${actionName}-container-assignment`, @@ -432,7 +429,7 @@ const messageHandler = { case "setOrRemoveAssignment": response = browser.tabs.get(m.tabId).then((tab) => { const userContextId = assignManager.getUserContextIdFromCookieStore(tab); - return assignManager._setOrRemoveAssignment(tab.url, userContextId, m.value); + return assignManager._setOrRemoveAssignment(tab.id, tab.url, userContextId, m.value); }); break; case "exemptContainerAssignment": diff --git a/webextension/css/content.css b/webextension/css/content.css new file mode 100644 index 0000000..885b0fb --- /dev/null +++ b/webextension/css/content.css @@ -0,0 +1,22 @@ +.container-notification { + background: #33f70c; + color: #003f07; + display: block; + inline-size: 100vw; + offset-block-start: 0; + offset-inline-start: 0; + padding-block-end: 8px; + padding-block-start: 8px; + padding-inline-end: 8px; + padding-inline-start: 8px; + position: fixed; + transform: translateY(-100%); + transition: transform 0.3s cubic-bezier(0.07, 0.95, 0, 1) 0.3s; + z-index: 999999999999; +} + +.container-notification img { + block-size: 16px; + inline-size: 16px; + margin-inline-end: 3px; +} diff --git a/webextension/js/content-script.js b/webextension/js/content-script.js new file mode 100644 index 0000000..ef6b4c4 --- /dev/null +++ b/webextension/js/content-script.js @@ -0,0 +1,53 @@ +async function delayAnimation(delay = 350) { + return new Promise((resolve) => { + setTimeout(resolve, delay); + }); +} + +async function doAnimation(element, property, value) { + return new Promise((resolve) => { + const handler = () => { + resolve(); + element.removeEventListener("transitionend", handler); + }; + element.addEventListener("transitionend", handler); + window.requestAnimationFrame(() => { + element.style[property] = value; + }); + }); +} +/* +async function awaitEvent(eventName) { + return new Promise((resolve) => { + const handler = () => { + resolve(); + divElement.removeEventListener(eventName, handler); + }; + divElement.addEventListener(eventName, handler); + }); +} +*/ + +async function addMessage(message) { + const divElement = document.createElement("div"); + divElement.classList.add("container-notification"); + // For the eager eyed, this is an experiment. It is however likely that a website will know it is "contained" anyway + divElement.innerText = message.text; + + const imageElement = document.createElement("img"); + imageElement.src = browser.extension.getURL("/img/container-site-d-24.png"); + divElement.prepend(imageElement); + + document.body.appendChild(divElement); + + await delayAnimation(100); + await doAnimation(divElement, "transform", "translateY(0)"); + await delayAnimation(2000); + await doAnimation(divElement, "transform", "translateY(-100%)"); + + divElement.remove(); +} + +browser.runtime.onMessage.addListener((message) => { + addMessage(message); +}); diff --git a/webextension/manifest.json b/webextension/manifest.json index b99a5f3..19b99bc 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -26,7 +26,6 @@ "contextualIdentities", "history", "idle", - "notifications", "storage", "tabs", "webRequestBlocking", @@ -54,5 +53,17 @@ "background": { "scripts": ["background.js"] - } + }, + + "content_scripts": [ + { + "matches": [""], + "js": ["js/content-script.js"], + "css": ["css/content.css"] + } + ], + + "web_accessible_resources": [ + "/img/container-site-d-24.png" + ] } From 15477dc384b7f7d03015d61d8d6fde02bbe160a6 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 2 Jun 2017 03:23:11 +0100 Subject: [PATCH 26/69] Start fixing styles --- webextension/css/popup.css | 197 ++++++++++++++++++++++++++++--------- webextension/js/popup.js | 8 +- webextension/popup.html | 16 +-- 3 files changed, 161 insertions(+), 60 deletions(-) diff --git a/webextension/css/popup.css b/webextension/css/popup.css index bd524a1..e6c0385 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -1,16 +1,55 @@ /* General Rules and Resets */ -body { - inline-size: 300px; - max-inline-size: 300px; +* { + font-size: inherit; + margin-block-end: 0; + margin-block-start: 0; + margin-inline-end: 0; + margin-inline-start: 0; + padding-block-end: 0; + padding-block-start: 0; + padding-inline-end: 0; + padding-inline-start: 0; } html { box-sizing: border-box; + font-size: 12px; +} + +body { + font-family: Roboto, Noto, "San Francisco", Ubuntu, "Segoe UI", "Fira Sans", message-box, Arial, sans-serif; + inline-size: 300px; + max-inline-size: 300px; } :root { - --font-size-heading: 16px; --primary-action-color: #248aeb; + --title-text-color: #000; + --text-normal-color: #4a4a4a; + --text-heading-color: #000; + + /* calculated from 12px */ + --font-size-heading: 1.33rem; /* 16px */ + --block-line-space-size: 0.5rem; /* 6px */ + --inline-item-space-size: 0.5rem; /* 6px */ + --block-line-separation-size: 0.33rem; /* 10px */ + --inline-icon-space-size: 0.833rem; /* 10px */ + + /* Use for url and icon size */ + --block-url-label-size: 2rem; /* 24px */ + --inline-start-size: 1.66rem; /* 20px */ + --inline-button-size: 5.833rem; /* 70px */ + --icon-size: 1.166rem; /* 14px */ + + --small-text-size: 0.833rem; /* 10px */ + --small-radius: 3px; + --icon-button-size: calc(calc(var(--block-line-separation-size) * 2) + 1.66rem); /* 20px */ +} + +@media (min-resolution: 1dppx) { + html { + font-size: 14px; + } } *, @@ -35,6 +74,7 @@ table { } .scrollable { + border-block-start: 1px solid #f1f1f1; inline-size: 100%; max-block-size: 400px; overflow: auto; @@ -146,6 +186,10 @@ table { } /* Buttons */ +.button { + color: black; +} + .button.primary { background-color: #0996f8; color: white; @@ -216,7 +260,7 @@ table { .column-panel-content .button, .panel-footer .button { align-items: center; - block-size: 54px; + block-size: 100%; display: flex; flex: 1; justify-content: center; @@ -265,7 +309,7 @@ table { } .onboarding p { - color: #4a4a4a; + color: var(--text-normal-color); font-size: 14px; margin-block-end: 16px; max-inline-size: 84%; @@ -294,9 +338,10 @@ table { manage things like container crud */ .pop-button { align-items: center; - block-size: 48px; + block-size: var(--icon-button-size); + cursor: pointer; display: flex; - flex: 0 0 48px; + flex: 0 0 var(--icon-button-size); justify-content: center; } @@ -321,6 +366,10 @@ manage things like container crud */ .pop-button-image { block-size: 20px; flex: 0 0 20px; + margin-block-end: auto; + margin-block-start: auto; + margin-inline-end: auto; + margin-inline-start: auto; } .pop-button-image-small { @@ -332,18 +381,21 @@ manage things like container crud */ .panel-header { align-items: center; block-size: 48px; - border-block-end: 1px solid #ebebeb; display: flex; justify-content: space-between; } +.panel-header .usercontext-icon { + inline-size: var(--icon-button-size); +} + .column-panel-content .panel-header { flex: 0 0 48px; inline-size: 100%; } .panel-header-text { - color: #4a4a4a; + color: var(--text-normal-color); flex: 1; font-size: var(--font-size-heading); font-weight: normal; @@ -371,8 +423,31 @@ manage things like container crud */ text-transform: uppercase; } +.container-panel-controls { + display: flex; + justify-content: flex-end; + margin-block-end: var(--block-line-space-size); + margin-block-start: var(--block-line-space-size); + margin-inline-end: var(--inline-item-space-size); + margin-inline-start: var(--inline-item-space-size); +} + #container-panel #sort-containers-link { - margin-inline-end: 16px; + align-items: center; + block-size: var(--block-url-label-size); + border: 1px solid #d8d8d8; + border-radius: var(--small-radius); + color: var(--title-text-color); + display: flex; + font-size: var(--small-text-size); + inline-size: var(--inline-button-size); + justify-content: center; + text-decoration: none; +} + +#container-panel #sort-containers-link:hover, +#container-panel #sort-containers-link:focus { + background: #f2f2f2; } span ~ .panel-header-text { @@ -383,41 +458,75 @@ span ~ .panel-header-text { } #current-tab { + align-items: center; + color: var(--text-normal-color); + display: grid; + font-size: var(--small-text-size); + grid-column-gap: var(--inline-item-space-size); + grid-row-gap: var(--block-line-space-size); + grid-template-columns: var(--icon-size) var(--icon-size) 1fr; + margin-block-end: var(--block-line-space-size); + margin-block-start: var(--block-line-separation-size); + margin-inline-end: var(--inline-start-size); + margin-inline-start: var(--inline-start-size); max-inline-size: 100%; - min-block-size: 91px; - padding-block-end: 13px; - padding-block-start: 13px; - padding-inline-end: 16px; - padding-inline-start: 16px; +} + +#current-tab img { + max-block-size: var(--icon-size); } #current-tab > h3 { - color: #4a4a4a; - font-size: var(--font-size-heading); + color: var(--text-heading-color); font-weight: normal; - margin-block-end: 3px; + grid-column: span 3; + margin-block-end: 0; margin-block-start: 0; + margin-inline-end: 0; + margin-inline-start: 0; } -#current-page > img { - block-size: 16px; - inline-size: 16px; +#current-page { + display: contents; +} + +#current-tab .page-title { + font-size: var(--font-size-heading); + grid-column: 2 / 4; } #current-tab > label { - align-items: center; - display: flex; - margin-inline-start: 17px; - white-space: nowrap; + display: contents; + font-size: var(--small-text-size); } #current-tab > label > input { - display: inline; + -moz-appearance: none; + block-size: var(--icon-size); + border: 1px solid #d8d8d8; + border-radius: var(--small-radius); + display: block; + grid-column-start: 2; + inline-size: var(--icon-size); + margin-block-end: 0; + margin-block-start: 0; + margin-inline-end: 0; + margin-inline-start: 0; +} + +#current-tab > label > input[disabled] { + background-color: #efefef; +} + +#current-tab > label > input:checked { + background-image: url("chrome://global/skin/in-content/check.svg#check-native"); + background-position: -1px -1px; + background-size: var(--icon-size); } #current-container { + color: var(--identity-tab-color); flex: 1; - text-transform: lowercase; } #current-tab > label > .usercontext-icon { @@ -434,7 +543,6 @@ span ~ .panel-header-text { .container-panel-row { align-items: center; background-color: #fefefe !important; - block-size: 48px; border-block-end: 1px solid #f1f1f1; box-sizing: border-box; display: flex; @@ -465,8 +573,9 @@ span ~ .panel-header-text { } .userContext-icon-wrapper { - block-size: 48px; - flex: 0 0 48px; + block-size: var(--icon-button-size); + flex: 0 0 var(--icon-button-size); + margin-inline-start: var(--inline-icon-space-size); } /* .userContext-icon is used natively, Bug 1333811 was raised to fix */ @@ -475,24 +584,28 @@ span ~ .panel-header-text { background-position: center center; background-repeat: no-repeat; background-size: 20px 20px; - block-size: 48px; + block-size: 100%; fill: var(--identity-icon-color); filter: url('/img/filters.svg#fill'); - flex: 0 0 48px; } .container-panel-row:hover .clickable .usercontext-icon, .container-panel-row:focus .clickable .usercontext-icon { background-image: url('/img/container-newtab.svg'); - fill: 'gray'; + fill: #979797; filter: url('/img/filters.svg#fill'); } +.container-panel-row .clickable:hover .usercontext-icon, +.container-panel-row .clickable:focus .usercontext-icon { + fill: #0094fb; +} + /* Panel Footer */ .panel-footer { align-items: center; background: #efefef; - block-size: 54px; + block-size: var(--icon-button-size); border-block-end: 1px solid #d8d8d8; color: #000; display: flex; @@ -501,14 +614,9 @@ span ~ .panel-header-text { justify-content: space-between; } -.panel-footer .pop-button { - block-size: 54px; - flex: 0 0 54px; -} - .edit-containers-text { align-items: center; - block-size: 54px; + block-size: 100%; border-inline-end: solid 1px #d8d8d8; display: flex; flex: 1; @@ -517,7 +625,7 @@ span ~ .panel-header-text { .edit-containers-text a { align-items: center; - block-size: 54px; + block-size: 100%; color: #0a0a0a; display: flex; flex: 1; @@ -525,10 +633,6 @@ span ~ .panel-header-text { } /* Container info list */ -#container-info-name { - margin-inline-end: 0.5rem; -} - .container-info-tab-title { flex: 1; } @@ -578,7 +682,6 @@ span ~ .panel-header-text { } .container-info-list { - border-block-start: 1px solid #ebebeb; display: flex; flex-direction: column; margin-block-start: 4px; diff --git a/webextension/js/popup.js b/webextension/js/popup.js index cac57b6..c4cf13f 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -445,16 +445,14 @@ Logic.registerPanel(P_CONTAINERS_LIST, { const siteSettings = await Logic.getAssignment(currentTab); this.setupAssignmentCheckbox(siteSettings); const currentPage = document.getElementById("current-page"); - currentPage.innerHTML = escaped`${currentTab.title}`; + currentPage.innerHTML = escaped`${currentTab.title}`; const favIconElement = Utils.createFavIconElement(currentTab.favIconUrl || ""); currentPage.prepend(favIconElement); const currentContainer = document.getElementById("current-container"); currentContainer.innerText = identity.name; - const currentContainerIcon = document.getElementById("current-container-icon"); - currentContainerIcon.setAttribute("data-identity-icon", identity.icon); - currentContainerIcon.setAttribute("data-identity-color", identity.color); + currentContainer.setAttribute("data-identity-color", identity.color); } }, @@ -516,7 +514,7 @@ Logic.registerPanel(P_CONTAINERS_LIST, { }); }); - const list = document.querySelector(".identities-list"); + const list = document.querySelector(".identities-list tbody"); list.innerHTML = ""; list.appendChild(fragment); diff --git a/webextension/popup.html b/webextension/popup.html index a524f7e..9dfdccb 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -40,21 +40,21 @@

Current Tab

-
+
-
-

Containers

+
-
${tab.title}${tab.title}

Current Tab

-
+
@@ -74,7 +76,7 @@
-

+

Hide Container icon From 45f34a586a4ac5747b1d58c27ed0d9804ebb9d6c Mon Sep 17 00:00:00 2001 From: groovecoder Date: Tue, 30 May 2017 12:28:13 -0500 Subject: [PATCH 19/69] only start study for @shield-study-privacy --- index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index e9f57be..435e4fa 100644 --- a/index.js +++ b/index.js @@ -322,7 +322,9 @@ const ContainerService = { Services.obs.addObserver(this, "lightweight-theme-changed", false); - study.startup(reason); + if (self.id === "@shield-study-containers") { + study.startup(reason); + } }, registerBackgroundConnection(api) { From df8bf4e5e4edb9b8e21ec42a04951fe300fcac03 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 26 May 2017 14:11:06 +0100 Subject: [PATCH 20/69] Force removal of assign context menu entries before anything async happens to prevent the wrong tabs assign preference showing. Fixes #539 --- webextension/background.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/webextension/background.js b/webextension/background.js index ca01d89..26d4a10 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -104,6 +104,7 @@ const assignManager = { if (options.frameId !== 0 || options.tabId === -1) { return {}; } + this.removeContextMenu(); return Promise.all([ browser.tabs.get(options.tabId), this.storageArea.get(options.url) @@ -227,7 +228,7 @@ const assignManager = { return false; }, - async calculateContextMenu(tab) { + removeContextMenu() { // There is a focus issue in this menu where if you change window with a context menu click // you get the wrong menu display because of async // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1215376#c16 @@ -235,6 +236,10 @@ const assignManager = { // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1352102 browser.contextMenus.remove(this.MENU_ASSIGN_ID); browser.contextMenus.remove(this.MENU_REMOVE_ID); + }, + + async calculateContextMenu(tab) { + this.removeContextMenu(); const siteSettings = await this._getAssignment(tab); // ✓ This is to mitigate https://bugzilla.mozilla.org/show_bug.cgi?id=1351418 let prefix = " "; // Alignment of non breaking space, unknown why this requires so many spaces to align with the tick @@ -474,6 +479,7 @@ const messageHandler = { }); browser.tabs.onActivated.addListener((info) => { + assignManager.removeContextMenu(); browser.tabs.get(info.tabId).then((tab) => { tabPageCounter.initTabCounter(tab); assignManager.calculateContextMenu(tab); @@ -483,6 +489,7 @@ const messageHandler = { }); browser.windows.onFocusChanged.addListener((windowId) => { + assignManager.removeContextMenu(); browser.tabs.query({active: true, windowId}).then((tabs) => { if (tabs && tabs[0]) { tabPageCounter.initTabCounter(tabs[0]); @@ -511,6 +518,7 @@ const messageHandler = { if (details.frameId !== 0 || details.tabId === -1) { return {}; } + assignManager.removeContextMenu(); browser.tabs.get(details.tabId).then((tab) => { tabPageCounter.incrementTabCount(tab); From 094a0e23919efb43dfafa69a5f435c680d3b094e Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Sun, 4 Jun 2017 22:22:42 +0100 Subject: [PATCH 21/69] Update README.md to bump Firefox version number --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd1db0d..2fcb518 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ For more info, see: ## Requirements * node 7+ (for jpm) -* Firefox 51+ +* Firefox 53+ ## Development From e467988a7197c866bc66281085bb6ba3dde83d09 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 31 May 2017 15:03:29 +0100 Subject: [PATCH 22/69] Fixing favicon loading for all icons. Part of #561 --- .eslintrc.js | 1 + webextension/confirm-page.html | 1 + webextension/js/confirm-page.js | 9 ++++----- webextension/js/popup.js | 23 +++++------------------ webextension/js/utils.js | 23 +++++++++++++++++++++++ webextension/popup.html | 2 +- 6 files changed, 35 insertions(+), 24 deletions(-) create mode 100644 webextension/js/utils.js diff --git a/.eslintrc.js b/.eslintrc.js index f2a7957..d9f7270 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,6 +9,7 @@ module.exports = { "webextensions": true }, "globals": { + "Utils": true, "CustomizableUI": true, "CustomizableWidgets": true, "SessionStore": true, diff --git a/webextension/confirm-page.html b/webextension/confirm-page.html index 09d4852..20d11c8 100644 --- a/webextension/confirm-page.html +++ b/webextension/confirm-page.html @@ -27,6 +27,7 @@ + diff --git a/webextension/js/confirm-page.js b/webextension/js/confirm-page.js index 5c5038b..83a8435 100644 --- a/webextension/js/confirm-page.js +++ b/webextension/js/confirm-page.js @@ -5,7 +5,7 @@ async function load() { const currentCookieStoreId = searchParams.get("currentCookieStoreId"); const redirectUrlElement = document.getElementById("redirect-url"); redirectUrlElement.textContent = redirectUrl; - createFavicon(redirectUrl, redirectUrlElement); + appendFavicon(redirectUrl, redirectUrlElement); const container = await browser.contextualIdentities.get(cookieStoreId); [...document.querySelectorAll(".container-name")].forEach((containerNameElement) => { @@ -32,12 +32,11 @@ async function load() { }); } -function createFavicon(pageUrl, redirectUrlElement) { +function appendFavicon(pageUrl, redirectUrlElement) { const origin = new URL(pageUrl).origin; - const imageElement = document.createElement("img"); - imageElement.src = `${origin}/favicon.ico`; + const favIconElement = Utils.createFavIconElement(`${origin}/favicon.ico`); - redirectUrlElement.prepend(imageElement); + redirectUrlElement.prepend(favIconElement); } function confirmSubmit(redirectUrl, cookieStoreId) { diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 12ca299..7c43e5d 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -18,7 +18,6 @@ const P_CONTAINERS_EDIT = "containersEdit"; const P_CONTAINER_INFO = "containerInfo"; const P_CONTAINER_EDIT = "containerEdit"; const P_CONTAINER_DELETE = "containerDelete"; -const DEFAULT_FAVICON = "moz-icon://goat?size=16"; /** * Escapes any occurances of &, ", <, > or / with XML entities. @@ -446,22 +445,9 @@ Logic.registerPanel(P_CONTAINERS_LIST, { const siteSettings = await Logic.getAssignment(currentTab); this.setupAssignmentCheckbox(siteSettings); const currentPage = document.getElementById("current-page"); - const favIconUrl = currentTab.favIconUrl || ""; - currentPage.innerHTML = escaped` - ${currentTab.title} - `; - - const imageElement = currentPage.querySelector("img"); - const loadListener = (e) => { - e.target.classList.remove("offpage"); - e.target.removeEventListener("load", loadListener); - e.target.removeEventListener("error", errorListener); - }; - const errorListener = (e) => { - e.target.src = DEFAULT_FAVICON; - }; - imageElement.addEventListener("error", errorListener); - imageElement.addEventListener("load", loadListener); + currentPage.innerHTML = escaped`${currentTab.title}`; + const favIconElement = Utils.createFavIconElement(currentTab.favIconUrl || ""); + currentPage.prepend(favIconElement); const currentContainer = document.getElementById("current-container"); currentContainer.innerText = identity.name; @@ -645,8 +631,9 @@ Logic.registerPanel(P_CONTAINER_INFO, { fragment.appendChild(tr); tr.classList.add("container-info-tab-row"); tr.innerHTML = escaped` -
${tab.title}
- +
+
From 5c5cf02249f778a09902d3d535061700ccd7aee1 Mon Sep 17 00:00:00 2001 From: baku Date: Tue, 30 May 2017 19:10:27 +0200 Subject: [PATCH 29/69] Reset color and icon when disabled - issue #398 --- index.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/index.js b/index.js index 435e4fa..937dc61 100644 --- a/index.js +++ b/index.js @@ -42,6 +42,14 @@ const IDENTITY_ICONS = [ { 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.ui.enabled", false ], @@ -1407,6 +1415,8 @@ ContainerWindow.prototype = { this._shutdownFileMenu(); this._shutdownAllTabsMenu(); this._shutdownContextMenu(); + + this._shutdownContainers(); }, _shutDownPlusButtonMenuElement(buttonElement) { @@ -1473,6 +1483,36 @@ ContainerWindow.prototype = { return true; }, + + _shutdownContainers() { + 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 --------------------------------------------------- From 090ae1f139560a9edad951d1b0f395913212bc2c Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Tue, 13 Jun 2017 15:02:47 +0100 Subject: [PATCH 30/69] Fixing popup to use tabId for messaging assignment change --- webextension/js/popup.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 9122add..5ea4609 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -247,9 +247,10 @@ const Logic = { }); }, - setOrRemoveAssignment(url, userContextId, value) { + setOrRemoveAssignment(tabId, url, userContextId, value) { return browser.runtime.sendMessage({ method: "setOrRemoveAssignment", + tabId, url, userContextId, value @@ -452,7 +453,7 @@ Logic.registerPanel(P_CONTAINERS_LIST, { const assignmentCheckboxElement = document.getElementById("container-page-assigned"); assignmentCheckboxElement.addEventListener("change", () => { const userContextId = Logic.userContextId(currentTab.cookieStoreId); - Logic.setOrRemoveAssignment(currentTab.url, userContextId, !assignmentCheckboxElement.checked); + Logic.setOrRemoveAssignment(currentTab.id, currentTab.url, userContextId, !assignmentCheckboxElement.checked); }); currentTabElement.hidden = !currentTab; this.setupAssignmentCheckbox(false); @@ -806,9 +807,13 @@ Logic.registerPanel(P_CONTAINER_EDIT, { const deleteButton = trElement.querySelector(".delete-assignment"); Logic.addEnterHandler(deleteButton, () => { const userContextId = Logic.currentUserContextId(); - Logic.setOrRemoveAssignment(assumedUrl, userContextId, true); - delete assignments[siteKey]; - this.showAssignedContainers(assignments); + // Lets show the message to the current tab + // TODO remove then when firefox supports arrow fn async + Logic.currentTab().then((currentTab) => { + Logic.setOrRemoveAssignment(currentTab.id, assumedUrl, userContextId, true); + delete assignments[siteKey]; + this.showAssignedContainers(assignments); + }); }); trElement.classList.add("container-info-tab-row", "clickable"); tableElement.appendChild(trElement); From bf75f52a5280a3f81217d1176f813d2068c84e1b Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Tue, 13 Jun 2017 16:36:00 +0100 Subject: [PATCH 31/69] Fix linting --- webextension/js/popup.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 5ea4609..addd036 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -813,6 +813,8 @@ Logic.registerPanel(P_CONTAINER_EDIT, { Logic.setOrRemoveAssignment(currentTab.id, assumedUrl, userContextId, true); delete assignments[siteKey]; this.showAssignedContainers(assignments); + }).catch((e) => { + throw e; }); }); trElement.classList.add("container-info-tab-row", "clickable"); From c2ed5420a4e5248adc9050de6cc68d1b0c344e10 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 14 Jun 2017 11:41:28 +0100 Subject: [PATCH 32/69] Fixing keyboard focus issues to new layout --- webextension/css/popup.css | 3 ++- webextension/js/popup.js | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/webextension/css/popup.css b/webextension/css/popup.css index a0f2c52..e9c7699 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -597,7 +597,8 @@ span ~ .panel-header-text { } .container-panel-row:hover .clickable .usercontext-icon, -.container-panel-row:focus .clickable .usercontext-icon { +.container-panel-row:focus .clickable .usercontext-icon, +.container-panel-row .clickable:focus .usercontext-icon { background-image: url('/img/container-newtab.svg'); fill: #979797; filter: url('/img/filters.svg#fill'); diff --git a/webextension/js/popup.js b/webextension/js/popup.js index addd036..23b49ee 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -398,13 +398,13 @@ Logic.registerPanel(P_CONTAINERS_LIST, { function next() { const nextElement = element.nextElementSibling; if (nextElement) { - nextElement.focus(); + nextElement.querySelector("td[tabindex=0]").focus(); } } function previous() { const previousElement = element.previousElementSibling; if (previousElement) { - previousElement.focus(); + previousElement.querySelector("td[tabindex=0]").focus(); } } switch (e.keyCode) { @@ -487,10 +487,9 @@ Logic.registerPanel(P_CONTAINERS_LIST, { tr.classList.add("container-panel-row"); - tr.setAttribute("tabindex", "0"); - context.classList.add("userContext-wrapper", "open-newtab", "clickable"); manage.classList.add("show-tabs", "pop-button"); + context.setAttribute("tabindex", "0"); context.innerHTML = escaped`
{ - list.querySelector("tr").focus(); + list.querySelector("tr .clickable").focus(); document.removeEventListener("focus", focusHandler); }; document.addEventListener("focus", focusHandler); From 06d381b93165fc8d64ffd0ae71ddbc17c7779b73 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 14 Jun 2017 12:09:18 +0100 Subject: [PATCH 33/69] Fix reload telemetry payload. --- webextension/js/confirm-page.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/webextension/js/confirm-page.js b/webextension/js/confirm-page.js index 83a8435..4077d6b 100644 --- a/webextension/js/confirm-page.js +++ b/webextension/js/confirm-page.js @@ -63,6 +63,10 @@ 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); } @@ -70,10 +74,6 @@ load(); async function openInContainer(redirectUrl, cookieStoreId) { const tabs = await browser.tabs.query({active: true}); - browser.runtime.sendMessage({ - method: "sendTelemetryPayload", - event: "click-to-reload-page-in-same-container", - }); await browser.tabs.create({ cookieStoreId, url: redirectUrl From d2b4d972e1979749620cf172d76f652ce1f26a67 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 14 Jun 2017 13:49:13 +0100 Subject: [PATCH 34/69] Fixing showing of assignment menu. Fixes #579 --- webextension/background.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/webextension/background.js b/webextension/background.js index ebedf66..9d64504 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -189,6 +189,7 @@ const assignManager = { // Ensure we are not in incognito mode const url = new URL(tab.url); if (url.protocol === "about:" + || url.protocol === "moz-extension:" || tab.incognito) { return false; } @@ -244,6 +245,11 @@ const assignManager = { async calculateContextMenu(tab) { this.removeContextMenu(); const siteSettings = await this._getAssignment(tab); + // Return early and not add an item if we have false + // False represents assignment is not permitted + if (siteSettings === false) { + return false; + } // ✓ This is to mitigate https://bugzilla.mozilla.org/show_bug.cgi?id=1351418 let prefix = " "; // Alignment of non breaking space, unknown why this requires so many spaces to align with the tick let menuId = this.MENU_ASSIGN_ID; @@ -262,6 +268,7 @@ const assignManager = { reloadPageInContainer(url, currentUserContextId, userContextId, index, neverAsk = false) { const cookieStoreId = backgroundLogic.cookieStoreId(userContextId); const loadPage = browser.extension.getURL("confirm-page.html"); + // False represents assignment is not permitted // 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}); From dfd420d1a5cc064dd898295e9e4403d310dd928b Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 14 Jun 2017 15:10:05 +0100 Subject: [PATCH 35/69] Fix which assignment is being changed. Fixes #580 --- webextension/background.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webextension/background.js b/webextension/background.js index 9d64504..7b43b3c 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -448,9 +448,10 @@ const messageHandler = { response = assignManager._getByContainer(m.message.userContextId); break; case "setOrRemoveAssignment": + // m.tabId is used for where to place the in content message + // m.url is the assignment to be removed/added response = browser.tabs.get(m.tabId).then((tab) => { - const userContextId = assignManager.getUserContextIdFromCookieStore(tab); - return assignManager._setOrRemoveAssignment(tab.id, tab.url, userContextId, m.value); + return assignManager._setOrRemoveAssignment(tab.id, m.url, m.userContextId, m.value); }); break; case "exemptContainerAssignment": From 78ef2e830439c978b723d0bf94d95d3164721ea0 Mon Sep 17 00:00:00 2001 From: groovecoder Date: Wed, 14 Jun 2017 15:50:46 -0500 Subject: [PATCH 36/69] update README for shield per @kjozwiak --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2fcb518..c3aa7ed 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,9 @@ Check out the [Browser Toolbox](https://developer.mozilla.org/en-US/docs/Tools/B 1. `git clone git@github.com:mozilla/testpilot-containers.git` 2. `cd testpilot-containers` -3. `npm install -g shield-study-cli` -4. `shield run . -- --binary Nightly` +3. `npm install` +4. `npm install -g shield-study-cli` +5. `shield run . -- --binary Nightly` ### Building .xpi From bfc6f68978030ec45a4f0f8ce79c9828ac76e1d8 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 14 Jun 2017 23:35:33 +0100 Subject: [PATCH 37/69] Fix for current tab showing the wrong window. Fixes #592 --- webextension/js/popup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 23b49ee..2069560 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -134,7 +134,7 @@ const Logic = { }, async currentTab() { - const activeTabs = await browser.tabs.query({active: true}); + const activeTabs = await browser.tabs.query({active: true, windowId: browser.windows.WINDOW_ID_CURRENT}); if (activeTabs.length > 0) { return activeTabs[0]; } From 68c21624e28c55409b5ab936aeb9e6d8bcbb52a8 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 15 Jun 2017 13:39:22 +0100 Subject: [PATCH 38/69] Reset context menu when assignment changes. Fixes #589 --- webextension/background.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webextension/background.js b/webextension/background.js index 7b43b3c..96fff29 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -163,7 +163,6 @@ const assignManager = { remove = true; } await this._setOrRemoveAssignment(tab.id, info.pageUrl, userContextId, remove); - this.calculateContextMenu(tab); } }, @@ -216,6 +215,8 @@ const assignManager = { event: `${actionName}-container-assignment`, userContextId: userContextId, }); + const tab = await browser.tabs.get(tabId); + this.calculateContextMenu(tab); }, async _getAssignment(tab) { From 5d75d4525d967fe302933333b191e1f94cf8fb80 Mon Sep 17 00:00:00 2001 From: groovecoder Date: Thu, 15 Jun 2017 11:42:34 -0500 Subject: [PATCH 39/69] document "alltabs-menu" as an open tab source --- docs/metrics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/metrics.md b/docs/metrics.md index af1ae8d..aefe38e 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -76,7 +76,7 @@ of a `testpilottest` telemetry ping for each scenario. "userContextId": , "clickedContainerTabCount": , "event": "open-tab", - "eventSource": ["tab-bar"|"pop-up"|"file-menu"] + "eventSource": ["tab-bar"|"pop-up"|"file-menu"|"alltabs-menu"] } ``` From 9b0fe826de285dd3a845235a6ed4f07c56d93888 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 16 Jun 2017 15:14:21 +0100 Subject: [PATCH 40/69] Fix icons in shield study. Fixes #586 --- data/usercontext.css | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/data/usercontext.css b/data/usercontext.css index 7ee9bf5..11530b7 100644 --- a/data/usercontext.css +++ b/data/usercontext.css @@ -52,55 +52,55 @@ value, or chrome url path as an alternate selector mitiages this bug.*/ [data-identity-icon="fingerprint"], [data-identity-icon="chrome://browser/skin/usercontext/personal.svg"] { - --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#fingerprint"); + --identity-icon: url("/data/usercontext.svg#fingerprint"); } [data-identity-icon="briefcase"], [data-identity-icon="chrome://browser/skin/usercontext/work.svg"] { - --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#briefcase"); + --identity-icon: url("/data/usercontext.svg#briefcase"); } [data-identity-icon="dollar"], [data-identity-icon="chrome://browser/skin/usercontext/banking.svg"] { - --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#dollar"); + --identity-icon: url("/data/usercontext.svg#dollar"); } [data-identity-icon="cart"], [data-identity-icon="chrome://browser/skin/usercontext/cart.svg"], [data-identity-icon="chrome://browser/skin/usercontext/shopping.svg"] { - --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#cart"); + --identity-icon: url("/data/usercontext.svg#cart"); } [data-identity-icon="circle"] { - --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#circle"); + --identity-icon: url("/data/usercontext.svg#circle"); } [data-identity-icon="gift"] { - --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#gift"); + --identity-icon: url("/data/usercontext.svg#gift"); } [data-identity-icon="vacation"] { - --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#vacation"); + --identity-icon: url("/data/usercontext.svg#vacation"); } [data-identity-icon="food"] { - --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#food"); + --identity-icon: url("/data/usercontext.svg#food"); } [data-identity-icon="fruit"] { - --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#fruit"); + --identity-icon: url("/data/usercontext.svg#fruit"); } [data-identity-icon="pet"] { - --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#pet"); + --identity-icon: url("/data/usercontext.svg#pet"); } [data-identity-icon="tree"] { - --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#tree"); + --identity-icon: url("/data/usercontext.svg#tree"); } [data-identity-icon="chill"] { - --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#chill"); + --identity-icon: url("/data/usercontext.svg#chill"); } #userContext-indicator { @@ -139,7 +139,7 @@ value, or chrome url path as an alternate selector mitiages this bug.*/ background-size: contain; fill: var(--identity-icon-color) !important; filter: url(/img/filters.svg#fill); - filter: url(resource://testpilot-containers/data/filters.svg#fill); + filter: url(/data/filters.svg#fill); } /* containers experiment */ @@ -200,7 +200,7 @@ special cases are addressed below */ } #new-tab-overlay { - --icon-size: 26px; + --icon-size: 16px; -moz-appearance: none; background: transparent; font-style: -moz-use-system-font; @@ -252,8 +252,8 @@ special cases are addressed below */ } #new-tab-overlay .menuitem-iconic[data-usercontextid] > .menu-iconic-left > .menu-iconic-icon { - block-height: var(--icon-size); - block-width: var(--icon-size); + block-size: var(--icon-size); + inline-size: var(--icon-size); } .menuitem-iconic[data-usercontextid] > .menu-iconic-left { From 2278498b060cf7412185cff5794a1bd581453ada Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Mon, 19 Jun 2017 11:46:41 +0100 Subject: [PATCH 41/69] WIP edit-containers restyle --- webextension/css/popup.css | 27 +++++++++++++++------------ webextension/js/popup.js | 6 ++++-- webextension/popup.html | 4 ++-- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/webextension/css/popup.css b/webextension/css/popup.css index e9c7699..832b6b4 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -776,34 +776,37 @@ span ~ .panel-header-text { display: block; } -.column-panel-content form span { +.radio-choice > .radio-container { align-items: center; - block-size: 44px; + block-size: 29px; display: flex; - flex: 0 0 25%; - justify-content: center; + flex: 0 0 calc(100% / 8); } -.edit-container-panel label { +.radio-choice > .radio-container > label { background-image: var(--identity-icon); - background-size: 26px 26px; - block-size: 34px; + background-size: 16px; + block-size: 22px; fill: var(--identity-icon-color); filter: url('/img/filters.svg#fill'); - flex: 0 0 34px; + flex: 0 0 22px; + margin-inline-start: 2px; position: relative; } -.edit-container-panel label::before { +.radio-choice > .radio-container > label::before { + block-size: 0; + inline-size: 0; opacity: 0 !important; } -.edit-container-panel [type="radio"] { +.radio-choice > .radio-container > [type="radio"] { display: inline; + -moz-appearance: none; opacity: 0; } -.edit-container-panel [type="radio"]:checked + label { +.radio-choice > .radio-container > [type="radio"]:checked + label { outline: 2px solid grey; -moz-outline-radius: 50px; } @@ -842,5 +845,5 @@ span ~ .panel-header-text { .edit-container-panel legend { flex: 1 0; font-size: 14px !important; - padding-block-end: 5px; + padding-block-end: 6px; } diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 2069560..6023551 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -830,7 +830,8 @@ Logic.registerPanel(P_CONTAINER_EDIT, { const colors = ["blue", "turquoise", "green", "yellow", "orange", "red", "pink", "purple" ]; const colorRadioFieldset = document.getElementById("edit-container-panel-choose-color"); colors.forEach((containerColor) => { - const templateInstance = document.createElement("span"); + const templateInstance = document.createElement("div"); + templateInstance.classList.add("radio-container"); // eslint-disable-next-line no-unsanitized/property templateInstance.innerHTML = colorRadioTemplate(containerColor); colorRadioFieldset.appendChild(templateInstance); @@ -843,7 +844,8 @@ Logic.registerPanel(P_CONTAINER_EDIT, { const icons = ["fingerprint", "briefcase", "dollar", "cart", "vacation", "gift", "food", "fruit", "pet", "tree", "chill", "circle"]; const iconRadioFieldset = document.getElementById("edit-container-panel-choose-icon"); icons.forEach((containerIcon) => { - const templateInstance = document.createElement("span"); + const templateInstance = document.createElement("div"); + templateInstance.classList.add("radio-container"); // eslint-disable-next-line no-unsanitized/property templateInstance.innerHTML = iconRadioTemplate(containerIcon); iconRadioFieldset.appendChild(templateInstance); diff --git a/webextension/popup.html b/webextension/popup.html index b641895..fbf03f6 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -119,10 +119,10 @@ Name -
+
Choose a color
-
+
Choose an icon
From 13e4b4e7f7683a31f626de92dd03ca33da3f19eb Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Mon, 19 Jun 2017 13:54:19 +0100 Subject: [PATCH 42/69] WIP styles improving radio styles --- webextension/css/popup.css | 46 +++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/webextension/css/popup.css b/webextension/css/popup.css index 832b6b4..db38674 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -114,6 +114,11 @@ table { --identity-icon-color: #51cd00; } +[data-identity-color="grey"] { + /* Only used for the edit panel */ + --identity-icon-color: #616161; +} + [data-identity-color="yellow"] { --identity-tab-color: #ffcb00; --identity-icon-color: #ffcb00; @@ -784,20 +789,29 @@ span ~ .panel-header-text { } .radio-choice > .radio-container > label { - background-image: var(--identity-icon); - background-size: 16px; - block-size: 22px; - fill: var(--identity-icon-color); - filter: url('/img/filters.svg#fill'); - flex: 0 0 22px; - margin-inline-start: 2px; - position: relative; + background: none; + block-size: 23px; + border: 0; + filter: none; + inline-size: 23px; + margin: 0; + padding: 0; } .radio-choice > .radio-container > label::before { - block-size: 0; - inline-size: 0; - opacity: 0 !important; + background-color: unset; + background-image: var(--identity-icon); + background-position: center; + background-repeat: no-repeat; + background-size: 16px; + block-size: 23px; + fill: var(--identity-icon-color); + filter: url('/img/filters.svg#fill'); + position: relative; + content: ""; + display: block; + border: none; + inline-size: 23px; } .radio-choice > .radio-container > [type="radio"] { @@ -807,8 +821,14 @@ span ~ .panel-header-text { } .radio-choice > .radio-container > [type="radio"]:checked + label { - outline: 2px solid grey; - -moz-outline-radius: 50px; + background: #d3d3d3; + border-radius: 100%; +} + +/* When focusing the element add a thin blue highlight to match input fields. This gives a distinction to other selected radio items */ +.radio-choice > .radio-container > [type="radio"]:focus + label { + outline: 1px solid #1f9ffc; + -moz-outline-radius: 100%; } .edit-container-panel fieldset { From 4e0180d52113386c262ad2cc8a46cecd60981c96 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Mon, 19 Jun 2017 14:33:52 +0100 Subject: [PATCH 43/69] Improve assignment styles part of #561 --- webextension/css/popup.css | 21 +++++++++++++++------ webextension/js/popup.js | 19 ++++++++++--------- webextension/popup.html | 12 +++--------- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/webextension/css/popup.css b/webextension/css/popup.css index db38674..db23c24 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -762,25 +762,34 @@ span ~ .panel-header-text { #edit-sites-assigned h3 { font-size: 14px; font-weight: normal; - padding-block-end: 5px; + padding-block-end: 6px; + padding-block-start: 6px; padding-inline-end: 16px; padding-inline-start: 16px; } -#edit-sites-assigned table td { +.assigned-sites-list > div { display: flex; - padding-inline-end: 16px; - padding-inline-start: 16px; + padding-block-end: 6px; + padding-block-start: 6px; } -#edit-sites-assigned .delete-assignment { +.assigned-sites-list > div > .icon { + margin-inline-end: 10px; +} + +.assigned-sites-list > div > .delete-assignment { display: none; } -#edit-sites-assigned tr:hover > td > .delete-assignment { +.assigned-sites-list > div:hover > .delete-assignment { display: block; } +.assigned-sites-list > div > .hostname { + flex: 1; +} + .radio-choice > .radio-container { align-items: center; block-size: 29px; diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 6023551..eef83f8 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -783,7 +783,7 @@ Logic.registerPanel(P_CONTAINER_EDIT, { const assignmentKeys = Object.keys(assignments); assignmentPanel.hidden = !(assignmentKeys.length > 0); if (assignments) { - const tableElement = assignmentPanel.querySelector("table > tbody"); + const tableElement = assignmentPanel.querySelector(".assigned-sites-list"); /* Remove previous assignment list, after removing one we rerender the list */ while (tableElement.firstChild) { @@ -791,18 +791,19 @@ Logic.registerPanel(P_CONTAINER_EDIT, { } assignmentKeys.forEach((siteKey) => { const site = assignments[siteKey]; - const trElement = document.createElement("tr"); + const trElement = document.createElement("div"); /* As we don't have the full or correct path the best we can assume is the path is HTTPS and then replace with a broken icon later if it doesn't load. This is pending a better solution for favicons from web extensions */ const assumedUrl = `https://${site.hostname}`; trElement.innerHTML = escaped` - - ${site.hostname} - - `; + +
+ ${site.hostname} +
+ `; const deleteButton = trElement.querySelector(".delete-assignment"); Logic.addEnterHandler(deleteButton, () => { const userContextId = Logic.currentUserContextId(); diff --git a/webextension/popup.html b/webextension/popup.html index fbf03f6..f5c7224 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -127,15 +127,9 @@
From bc847b53f5f276711665b3673e370849d2b27eb2 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Mon, 19 Jun 2017 15:51:29 +0100 Subject: [PATCH 44/69] Making create screen have buttons again --- webextension/background.js | 2 +- webextension/css/popup.css | 24 +++++++++++++++++------- webextension/js/popup.js | 29 ++++++++++++++++++++++++++--- webextension/popup.html | 5 +++++ 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/webextension/background.js b/webextension/background.js index 96fff29..d1935f7 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -339,7 +339,7 @@ const backgroundLogic = { createOrUpdateContainer(options) { let donePromise; - if (options.userContextId) { + if (options.userContextId !== "new") { donePromise = browser.contextualIdentities.update( this.cookieStoreId(options.userContextId), options.params diff --git a/webextension/css/popup.css b/webextension/css/popup.css index db23c24..e86a7d1 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -91,6 +91,10 @@ table { opacity: 0; } +[hidden] { + display: none !important; +} + /* Effect borrowed from tabs in Firefox, ensure that the element flexes to the full width */ .truncate-text { mask-image: linear-gradient(to left, transparent, black 1em); @@ -803,8 +807,14 @@ span ~ .panel-header-text { border: 0; filter: none; inline-size: 23px; - margin: 0; - padding: 0; + margin-block-end: 0; + margin-block-start: 0; + margin-inline-end: 0; + margin-inline-start: 0; + padding-block-end: 0; + padding-block-start: 0; + padding-inline-end: 0; + padding-inline-start: 0; } .radio-choice > .radio-container > label::before { @@ -814,18 +824,18 @@ span ~ .panel-header-text { background-repeat: no-repeat; background-size: 16px; block-size: 23px; - fill: var(--identity-icon-color); - filter: url('/img/filters.svg#fill'); - position: relative; + border: none; content: ""; display: block; - border: none; + fill: var(--identity-icon-color); + filter: url('/img/filters.svg#fill'); inline-size: 23px; + position: relative; } .radio-choice > .radio-container > [type="radio"] { - display: inline; -moz-appearance: none; + display: inline; opacity: 0; } diff --git a/webextension/js/popup.js b/webextension/js/popup.js index eef83f8..4f3e391 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -7,6 +7,7 @@ const CONTAINER_UNHIDE_SRC = "/img/container-unhide.svg"; const DEFAULT_COLOR = "blue"; const DEFAULT_ICON = "circle"; +const NEW_CONTAINER_ID = "new"; // List of panels const P_ONBOARDING_1 = "onboarding1"; @@ -750,10 +751,30 @@ Logic.registerPanel(P_CONTAINER_EDIT, { this.initializeRadioButtons(); Logic.addEnterHandler(document.querySelector("#edit-container-panel-back-arrow"), () => { + const formValues = new FormData(this._editForm); + if (formValues.get("container-id") !== NEW_CONTAINER_ID) { + this._submitForm(); + } else { + Logic.showPreviousPanel(); + } + }); + + Logic.addEnterHandler(document.querySelector("#edit-container-cancel-link"), () => { + Logic.showPreviousPanel(); + }); + + this._editForm = document.getElementById("edit-container-panel-form"); + const editLink = document.querySelector("#edit-container-ok-link"); + Logic.addEnterHandler(editLink, () => { this._submitForm(); }); - this._editForm = document.getElementById("edit-container-panel-form"); - this._editForm.addEventListener("submit", this._submitForm.bind(this)); + editLink.addEventListener("submit", () => { + this._submitForm(); + }); + this._editForm.addEventListener("submit", () => { + this._submitForm(); + }); + }, @@ -762,7 +783,7 @@ Logic.registerPanel(P_CONTAINER_EDIT, { return browser.runtime.sendMessage({ method: "createOrUpdateContainer", message: { - userContextId: Logic.currentUserContextId() || false, + userContextId: formValues.get("container-id") || NEW_CONTAINER_ID, params: { name: document.getElementById("edit-container-panel-name-input").value || Logic.generateIdentityName(), icon: formValues.get("container-icon") || DEFAULT_ICON, @@ -860,8 +881,10 @@ Logic.registerPanel(P_CONTAINER_EDIT, { const userContextId = Logic.currentUserContextId(); const assignments = await Logic.getAssignmentObjectByContainer(userContextId); this.showAssignedContainers(assignments); + document.querySelector("#edit-container-panel .panel-footer").hidden = !!userContextId; document.querySelector("#edit-container-panel-name-input").value = identity.name || ""; + document.querySelector("#edit-container-panel-usercontext-input").value = userContextId || NEW_CONTAINER_ID; [...document.querySelectorAll("[name='container-color']")].forEach(colorInput => { colorInput.checked = colorInput.value === identity.color; }); diff --git a/webextension/popup.html b/webextension/popup.html index f5c7224..d7a930c 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -115,6 +115,7 @@
+
Name @@ -131,6 +132,10 @@
+ From 5237e67fa6a3f12f5e96239c760b7805da06f95a Mon Sep 17 00:00:00 2001 From: groovecoder Date: Wed, 14 Jun 2017 15:46:21 -0500 Subject: [PATCH 45/69] for #498: security onboarding panels and logic --- index.js | 6 +++++ package.json | 3 ++- study.js | 4 +-- webextension/js/popup.js | 54 +++++++++++++++++++++++++++++++--------- webextension/popup.html | 34 ++++++++++++++++++++----- 5 files changed, 79 insertions(+), 22 deletions(-) diff --git a/index.js b/index.js index 937dc61..da9cf8d 100644 --- a/index.js +++ b/index.js @@ -222,6 +222,7 @@ const ContainerService = { "getPreference", "sendTelemetryPayload", "getTheme", + "getShieldStudyVariation", "refreshNeeded", "forgetIdentityAndRefresh", "checkIncompatibleAddons" @@ -332,6 +333,7 @@ const ContainerService = { if (self.id === "@shield-study-containers") { study.startup(reason); + this.shieldStudyVariation = study.variation; } }, @@ -375,6 +377,10 @@ const ContainerService = { }); }, + getShieldStudyVariation() { + return this.shieldStudyVariation; + }, + // utility methods _containerTabCount(userContextId) { diff --git a/package.json b/package.json index f910d9c..e226aad 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ }, "scripts": { "build": "npm test && jpm xpi", - "build-shield": "npm test && json -I -f package.json -e 'this.name=\"shield-study-containers\"' && jpm xpi && json -I -f package.json -e 'this.name=\"testpilot-containers\"'", + "build-shield": "npm test && npm run package-shield", "deploy": "deploy-txp", "lint": "npm-run-all lint:*", "lint:addon": "addons-linter webextension --self-hosted", @@ -51,6 +51,7 @@ "lint:html": "htmllint webextension/*.html", "lint:js": "eslint .", "package": "npm run build && mv testpilot-containers.xpi addon.xpi", + "package-shield": "json -I -f package.json -e 'this.name=\"shield-study-containers\"' && jpm xpi && 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 index 672e5f6..fa2bf58 100644 --- a/study.js +++ b/study.js @@ -15,9 +15,7 @@ const studyConfig = { }, variations: { "control": () => {}, - "privacyOnboarding": () => {}, - "onlineAccountsOnboarding": () => {}, - "tabManagementOnboarding": () => {} + "securityOnboarding": () => {} } }; diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 4f3e391..557d383 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -70,15 +70,19 @@ const Logic = { _currentPanel: null, _previousPanel: null, _panels: {}, + _onboardingVariation: null, init() { // Remove browserAction "upgraded" badge when opening panel this.clearBrowserActionBadge(); // Retrieve the list of identities. - this.refreshIdentities() + const identitiesPromise = this.refreshIdentities(); + // Get the onboarding variation + const variationPromise = this.getShieldStudyVariation(); // Routing to the correct panel. + Promise.all([identitiesPromise, variationPromise]) .then(() => { // If localStorage is disabled, we don't show the onboarding. if (!localStorage || localStorage.getItem("onboarded4")) { @@ -160,6 +164,15 @@ const Logic = { }).catch((e) => {throw e;}); }, + getPanelSelector(panel) { + if (this._onboardingVariation === "securityOnboarding" && + panel.hasOwnProperty("securityPanelSelector")) { + return panel.securityPanelSelector; + } else { + return panel.panelSelector; + } + }, + async showPanel(panel, currentIdentity = null) { // Invalid panel... ?!? if (!(panel in this._panels)) { @@ -175,7 +188,7 @@ const Logic = { await this._panels[panel].prepare(); Object.keys(this._panels).forEach((panelKey) => { const panelItem = this._panels[panelKey]; - const panelElement = document.querySelector(panelItem.panelSelector); + const panelElement = document.querySelector(this.getPanelSelector(panelItem)); if (!panelElement.classList.contains("hide")) { panelElement.classList.add("hide"); if ("unregister" in panelItem) { @@ -183,7 +196,7 @@ const Logic = { } } }); - document.querySelector(this._panels[panel].panelSelector).classList.remove("hide"); + document.querySelector(this.getPanelSelector(this._panels[panel])).classList.remove("hide"); }, showPreviousPanel() { @@ -258,6 +271,14 @@ const Logic = { }); }, + getShieldStudyVariation() { + return browser.runtime.sendMessage({ + method: "getShieldStudyVariation" + }).then(variation => { + this._onboardingVariation = variation; + }); + }, + generateIdentityName() { const defaultName = "Container #"; const ids = []; @@ -286,13 +307,16 @@ const Logic = { Logic.registerPanel(P_ONBOARDING_1, { panelSelector: ".onboarding-panel-1", + securityPanelSelector: ".security-onboarding-panel-1", // This method is called when the object is registered. initialize() { // Let's move to the next panel. - Logic.addEnterHandler(document.querySelector("#onboarding-start-button"), () => { - localStorage.setItem("onboarded1", true); - Logic.showPanel(P_ONBOARDING_2); + [...document.querySelectorAll(".onboarding-start-button")].forEach(startElement => { + Logic.addEnterHandler(startElement, () => { + localStorage.setItem("onboarded1", true); + Logic.showPanel(P_ONBOARDING_2); + }); }); }, @@ -307,13 +331,16 @@ Logic.registerPanel(P_ONBOARDING_1, { Logic.registerPanel(P_ONBOARDING_2, { panelSelector: ".onboarding-panel-2", + securityPanelSelector: ".security-onboarding-panel-2", // This method is called when the object is registered. initialize() { // Let's move to the containers list panel. - Logic.addEnterHandler(document.querySelector("#onboarding-next-button"), () => { - localStorage.setItem("onboarded2", true); - Logic.showPanel(P_ONBOARDING_3); + [...document.querySelectorAll(".onboarding-next-button")].forEach(nextElement => { + Logic.addEnterHandler(nextElement, () => { + localStorage.setItem("onboarded2", true); + Logic.showPanel(P_ONBOARDING_3); + }); }); }, @@ -328,13 +355,16 @@ Logic.registerPanel(P_ONBOARDING_2, { Logic.registerPanel(P_ONBOARDING_3, { panelSelector: ".onboarding-panel-3", + securityPanelSelector: ".security-onboarding-panel-3", // This method is called when the object is registered. initialize() { // Let's move to the containers list panel. - Logic.addEnterHandler(document.querySelector("#onboarding-almost-done-button"), () => { - localStorage.setItem("onboarded3", true); - Logic.showPanel(P_ONBOARDING_4); + [...document.querySelectorAll(".onboarding-almost-done-button")].forEach(almostElement => { + Logic.addEnterHandler(almostElement, () => { + localStorage.setItem("onboarded3", true); + Logic.showPanel(P_ONBOARDING_4); + }); }); }, diff --git a/webextension/popup.html b/webextension/popup.html index d7a930c..ad5ed40 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -7,28 +7,50 @@ -
+
Container Tabs Overview

A better way to manage all the things you do online

Use containers to organize tasks, manage accounts, and keep your focus where you want it.

- Get Started + Get Started
+
+ Container Tabs Overview +

A simple and secure way to manage your online life

+

+ Use containers to organize tasks, manage accounts, and keep your sensitive data where you want it. +

+ Get Started +
-
+
How Containers Work

Put containers to work for you.

Features like color-coding and separate container tabs help you find things easily, focus your attention, and minimize distractions.

- Next + Next
-
+
+ How Containers Work +

Put containers to work for you.

+

Color-coding helps you categorize your online life, find things easily, and minimize distractions.

+ Next +
+ +
How Containers Work

A place for everything, and everything in its place.

Start with the containers we've created, or create your own.

- Next + Next +
+ +
+ How Containers Work +

Use containers to secure everything within.

+

Containers separate cookies and sensitive data from other containers, like surrounded by firewalls to block tracking.

+ Next
From 4ed136299bcbe95bf6aab360484da7b98a80139c Mon Sep 17 00:00:00 2001 From: groovecoder Date: Thu, 15 Jun 2017 11:23:39 -0500 Subject: [PATCH 46/69] replace a .then() with await Promise.all() --- webextension/js/popup.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 557d383..51c6cd6 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -72,7 +72,7 @@ const Logic = { _panels: {}, _onboardingVariation: null, - init() { + async init() { // Remove browserAction "upgraded" badge when opening panel this.clearBrowserActionBadge(); @@ -81,26 +81,26 @@ const Logic = { // Get the onboarding variation const variationPromise = this.getShieldStudyVariation(); - // Routing to the correct panel. - Promise.all([identitiesPromise, variationPromise]) - .then(() => { - // If localStorage is disabled, we don't show the onboarding. - if (!localStorage || localStorage.getItem("onboarded4")) { - this.showPanel(P_CONTAINERS_LIST); - } else if (localStorage.getItem("onboarded3")) { - this.showPanel(P_ONBOARDING_4); - } else if (localStorage.getItem("onboarded2")) { - this.showPanel(P_ONBOARDING_3); - } else if (localStorage.getItem("onboarded1")) { - this.showPanel(P_ONBOARDING_2); - } else { - this.showPanel(P_ONBOARDING_1); - } - }) + try { + await Promise.all([identitiesPromise, variationPromise]); + } catch(e) { + throw new Error("Failed to retrieve the identities or variation. We cannot continue. ", e.message); + } + + // Routing to the correct panel. + // If localStorage is disabled, we don't show the onboarding. + if (!localStorage || localStorage.getItem("onboarded4")) { + this.showPanel(P_CONTAINERS_LIST); + } else if (localStorage.getItem("onboarded3")) { + this.showPanel(P_ONBOARDING_4); + } else if (localStorage.getItem("onboarded2")) { + this.showPanel(P_ONBOARDING_3); + } else if (localStorage.getItem("onboarded1")) { + this.showPanel(P_ONBOARDING_2); + } else { + this.showPanel(P_ONBOARDING_1); + } - .catch(() => { - throw new Error("Failed to retrieve the identities. We cannot continue."); - }); }, async clearBrowserActionBadge() { From af966d6d297767e0528888b7f3c30e49f305f48c Mon Sep 17 00:00:00 2001 From: groovecoder Date: Mon, 19 Jun 2017 13:36:30 -0500 Subject: [PATCH 47/69] for #498 add onboarding-3-security.png asset remove tab.create orientation --- study.js | 11 ----------- webextension/img/onboarding-3-security.png | Bin 0 -> 25006 bytes webextension/popup.html | 2 +- 3 files changed, 1 insertion(+), 12 deletions(-) create mode 100644 webextension/img/onboarding-3-security.png diff --git a/study.js b/study.js index fa2bf58..5862e1c 100644 --- a/study.js +++ b/study.js @@ -4,7 +4,6 @@ const self = require("sdk/self"); const { when: unload } = require("sdk/system/unload"); -const tabs = require("sdk/tabs"); const shield = require("./lib/shield/index"); @@ -25,16 +24,6 @@ class ContainersStudy extends shield.Study { // Test Pilot experiment, so exclude them. return super.isEligible(); } - - whenEligible () { - } - - whenInstalled () { - tabs.open(`data:text/html, Thank you for helping us study Containers in Firefox. You are in the ${this.variation} variation.`); - } - - cleanup() { - } } const thisStudy = new ContainersStudy(studyConfig); diff --git a/webextension/img/onboarding-3-security.png b/webextension/img/onboarding-3-security.png new file mode 100644 index 0000000000000000000000000000000000000000..8bd0205d2407fa74ee1e3af45d634a830a493924 GIT binary patch literal 25006 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 kpwilYXV3KY8VukY<>LEc
- How Containers Work + How Containers Work

Use containers to secure everything within.

Containers separate cookies and sensitive data from other containers, like surrounded by firewalls to block tracking.

Next From 2f5e195c914e6d0c4082a621c125d95e73d14f45 Mon Sep 17 00:00:00 2001 From: groovecoder Date: Mon, 19 Jun 2017 14:15:29 -0500 Subject: [PATCH 48/69] for #498: use local node_modules/.bin/json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e226aad..9c2ab39 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "lint:html": "htmllint webextension/*.html", "lint:js": "eslint .", "package": "npm run build && mv testpilot-containers.xpi addon.xpi", - "package-shield": "json -I -f package.json -e 'this.name=\"shield-study-containers\"' && jpm xpi && json -I -f package.json -e 'this.name=\"testpilot-containers\"'", + "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" From 6292d9b25d825a53d248ad9ec472781701231c44 Mon Sep 17 00:00:00 2001 From: groovecoder Date: Tue, 20 Jun 2017 16:12:42 -0500 Subject: [PATCH 49/69] fix #498: final copy for security onboarding panels --- webextension/popup.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webextension/popup.html b/webextension/popup.html index df7d21c..40e52e1 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -20,7 +20,7 @@ Container Tabs Overview

A simple and secure way to manage your online life

- Use containers to organize tasks, manage accounts, and keep your sensitive data where you want it. + Use containers to organize tasks, manage accounts, and store sensitive data.

Get Started
@@ -48,8 +48,8 @@
How Containers Work -

Use containers to secure everything within.

-

Containers separate cookies and sensitive data from other containers, like surrounded by firewalls to block tracking.

+

Set boundaries for your browsing.

+

Cookies are stored within a container, so you can segment sensitive data and browsing history to stay organized and to limit the impact of online trackers.

Next
From 83e8340a7051291ddb449b27637ea746ad76ddb2 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 22 Jun 2017 13:45:31 +0100 Subject: [PATCH 50/69] Close correct assignment window on confirmation page. Fixes #606 --- webextension/js/confirm-page.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/webextension/js/confirm-page.js b/webextension/js/confirm-page.js index 4077d6b..78e427a 100644 --- a/webextension/js/confirm-page.js +++ b/webextension/js/confirm-page.js @@ -56,8 +56,15 @@ function confirmSubmit(redirectUrl, cookieStoreId) { openInContainer(redirectUrl, cookieStoreId); } +function getCurrentTab() { + return browser.tabs.query({ + active: true, + windowId: browser.windows.WINDOW_ID_CURRENT + }); +} + async function denySubmit(redirectUrl) { - const tab = await browser.tabs.query({active: true}); + const tab = await getCurrentTab(); await browser.runtime.sendMessage({ method: "exemptContainerAssignment", tabId: tab[0].id, @@ -73,12 +80,12 @@ async function denySubmit(redirectUrl) { load(); async function openInContainer(redirectUrl, cookieStoreId) { - const tabs = await browser.tabs.query({active: true}); + const tab = await getCurrentTab(); await browser.tabs.create({ cookieStoreId, url: redirectUrl }); - if (tabs.length > 0) { - browser.tabs.remove(tabs[0].id); + if (tab.length > 0) { + browser.tabs.remove(tab[0].id); } } From 214a83deda02732f9aee7237898e9f521741a439 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 22 Jun 2017 12:09:32 +0100 Subject: [PATCH 51/69] Adding tooltips. Fixes #615. Fixes #609. Fixes #112 --- .gitignore | 1 + index.js | 1 + webextension/js/popup.js | 10 ++++++---- webextension/popup.html | 6 +++--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 6ff00a9..5e16ffe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +package-lock.json node_modules README.html *.xpi diff --git a/index.js b/index.js index da9cf8d..4ebce44 100644 --- a/index.js +++ b/index.js @@ -1213,6 +1213,7 @@ ContainerWindow.prototype = { const menuItemElement = this._window.document.createElementNS(XUL_NS, "menuitem"); this._panelElement.appendChild(menuItemElement); menuItemElement.className = "menuitem-iconic"; + menuItemElement.setAttribute("tooltiptext", identity.name); menuItemElement.setAttribute("label", identity.name); menuItemElement.setAttribute("data-usercontextid", identity.userContextId); menuItemElement.setAttribute("data-identity-icon", identity.icon); diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 51c6cd6..6ec07ad 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -520,7 +520,9 @@ Logic.registerPanel(P_CONTAINERS_LIST, { context.classList.add("userContext-wrapper", "open-newtab", "clickable"); manage.classList.add("show-tabs", "pop-button"); + manage.title = escaped`View ${identity.name} container`; context.setAttribute("tabindex", "0"); + context.title = escaped`Create ${identity.name} tab`; context.innerHTML = escaped`
- ${tab.title}`; + ${tab.title}`; tr.querySelector("td").appendChild(Utils.createFavIconElement(tab.favicon)); // On click, we activate this tab. But only if this tab is active. @@ -741,15 +743,15 @@ Logic.registerPanel(P_CONTAINERS_EDIT, { src="/img/container-edit.svg" class="pop-button-image" /> - + `; tr.querySelector(".container-name").textContent = identity.name; - tr.querySelector(".edit-container .pop-button-image").setAttribute("title", `Edit ${identity.name} container`); - tr.querySelector(".remove-container .pop-button-image").setAttribute("title", `Edit ${identity.name} container`); + tr.querySelector(".edit-container").setAttribute("title", `Edit ${identity.name} container`); + tr.querySelector(".remove-container").setAttribute("title", `Delete ${identity.name} container`); Logic.addEnterHandler(tr, e => { diff --git a/webextension/popup.html b/webextension/popup.html index 40e52e1..bfca0ec 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -73,7 +73,7 @@
@@ -84,8 +84,8 @@ - - Create new container icon + + Create new container icon From 7eb752c2f76fb17c2e5a747673c6d4addc91899e Mon Sep 17 00:00:00 2001 From: groovecoder Date: Thu, 22 Jun 2017 08:39:07 -0500 Subject: [PATCH 52/69] fix #614: only call thisStudy.shutdown in shield --- study.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/study.js b/study.js index 5862e1c..e224f57 100644 --- a/study.js +++ b/study.js @@ -28,6 +28,8 @@ class ContainersStudy extends shield.Study { const thisStudy = new ContainersStudy(studyConfig); -unload((reason) => thisStudy.shutdown(reason)); +if (self.id === "@shield-study-containers") { + unload((reason) => thisStudy.shutdown(reason)); +} exports.study = thisStudy; From e191255c472ef47dd39c8d70f7cf584fa947c019 Mon Sep 17 00:00:00 2001 From: groovecoder Date: Thu, 22 Jun 2017 09:26:30 -0500 Subject: [PATCH 53/69] fix #608: re-"render" badge on window focus change --- webextension/background.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/webextension/background.js b/webextension/background.js index d1935f7..6e19420 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -505,6 +505,11 @@ const messageHandler = { browser.windows.onFocusChanged.addListener((windowId) => { assignManager.removeContextMenu(); + // browserAction loses background color in new windows ... + // https://bugzil.la/1314674 + // https://github.com/mozilla/testpilot-containers/issues/608 + // ... so re-call displayBrowserActionBadge on window changes + displayBrowserActionBadge(); browser.tabs.query({active: true, windowId}).then((tabs) => { if (tabs && tabs[0]) { tabPageCounter.initTabCounter(tabs[0]); From a8cac471255ff75cefbb3103e70ed521fc20908f Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 22 Jun 2017 15:34:36 +0100 Subject: [PATCH 54/69] Fixing alignment of checkbox on confirm screen. Fixes #607 --- webextension/confirm-page.html | 5 ++++- webextension/css/confirm-page.css | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/webextension/confirm-page.html b/webextension/confirm-page.html index 20d11c8..da213f7 100644 --- a/webextension/confirm-page.html +++ b/webextension/confirm-page.html @@ -18,7 +18,10 @@

Would you still like to open in this current container?



- +
diff --git a/webextension/css/confirm-page.css b/webextension/css/confirm-page.css index 24baa7c..3d24c26 100644 --- a/webextension/css/confirm-page.css +++ b/webextension/css/confirm-page.css @@ -71,3 +71,8 @@ dfn { .button-container > button { min-inline-size: 240px; } + +.check-label { + align-items: center; + display: flex; +} From 0566c9f962c59de4405bd716860595cb0873b183 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 22 Jun 2017 14:38:45 +0100 Subject: [PATCH 55/69] Fixing the assignment toggle for when a different container is open. Fixes #611 --- webextension/background.js | 4 +++- webextension/js/popup.js | 17 ++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/webextension/background.js b/webextension/background.js index 6e19420..754fd7c 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -254,7 +254,9 @@ const assignManager = { // ✓ This is to mitigate https://bugzilla.mozilla.org/show_bug.cgi?id=1351418 let prefix = " "; // Alignment of non breaking space, unknown why this requires so many spaces to align with the tick let menuId = this.MENU_ASSIGN_ID; - if (siteSettings) { + const tabUserContextId = this.getUserContextIdFromCookieStore(tab); + if (siteSettings && + Number(siteSettings.userContextId) === Number(tabUserContextId)) { prefix = "✓"; menuId = this.MENU_REMOVE_ID; } diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 6ec07ad..bd371d2 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -467,10 +467,13 @@ Logic.registerPanel(P_CONTAINERS_LIST, { browser.tabs.onUpdated.removeListener(this.tabUpdateHandler); }, - setupAssignmentCheckbox(siteSettings) { + setupAssignmentCheckbox(siteSettings, currentUserContextId) { const assignmentCheckboxElement = document.getElementById("container-page-assigned"); - // Cater for null and false - assignmentCheckboxElement.checked = !!siteSettings; + let checked = false; + if (siteSettings && Number(siteSettings.userContextId) === currentUserContextId) { + checked = true; + } + assignmentCheckboxElement.checked = checked; let disabled = false; if (siteSettings === false) { disabled = true; @@ -482,16 +485,16 @@ Logic.registerPanel(P_CONTAINERS_LIST, { const currentTab = await Logic.currentTab(); const currentTabElement = document.getElementById("current-tab"); const assignmentCheckboxElement = document.getElementById("container-page-assigned"); + const currentTabUserContextId = Logic.userContextId(currentTab.cookieStoreId); assignmentCheckboxElement.addEventListener("change", () => { - const userContextId = Logic.userContextId(currentTab.cookieStoreId); - Logic.setOrRemoveAssignment(currentTab.id, currentTab.url, userContextId, !assignmentCheckboxElement.checked); + Logic.setOrRemoveAssignment(currentTab.id, currentTab.url, currentTabUserContextId, !assignmentCheckboxElement.checked); }); currentTabElement.hidden = !currentTab; - this.setupAssignmentCheckbox(false); + this.setupAssignmentCheckbox(false, currentTabUserContextId); if (currentTab) { const identity = await Logic.identity(currentTab.cookieStoreId); const siteSettings = await Logic.getAssignment(currentTab); - this.setupAssignmentCheckbox(siteSettings); + this.setupAssignmentCheckbox(siteSettings, currentTabUserContextId); const currentPage = document.getElementById("current-page"); currentPage.innerHTML = escaped`${currentTab.title}`; const favIconElement = Utils.createFavIconElement(currentTab.favIconUrl || ""); From e28b25e04ce44fb328d699ea3b53d7b08f4e4713 Mon Sep 17 00:00:00 2001 From: groovecoder Date: Thu, 22 Jun 2017 15:44:55 -0500 Subject: [PATCH 56/69] fix #626: cast message userContextId to string --- webextension/background.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/webextension/background.js b/webextension/background.js index 754fd7c..9ac0f85 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -197,6 +197,12 @@ const assignManager = { async _setOrRemoveAssignment(tabId, pageUrl, userContextId, remove) { let actionName; + + // https://github.com/mozilla/testpilot-containers/issues/626 + // Context menu has stored context IDs as strings, so we need to coerce + // the value to a string for accurate checking + userContextId = String(userContextId); + if (!remove) { await this.storageArea.set(pageUrl, { userContextId, From a86bcf79831cbe4f13cc4948d94a3a25993b3994 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 22 Jun 2017 20:04:27 +0100 Subject: [PATCH 57/69] Fix different display in content warning notice. Fixes #630 --- webextension/css/content.css | 6 +++++- webextension/js/content-script.js | 11 ----------- webextension/manifest.json | 3 ++- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/webextension/css/content.css b/webextension/css/content.css index 885b0fb..f3d764b 100644 --- a/webextension/css/content.css +++ b/webextension/css/content.css @@ -1,8 +1,10 @@ .container-notification { background: #33f70c; color: #003f07; - display: block; + display: flex; + font: 12px sans-serif; inline-size: 100vw; + justify-content: start; offset-block-start: 0; offset-inline-start: 0; padding-block-end: 8px; @@ -10,6 +12,7 @@ padding-inline-end: 8px; padding-inline-start: 8px; position: fixed; + text-align: start; transform: translateY(-100%); transition: transform 0.3s cubic-bezier(0.07, 0.95, 0, 1) 0.3s; z-index: 999999999999; @@ -17,6 +20,7 @@ .container-notification img { block-size: 16px; + display: inline-block; inline-size: 16px; margin-inline-end: 3px; } diff --git a/webextension/js/content-script.js b/webextension/js/content-script.js index ef6b4c4..bd3aa43 100644 --- a/webextension/js/content-script.js +++ b/webextension/js/content-script.js @@ -16,17 +16,6 @@ async function doAnimation(element, property, value) { }); }); } -/* -async function awaitEvent(eventName) { - return new Promise((resolve) => { - const handler = () => { - resolve(); - divElement.removeEventListener(eventName, handler); - }; - divElement.addEventListener(eventName, handler); - }); -} -*/ async function addMessage(message) { const divElement = document.createElement("div"); diff --git a/webextension/manifest.json b/webextension/manifest.json index d700e15..e0ddc0e 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -59,7 +59,8 @@ { "matches": [""], "js": ["js/content-script.js"], - "css": ["css/content.css"] + "css": ["css/content.css"], + "run_at": "document_start" } ], From 10e83d37957ed632fa9e4e55ecbf7e162e1b1999 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 22 Jun 2017 22:22:45 +0100 Subject: [PATCH 58/69] Aligning items to center for container notification. --- webextension/css/content.css | 1 + 1 file changed, 1 insertion(+) diff --git a/webextension/css/content.css b/webextension/css/content.css index f3d764b..6fb2681 100644 --- a/webextension/css/content.css +++ b/webextension/css/content.css @@ -1,4 +1,5 @@ .container-notification { + align-items: center; background: #33f70c; color: #003f07; display: flex; From d0037d1377f441d9492bce001a5d43f84e4441ad Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 23 Jun 2017 12:50:47 +0100 Subject: [PATCH 59/69] Cleanup exemption code. On assignment set/remove clear exemptions and set based on existing tabs. Fixes #635 --- webextension/background.js | 47 +++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/webextension/background.js b/webextension/background.js index 9ac0f85..a4e5d3d 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -15,19 +15,24 @@ const assignManager = { }, setExempted(pageUrl, tabId) { - if (!(tabId in this.exemptedTabs)) { - this.exemptedTabs[tabId] = []; - } const siteStoreKey = this.getSiteStoreKey(pageUrl); - this.exemptedTabs[tabId].push(siteStoreKey); + if (!(siteStoreKey in this.exemptedTabs)) { + this.exemptedTabs[siteStoreKey] = []; + } + this.exemptedTabs[siteStoreKey].push(tabId); + }, + + removeExempted(pageUrl) { + const siteStoreKey = this.getSiteStoreKey(pageUrl); + this.exemptedTabs[siteStoreKey] = []; }, isExempted(pageUrl, tabId) { - if (!(tabId in this.exemptedTabs)) { + const siteStoreKey = this.getSiteStoreKey(pageUrl); + if (!(siteStoreKey in this.exemptedTabs)) { return false; } - const siteStoreKey = this.getSiteStoreKey(pageUrl); - return this.exemptedTabs[tabId].includes(siteStoreKey); + return this.exemptedTabs[siteStoreKey].includes(tabId); }, get(pageUrl) { @@ -44,8 +49,13 @@ const assignManager = { }); }, - set(pageUrl, data) { + set(pageUrl, data, exemptedTabIds) { const siteStoreKey = this.getSiteStoreKey(pageUrl); + if (exemptedTabIds) { + exemptedTabIds.forEach((tabId) => { + this.setExempted(pageUrl, tabId); + }); + } return this.area.set({ [siteStoreKey]: data }); @@ -53,6 +63,8 @@ const assignManager = { remove(pageUrl) { const siteStoreKey = this.getSiteStoreKey(pageUrl); + // When we remove an assignment we should clear all the exemptions + this.removeExempted(pageUrl); return this.area.remove([siteStoreKey]); }, @@ -204,11 +216,24 @@ const assignManager = { userContextId = String(userContextId); if (!remove) { + const tabs = await browser.tabs.query({}); + const assignmentStoreKey = this.storageArea.getSiteStoreKey(pageUrl); + const exemptedTabIds = tabs.filter((tab) => { + const tabStoreKey = this.storageArea.getSiteStoreKey(tab.url); + /* Auto exempt all tabs that exist for this hostname that are not in the same container */ + if (tabStoreKey === assignmentStoreKey && + this.getUserContextIdFromCookieStore(tab) !== userContextId) { + return true; + } + return false; + }).map((tab) => { + return tab.id; + }); + await this.storageArea.set(pageUrl, { userContextId, - neverAsk: false, - exempted: [] - }); + neverAsk: false + }, exemptedTabIds); actionName = "added"; } else { await this.storageArea.remove(pageUrl); From 30c55e093c029325128d4017b1bad8aa27b59b48 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 23 Jun 2017 14:22:26 +0100 Subject: [PATCH 60/69] Add a code of conduct and contributing file. --- CODE_OF_CONDUCT.md | 3 +++ CONTRIBUTING.md | 35 +++++++++++++++++++++++++++++++++++ README.md | 8 ++++++-- 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..2fb0cb6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code Of Conduct + +This add-on follows the [Mozilla Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/) for our code of conduct. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0681439 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contributing + +Everyone is welcome to contribute to containers. Reach out to team members if you have questions: + +- IRC: #containers on irc.mozilla.org +- Email: containers@mozilla.com + +## Filing bugs + +If you find a bug with containers, please file a issue. + +Check first if the bug might already exist: https://github.com/mozilla/testpilot-containers/issues + +[Open an issue](https://github.com/mozilla/testpilot-containers/issues/new) + +1. Visit about:support +2. Click "Copy raw data to clipboard" and paste into the bug. Alternatively copy the following sections into the issue: + - Application Basics + - Nightly Features (if you are in nightly) + - Extensions + - Experimental Features +3. Include clear steps to reproduce the issue you have experienced. +4. Include screenshots if possible. + +## Sending Pull Requests + +Patches should be submitted as pull requests. When submitting patches as PRs: + +- You agree to license your code under the project's open source license (MPL 2.0). +- Base your branch off the current master (see below for an example workflow). +- Add both your code and new tests if relevant. +- Run npm test to make sure all tests still pass. +- Please do not include merge commits in pull requests; include only commits with the new relevant code. + +See the main [README](./README.md) for information on prerequisites, installing, running and testing. diff --git a/README.md b/README.md index c3aa7ed..027dc58 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,6 @@ Check out the [Browser Toolbox](https://developer.mozilla.org/en-US/docs/Tools/B 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 @@ -79,6 +78,11 @@ add-on](https://addons.mozilla.org/en-US/developers/addon/containers-experiment/ ### Testing TBD - ### Distributing TBD + +### Links + +- [Licence](./LICENSE.txt) +- [Contributing](./CONTRIBUTING.md) +- [Code Of Conduct](./CODE_OF_CONDUCT.md) From 1c38e09dcc5de4c0ae2631069dd77db7d2444584 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 29 Jun 2017 09:42:41 -0700 Subject: [PATCH 61/69] Changing the notification colour to be less alien --- webextension/css/content.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webextension/css/content.css b/webextension/css/content.css index 6fb2681..5681887 100644 --- a/webextension/css/content.css +++ b/webextension/css/content.css @@ -1,6 +1,6 @@ .container-notification { align-items: center; - background: #33f70c; + background: #efefef; color: #003f07; display: flex; font: 12px sans-serif; From 63025ab3d69608a779834787f9bc0b0854c01b90 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 29 Jun 2017 09:59:53 -0700 Subject: [PATCH 62/69] Removal of hover menu for plus menu --- index.js | 131 ------------------------------------------------------- 1 file changed, 131 deletions(-) diff --git a/index.js b/index.js index 4ebce44..9830880 100644 --- a/index.js +++ b/index.js @@ -6,9 +6,6 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; const DEFAULT_TAB = "about:newtab"; const LOOKUP_KEY = "$ref"; -const SHOW_MENU_TIMEOUT = 100; -const HIDE_MENU_TIMEOUT = 300; - const INCOMPATIBLE_ADDON_IDS = [ "pulse@mozilla.com", "snoozetabs@mozilla.com", @@ -941,12 +938,6 @@ const ContainerService = { return this._configureWindows(); }, - _hideAllPanels() { - for (let windowObject of this._windowMap.values()) { // eslint-disable-line prefer-const - windowObject.hidePanel(); - } - }, - _restyleActiveTab(tab) { if (!tab) { return Promise.resolve(null); @@ -1105,22 +1096,17 @@ ContainerWindow.prototype = { _timeoutStore: new Map(), _elementCache: new Map(), _tooltipCache: new Map(), - _plusButton: null, - _overflowPlusButton: null, _tabsElement: null, _init(window) { this._window = window; this._tabsElement = this._window.document.getElementById("tabbrowser-tabs"); - this._plusButton = this._window.document.getAnonymousElementByAttribute(this._tabsElement, "anonid", "tabs-newtab-button"); - this._overflowPlusButton = this._window.document.getElementById("new-tab-button"); this._style = Style({ uri: self.data.url("usercontext.css") }); attachTo(this._style, this._window); }, configure() { return Promise.all([ - this._configurePlusButtonMenu(), this._configureActiveTab(), this._configureFileMenu(), this._configureAllTabsMenu(), @@ -1133,113 +1119,6 @@ ContainerWindow.prototype = { return this._configureContextMenu(); }, - handleEvent(e) { - let el = e.target; - switch (e.type) { - case "mouseover": - this._createTimeout("show", () => { - this.showPopup(el); - }, SHOW_MENU_TIMEOUT); - break; - case "click": - this.hidePanel(); - break; - case "mouseout": - while(el) { - if (el === this._panelElement || - el === this._plusButton || - el === this._overflowPlusButton) { - // Clear show timeout so we don't hide and reshow - this._cleanTimeout("show"); - this._createTimeout("hidden", () => { - this.hidePanel(); - }, HIDE_MENU_TIMEOUT); - return; - } - el = el.parentElement; - } - break; - } - }, - - showPopup(buttonElement) { - this._cleanAllTimeouts(); - this._panelElement.openPopup(buttonElement); - }, - - _configurePlusButtonMenuElement(buttonElement) { - if (buttonElement) { - // Let's remove the tooltip because it can go over our panel. - this._tooltipCache.set(buttonElement, buttonElement.getAttribute("tooltip")); - buttonElement.setAttribute("tooltip", ""); - this._disableElement(buttonElement); - - buttonElement.addEventListener("mouseover", this); - buttonElement.addEventListener("click", this); - buttonElement.addEventListener("mouseout", this); - } - }, - - async _configurePlusButtonMenu() { - const mainPopupSetElement = this._window.document.getElementById("mainPopupSet"); - - // Let's remove all the previous panels. - if (this._panelElement) { - this._panelElement.remove(); - } - - this._panelElement = this._window.document.createElementNS(XUL_NS, "panel"); - this._panelElement.setAttribute("id", "new-tab-overlay"); - this._panelElement.setAttribute("position", "bottomcenter topleft"); - this._panelElement.setAttribute("side", "top"); - this._panelElement.setAttribute("flip", "side"); - this._panelElement.setAttribute("type", "arrow"); - this._panelElement.setAttribute("animate", "open"); - this._panelElement.setAttribute("consumeoutsideclicks", "never"); - mainPopupSetElement.appendChild(this._panelElement); - - this._configurePlusButtonMenuElement(this._plusButton); - this._configurePlusButtonMenuElement(this._overflowPlusButton); - - this._panelElement.addEventListener("mouseout", this); - - this._panelElement.addEventListener("mouseover", () => { - this._cleanAllTimeouts(); - }); - - try { - const identities = await ContainerService.queryIdentities(); - identities.forEach(identity => { - const menuItemElement = this._window.document.createElementNS(XUL_NS, "menuitem"); - this._panelElement.appendChild(menuItemElement); - menuItemElement.className = "menuitem-iconic"; - menuItemElement.setAttribute("tooltiptext", identity.name); - menuItemElement.setAttribute("label", identity.name); - menuItemElement.setAttribute("data-usercontextid", identity.userContextId); - menuItemElement.setAttribute("data-identity-icon", identity.icon); - menuItemElement.setAttribute("data-identity-color", identity.color); - - menuItemElement.addEventListener("command", (e) => { - ContainerService.openTab({ - userContextId: identity.userContextId, - source: "tab-bar" - }); - e.stopPropagation(); - }); - - menuItemElement.addEventListener("mouseover", () => { - this._cleanAllTimeouts(); - }); - - menuItemElement.addEventListener("mouseout", this); - - this._panelElement.appendChild(menuItemElement); - }); - } catch (e) { - this.hidePanel(); - } - }, - _configureTabStyle() { const promises = []; for (let tab of modelFor(this._window).tabs) { // eslint-disable-line prefer-const @@ -1409,11 +1288,6 @@ ContainerWindow.prototype = { } }, - hidePanel() { - this._cleanAllTimeouts(); - this._panelElement.hidePopup(); - }, - shutdown() { // CSS must be removed. detachFrom(this._style, this._window); @@ -1437,11 +1311,6 @@ ContainerWindow.prototype = { } }, - _shutdownPlusButtonMenu() { - this._shutDownPlusButtonMenuElement(this._plusButton); - this._shutDownPlusButtonMenuElement(this._overflowPlusButton); - }, - _shutdownFileMenu() { this._shutdownMenu("menu_newUserContext"); }, From da39d18ce05712b8552e0c1ba0262be0bf2b63df Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 29 Jun 2017 10:00:54 -0700 Subject: [PATCH 63/69] Increasing notification timeout --- webextension/js/content-script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webextension/js/content-script.js b/webextension/js/content-script.js index bd3aa43..8485d30 100644 --- a/webextension/js/content-script.js +++ b/webextension/js/content-script.js @@ -31,7 +31,7 @@ async function addMessage(message) { await delayAnimation(100); await doAnimation(divElement, "transform", "translateY(0)"); - await delayAnimation(2000); + await delayAnimation(3000); await doAnimation(divElement, "transform", "translateY(-100%)"); divElement.remove(); From e7ac72a6a2e3b86bf714e0051d6479ca63688d70 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 29 Jun 2017 13:46:06 -0700 Subject: [PATCH 64/69] Add new telemetry to the old plus button menu --- docs/metrics.md | 14 ++++++++++++-- index.js | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/docs/metrics.md b/docs/metrics.md index aefe38e..317ff4e 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -68,6 +68,16 @@ Containers will use Test Pilot Telemetry with no batching of data. Details of when pings are sent are below, along with examples of the `payload` portion of a `testpilottest` telemetry ping for each scenario. +* The user shows the new tab menu + +```js + { + "uuid": , + "event": "show-plus-button-menu", + "eventSource": ["plus-button"] + } +``` + * The user clicks on a container name to open a tab in that container ```js @@ -76,7 +86,7 @@ of a `testpilottest` telemetry ping for each scenario. "userContextId": , "clickedContainerTabCount": , "event": "open-tab", - "eventSource": ["tab-bar"|"pop-up"|"file-menu"|"alltabs-menu"] + "eventSource": ["tab-bar"|"pop-up"|"file-menu"|"alltabs-menu"|"plus-button"] } ``` @@ -269,7 +279,7 @@ local schema = { ### Valid data should be enforced on the server side: -* `eventSource` should be one of `tab-bar`, `pop-up`, or `file-menu`. +* `eventSource` should be one of `tab-bar`, `pop-up`, `file-menu`, "alltabs-nmenu" or "plus-button". All Mozilla data is kept by default for 180 days and in accordance with our privacy policies. diff --git a/index.js b/index.js index 9830880..aefc544 100644 --- a/index.js +++ b/index.js @@ -243,18 +243,15 @@ const ContainerService = { } tabs.on("open", tab => { - this._hideAllPanels(); this._restyleTab(tab); this._remapTab(tab); }); tabs.on("close", tab => { - this._hideAllPanels(); this._remapTab(tab); }); tabs.on("activate", tab => { - this._hideAllPanels(); this._restyleActiveTab(tab).catch(() => {}); this._configureActiveWindows(); this._remapTab(tab); @@ -1102,9 +1099,42 @@ ContainerWindow.prototype = { 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", () => { + ContainerService.sendTelemetryPayload({ + "event": "show-plus-button-menu", + "eventSource": source + }); + 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(), From 315c75f2ac4807eb8f8c60747a79edd427fdbc09 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 5 Jul 2017 13:08:33 -0700 Subject: [PATCH 65/69] Fix non nightly long press menu. Fixes #658 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index aefc544..afdfa61 100644 --- a/index.js +++ b/index.js @@ -49,6 +49,7 @@ const IDENTITY_ICONS_STANDARD = [ const PREFS = [ [ "privacy.userContext.enabled", true ], + [ "privacy.userContext.longPressBehavior", 2 ], [ "privacy.userContext.ui.enabled", false ], [ "privacy.usercontext.about_newtab_segregation.enabled", true ], ]; @@ -1322,7 +1323,6 @@ ContainerWindow.prototype = { // CSS must be removed. detachFrom(this._style, this._window); - this._shutdownPlusButtonMenu(); this._shutdownFileMenu(); this._shutdownAllTabsMenu(); this._shutdownContextMenu(); From 69f06f96cc828ed0e6c4ff43584dde11583db259 Mon Sep 17 00:00:00 2001 From: groovecoder Date: Wed, 5 Jul 2017 13:27:59 -0500 Subject: [PATCH 66/69] add onboarding panel for long-press --- package.json | 2 +- webextension/background.js | 2 +- webextension/js/popup.js | 26 +++++++++++++++++++++++++- webextension/manifest.json | 2 +- webextension/popup.html | 9 ++++++++- 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9c2ab39..6311822 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": "2.3.0", + "version": "2.4.0", "author": "Andrea Marchesini, Luke Crouch and Jonathan Kingston", "bugs": { "url": "https://github.com/mozilla/testpilot-containers/issues" diff --git a/webextension/background.js b/webextension/background.js index a4e5d3d..2884dfd 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -1,4 +1,4 @@ -const MAJOR_VERSIONS = ["2.3.0"]; +const MAJOR_VERSIONS = ["2.3.0", "2.4.0"]; const LOOKUP_KEY = "$ref"; const assignManager = { diff --git a/webextension/js/popup.js b/webextension/js/popup.js index bd371d2..0387aef 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -14,6 +14,7 @@ const P_ONBOARDING_1 = "onboarding1"; const P_ONBOARDING_2 = "onboarding2"; const P_ONBOARDING_3 = "onboarding3"; const P_ONBOARDING_4 = "onboarding4"; +const P_ONBOARDING_5 = "onboarding5"; const P_CONTAINERS_LIST = "containersList"; const P_CONTAINERS_EDIT = "containersEdit"; const P_CONTAINER_INFO = "containerInfo"; @@ -89,8 +90,10 @@ const Logic = { // Routing to the correct panel. // If localStorage is disabled, we don't show the onboarding. - if (!localStorage || localStorage.getItem("onboarded4")) { + if (!localStorage || localStorage.getItem("onboarded5")) { this.showPanel(P_CONTAINERS_LIST); + } else if (localStorage.getItem("onboarded4")) { + this.showPanel(P_ONBOARDING_5); } else if (localStorage.getItem("onboarded3")) { this.showPanel(P_ONBOARDING_4); } else if (localStorage.getItem("onboarded2")) { @@ -385,6 +388,27 @@ Logic.registerPanel(P_ONBOARDING_4, { // Let's move to the containers list panel. document.querySelector("#onboarding-done-button").addEventListener("click", () => { localStorage.setItem("onboarded4", true); + Logic.showPanel(P_ONBOARDING_5); + }); + }, + + // This method is called when the panel is shown. + prepare() { + return Promise.resolve(null); + }, +}); + +// P_ONBOARDING_5: Fifth page for Onboarding: new tab long-press behavior +// ---------------------------------------------------------------------------- + +Logic.registerPanel(P_ONBOARDING_5, { + panelSelector: ".onboarding-panel-5", + + // This method is called when the object is registered. + initialize() { + // Let's move to the containers list panel. + document.querySelector("#onboarding-longpress-button").addEventListener("click", () => { + localStorage.setItem("onboarded5", true); Logic.showPanel(P_CONTAINERS_LIST); }); }, diff --git a/webextension/manifest.json b/webextension/manifest.json index e0ddc0e..c2413a9 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Containers Experiment", - "version": "2.3.0", + "version": "2.4.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": { diff --git a/webextension/popup.html b/webextension/popup.html index bfca0ec..8123f97 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -57,7 +57,14 @@ How to assign sites to containers

Always open sites in the containers you want.

Right-click inside a container tab to assign the site to always open in the container.

- Done + Next +
+ +
+ Long-press the New Tab button to create a new container tab. +

Container tabs when you need them.

+

Long-press the New Tab button to create a new container tab.

+ Done
From e0abaa67e2fcea54d259842f030eff0d4ce52d6d Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 5 Jul 2017 16:07:24 -0700 Subject: [PATCH 67/69] Using browser.storage.local for onboarding stage --- webextension/js/popup.js | 78 ++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 0387aef..1006dab 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -9,6 +9,8 @@ const DEFAULT_COLOR = "blue"; const DEFAULT_ICON = "circle"; const NEW_CONTAINER_ID = "new"; +const ONBOARDING_STORAGE_KEY = "onboarding-stage"; + // List of panels const P_ONBOARDING_1 = "onboarding1"; const P_ONBOARDING_2 = "onboarding2"; @@ -90,20 +92,52 @@ const Logic = { // Routing to the correct panel. // If localStorage is disabled, we don't show the onboarding. - if (!localStorage || localStorage.getItem("onboarded5")) { - this.showPanel(P_CONTAINERS_LIST); - } else if (localStorage.getItem("onboarded4")) { - this.showPanel(P_ONBOARDING_5); - } else if (localStorage.getItem("onboarded3")) { - this.showPanel(P_ONBOARDING_4); - } else if (localStorage.getItem("onboarded2")) { - this.showPanel(P_ONBOARDING_3); - } else if (localStorage.getItem("onboarded1")) { - this.showPanel(P_ONBOARDING_2); - } else { - this.showPanel(P_ONBOARDING_1); + const data = await browser.storage.local.get([ONBOARDING_STORAGE_KEY]); + let onboarded = data[ONBOARDING_STORAGE_KEY]; + if (!onboarded) { + // Legacy local storage used before panel 5 + if (localStorage.getItem("onboarded4")) { + onboarded = 4; + } else if (localStorage.getItem("onboarded3")) { + onboarded = 3; + } else if (localStorage.getItem("onboarded2")) { + onboarded = 2; + } else if (localStorage.getItem("onboarded1")) { + onboarded = 1; + } else { + onboarded = 0; + } + this.setOnboardingStage(onboarded); } + switch (onboarded) { + case 5: + this.showPanel(P_CONTAINERS_LIST); + break; + case 4: + this.showPanel(P_ONBOARDING_5); + break; + case 3: + this.showPanel(P_ONBOARDING_4); + break; + case 2: + this.showPanel(P_ONBOARDING_3); + break; + case 1: + this.showPanel(P_ONBOARDING_2); + break; + case 0: + default: + this.showPanel(P_ONBOARDING_1); + break; + } + + }, + + setOnboardingStage(stage) { + return browser.storage.local.set({ + [ONBOARDING_STORAGE_KEY]: stage + }); }, async clearBrowserActionBadge() { @@ -316,8 +350,8 @@ Logic.registerPanel(P_ONBOARDING_1, { initialize() { // Let's move to the next panel. [...document.querySelectorAll(".onboarding-start-button")].forEach(startElement => { - Logic.addEnterHandler(startElement, () => { - localStorage.setItem("onboarded1", true); + Logic.addEnterHandler(startElement, async function () { + await Logic.setOnboardingStage(1); Logic.showPanel(P_ONBOARDING_2); }); }); @@ -340,8 +374,8 @@ Logic.registerPanel(P_ONBOARDING_2, { initialize() { // Let's move to the containers list panel. [...document.querySelectorAll(".onboarding-next-button")].forEach(nextElement => { - Logic.addEnterHandler(nextElement, () => { - localStorage.setItem("onboarded2", true); + Logic.addEnterHandler(nextElement, async function () { + await Logic.setOnboardingStage(2); Logic.showPanel(P_ONBOARDING_3); }); }); @@ -364,8 +398,8 @@ Logic.registerPanel(P_ONBOARDING_3, { initialize() { // Let's move to the containers list panel. [...document.querySelectorAll(".onboarding-almost-done-button")].forEach(almostElement => { - Logic.addEnterHandler(almostElement, () => { - localStorage.setItem("onboarded3", true); + Logic.addEnterHandler(almostElement, async function () { + await Logic.setOnboardingStage(3); Logic.showPanel(P_ONBOARDING_4); }); }); @@ -386,8 +420,8 @@ Logic.registerPanel(P_ONBOARDING_4, { // This method is called when the object is registered. initialize() { // Let's move to the containers list panel. - document.querySelector("#onboarding-done-button").addEventListener("click", () => { - localStorage.setItem("onboarded4", true); + Logic.addEnterHandler(document.querySelector("#onboarding-done-button"), async function () { + await Logic.setOnboardingStage(4); Logic.showPanel(P_ONBOARDING_5); }); }, @@ -407,8 +441,8 @@ Logic.registerPanel(P_ONBOARDING_5, { // This method is called when the object is registered. initialize() { // Let's move to the containers list panel. - document.querySelector("#onboarding-longpress-button").addEventListener("click", () => { - localStorage.setItem("onboarded5", true); + Logic.addEnterHandler(document.querySelector("#onboarding-longpress-button"), async function () { + await Logic.setOnboardingStage(5); Logic.showPanel(P_CONTAINERS_LIST); }); }, From 080e9dd22ddb0b4270f07be251e012de22b5a451 Mon Sep 17 00:00:00 2001 From: groovecoder Date: Tue, 11 Jul 2017 10:26:46 -0500 Subject: [PATCH 68/69] add surveyUrl for shield study participants --- study.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/study.js b/study.js index e224f57..df59555 100644 --- a/study.js +++ b/study.js @@ -7,10 +7,15 @@ 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": () => {}, From 366f9ec0478f8fee738ded85851c64ef6d6b15dc Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 13 Jul 2017 21:42:07 +0100 Subject: [PATCH 69/69] Adding a keyboard shortcut that will work on MacOS. Ctrl+. --- webextension/manifest.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webextension/manifest.json b/webextension/manifest.json index c2413a9..7722b43 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -35,7 +35,8 @@ "commands": { "_execute_browser_action": { "suggested_key": { - "default": "Ctrl+Period" + "default": "Ctrl+Period", + "mac": "MacCtrl+Period" }, "description": "Open containers panel" }