Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ddee7f9d0 | |||
| bea201a389 | |||
| d944116e3e | |||
| 4a1597c87f | |||
| f87bf2a861 | |||
| ef45cde290 | |||
| 752b1c3b27 | |||
| cf26d8547a | |||
| 1d78febafc | |||
| f483119a40 | |||
| abd2b73fca | |||
| 31ac365e6d | |||
| df8471a4dd | |||
| 18539f2540 | |||
| 7c1105a2b7 | |||
| 31298146f3 | |||
| 4e6eee220c | |||
| a7be3c9935 | |||
| f512473986 | |||
| 8166a37722 |
@@ -3,8 +3,9 @@ package-lock.json
|
||||
node_modules
|
||||
README.html
|
||||
*.xpi
|
||||
*.swp
|
||||
*.swo
|
||||
*.sw*
|
||||
.vimrc
|
||||
.env
|
||||
addon.env
|
||||
|
||||
src/web-ext-artifacts/*
|
||||
|
||||
@@ -19,3 +19,4 @@ bin/
|
||||
.vimrc
|
||||
.DS_Store
|
||||
.gdb_history
|
||||
*.sw*
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
"extends": "stylelint-config-standard",
|
||||
|
||||
"ignoreFiles": ["webextension/css/*.min.css"],
|
||||
"ignoreFiles": ["src/css/*.min.css"],
|
||||
|
||||
"rules": {
|
||||
"declaration-block-no-duplicate-properties": true,
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
# Multi-Account Containers
|
||||
|
||||
|
||||
The Firefox Multi-Account Containers extension lets you carve out a separate box for each of your online lives – no more opening a different browser just to check your work email! [Learn More Here](https://blog.mozilla.org/firefox/introducing-firefox-multi-account-containers/)
|
||||
|
||||
[Available on addons.mozilla.org](https://addons.mozilla.org/en-GB/firefox/addon/multi-account-containers/)
|
||||
|
||||
**Note:** Firefox 57 + 58 users should Install from our [latest GitHub Release](https://github.com/mozilla/testpilot-containers/releases/latest)
|
||||
|
||||
For more info, see:
|
||||
|
||||
* [Test Pilot Product Hypothesis Document](https://docs.google.com/document/d/1WQdHTVXROk7dYkSFluc6_hS44tqZjIrG9I-uPyzevE8/edit#)
|
||||
@@ -16,85 +13,44 @@ For more info, see:
|
||||
## Requirements
|
||||
|
||||
* node 7+ (for jpm)
|
||||
* Firefox 53+
|
||||
* Firefox 57+
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
### Web Extension Development
|
||||
|
||||
Since Firefox 57, this extension can now be run without any of the legacy components that were previously needed.
|
||||
|
||||
1. Install web-ext with npm
|
||||
2. cd webextension; web-ext run -f Nightly
|
||||
|
||||
This will work in other builds of Firefox however certain features won't work and you will need to manually flip preferences to enable containers. All other sections of this guide talk about using the legacy setup with jpm.
|
||||
|
||||
|
||||
## Legacy 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:
|
||||
|
||||
#### 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):
|
||||
|
||||
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
|
||||
|
||||
#### Correct prefs
|
||||
|
||||
Whilst this is still using legacy code to test you will need the following in your profile:
|
||||
|
||||
Change the following prefs in about:config:
|
||||
|
||||
- extensions.legacy.enabled = true
|
||||
- xpinstall.signatures.required = false
|
||||
|
||||
|
||||
#### Run the TxP experiment with `jpm`
|
||||
|
||||
1. `git clone git@github.com:mozilla/testpilot-containers.git`
|
||||
2. `cd testpilot-containers`
|
||||
3. `npm install`
|
||||
4. `./node_modules/.bin/jpm run -p /Path/To/Firefox/Profiles/{junk}.addon_dev -b FirefoxBeta` (where FirefoxBeta might be: ~/<reponame>/obj-x86_64-pc-linux-gnu/dist/bin/firefox or ~/<downloadedFirefoxBeta>/firefox)
|
||||
|
||||
Check out the [Browser Toolbox](https://developer.mozilla.org/en-US/docs/Tools/Browser_Toolbox) for more information about debugging add-on code.
|
||||
|
||||
### Building .xpi
|
||||
|
||||
To build a local testpilot-containers.xpi, use the plain [`jpm
|
||||
xpi`](https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/jpm#jpm_xpi) command,
|
||||
or run `npm run build`.
|
||||
|
||||
### Signing an .xpi
|
||||
|
||||
To sign an .xpi, use [`jpm
|
||||
sign`](https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/jpm#jpm_sign)
|
||||
command.
|
||||
|
||||
Note: You will need to be [an author on the AMO
|
||||
add-on](https://addons.mozilla.org/en-US/developers/addon/containers-experiment/ownership).
|
||||
1. `npm install`
|
||||
2. `./node_modules/.bin/web-ext run -s src/
|
||||
|
||||
### Testing
|
||||
TBD
|
||||
|
||||
### Distributing
|
||||
TBD
|
||||
#### Make the new version
|
||||
|
||||
1. Bump the version number in `package.json` and `manifest.json`
|
||||
2. Commit the version number bump
|
||||
3. Create a git tag for the version: `git tag <version>`
|
||||
4. Push the tag up to GitHub: `git push --tags`
|
||||
|
||||
#### Publish to AMO
|
||||
|
||||
1. `npm run-script build`
|
||||
2. [Upload the `.zip` to AMO](https://addons.mozilla.org/en-US/developers/addon/multi-account-containers/versions/submit/)
|
||||
|
||||
#### Publish to GitHub
|
||||
Finally, we also publish the release to GitHub for those followers.
|
||||
|
||||
1. Download the signed `.xpi` from [the addon versions page](https://addons.mozilla.org/en-US/developers/addon/multi-account-containers/versions)
|
||||
2. [Make the new release on
|
||||
GitHub](https://github.com/mozilla/multi-account-containers/releases/new)
|
||||
* Use the version number for "Tag version" and "Release title"
|
||||
* Release notes: copy the output of `git log --no-merges --pretty=format:"%h %s" <previous-version>..<new-version>`
|
||||
* Attach binaries: select the signed `.xpi` file
|
||||
|
||||
### Links
|
||||
|
||||
Facebook & Twitter icons CC-Attrib http://fairheadcreative.com.
|
||||
|
||||
- [Licence](./LICENSE.txt)
|
||||
- [Contributing](./CONTRIBUTING.md)
|
||||
- [Code Of Conduct](./CODE_OF_CONDUCT.md)
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const PREFS = [
|
||||
{
|
||||
name: "privacy.userContext.enabled",
|
||||
value: true,
|
||||
type: "bool",
|
||||
default: false
|
||||
},
|
||||
{
|
||||
name: "privacy.userContext.longPressBehavior",
|
||||
value: 2,
|
||||
type: "int",
|
||||
default: 0
|
||||
},
|
||||
{
|
||||
name: "privacy.userContext.ui.enabled",
|
||||
value: true, // Post web ext we will be setting this true
|
||||
type: "bool",
|
||||
default: true
|
||||
},
|
||||
{
|
||||
name: "privacy.usercontext.about_newtab_segregation.enabled",
|
||||
value: true,
|
||||
type: "bool",
|
||||
default: false
|
||||
},
|
||||
];
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const { TextDecoder, TextEncoder } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
"resource://gre/modules/osfile.jsm");
|
||||
|
||||
const JETPACK_DIR_BASENAME = "jetpack";
|
||||
const EXTENSION_ID = "@testpilot-containers";
|
||||
|
||||
function loadStyles(resourceURI) {
|
||||
const styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
|
||||
.getService(Ci.nsIStyleSheetService);
|
||||
const styleURI = styleSheet(resourceURI);
|
||||
const sheetType = styleSheetService.AGENT_SHEET;
|
||||
styleSheetService.loadAndRegisterSheet(styleURI, sheetType);
|
||||
}
|
||||
|
||||
function styleSheet(resourceURI) {
|
||||
return Services.io.newURI("data/usercontext.css", null, resourceURI);
|
||||
}
|
||||
|
||||
function unloadStyles(resourceURI) {
|
||||
const styleURI = styleSheet(resourceURI);
|
||||
const styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
|
||||
.getService(Ci.nsIStyleSheetService);
|
||||
const sheetType = styleSheetService.AGENT_SHEET;
|
||||
if (styleSheetService.sheetRegistered(styleURI, sheetType)) {
|
||||
styleSheetService.unregisterSheet(styleURI, sheetType);
|
||||
}
|
||||
}
|
||||
|
||||
function filename() {
|
||||
const storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||
storeFile.append(JETPACK_DIR_BASENAME);
|
||||
storeFile.append(EXTENSION_ID);
|
||||
storeFile.append("simple-storage");
|
||||
storeFile.append("store.json");
|
||||
return storeFile.path;
|
||||
}
|
||||
|
||||
async function makeFilepath() {
|
||||
const storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||
storeFile.append(JETPACK_DIR_BASENAME);
|
||||
await OS.File.makeDir(storeFile.path, { ignoreExisting: true });
|
||||
storeFile.append(EXTENSION_ID);
|
||||
await OS.File.makeDir(storeFile.path, { ignoreExisting: true });
|
||||
storeFile.append("simple-storage");
|
||||
await OS.File.makeDir(storeFile.path, { ignoreExisting: true });
|
||||
}
|
||||
|
||||
async function getConfig() {
|
||||
let savedConfig = {savedConfiguration: {}};
|
||||
try {
|
||||
const bytes = await OS.File.read(filename());
|
||||
const raw = new TextDecoder().decode(bytes) || "";
|
||||
if (raw) {
|
||||
savedConfig = JSON.parse(raw);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore file read errors, sometimes they happen and I'm not sure if we can fix
|
||||
}
|
||||
return savedConfig;
|
||||
}
|
||||
|
||||
async function initConfig() {
|
||||
const savedConfig = await getConfig();
|
||||
savedConfig.savedConfiguration.version = 2;
|
||||
if (!("prefs" in savedConfig.savedConfiguration)) {
|
||||
savedConfig.savedConfiguration.prefs = {};
|
||||
PREFS.forEach((pref) => {
|
||||
if ("int" === pref.type) {
|
||||
savedConfig.savedConfiguration.prefs[pref.name] = Services.prefs.getIntPref(pref.name);
|
||||
} else {
|
||||
savedConfig.savedConfiguration.prefs[pref.name] = Services.prefs.getBoolPref(pref.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
const serialized = JSON.stringify(savedConfig);
|
||||
const bytes = new TextEncoder().encode(serialized) || "";
|
||||
await makeFilepath();
|
||||
await OS.File.writeAtomic(filename(), bytes, { });
|
||||
}
|
||||
|
||||
function setPrefs() {
|
||||
PREFS.forEach((pref) => {
|
||||
if ("int" === pref.type) {
|
||||
Services.prefs.setIntPref(pref.name, pref.value);
|
||||
} else {
|
||||
Services.prefs.setBoolPref(pref.name, pref.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async function install() {
|
||||
await initConfig();
|
||||
setPrefs();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async function uninstall(aData, aReason) {
|
||||
if (aReason === ADDON_UNINSTALL
|
||||
|| aReason === ADDON_DISABLE) {
|
||||
const config = await getConfig();
|
||||
const storedPrefs = config.savedConfiguration.prefs || {};
|
||||
PREFS.forEach((pref) => {
|
||||
let value = pref.default;
|
||||
if (pref.name in storedPrefs) {
|
||||
value = storedPrefs[pref.name];
|
||||
}
|
||||
if ("int" === pref.type) {
|
||||
Services.prefs.setIntPref(pref.name, value);
|
||||
} else {
|
||||
Services.prefs.setBoolPref(pref.name, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function startup({webExtension, resourceURI}) {
|
||||
const version = Services.appinfo.version;
|
||||
const versionMatch = version.match(/^([0-9]+)\./)[1];
|
||||
if (versionMatch === "55"
|
||||
|| versionMatch === "56") {
|
||||
loadStyles(resourceURI);
|
||||
}
|
||||
// Reset prefs that may have changed, or are legacy
|
||||
install();
|
||||
// Start the embedded webextension.
|
||||
webExtension.startup();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function shutdown({resourceURI}) {
|
||||
unloadStyles(resourceURI);
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>@testpilot-containers</em:id>
|
||||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:multiprocessCompatible>true</em:multiprocessCompatible>
|
||||
<em:hasEmbeddedWebExtension>true</em:hasEmbeddedWebExtension>
|
||||
<em:name>Multi-Account Containers</em:name>
|
||||
<em:description>Containers helps you keep all the parts of your online life contained in different tabs. Custom labels and color-coded tabs help keep different activities — like online shopping, travel planning, or checking work email — separate.</em:description>
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!--Firefox-->
|
||||
<em:minVersion>53.0</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:version>4.0.3</em:version>
|
||||
<em:unpack>false</em:unpack>
|
||||
</Description>
|
||||
</RDF>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "testpilot-containers",
|
||||
"title": "Multi-Account Containers",
|
||||
"description": "Containers helps you keep all the parts of your online life contained in different tabs. Custom labels and color-coded tabs help keep different activities — like online shopping, travel planning, or checking work email — separate.",
|
||||
"version": "4.0.3",
|
||||
"version": "5.0.1",
|
||||
"author": "Andrea Marchesini, Luke Crouch and Jonathan Kingston",
|
||||
"bugs": {
|
||||
"url": "https://github.com/mozilla/testpilot-containers/issues"
|
||||
@@ -15,12 +15,12 @@
|
||||
"eslint-plugin-no-unsanitized": "^2.0.0",
|
||||
"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",
|
||||
"stylelint": "^7.9.0",
|
||||
"stylelint-config-standard": "^16.0.0",
|
||||
"stylelint-order": "^0.3.0"
|
||||
"stylelint-order": "^0.3.0",
|
||||
"web-ext": "^2.2.2"
|
||||
},
|
||||
"homepage": "https://github.com/mozilla/testpilot-containers#readme",
|
||||
"license": "MPL-2.0",
|
||||
@@ -30,14 +30,14 @@
|
||||
"url": "git+https://github.com/mozilla/testpilot-containers.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm test && jpm xpi",
|
||||
"build": "npm test && cd src && web-ext build --overwrite-dest",
|
||||
"deploy": "deploy-txp",
|
||||
"lint": "npm-run-all lint:*",
|
||||
"lint:addon": "addons-linter webextension --self-hosted",
|
||||
"lint:css": "stylelint webextension/css/*.css",
|
||||
"lint:html": "htmllint webextension/*.html",
|
||||
"lint:addon": "addons-linter src --self-hosted",
|
||||
"lint:css": "stylelint src/css/*.css",
|
||||
"lint:html": "htmllint *.html",
|
||||
"lint:js": "eslint .",
|
||||
"package": "npm run build && mv testpilot-containers.xpi addon.xpi",
|
||||
"package": "rm -rf src/web-ext-artifacts && npm run build && mv src/web-ext-artifacts/firefox_multi-account_containers-*.zip addon.xpi",
|
||||
"test": "npm run lint"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +242,8 @@ table {
|
||||
min-block-size: 400px;
|
||||
}
|
||||
|
||||
.panel.onboarding {
|
||||
.panel.onboarding,
|
||||
.achievement-panel {
|
||||
align-items: center;
|
||||
block-size: 360px;
|
||||
margin-block-end: 16px;
|
||||
@@ -887,3 +888,53 @@ span ~ .panel-header-text {
|
||||
font-size: 14px !important;
|
||||
padding-block-end: 6px;
|
||||
}
|
||||
|
||||
/* Achievement panel elements */
|
||||
.share-ctas {
|
||||
padding-block-end: 0.5em;
|
||||
padding-block-start: 0.5em;
|
||||
padding-inline-end: 0.5em;
|
||||
padding-inline-start: 0.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cta-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.cta {
|
||||
color: #fff;
|
||||
font-size: 0.7em;
|
||||
font-weight: bold;
|
||||
margin-block-end: 0.4em;
|
||||
margin-block-start: 0.4em;
|
||||
margin-inline-end: 0.4em;
|
||||
margin-inline-start: 0.4em;
|
||||
padding-block-end: 0.5em;
|
||||
padding-block-start: 0.5em;
|
||||
padding-inline-end: 0.5em;
|
||||
padding-inline-start: 0.5em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.cta-icon {
|
||||
height: 18px;
|
||||
padding-right: 0.5em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.fb-share-cta {
|
||||
background: #375496;
|
||||
}
|
||||
|
||||
.fb-share-cta .cta-icon {
|
||||
margin-block-start: -5px;
|
||||
}
|
||||
|
||||
.tweet-cta {
|
||||
background: #37bae7;
|
||||
}
|
||||
|
||||
.amo-rate-cta {
|
||||
background: #0f1126;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg width="32px" height="33px" viewBox="0 0 32 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch --> <desc>Created with Sketch.</desc> <defs> <linearGradient x1="74.0423237%" y1="18.5882821%" x2="0%" y2="100%" id="linearGradient-1"> <stop stop-color="#00FEFF" offset="0%"/> <stop stop-color="#3D85FF" offset="100%"/> </linearGradient> </defs> <g id="Specs" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="Header-Copy" transform="translate(-182.000000, -152.000000)" fill="url(#linearGradient-1)"> <path d="M205.58574,176.859518 L205.58574,169.287998 C205.58574,169.287998 205.800116,167.315137 207.086372,167.315137 C208.372629,167.315137 208.265441,169.394639 210.677171,169.394639 C211.909834,169.394639 214,168.754792 214,165.022352 C214,161.289912 211.909834,160.810027 210.677171,160.810027 C208.265441,160.810027 208.372629,162.782888 207.086372,162.782888 C205.800116,162.782888 205.58574,160.756707 205.58574,160.756707 L205.58574,157.664114 C205.58574,156.491061 204.621048,155.531291 203.44198,155.531291 L197.814608,155.531291 C197.814608,155.531291 195.992412,155.211368 195.992412,153.931674 C195.992412,152.65198 198.028985,152.545339 198.028985,150.145914 C198.028985,148.91954 197.332262,147 193.580682,147 C189.829101,147 189.293161,148.91954 189.293161,150.145914 C189.293161,152.545339 191.115357,152.65198 191.115357,153.931674 C191.115357,155.211368 189.293161,155.531291 189.293161,155.531291 L184.148135,155.531291 C182.969067,155.531291 182.004375,156.491061 182.004375,157.664114 L182.004375,161.823118 C182.004375,161.823118 181.789999,165.022352 184.362512,165.022352 C186.023926,165.022352 186.07752,162.836209 188.274874,162.836209 C189.346755,162.836209 190.418635,163.8493 190.418635,166.035443 C190.418635,168.274907 189.346755,169.394639 188.274874,169.394639 C186.131114,169.394639 186.023926,167.208496 184.362512,167.208496 C181.789999,167.208496 182.004375,170.301089 182.004375,170.301089 L182.004375,176.859518 C182.004375,178.032571 182.969067,178.992341 184.148135,178.992341 L191.115357,178.992341 C191.115357,178.992341 194.49178,179.205623 194.49178,176.646236 C194.49178,174.993299 192.348019,174.726696 192.348019,172.540552 C192.348019,171.474141 193.527088,170.141127 195.778036,170.141127 C198.028985,170.141127 199.315241,171.474141 199.315241,172.540552 C199.315241,174.673375 197.225074,174.993299 197.225074,176.646236 C197.225074,179.258944 200.601497,178.992341 200.601497,178.992341 L203.44198,178.992341 C204.621048,178.992341 205.58574,178.032571 205.58574,176.859518 Z" id="Shape-Copy-23" transform="translate(198.000000, 163.000000) rotate(-42.000000) translate(-198.000000, -163.000000) "/> </g> </g> </svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 307 B After Width: | Height: | Size: 307 B |
|
Before Width: | Height: | Size: 595 B After Width: | Height: | Size: 595 B |
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 520 B |
|
Before Width: | Height: | Size: 626 B After Width: | Height: | Size: 626 B |
|
Before Width: | Height: | Size: 603 B After Width: | Height: | Size: 603 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 883 B After Width: | Height: | Size: 883 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 342 B |
|
Before Width: | Height: | Size: 578 B After Width: | Height: | Size: 578 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 307 B After Width: | Height: | Size: 307 B |
|
Before Width: | Height: | Size: 534 B After Width: | Height: | Size: 534 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 755 B After Width: | Height: | Size: 755 B |
|
Before Width: | Height: | Size: 399 B After Width: | Height: | Size: 399 B |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="-14 -14 48 48" enable-background="new -14 -14 48 48" xml:space="preserve">
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="90.0527" y1="-99.7603" x2="90.0527" y2="-106.3809" gradientTransform="matrix(7.2338 0 0 -7.2338 -641.4998 -735.5619)">
|
||||
<stop offset="0" style="stop-color:#4B71B8"/>
|
||||
<stop offset="1" style="stop-color:#293F7E"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_1_)" d="M33.931,27.993c0,3.304-2.689,5.983-6.002,5.983H-8.082c-3.315,0-6.001-2.683-6.001-5.983V-7.928
|
||||
c0-3.308,2.687-5.988,6.001-5.988h36.011c3.312,0,6.002,2.681,6.002,5.988V27.993z"/>
|
||||
<path fill="#FFFFFF" d="M25.613-4.557c0,0-3.707,0-6.166,0c-3.662,0-7.732,1.535-7.732,6.835c0.019,1.845,0,3.613,0,5.603H7.481
|
||||
v6.728h4.366v19.37h8.021V14.48h5.295l0.479-6.618h-5.913c0,0,0.016-2.946,0-3.8c0-2.093,2.184-1.974,2.312-1.974
|
||||
c1.042,0,3.059,0.003,3.578,0v-6.646H25.613z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="23.9995" y1="0" x2="23.9995" y2="48.0005">
|
||||
<stop offset="0" style="stop-color:#4BD0EF"/>
|
||||
<stop offset="1" style="stop-color:#29AAE1"/>
|
||||
</linearGradient>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="url(#SVGID_1_)" d="M48,42c0,3.313-2.687,6-6,6H6c-3.313,0-6-2.687-6-6V6
|
||||
c0-3.313,2.687-6,6-6h36c3.313,0,6,2.687,6,6V42z"/>
|
||||
<path fill="#29AAE1" d="M40.231,13.413c-1.12,0.497-2.323,0.833-3.588,0.984c1.291-0.774,2.28-1.998,2.747-3.457
|
||||
c-1.206,0.716-2.543,1.236-3.968,1.516c-1.139-1.214-2.763-1.972-4.56-1.972c-3.449,0-6.246,2.796-6.246,6.247
|
||||
c0,0.49,0.055,0.966,0.161,1.424c-5.192-0.261-9.795-2.749-12.876-6.528c-0.538,0.923-0.846,1.996-0.846,3.141
|
||||
c0,2.167,1.103,4.08,2.779,5.199c-1.024-0.032-1.987-0.313-2.83-0.781c0,0.026,0,0.053,0,0.079c0,3.026,2.153,5.551,5.011,6.125
|
||||
c-0.525,0.143-1.076,0.219-1.646,0.219c-0.403,0-0.794-0.038-1.176-0.11c0.795,2.48,3.102,4.287,5.835,4.338
|
||||
c-2.138,1.675-4.832,2.675-7.758,2.675c-0.504,0-1.002-0.03-1.491-0.089c2.765,1.773,6.048,2.808,9.576,2.808
|
||||
c11.49,0,17.774-9.519,17.774-17.774c0-0.271-0.006-0.54-0.019-0.809C38.334,15.766,39.394,14.666,40.231,13.413z"/>
|
||||
<path fill="#FFFFFF" d="M40.231,14.739c-1.12,0.497-2.323,0.833-3.588,0.984c1.291-0.773,2.28-1.998,2.747-3.456
|
||||
c-1.206,0.716-2.543,1.236-3.968,1.516c-1.139-1.214-2.763-1.972-4.56-1.972c-3.449,0-6.246,2.796-6.246,6.247
|
||||
c0,0.489,0.055,0.966,0.161,1.424c-5.192-0.261-9.795-2.748-12.876-6.527c-0.538,0.923-0.846,1.996-0.846,3.141
|
||||
c0,2.167,1.103,4.079,2.779,5.199c-1.024-0.032-1.987-0.313-2.83-0.781c0,0.026,0,0.052,0,0.079c0,3.027,2.153,5.551,5.011,6.125
|
||||
c-0.525,0.144-1.076,0.219-1.646,0.219c-0.403,0-0.794-0.038-1.176-0.11c0.795,2.481,3.102,4.287,5.835,4.338
|
||||
c-2.138,1.676-4.832,2.675-7.758,2.675c-0.504,0-1.002-0.03-1.491-0.089c2.765,1.773,6.048,2.808,9.576,2.808
|
||||
c11.49,0,17.774-9.519,17.774-17.774c0-0.271-0.006-0.54-0.019-0.808C38.334,17.092,39.394,15.992,40.231,14.739z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -144,7 +144,7 @@ const assignManager = {
|
||||
return {};
|
||||
}
|
||||
|
||||
this.reloadPageInContainer(options.url, userContextId, siteSettings.userContextId, tab.index + 1, siteSettings.neverAsk);
|
||||
this.reloadPageInContainer(options.url, userContextId, siteSettings.userContextId, tab.index + 1, tab.active, siteSettings.neverAsk);
|
||||
this.calculateContextMenu(tab);
|
||||
|
||||
/* Removal of existing tabs:
|
||||
@@ -350,13 +350,13 @@ const assignManager = {
|
||||
});
|
||||
},
|
||||
|
||||
reloadPageInContainer(url, currentUserContextId, userContextId, index, neverAsk = false) {
|
||||
reloadPageInContainer(url, currentUserContextId, userContextId, index, active, 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});
|
||||
browser.tabs.create({url, cookieStoreId, index, active});
|
||||
} else {
|
||||
let confirmUrl = `${loadPage}?url=${this.encodeURLProperty(url)}&cookieStoreId=${cookieStoreId}`;
|
||||
let currentCookieStoreId;
|
||||
@@ -367,7 +367,8 @@ const assignManager = {
|
||||
browser.tabs.create({
|
||||
url: confirmUrl,
|
||||
cookieStoreId: currentCookieStoreId,
|
||||
index
|
||||
index,
|
||||
active
|
||||
}).then(() => {
|
||||
// We don't want to sync this URL ever nor clutter the users history
|
||||
browser.history.deleteUrl({url: confirmUrl});
|
||||
@@ -112,6 +112,14 @@ const messageHandler = {
|
||||
// lets remember the last tab created so we can close it if it looks like a redirect
|
||||
this.lastCreatedTab = tab;
|
||||
if (tab.cookieStoreId) {
|
||||
// Don't count firefox-default, firefox-private, nor our own confirm page loads
|
||||
if (tab.cookieStoreId !== "firefox-default" &&
|
||||
tab.cookieStoreId !== "firefox-private" &&
|
||||
!tab.url.startsWith("moz-extension")) {
|
||||
// increment the counter of container tabs opened
|
||||
this.incrementCountOfContainerTabsOpened();
|
||||
}
|
||||
|
||||
this.unhideContainer(tab.cookieStoreId);
|
||||
}
|
||||
setTimeout(() => {
|
||||
@@ -120,6 +128,24 @@ const messageHandler = {
|
||||
});
|
||||
},
|
||||
|
||||
async incrementCountOfContainerTabsOpened() {
|
||||
const key = "containerTabsOpened";
|
||||
const count = await browser.storage.local.get({[key]: 0});
|
||||
const countOfContainerTabsOpened = ++count[key];
|
||||
browser.storage.local.set({[key]: countOfContainerTabsOpened});
|
||||
|
||||
// When the user opens their _ tab, give them the achievement
|
||||
if (countOfContainerTabsOpened === 100) {
|
||||
const storage = await browser.storage.local.get({achievements: []});
|
||||
storage.achievements.push({"name": "manyContainersOpened", "done": false});
|
||||
// use set and spread to create a unique array
|
||||
const achievements = [...new Set(storage.achievements)];
|
||||
browser.storage.local.set({achievements});
|
||||
browser.browserAction.setBadgeBackgroundColor({color: "rgba(0,217,0,255)"});
|
||||
browser.browserAction.setBadgeText({text: "NEW"});
|
||||
}
|
||||
},
|
||||
|
||||
async unhideContainer(cookieStoreId) {
|
||||
if (!this.unhideQueue.includes(cookieStoreId)) {
|
||||
this.unhideQueue.push(cookieStoreId);
|
||||
@@ -22,6 +22,7 @@ const P_CONTAINERS_EDIT = "containersEdit";
|
||||
const P_CONTAINER_INFO = "containerInfo";
|
||||
const P_CONTAINER_EDIT = "containerEdit";
|
||||
const P_CONTAINER_DELETE = "containerDelete";
|
||||
const P_CONTAINERS_ACHIEVEMENT = "containersAchievement";
|
||||
|
||||
/**
|
||||
* Escapes any occurances of &, ", <, > or / with XML entities.
|
||||
@@ -90,8 +91,8 @@ const Logic = {
|
||||
|
||||
// Routing to the correct panel.
|
||||
// If localStorage is disabled, we don't show the onboarding.
|
||||
const data = await browser.storage.local.get([ONBOARDING_STORAGE_KEY]);
|
||||
let onboarded = data[ONBOARDING_STORAGE_KEY];
|
||||
const onboardingData = await browser.storage.local.get([ONBOARDING_STORAGE_KEY]);
|
||||
let onboarded = onboardingData[ONBOARDING_STORAGE_KEY];
|
||||
if (!onboarded) {
|
||||
onboarded = 0;
|
||||
this.setOnboardingStage(onboarded);
|
||||
@@ -99,7 +100,7 @@ const Logic = {
|
||||
|
||||
switch (onboarded) {
|
||||
case 5:
|
||||
this.showPanel(P_CONTAINERS_LIST);
|
||||
this.showAchievementOrContainersListPanel();
|
||||
break;
|
||||
case 4:
|
||||
this.showPanel(P_ONBOARDING_5);
|
||||
@@ -121,6 +122,37 @@ const Logic = {
|
||||
|
||||
},
|
||||
|
||||
async showAchievementOrContainersListPanel() {
|
||||
// Do we need to show an achievement panel?
|
||||
let showAchievements = false;
|
||||
const achievementsStorage = await browser.storage.local.get({achievements: []});
|
||||
for (const achievement of achievementsStorage.achievements) {
|
||||
if (!achievement.done) {
|
||||
showAchievements = true;
|
||||
}
|
||||
}
|
||||
if (showAchievements) {
|
||||
this.showPanel(P_CONTAINERS_ACHIEVEMENT);
|
||||
} else {
|
||||
this.showPanel(P_CONTAINERS_LIST);
|
||||
}
|
||||
},
|
||||
|
||||
// In case the user wants to click multiple actions,
|
||||
// they have to click the "Done" button to stop the panel
|
||||
// from showing
|
||||
async setAchievementDone(achievementName) {
|
||||
const achievementsStorage = await browser.storage.local.get({achievements: []});
|
||||
const achievements = achievementsStorage.achievements;
|
||||
achievements.forEach((achievement, index, achievementsArray) => {
|
||||
if (achievement.name === achievementName) {
|
||||
achievement.done = true;
|
||||
achievementsArray[index] = achievement;
|
||||
}
|
||||
});
|
||||
browser.storage.local.set({achievements});
|
||||
},
|
||||
|
||||
setOnboardingStage(stage) {
|
||||
return browser.storage.local.set({
|
||||
[ONBOARDING_STORAGE_KEY]: stage
|
||||
@@ -491,6 +523,14 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
|
||||
case 38:
|
||||
previous();
|
||||
break;
|
||||
default:
|
||||
if (e.keyCode >= 49 && e.keyCode <= 57) {
|
||||
const element = selectables[e.keyCode - 48];
|
||||
if (element) {
|
||||
element.click();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1015,4 +1055,25 @@ Logic.registerPanel(P_CONTAINER_DELETE, {
|
||||
},
|
||||
});
|
||||
|
||||
// P_CONTAINERS_ACHIEVEMENT: Page for achievement.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
Logic.registerPanel(P_CONTAINERS_ACHIEVEMENT, {
|
||||
panelSelector: ".achievement-panel",
|
||||
|
||||
// This method is called when the object is registered.
|
||||
initialize() {
|
||||
// Set done and move to the containers list panel.
|
||||
Logic.addEnterHandler(document.querySelector("#achievement-done-button"), async function () {
|
||||
await Logic.setAchievementDone("manyContainersOpened");
|
||||
Logic.showPanel(P_CONTAINERS_LIST);
|
||||
});
|
||||
},
|
||||
|
||||
// This method is called when the panel is shown.
|
||||
prepare() {
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
});
|
||||
|
||||
Logic.init();
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Firefox Multi-Account Containers",
|
||||
"version": "4.0.3",
|
||||
"version": "5.0.1",
|
||||
|
||||
"description": "Multi-Account Containers helps you keep all the parts of your online life contained in different tabs. Custom labels and color-coded tabs help keep different activities — like online shopping, travel planning, or checking work email — separate.",
|
||||
"icons": {
|
||||
@@ -11,7 +11,8 @@
|
||||
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"strict_min_version": "53.0"
|
||||
"id": "@testpilot-containers",
|
||||
"strict_min_version": "57.0"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -67,6 +67,34 @@
|
||||
<a href="#" id="onboarding-longpress-button" class="onboarding-button">Done</a>
|
||||
</div>
|
||||
|
||||
<div class="panel achievement-panel hide" id="achievement-panel">
|
||||
<img class="onboarding-img" alt="You achieved a Containers milestone!" src="/img/onboarding-3.png" />
|
||||
<h3 class="onboarding-title">100 tabs!</h3>
|
||||
<p>You've opened 100 Container tabs.</p>
|
||||
<p>If you enjoy Containers, help us spread the word!</p>
|
||||
<p class="share-ctas">
|
||||
<a class="cta-link" href="https://mzl.la/2gJtIZ4" id="achievement-rate-button" target="_blank">
|
||||
<span class="cta amo-rate-cta">
|
||||
<img src="/img/amo-icon.svg" class="cta-icon" alt="addons.mozilla.org Icon">
|
||||
Rate
|
||||
</span>
|
||||
</a>
|
||||
<a class="cta-link" href="https://bit.ly/fb-share-mac-addon" target="_blank">
|
||||
<span class="cta fb-share-cta">
|
||||
<img src="/img/webicon-facebook.svg" class="cta-icon" alt="Facebook Icon">
|
||||
Share
|
||||
</span>
|
||||
</a>
|
||||
<a class="cta-link" href="http://bit.ly/tweet-100-tabs-mac-addon" target="_blank">
|
||||
<span class="cta tweet-cta">
|
||||
<img src="/img/webicon-twitter.svg" class="cta-icon" alt="Twitter Icon">
|
||||
Tweet
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<a href="#" id="achievement-done-button" class="onboarding-button">Done</a>
|
||||
</div>
|
||||
|
||||
<div class="panel container-panel hide" id="container-panel">
|
||||
<div id="current-tab">
|
||||
<h3>Current Tab</h3>
|
||||
@@ -1,19 +0,0 @@
|
||||
const main = require("../");
|
||||
|
||||
exports["test main"] = function(assert) {
|
||||
assert.pass("Unit test running!");
|
||||
};
|
||||
|
||||
exports["test main async"] = function(assert, done) {
|
||||
assert.pass("async Unit test running!");
|
||||
done();
|
||||
};
|
||||
|
||||
exports["test dummy"] = function(assert, done) {
|
||||
main.dummy("foo", function(text) {
|
||||
assert.ok((text === "foo"), "Is the text actually 'foo'");
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
require("sdk/test").run(exports);
|
||||