Compare commits

...

70 Commits

Author SHA1 Message Date
luke crouch 31ac365e6d Merge pull request #974 from mozilla/bump-version-to-4.1.0
bump version to 4.1.0
2017-11-16 09:50:59 -06:00
groovecoder df8471a4dd bump version to 4.1.0 2017-11-16 09:47:18 -06:00
groovecoder 18539f2540 ignore webextension/web-ext-artifacts 2017-11-16 15:38:34 +00:00
Jonathan Kingston 7c1105a2b7 Change (un)install changes to only work for >57. Fixes #858 and Fixes #900 2017-11-16 15:38:34 +00:00
groovecoder 31298146f3 when user reaches container tab count, show achievement panel 2017-11-16 15:30:02 +00:00
groovecoder 4e6eee220c start containerTabsOpened counter 2017-11-16 15:30:02 +00:00
baku a7be3c9935 TextEncoder/TextDecoder from Cu.importGlobalProperties - #949 2017-11-14 19:36:34 +00:00
luke crouch f512473986 Merge pull request #882 from mozilla/update-README-distribute-steps
add steps for signing, AMO, and GitHub to README
2017-09-29 14:21:22 -05:00
groovecoder 8166a37722 add steps for signing, AMO, and GitHub to README 2017-09-29 13:15:16 -05:00
groovecoder adadb98482 bump version to 4.0.3 2017-09-29 18:04:53 +01:00
luke crouch 25e760cd64 Merge pull request #879 from jonathanKingston/fix-disable-notice
Clean up disabled Private Mode notice. Fixes #878
2017-09-29 11:24:24 -05:00
luke crouch 0ff8e17005 Merge pull request #870 from jonathanKingston/delete-non-existent-container
Fix assignment of stale containers. Fixes #803
2017-09-29 11:06:03 -05:00
Jonathan Kingston c433c6b39e Clean up disabled Private Mode notice. Fixes #878 2017-09-29 16:59:51 +01:00
Jonathan Kingston 1c09c29104 Fix assignment of stale containers. Fixes #803 Fixes #827 2017-09-27 13:51:40 +01:00
luke crouch c1e9cc3c56 Merge pull request #859 from jonathanKingston/prevent-enter-handler-calling-click
Preventing default on enter handler as it seems to call click handler…
2017-09-26 10:46:53 -05:00
luke crouch 27296d24c5 Merge pull request #857 from jonathanKingston/change-wording
Change delete title to remove. Fixes #700
2017-09-26 10:38:57 -05:00
luke crouch 030e635417 Merge pull request #853 from jonathanKingston/bump-version-and-min-ff-version
Add strict min version and extension id and bump version to 4.0.2. Fixes
2017-09-26 10:37:21 -05:00
Jonathan Kingston 07711aaecc Preventing default on enter handler as it seems to call click handlers now. Fixes #856 2017-09-26 11:16:58 +01:00
Jonathan Kingston 16ed8992e2 Change delete title to remove. Fixes #700 2017-09-26 02:50:10 +01:00
Jonathan Kingston 88e6dc7a05 Add strict min version and extension id and bump version to 4.0.2. Fixes #692. Fixes #852 2017-09-23 23:36:24 +01:00
luke crouch 28e8d46743 Merge pull request #834 from jonathanKingston/storage-clean
Store only one of the current version opened. Fixes #833
2017-09-20 15:47:06 -05:00
Jonathan Kingston 77ba1b723f Store only one of the current version opened. Fixes #833 2017-09-20 02:26:50 +01:00
Jonathan Kingston b0cc6e7c2f Merge pull request #818 from jonathanKingston/update-readme-launch-notice
Minor README edit
2017-09-15 00:17:42 +01:00
Jonathan Kingston e84e482130 Minor README edit 2017-09-15 00:17:05 +01:00
Jonathan Kingston b5ae20b874 Merge pull request #817 from jonathanKingston/update-readme-launch-notice
Add launch notice in README
2017-09-14 23:16:55 +01:00
Jonathan Kingston 3ec81e3d1f Add launch notice in README 2017-09-14 23:15:01 +01:00
luke crouch fb5436c287 Merge pull request #815 from jonathanKingston/blob-image
Fix dumping UUID image into the page. Fixes #812
2017-09-13 20:04:05 -05:00
Jonathan Kingston 01a628822b Fix dumping UUID image into the page. Fixes #812 2017-09-14 01:48:32 +01:00
Jonathan Kingston 66e2c8e297 Merge pull request #811 from mozilla/bump-version-4.0.2
bump version to 4.0.2
2017-09-13 22:32:54 +01:00
groovecoder 80661d68f2 fix #809: use "Containers" for name for context menu 2017-09-13 16:16:43 -05:00
groovecoder ef8aa3be75 bump version to 4.0.2 2017-09-13 10:07:56 -05:00
luke crouch 6bc056e019 Merge pull request #794 from jonathanKingston/hide-button-in-queue
Add show button to use showing queue to prevent dupes. Fixes #793
2017-09-07 14:28:47 -05:00
Jonathan Kingston 75deab139b Fix a moving hidden tabs to a new window. Fixes #797 2017-09-07 12:03:28 -07:00
Jonathan Kingston ae79f0a303 Ignore non permissible urls when hiding as we can't open them which causes issues. Fixes #793 2017-09-07 10:12:25 -07:00
Jonathan Kingston 9b83068234 Add show button to use showing queue to prevent dupes. Fixes #791 2017-09-07 09:25:13 -07:00
luke crouch fec2be9429 Merge pull request #789 from jonathanKingston/encode-url-fix
Encode non conforming chars that break moz-extension urls. Fixes #787
2017-09-07 09:05:16 -05:00
luke crouch 9f1b06ddd3 Merge pull request #790 from jonathanKingston/jpm-ignore-more
Ignore more files with .jpmignore
2017-09-06 14:46:45 -05:00
Jonathan Kingston ad2198e8b5 Encode non conforming chars that break moz-extension urls. Fixes #787 2017-09-05 17:10:07 -07:00
Jonathan Kingston 1791fdf0ef Ignore more files with .jpmignore 2017-09-05 17:09:36 -07:00
luke crouch 4ab705081e Merge pull request #788 from mozilla/bump-version-to-4.0.0
bump version to 4 for AMO
2017-09-05 14:25:57 -05:00
groovecoder 15b9dce1a9 AMO needs another version bump 2017-09-05 14:23:33 -05:00
groovecoder da3fc2ede2 bump version to 4.0.0 2017-09-05 13:33:34 -05:00
luke crouch 385c585888 Merge pull request #784 from jonathanKingston/fix-new-tab
Simplify new tab creation in the popup fixing issues. Fixes #781
2017-09-05 13:30:34 -05:00
luke crouch 734b97beb0 Merge pull request #786 from jonathanKingston/name-change
Name change. Fixes #763
2017-09-05 10:15:53 -05:00
Jonathan Kingston 3aa311a3c1 Name change. Fixes #763 2017-09-01 15:16:56 -07:00
Jonathan Kingston df7d7f9c38 Simplify new tab creation in the popup fixing issues. Fixes #781 2017-09-01 12:27:40 -07:00
luke crouch 65be77665a Merge pull request #783 from jonathanKingston/reset-prefs-on-uninstall
Reset prefs on uninstall
2017-09-01 11:08:06 -05:00
luke crouch 44548659db Merge pull request #780 from jonathanKingston/apply-styles-to-legacy
Add styles for 55+56 versions of Firefox so everyone gets a consisten…
2017-09-01 11:01:02 -05:00
Jonathan Kingston 10c4395efd Reset prefs for new users on uninstall. Fixes #782 2017-08-31 15:00:40 -07:00
Jonathan Kingston 27b2a4b5f2 Add styles for 55+56 versions of Firefox so everyone gets a consistent underline. Fixes #779 2017-08-31 11:58:03 -07:00
luke crouch 6f54e7ff7f Merge pull request #776 from jonathanKingston/context-menu-neaten
Add move and hide to context menu and neaten using checkboxes. Fixes …
2017-08-30 15:48:38 -05:00
Jonathan Kingston cb6726b667 Add move and hide to context menu and neaten using checkboxes. Fixes #711 2017-08-30 13:20:57 -07:00
luke crouch 0964311fa1 Merge pull request #778 from jonathanKingston/web-ext-advice
Add web extension specific advice on building. Fixes #751
2017-08-30 15:13:44 -05:00
luke crouch 2831d019f5 Merge pull request #777 from jonathanKingston/container-removal-fixes
Handle removing containers to refresh menus and remove assignments. F…
2017-08-30 15:13:03 -05:00
luke crouch 1cc58cad9b Merge pull request #775 from jonathanKingston/remove-unusable-paths-on-show
Remove about: paths from showTabs as it prevents the tabs being creat…
2017-08-30 15:00:26 -05:00
luke crouch bc9660f76e Merge pull request #774 from jonathanKingston/hidden-tab-open
Add a tab observer to show hidden tabs as there are many tab creation…
2017-08-30 14:55:06 -05:00
Jonathan Kingston f526caca50 Add web extension specific advice on building. Fixes #751 2017-08-30 11:12:55 -07:00
Jonathan Kingston b6a98fb83e Handle removing containers to refresh menus and remove assignments. Fixes: #761, Fixes: #752 2017-08-28 21:55:29 -07:00
Jonathan Kingston af2b4b79a9 Remove about: paths from showTabs as it prevents the tabs being created. Fixes #773 2017-08-28 17:46:55 -07:00
Jonathan Kingston a762b5eca2 Add a tab observer to show hidden tabs as there are many tab creation routes. Uses a queue to prevent multiple triggers. Fixes #765 2017-08-28 17:07:46 -07:00
luke crouch 3cc40344af Merge pull request #760 from jonathanKingston/removalOfTabPageCounter
Removal of tab page counter code. Fixes #759
2017-08-25 11:44:51 -05:00
luke crouch 278cdb7f69 Merge pull request #762 from jonathanKingston/change-to-window-only
Remove tab counting code as also not needed, change tab counting to b…
2017-08-25 11:27:45 -05:00
Jonathan Kingston 0be03ebeb7 Remove tab counting code as also not needed, change tab counting to be per window. Prevent reopening of hidden tabs doesn't call itself. Fixes #750, Fixes #753, Fixes #756 2017-08-24 16:05:14 +01:00
Jonathan Kingston c69f37a2de Removal of tab page counter code. Fixes #759 2017-08-24 15:04:47 +01:00
Jonathan Kingston b20ac8169a Merge pull request #758 from mozilla/move-to-AMO-665
for #665: update title, desc, version for AMO
2017-08-24 15:02:43 +01:00
groovecoder 9e98d35b45 for #665: update title, desc, version for AMO 2017-08-23 12:53:23 -05:00
luke crouch 17f2781e07 Merge pull request #757 from jonathanKingston/remove-pb-mode
Extension shouldn't be enabled in pb mode as it's currently not worki…
2017-08-23 12:48:38 -05:00
luke crouch 0acf9cc0e6 Merge pull request #754 from jonathanKingston/move-containers-fix
Fix moving of more than one tab to a new window. Fixes #746
2017-08-23 12:40:11 -05:00
Jonathan Kingston 70573d0559 Extension shouldn't be enabled in pb mode as it's currently not working in many places. Fixes #756 2017-08-23 15:23:49 +01:00
Jonathan Kingston da239237f7 Fix moving of more than one tab to a new window. Fixes #746 2017-08-22 18:54:32 +01:00
26 changed files with 692 additions and 882 deletions
+2
View File
@@ -8,3 +8,5 @@ README.html
.vimrc .vimrc
.env .env
addon.env addon.env
webextension/web-ext-artifacts/*
+2
View File
@@ -3,6 +3,7 @@ docs/
test/ test/
.npm/ .npm/
node_modules/ node_modules/
bin/
.env .env
.eslintrc.js .eslintrc.js
@@ -14,6 +15,7 @@ node_modules/
.stylelintrc .stylelintrc
.travis.yml .travis.yml
*.xpi *.xpi
*.md
.vimrc .vimrc
.DS_Store .DS_Store
.gdb_history .gdb_history
+49 -16
View File
@@ -1,11 +1,11 @@
# Containers Add-on # Multi-Account Containers
[![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 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: 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/)
* Will a general Firefox audience understand the Containers feature? [Available on addons.mozilla.org](https://addons.mozilla.org/en-GB/firefox/addon/multi-account-containers/)
* Is the UI as currently implemented in Nightly clear or discoverable?
**Note:** Firefox 57 + 58 users should Install from our [latest GitHub Release](https://github.com/mozilla/testpilot-containers/releases/latest)
For more info, see: For more info, see:
@@ -20,6 +20,19 @@ For more info, see:
## Development ## 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 ### 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. 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.
@@ -44,7 +57,6 @@ Release & Beta channels do not allow un-signed add-ons, even with the DevPrefs.
Whilst this is still using legacy code to test you will need the following in your profile: Whilst this is still using legacy code to test you will need the following in your profile:
Change the following prefs in about:config: Change the following prefs in about:config:
- extensions.legacy.enabled = true - extensions.legacy.enabled = true
@@ -66,23 +78,44 @@ 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, xpi`](https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/jpm#jpm_xpi) command,
or run `npm run build`. 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).
### Testing ### Testing
TBD TBD
### Distributing ### Distributing
TBD #### Make the new version
1. Bump the version number in `package.json`, `install.rdf`, 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
While the add-on is an Embedded Web Extension, we have to use the [Mozilla
Internal Signing
Service](https://mana.mozilla.org/wiki/display/FIREFOX/Internal+Extension+Signing)
to sign it as a Mozilla extension exempt from AMO's Web Extension restrictions.
So, to distribute the add-on to AMO:
1. Use `jpm xpi` to build the `.xpi` file
2. [Submit the `.xpi` to the Internal Signing Service and download the signed `.xpi`](https://mana.mozilla.org/wiki/display/SVCOPS/Sign+a+Mozilla+Internal+Extension)
3. [Upload the signed `.xpi` file 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. [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 ### Links
Facebook & Twitter icons CC-Attrib http://fairheadcreative.com.
- [Licence](./LICENSE.txt) - [Licence](./LICENSE.txt)
- [Contributing](./CONTRIBUTING.md) - [Contributing](./CONTRIBUTING.md)
- [Code Of Conduct](./CODE_OF_CONDUCT.md) - [Code Of Conduct](./CODE_OF_CONDUCT.md)
+99 -32
View File
@@ -4,29 +4,34 @@ const PREFS = [
{ {
name: "privacy.userContext.enabled", name: "privacy.userContext.enabled",
value: true, value: true,
type: "bool" type: "bool",
default: false
}, },
{ {
name: "privacy.userContext.longPressBehavior", name: "privacy.userContext.longPressBehavior",
value: 2, value: 2,
type: "int" type: "int",
default: 0
}, },
{ {
name: "privacy.userContext.ui.enabled", name: "privacy.userContext.ui.enabled",
value: true, // Post web ext we will be setting this true value: true, // Post web ext we will be setting this true
type: "bool" type: "bool",
default: true
}, },
{ {
name: "privacy.usercontext.about_newtab_segregation.enabled", name: "privacy.usercontext.about_newtab_segregation.enabled",
value: true, value: true,
type: "bool" type: "bool",
default: false
}, },
]; ];
const Ci = Components.interfaces; const Ci = Components.interfaces;
const Cu = Components.utils; const Cu = Components.utils;
const Cc = Components.classes;
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const { TextDecoder, TextEncoder } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {}); Cu.importGlobalProperties(["TextEncoder", "TextDecoder"]);
XPCOMUtils.defineLazyModuleGetter(this, "OS", XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm"); "resource://gre/modules/osfile.jsm");
@@ -34,6 +39,28 @@ XPCOMUtils.defineLazyModuleGetter(this, "OS",
const JETPACK_DIR_BASENAME = "jetpack"; const JETPACK_DIR_BASENAME = "jetpack";
const EXTENSION_ID = "@testpilot-containers"; 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() { function filename() {
const storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile); const storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
storeFile.append(JETPACK_DIR_BASENAME); storeFile.append(JETPACK_DIR_BASENAME);
@@ -43,14 +70,27 @@ function filename() {
return storeFile.path; return storeFile.path;
} }
async function getConfig() { async function makeFilepath() {
const bytes = await OS.File.read(filename()); const storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
const raw = new TextDecoder().decode(bytes) || ""; storeFile.append(JETPACK_DIR_BASENAME);
let savedConfig = {savedConfiguration: {}}; await OS.File.makeDir(storeFile.path, { ignoreExisting: true });
if (raw) { storeFile.append(EXTENSION_ID);
savedConfig = JSON.parse(raw); 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; return savedConfig;
} }
@@ -61,14 +101,15 @@ async function initConfig() {
savedConfig.savedConfiguration.prefs = {}; savedConfig.savedConfiguration.prefs = {};
PREFS.forEach((pref) => { PREFS.forEach((pref) => {
if ("int" === pref.type) { if ("int" === pref.type) {
savedConfig.savedConfiguration.prefs[pref.name] = Services.prefs.getIntPref(pref.name, pref.name); savedConfig.savedConfiguration.prefs[pref.name] = Services.prefs.getIntPref(pref.name);
} else { } else {
savedConfig.savedConfiguration.prefs[pref.name] = Services.prefs.getBoolPref(pref.name, pref.value); savedConfig.savedConfiguration.prefs[pref.name] = Services.prefs.getBoolPref(pref.name);
} }
}); });
} }
const serialized = JSON.stringify(savedConfig); const serialized = JSON.stringify(savedConfig);
const bytes = new TextEncoder().encode(serialized) || ""; const bytes = new TextEncoder().encode(serialized) || "";
await makeFilepath();
await OS.File.writeAtomic(filename(), bytes, { }); await OS.File.writeAtomic(filename(), bytes, { });
} }
@@ -89,32 +130,58 @@ async function install() {
} }
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
async function uninstall(aData, aReason) { async function uninstall({resourceURI}, aReason) {
if (aReason === ADDON_UNINSTALL if (checkLegacyFirefox()) {
|| aReason === ADDON_DISABLE) { if (aReason === ADDON_UNINSTALL) {
const config = await getConfig(); unloadStyles(resourceURI);
const storedPrefs = config.savedConfiguration.prefs; await removeChanges();
PREFS.forEach((pref) => { }
if (pref.name in storedPrefs) {
if ("int" === pref.type) {
Services.prefs.setIntPref(pref.name, storedPrefs[pref.name]);
} else {
Services.prefs.setBoolPref(pref.name, storedPrefs[pref.name]);
}
}
});
} }
} }
async function removeChanges() {
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);
}
});
}
function checkLegacyFirefox() {
const version = Services.appinfo.version;
const versionMatch = version.match(/^([0-9]+)\./)[1];
if (Number(versionMatch) <= 56) {
return true;
}
return false;
}
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
function startup({webExtension}) { function startup({webExtension, resourceURI}) {
// Reset prefs that may have changed, or are legacy if (checkLegacyFirefox()) {
setPrefs(); loadStyles(resourceURI);
// Reset prefs that may have changed, or are legacy
install();
}
// Start the embedded webextension. // Start the embedded webextension.
webExtension.startup(); webExtension.startup();
} }
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
function shutdown() { function shutdown({resourceURI}, aReason) {
if (checkLegacyFirefox()) {
unloadStyles(resourceURI);
if (aReason === ADDON_DISABLE) {
removeChanges();
}
}
} }
-118
View File
@@ -1,108 +1,3 @@
/* HACK: Custom Container vars do not propigate correctly
until the container tab is blurred and refocused,
adding the data-identity-color with the default hex
value, or chrome url path as an alternate selector mitiages this bug.*/
[data-identity-color="blue"],
[data-identity-color="#00a7e0"] {
--identity-tab-color: #37adff;
--identity-icon-color: #37adff;
}
[data-identity-color="turquoise"],
[data-identity-color="#01bdad"] {
--identity-tab-color: #00c79a;
--identity-icon-color: #00c79a;
}
[data-identity-color="green"],
[data-identity-color="#7dc14c"] {
--identity-tab-color: #51cd00;
--identity-icon-color: #51cd00;
}
[data-identity-color="yellow"],
[data-identity-color="#ffcb00"] {
--identity-tab-color: #ffcb00;
--identity-icon-color: #ffcb00;
}
[data-identity-color="orange"],
[data-identity-color="#f89c24"] {
--identity-tab-color: #ff9f00;
--identity-icon-color: #ff9f00;
}
[data-identity-color="red"],
[data-identity-color="#d92215"] {
--identity-tab-color: #ff613d;
--identity-icon-color: #ff613d;
}
[data-identity-color="pink"],
[data-identity-color="#ee5195"] {
--identity-tab-color: #ff4bda;
--identity-icon-color: #ff4bda;
}
[data-identity-color="purple"],
[data-identity-color="#7a2f7a"] {
--identity-tab-color: #af51f5;
--identity-icon-color: #af51f5;
}
[data-identity-icon="fingerprint"],
[data-identity-icon="chrome://browser/skin/usercontext/personal.svg"] {
--identity-icon: url("/data/usercontext.svg#fingerprint");
}
[data-identity-icon="briefcase"],
[data-identity-icon="chrome://browser/skin/usercontext/work.svg"] {
--identity-icon: url("/data/usercontext.svg#briefcase");
}
[data-identity-icon="dollar"],
[data-identity-icon="chrome://browser/skin/usercontext/banking.svg"] {
--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("/data/usercontext.svg#cart");
}
[data-identity-icon="circle"] {
--identity-icon: url("/data/usercontext.svg#circle");
}
[data-identity-icon="gift"] {
--identity-icon: url("/data/usercontext.svg#gift");
}
[data-identity-icon="vacation"] {
--identity-icon: url("/data/usercontext.svg#vacation");
}
[data-identity-icon="food"] {
--identity-icon: url("/data/usercontext.svg#food");
}
[data-identity-icon="fruit"] {
--identity-icon: url("/data/usercontext.svg#fruit");
}
[data-identity-icon="pet"] {
--identity-icon: url("/data/usercontext.svg#pet");
}
[data-identity-icon="tree"] {
--identity-icon: url("/data/usercontext.svg#tree");
}
[data-identity-icon="chill"] {
--identity-icon: url("/data/usercontext.svg#chill");
}
#userContext-indicator { #userContext-indicator {
height: 16px; height: 16px;
list-style-image: none !important; list-style-image: none !important;
@@ -129,19 +24,6 @@ value, or chrome url path as an alternate selector mitiages this bug.*/
display: none; display: none;
} }
.userContext-icon,
.menuitem-iconic[data-usercontextid] > .menu-iconic-left > .menu-iconic-icon,
.subviewbutton[usercontextid] > .toolbarbutton-icon,
#userContext-indicator {
background-image: var(--identity-icon) !important;
background-position: center center;
background-repeat: no-repeat;
background-size: contain;
fill: var(--identity-icon-color) !important;
filter: url(/img/filters.svg#fill);
filter: url(/data/filters.svg#fill);
}
/* containers experiment */ /* containers experiment */
/* reset nightly containers */ /* reset nightly containers */
-285
View File
@@ -1,285 +0,0 @@
# METRICS
## Data Analysis
The collected data will primarily be used to answer the following questions.
Images are used for visualization and are not composed of actual data.
### Do users install and run this?
What is the overall engagement of the Containers experiment?
**This is the standard Daily Active User (DAU) and Monthly Active User (MAU) analysis.**
This captures data from the users who have the add-on installed, regardless of
whether they are actively interacting with it.
![](kpi-1.png)
### Immediate Questions
* Do people use the containers feature & how do people create new container tabs?
* Click to create new container tab
* \+ `entry-point` value: "tab-bar" or "pop-up"
* Do people who use the containers feature continue to use it?
* Retention: opening a second container tab (second tab in the same container, or a tab in a second container?)
* What containers do people use?
* userContextId
* \+ Number of tabs in the container (when should we measure this? on every tab open?)
* Do people edit their containers?
* Click on "Edit Containers"
* Click to edit a single container
* Click "OK"
* Click to delete a single container
* Click "OK"
* Click to add a container
* Click "OK"
* Do people sort the tabs?
* Click sort
* \+ Number of tabs when clicked
* Average number of container tabs when sort was clicked
* Do users show and hide container tabs?
* Click hide
* \+ Number of tabs when clicked
* \+ Number of hidden containers when clicked
* Click show
* \+ Number of tabs when clicked
* \+ Number of shown containers when clicked
* Do users move container tabs to new windows?
* Click move
* \+ Number of tabs when clicked
* Average number of container tabs when new window was clicked
* How many containers do users have hidden at the same time? (when should we measure this? each time a container is hidden?)
* Do users pin container tabs? (do we have existing Telemetry for pinning?)
* Do users visit more pages in container tabs than non-container tabs?
### Follow-up Questions
What are some follow-up questions we anticipate we will ask based on any of the
above answers/data?
* What is the average lifespan of a container tab? Is that longer or shorter than a regular tab? (if we don't have data on the latter, the former probably isn't worth gathering data on since we will have nothing to compare it to).
## Data Collection
### Server Side
There is currently no server side component to Containers.
### Client Side
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": <uuid>,
"event": "show-plus-button-menu",
"eventSource": ["plus-button"]
}
```
* The user clicks on a container name to open a tab in that container
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"clickedContainerTabCount": <number-of-tabs-in-the-container>,
"event": "open-tab",
"eventSource": ["tab-bar"|"pop-up"|"file-menu"|"alltabs-menu"|"plus-button"]
}
```
* The user clicks "Edit Containers" in the pop-up
```js
{
"uuid": <uuid>,
"event": "edit-containers"
}
```
* The user clicks OK after clicking on a container edit icon in the pop-up
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"event": "edit-container"
}
```
* The user clicks OK after clicking on a container delete icon in the pop-up
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"event": "delete-container"
}
```
* The user clicks OK after clicking to add a container in the pop-up
```js
{
"uuid": <uuid>,
"event": "add-container"
}
```
* The user clicks the sort button/icon in the pop-up
```js
{
"uuid": <uuid>,
"event": "sort-tabs",
"shownContainersCount": <number-of-containers-with-tabs-shown>,
"totalContainerTabsCount": <number-of-all-container-tabs>,
"totalNonContainerTabsCount": <number-of-all-non-container-tabs>
}
```
* The user clicks "Hide these container tabs" in the popup
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"clickedContainerTabCount": <number-of-tabs-in-the-container>,
"event": "hide-tabs",
"hiddenContainersCount": <number-of-containers-with-tabs-hidden>,
"shownContainersCount": <number-of-containers-with-tabs-shown>,
"totalContainersCount": <number-of-containers-with-tabs-hidden-or-shown>
}
```
* The user clicks "Show these container tabs" in the popup
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"clickedContainerTabCount": <number-of-tabs-in-the-container>,
"event": "show-tabs",
"hiddenContainersCount": <number-of-containers-with-tabs-hidden>,
"shownContainersCount": <number-of-containers-with-tabs-shown>,
"totalContainersCount": <number-of-containers-with-tabs-hidden-or-shown>
}
```
* The user clicks "Move tabs to a new window" in the popup
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"clickedContainerTabCount": <number-of-tabs-in-the-container>,
"event": "move-tabs-to-window"
}
```
* When a user encounters the disabled "move" feature because of incompatible add-ons
```js
{
"uuid": <uuid>,
"event": "incompatible-addons-detected"
}
```
* The user closes a tab
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"event": "page-requests-completed-per-tab",
"pageRequestCount": <pageRequestCount>
}
```
* The user goes idle
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"event": "page-requests-completed-per-activity",
"pageRequestCount": <pageRequestCount>
}
```
* The user chooses "Always Open in this Container" context menu option. (Note: We send two separate event names: one for assigning a site to a container, one for removing a site from a container.)
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"event": "[added|removed]-container-assignment"
}
```
* Firefox prompts the user to reload a site into a container after the user picked "Always Open in this Container".
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"event": "prompt-reload-page-in-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
{
"uuid": <uuid>,
"event": "click-to-reload-page-in-container"
}
```
* 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": <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
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"event": "auto-reload-page-in-container"
}
```
### A Redshift schema for the payload:
```lua
local schema = {
-- column name field type length attributes field name
{"uuid", "VARCHAR", 255, nil, "Fields[payload.uuid]"},
{"userContextId", "INTEGER", 255, nil, "Fields[payload.userContextId]"},
{"clickedContainerTabCount", "INTEGER", 255, nil, "Fields[payload.clickedContainerTabCount]"},
{"eventSource", "VARCHAR", 255, nil, "Fields[payload.eventSource]"},
{"event", "VARCHAR", 255, nil, "Fields[payload.event]"},
{"pageRequestCount", "INTEGER", 255, nil, "Fields[payload.pageRequestCount]"}
{"hiddenContainersCount", "INTEGER", 255, nil, "Fields[payload.hiddenContainersCount]"},
{"shownContainersCount", "INTEGER", 255, nil, "Fields[payload.shownContainersCount]"},
{"totalContainersCount", "INTEGER", 255, nil, "Fields[payload.totalContainersCount]"},
{"totalContainerTabsCount", "INTEGER", 255, nil, "Fields[payload.totalContainerTabsCount]"},
{"totalNonContainerTabsCount", "INTEGER", 255, nil, "Fields[payload.totalNonContainerTabsCount]"}
}
```
### Valid data should be enforced on the server side:
* `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.
+4 -3
View File
@@ -7,15 +7,16 @@
<em:bootstrap>true</em:bootstrap> <em:bootstrap>true</em:bootstrap>
<em:multiprocessCompatible>true</em:multiprocessCompatible> <em:multiprocessCompatible>true</em:multiprocessCompatible>
<em:hasEmbeddedWebExtension>true</em:hasEmbeddedWebExtension> <em:hasEmbeddedWebExtension>true</em:hasEmbeddedWebExtension>
<em:name>Testpilot containers</em:name> <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> <em:targetApplication>
<Description> <Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!--Firefox--> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!--Firefox-->
<em:minVersion>51.0a1</em:minVersion> <em:minVersion>53.0</em:minVersion>
<em:maxVersion>*</em:maxVersion> <em:maxVersion>*</em:maxVersion>
</Description> </Description>
</em:targetApplication> </em:targetApplication>
<em:version>3.1.0</em:version> <em:version>4.1.0</em:version>
<em:unpack>false</em:unpack> <em:unpack>false</em:unpack>
</Description> </Description>
</RDF> </RDF>
+4 -5
View File
@@ -1,8 +1,8 @@
{ {
"name": "testpilot-containers", "name": "testpilot-containers",
"title": "Containers Experiment", "title": "Multi-Account Containers",
"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.", "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": "3.1.0", "version": "4.1.0",
"author": "Andrea Marchesini, Luke Crouch and Jonathan Kingston", "author": "Andrea Marchesini, Luke Crouch and Jonathan Kingston",
"bugs": { "bugs": {
"url": "https://github.com/mozilla/testpilot-containers/issues" "url": "https://github.com/mozilla/testpilot-containers/issues"
@@ -39,6 +39,5 @@
"lint:js": "eslint .", "lint:js": "eslint .",
"package": "npm run build && mv testpilot-containers.xpi addon.xpi", "package": "npm run build && mv testpilot-containers.xpi addon.xpi",
"test": "npm run lint" "test": "npm run lint"
}, }
"updateURL": "https://testpilot.firefox.com/files/@testpilot-containers/updates.json"
} }
+1 -1
View File
@@ -1,7 +1,7 @@
<html> <html>
<head> <head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"> <meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Containers confirm navigation</title> <title>Multi-Account Containers Confirm Navigation</title>
<link xmlns="http://www.w3.org/1999/xhtml" rel="stylesheet" href="chrome://browser/skin/aboutNetError.css" type="text/css" media="all" /> <link xmlns="http://www.w3.org/1999/xhtml" rel="stylesheet" href="chrome://browser/skin/aboutNetError.css" type="text/css" media="all" />
<link rel="stylesheet" href="/css/confirm-page.css" /> <link rel="stylesheet" href="/css/confirm-page.css" />
</head> </head>
+52 -1
View File
@@ -242,7 +242,8 @@ table {
min-block-size: 400px; min-block-size: 400px;
} }
.panel.onboarding { .panel.onboarding,
.achievement-panel {
align-items: center; align-items: center;
block-size: 360px; block-size: 360px;
margin-block-end: 16px; margin-block-end: 16px;
@@ -887,3 +888,53 @@ span ~ .panel-header-text {
font-size: 14px !important; font-size: 14px !important;
padding-block-end: 6px; 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;
}
+1
View File
@@ -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

+14
View File
@@ -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

+24
View File
@@ -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

+1 -2
View File
@@ -7,7 +7,6 @@ module.exports = {
"badge": true, "badge": true,
"backgroundLogic": true, "backgroundLogic": true,
"identityState": true, "identityState": true,
"messageHandler": true, "messageHandler": true
"tabPageCounter": true
} }
}; };
+117 -54
View File
@@ -1,6 +1,10 @@
const assignManager = { const assignManager = {
MENU_ASSIGN_ID: "open-in-this-container", MENU_ASSIGN_ID: "open-in-this-container",
MENU_REMOVE_ID: "remove-open-in-this-container", MENU_REMOVE_ID: "remove-open-in-this-container",
MENU_SEPARATOR_ID: "separator",
MENU_HIDE_ID: "hide-container",
MENU_MOVE_ID: "move-to-new-window-container",
storageArea: { storageArea: {
area: browser.storage.local, area: browser.storage.local,
exemptedTabs: {}, exemptedTabs: {},
@@ -109,6 +113,61 @@ const assignManager = {
return true; return true;
}, },
// Before a request is handled by the browser we decide if we should route through a different container
async onBeforeRequest(options) {
if (options.frameId !== 0 || options.tabId === -1) {
return {};
}
this.removeContextMenu();
const [tab, siteSettings] = await Promise.all([
browser.tabs.get(options.tabId),
this.storageArea.get(options.url)
]);
let container;
try {
container = await browser.contextualIdentities.get(backgroundLogic.cookieStoreId(siteSettings.userContextId));
} catch (e) {
container = false;
}
// The container we have in the assignment map isn't present any more so lets remove it
// then continue the existing load
if (!container) {
this.deleteContainer(siteSettings.userContextId);
return {};
}
const userContextId = this.getUserContextIdFromCookieStore(tab);
if (!siteSettings
|| userContextId === siteSettings.userContextId
|| tab.incognito
|| this.storageArea.isExempted(options.url, tab.id)) {
return {};
}
this.reloadPageInContainer(options.url, userContextId, siteSettings.userContextId, tab.index + 1, siteSettings.neverAsk);
this.calculateContextMenu(tab);
/* Removal of existing tabs:
We aim to open the new assigned container tab / warning prompt in it's own tab:
- As the history won't span from one container to another it seems most sane to not try and reopen a tab on history.back()
- When users open a new tab themselves we want to make sure we don't end up with three tabs as per: https://github.com/mozilla/testpilot-containers/issues/421
If we are coming from an internal url that are used for the new tab page (NEW_TAB_PAGES), we can safely close as user is unlikely losing history
Detecting redirects on "new tab" opening actions is pretty hard as we don't get tab history:
- Redirects happen from Short URLs and tracking links that act as a gateway
- Extensions don't provide a way to history crawl for tabs, we could inject content scripts to do this
however they don't run on about:blank so this would likely be just as hacky.
We capture the time the tab was created and close if it was within the timeout to try to capture pages which haven't had user interaction or history.
*/
if (backgroundLogic.NEW_TAB_PAGES.has(tab.url)
|| (messageHandler.lastCreatedTab
&& messageHandler.lastCreatedTab.id === tab.id)) {
browser.tabs.remove(tab.id);
}
return {
cancel: true,
};
},
init() { init() {
browser.contextMenus.onClicked.addListener((info, tab) => { browser.contextMenus.onClicked.addListener((info, tab) => {
this._onClickedHandler(info, tab); this._onClickedHandler(info, tab);
@@ -116,62 +175,38 @@ const assignManager = {
// Before a request is handled by the browser we decide if we should route through a different container // Before a request is handled by the browser we decide if we should route through a different container
browser.webRequest.onBeforeRequest.addListener((options) => { browser.webRequest.onBeforeRequest.addListener((options) => {
if (options.frameId !== 0 || options.tabId === -1) { return this.onBeforeRequest(options);
return {};
}
this.removeContextMenu();
return Promise.all([
browser.tabs.get(options.tabId),
this.storageArea.get(options.url)
]).then(([tab, siteSettings]) => {
const userContextId = this.getUserContextIdFromCookieStore(tab);
if (!siteSettings
|| userContextId === siteSettings.userContextId
|| tab.incognito
|| this.storageArea.isExempted(options.url, tab.id)) {
return {};
}
this.reloadPageInContainer(options.url, userContextId, siteSettings.userContextId, tab.index + 1, siteSettings.neverAsk);
this.calculateContextMenu(tab);
/* Removal of existing tabs:
We aim to open the new assigned container tab / warning prompt in it's own tab:
- As the history won't span from one container to another it seems most sane to not try and reopen a tab on history.back()
- When users open a new tab themselves we want to make sure we don't end up with three tabs as per: https://github.com/mozilla/testpilot-containers/issues/421
If we are coming from an internal url that are used for the new tab page (NEW_TAB_PAGES), we can safely close as user is unlikely losing history
Detecting redirects on "new tab" opening actions is pretty hard as we don't get tab history:
- Redirects happen from Short URLs and tracking links that act as a gateway
- Extensions don't provide a way to history crawl for tabs, we could inject content scripts to do this
however they don't run on about:blank so this would likely be just as hacky.
We capture the time the tab was created and close if it was within the timeout to try to capture pages which haven't had user interaction or history.
*/
if (backgroundLogic.NEW_TAB_PAGES.has(tab.url)
|| (messageHandler.lastCreatedTab
&& messageHandler.lastCreatedTab.id === tab.id)) {
browser.tabs.remove(tab.id);
}
return {
cancel: true,
};
}).catch((e) => {
throw e;
});
},{urls: ["<all_urls>"], types: ["main_frame"]}, ["blocking"]); },{urls: ["<all_urls>"], types: ["main_frame"]}, ["blocking"]);
}, },
async _onClickedHandler(info, tab) { async _onClickedHandler(info, tab) {
const userContextId = this.getUserContextIdFromCookieStore(tab); const userContextId = this.getUserContextIdFromCookieStore(tab);
// Mapping ${URL(info.pageUrl).hostname} to ${userContextId} // Mapping ${URL(info.pageUrl).hostname} to ${userContextId}
let remove;
if (userContextId) { if (userContextId) {
// let actionName; switch (info.menuItemId) {
let remove; case this.MENU_ASSIGN_ID:
if (info.menuItemId === this.MENU_ASSIGN_ID) { case this.MENU_REMOVE_ID:
remove = false; if (info.menuItemId === this.MENU_ASSIGN_ID) {
} else { remove = false;
remove = true; } else {
remove = true;
}
await this._setOrRemoveAssignment(tab.id, info.pageUrl, userContextId, remove);
break;
case this.MENU_MOVE_ID:
backgroundLogic.moveTabsToWindow({
cookieStoreId: tab.cookieStoreId,
windowId: tab.windowId,
});
break;
case this.MENU_HIDE_ID:
backgroundLogic.hideTabs({
cookieStoreId: tab.cookieStoreId,
windowId: tab.windowId,
});
break;
} }
await this._setOrRemoveAssignment(tab.id, info.pageUrl, userContextId, remove);
} }
}, },
@@ -260,6 +295,9 @@ const assignManager = {
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1352102 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1352102
browser.contextMenus.remove(this.MENU_ASSIGN_ID); browser.contextMenus.remove(this.MENU_ASSIGN_ID);
browser.contextMenus.remove(this.MENU_REMOVE_ID); browser.contextMenus.remove(this.MENU_REMOVE_ID);
browser.contextMenus.remove(this.MENU_SEPARATOR_ID);
browser.contextMenus.remove(this.MENU_HIDE_ID);
browser.contextMenus.remove(this.MENU_MOVE_ID);
}, },
async calculateContextMenu(tab) { async calculateContextMenu(tab) {
@@ -270,21 +308,46 @@ const assignManager = {
if (siteSettings === false) { if (siteSettings === false) {
return false; return false;
} }
// ✓ This is to mitigate https://bugzilla.mozilla.org/show_bug.cgi?id=1351418 let checked = false;
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; let menuId = this.MENU_ASSIGN_ID;
const tabUserContextId = this.getUserContextIdFromCookieStore(tab); const tabUserContextId = this.getUserContextIdFromCookieStore(tab);
if (siteSettings && if (siteSettings &&
Number(siteSettings.userContextId) === Number(tabUserContextId)) { Number(siteSettings.userContextId) === Number(tabUserContextId)) {
prefix = "✓"; checked = true;
menuId = this.MENU_REMOVE_ID; menuId = this.MENU_REMOVE_ID;
} }
browser.contextMenus.create({ browser.contextMenus.create({
id: menuId, id: menuId,
title: `${prefix} Always Open in This Container`, title: "Always Open in This Container",
checked: true, checked,
type: "checkbox",
contexts: ["all"], contexts: ["all"],
}); });
browser.contextMenus.create({
id: this.MENU_SEPARATOR_ID,
type: "separator",
contexts: ["all"],
});
browser.contextMenus.create({
id: this.MENU_HIDE_ID,
title: "Hide This Container",
contexts: ["all"],
});
browser.contextMenus.create({
id: this.MENU_MOVE_ID,
title: "Move Tabs to a New Window",
contexts: ["all"],
});
},
encodeURLProperty(url) {
return encodeURIComponent(url).replace(/[!'()*]/g, (c) => {
const charCode = c.charCodeAt(0).toString(16);
return `%${charCode}`;
});
}, },
reloadPageInContainer(url, currentUserContextId, userContextId, index, neverAsk = false) { reloadPageInContainer(url, currentUserContextId, userContextId, index, neverAsk = false) {
@@ -295,7 +358,7 @@ const assignManager = {
if (neverAsk) { if (neverAsk) {
browser.tabs.create({url, cookieStoreId, index}); browser.tabs.create({url, cookieStoreId, index});
} else { } else {
let confirmUrl = `${loadPage}?url=${encodeURIComponent(url)}&cookieStoreId=${cookieStoreId}`; let confirmUrl = `${loadPage}?url=${this.encodeURLProperty(url)}&cookieStoreId=${cookieStoreId}`;
let currentCookieStoreId; let currentCookieStoreId;
if (currentUserContextId) { if (currentUserContextId) {
currentCookieStoreId = backgroundLogic.cookieStoreId(currentUserContextId); currentCookieStoreId = backgroundLogic.cookieStoreId(currentUserContextId);
+110 -84
View File
@@ -25,13 +25,12 @@ const backgroundLogic = {
return false; return false;
}, },
async deleteContainer(userContextId) { async deleteContainer(userContextId, removed = false) {
await this._closeTabs(userContextId); await this._closeTabs(userContextId);
await browser.contextualIdentities.remove(this.cookieStoreId(userContextId)); if (!removed) {
await browser.contextualIdentities.remove(this.cookieStoreId(userContextId));
}
assignManager.deleteContainer(userContextId); assignManager.deleteContainer(userContextId);
await browser.runtime.sendMessage({
method: "forgetIdentityAndRefresh"
});
return {done: true, userContextId}; return {done: true, userContextId};
}, },
@@ -51,7 +50,7 @@ const backgroundLogic = {
}); });
}, },
async openTab(options) { async openNewTab(options) {
let url = options.url || undefined; let url = options.url || undefined;
const userContextId = ("userContextId" in options) ? options.userContextId : 0; const userContextId = ("userContextId" in options) ? options.userContextId : 0;
const active = ("nofocus" in options) ? options.nofocus : true; const active = ("nofocus" in options) ? options.nofocus : true;
@@ -64,10 +63,10 @@ const backgroundLogic = {
url = undefined; url = undefined;
} }
// Unhide all hidden tabs if (!this.isPermissibleURL(url)) {
this.showTabs({ return;
cookieStoreId }
});
return browser.tabs.create({ return browser.tabs.create({
url, url,
active, active,
@@ -76,61 +75,90 @@ const backgroundLogic = {
}); });
}, },
async getTabs(options) { isPermissibleURL(url) {
if (!("cookieStoreId" in options)) { const protocol = new URL(url).protocol;
return new Error("getTabs must be called with cookieStoreId argument."); // We can't open these we just have to throw them away
if (protocol === "about:"
|| protocol === "chrome:"
|| protocol === "moz-extension:") {
return false;
} }
return true;
},
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId); checkArgs(requiredArguments, options, methodName) {
await identityState.remapTabsIfMissing(options.cookieStoreId); requiredArguments.forEach((argument) => {
const isKnownContainer = await identityState._isKnownContainer(userContextId); if (!(argument in options)) {
if (!isKnownContainer) { return new Error(`${methodName} must be called with ${argument} argument.`);
return []; }
} });
},
async getTabs(options) {
const requiredArguments = ["cookieStoreId", "windowId"];
this.checkArgs(requiredArguments, options, "getTabs");
const { cookieStoreId, windowId } = options;
const list = []; const list = [];
const tabs = await this._containerTabs(options.cookieStoreId); const tabs = await browser.tabs.query({
cookieStoreId,
windowId
});
tabs.forEach((tab) => { tabs.forEach((tab) => {
list.push(identityState._createTabObject(tab)); list.push(identityState._createTabObject(tab));
}); });
const containerState = await identityState.storageArea.get(options.cookieStoreId); const containerState = await identityState.storageArea.get(cookieStoreId);
return list.concat(containerState.hiddenTabs); return list.concat(containerState.hiddenTabs);
}, },
async moveTabsToWindow(options) { async moveTabsToWindow(options) {
if (!("cookieStoreId" in options)) { const requiredArguments = ["cookieStoreId", "windowId"];
return new Error("moveTabsToWindow must be called with cookieStoreId argument."); this.checkArgs(requiredArguments, options, "moveTabsToWindow");
} const { cookieStoreId, windowId } = options;
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId); const list = await browser.tabs.query({
await identityState.remapTabsIfMissing(options.cookieStoreId); cookieStoreId,
if (!identityState._isKnownContainer(userContextId)) { windowId
return null; });
}
const list = await identityState._matchTabsByContainer(options.cookieStoreId); const containerState = await identityState.storageArea.get(cookieStoreId);
const containerState = await identityState.storageArea.get(options.cookieStoreId);
// Nothing to do // Nothing to do
if (list.length === 0 && if (list.length === 0 &&
containerState.hiddenTabs.length === 0) { containerState.hiddenTabs.length === 0) {
return; return;
} }
const window = await browser.windows.create({ let newWindowObj;
tabId: list.shift().id let hiddenDefaultTabToClose;
}); if (list.length) {
browser.tabs.move(list, { newWindowObj = await browser.windows.create({
windowId: window.id, tabId: list.shift().id
index: -1 });
}); browser.tabs.move(list.map((tab) => tab.id), {
windowId: newWindowObj.id,
index: -1
});
} else {
//As we get a blank tab here we will need to await the tabs creation
newWindowObj = await browser.windows.create({
});
hiddenDefaultTabToClose = true;
}
const showHiddenPromises = [];
// Let's show the hidden tabs. // Let's show the hidden tabs.
for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const
browser.tabs.create(object.url || DEFAULT_TAB, { showHiddenPromises.push(browser.tabs.create({
windowId: window.id, url: object.url || DEFAULT_TAB,
cookieStoreId: options.cookieStoreId windowId: newWindowObj.id,
}); cookieStoreId
}));
}
if (hiddenDefaultTabToClose) {
// Lets wait for hidden tabs to show before closing the others
await showHiddenPromises;
} }
containerState.hiddenTabs = []; containerState.hiddenTabs = [];
@@ -138,31 +166,46 @@ const backgroundLogic = {
// Let's close all the normal tab in the new window. In theory it // Let's close all the normal tab in the new window. In theory it
// should be only the first tab, but maybe there are addons doing // should be only the first tab, but maybe there are addons doing
// crazy stuff. // crazy stuff.
const tabs = browser.tabs.query({windowId: window.id}); const tabs = await browser.tabs.query({windowId: newWindowObj.id});
for (let tab of tabs) { // eslint-disable-line prefer-const for (let tab of tabs) { // eslint-disable-line prefer-const
if (tabs.cookieStoreId !== options.cookieStoreId) { if (tab.cookieStoreId !== cookieStoreId) {
browser.tabs.remove(tab.id); browser.tabs.remove(tab.id);
} }
} }
return await identityState.storageArea.set(options.cookieStoreId, containerState); return await identityState.storageArea.set(cookieStoreId, containerState);
}, },
async _closeTabs(userContextId) { async _closeTabs(userContextId, windowId = false) {
const cookieStoreId = this.cookieStoreId(userContextId); const cookieStoreId = this.cookieStoreId(userContextId);
const tabs = await this._containerTabs(cookieStoreId); let tabs;
/* if we have no windowId we are going to close all this container (used for deleting) */
if (windowId !== false) {
tabs = await browser.tabs.query({
cookieStoreId,
windowId
});
} else {
tabs = await browser.tabs.query({
cookieStoreId
});
}
const tabIds = tabs.map((tab) => tab.id); const tabIds = tabs.map((tab) => tab.id);
return browser.tabs.remove(tabIds); return browser.tabs.remove(tabIds);
}, },
async queryIdentitiesState() { async queryIdentitiesState(windowId) {
const identities = await browser.contextualIdentities.query({}); const identities = await browser.contextualIdentities.query({});
const identitiesOutput = {}; const identitiesOutput = {};
const identitiesPromise = identities.map(async function (identity) { const identitiesPromise = identities.map(async function (identity) {
await identityState.remapTabsIfMissing(identity.cookieStoreId); const { cookieStoreId } = identity;
const containerState = await identityState.storageArea.get(identity.cookieStoreId); const containerState = await identityState.storageArea.get(cookieStoreId);
identitiesOutput[identity.cookieStoreId] = { const openTabs = await browser.tabs.query({
cookieStoreId,
windowId
});
identitiesOutput[cookieStoreId] = {
hasHiddenTabs: !!containerState.hiddenTabs.length, hasHiddenTabs: !!containerState.hiddenTabs.length,
hasOpenTabs: !!containerState.openTabs hasOpenTabs: !!openTabs.length
}; };
return; return;
}); });
@@ -172,15 +215,15 @@ const backgroundLogic = {
async sortTabs() { async sortTabs() {
const windows = await browser.windows.getAll(); const windows = await browser.windows.getAll();
for (let window of windows) { // eslint-disable-line prefer-const for (let windowObj of windows) { // eslint-disable-line prefer-const
// First the pinned tabs, then the normal ones. // First the pinned tabs, then the normal ones.
await this._sortTabsInternal(window, true); await this._sortTabsInternal(windowObj, true);
await this._sortTabsInternal(window, false); await this._sortTabsInternal(windowObj, false);
} }
}, },
async _sortTabsInternal(window, pinnedTabs) { async _sortTabsInternal(windowObj, pinnedTabs) {
const tabs = await browser.tabs.query({windowId: window.id}); const tabs = await browser.tabs.query({windowId: windowObj.id});
let pos = 0; let pos = 0;
// Let's collect UCIs/tabs for this window. // Let's collect UCIs/tabs for this window.
@@ -212,28 +255,22 @@ const backgroundLogic = {
for (const tab of tabs) { for (const tab of tabs) {
++pos; ++pos;
browser.tabs.move(tab.id, { browser.tabs.move(tab.id, {
windowId: window.id, windowId: windowObj.id,
index: pos index: pos
}); });
//xulWindow.gBrowser.moveTabTo(tab, pos++);
} }
}); });
}, },
async hideTabs(options) { async hideTabs(options) {
if (!("cookieStoreId" in options)) { const requiredArguments = ["cookieStoreId", "windowId"];
return new Error("hideTabs must be called with cookieStoreId option."); this.checkArgs(requiredArguments, options, "hideTabs");
} const { cookieStoreId, windowId } = options;
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId); const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(cookieStoreId);
await identityState.remapTabsIfMissing(options.cookieStoreId);
const isKnownContainer = await identityState._isKnownContainer(userContextId);
if (!isKnownContainer) {
return null;
}
const containerState = await identityState.storeHidden(options.cookieStoreId); const containerState = await identityState.storeHidden(cookieStoreId, windowId);
await this._closeTabs(userContextId); await this._closeTabs(userContextId, windowId);
return containerState; return containerState;
}, },
@@ -243,17 +280,12 @@ const backgroundLogic = {
} }
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId); const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId);
await identityState.remapTabsIfMissing(options.cookieStoreId);
if (!identityState._isKnownContainer(userContextId)) {
return null;
}
const promises = []; const promises = [];
const containerState = await identityState.storageArea.get(options.cookieStoreId); const containerState = await identityState.storageArea.get(options.cookieStoreId);
for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const
promises.push(this.openTab({ promises.push(this.openNewTab({
userContextId: userContextId, userContextId: userContextId,
url: object.url, url: object.url,
nofocus: options.nofocus || false, nofocus: options.nofocus || false,
@@ -269,12 +301,6 @@ const backgroundLogic = {
cookieStoreId(userContextId) { cookieStoreId(userContextId) {
return `firefox-container-${userContextId}`; return `firefox-container-${userContextId}`;
}, }
_containerTabs(cookieStoreId) {
return browser.tabs.query({
cookieStoreId
}).catch((e) => {throw e;});
},
}; };
+9 -2
View File
@@ -1,8 +1,15 @@
const MAJOR_VERSIONS = ["2.3.0", "2.4.0"]; const MAJOR_VERSIONS = ["2.3.0", "2.4.0"];
const badge = { const badge = {
init() { async init() {
this.displayBrowserActionBadge(); const currentWindow = await browser.windows.getCurrent();
this.displayBrowserActionBadge(currentWindow.incognito);
}, },
disableAddon(tabId) {
browser.browserAction.disable(tabId);
browser.browserAction.setTitle({ tabId, title: "Containers disabled in Private Browsing Mode" });
},
async displayBrowserActionBadge() { async displayBrowserActionBadge() {
const extensionInfo = await backgroundLogic.getExtensionInfo(); const extensionInfo = await backgroundLogic.getExtensionInfo();
const storage = await browser.storage.local.get({browserActionBadgesClicked: []}); const storage = await browser.storage.local.get({browserActionBadgesClicked: []});
+10 -90
View File
@@ -13,7 +13,10 @@ const identityState = {
if (storageResponse && storeKey in storageResponse) { if (storageResponse && storeKey in storageResponse) {
return storageResponse[storeKey]; return storageResponse[storeKey];
} }
return null; const defaultContainerState = identityState._createIdentityState();
await this.set(cookieStoreId, defaultContainerState);
return defaultContainerState;
}, },
set(cookieStoreId, data) { set(cookieStoreId, data) {
@@ -29,21 +32,18 @@ const identityState = {
} }
}, },
async _isKnownContainer(userContextId) {
const cookieStoreId = backgroundLogic.cookieStoreId(userContextId);
const state = await this.storageArea.get(cookieStoreId);
return !!state;
},
_createTabObject(tab) { _createTabObject(tab) {
return Object.assign({}, tab); return Object.assign({}, tab);
}, },
async storeHidden(cookieStoreId) { async storeHidden(cookieStoreId, windowId) {
const containerState = await this.storageArea.get(cookieStoreId); const containerState = await this.storageArea.get(cookieStoreId);
const tabsByContainer = await this._matchTabsByContainer(cookieStoreId); const tabsByContainer = await browser.tabs.query({cookieStoreId, windowId});
tabsByContainer.forEach((tab) => { tabsByContainer.forEach((tab) => {
const tabObject = this._createTabObject(tab); const tabObject = this._createTabObject(tab);
if (!backgroundLogic.isPermissibleURL(tab.url)) {
return;
}
// This tab is going to be closed. Let's mark this tabObject as // This tab is going to be closed. Let's mark this tabObject as
// non-active. // non-active.
tabObject.active = false; tabObject.active = false;
@@ -54,89 +54,9 @@ const identityState = {
return this.storageArea.set(cookieStoreId, containerState); return this.storageArea.set(cookieStoreId, containerState);
}, },
async containersCounts() {
let containersCounts = { // eslint-disable-line prefer-const
"shown": 0,
"hidden": 0,
"total": 0
};
const containers = await browser.contextualIdentities.query({});
for (const id in containers) {
const container = containers[id];
await this.remapTabsIfMissing(container.cookieStoreId);
const containerState = await this.storageArea.get(container.cookieStoreId);
if (containerState.openTabs > 0) {
++containersCounts.shown;
++containersCounts.total;
continue;
} else if (containerState.hiddenTabs.length > 0) {
++containersCounts.hidden;
++containersCounts.total;
continue;
}
}
return containersCounts;
},
async containerTabCount(cookieStoreId) {
// Returns the total of open and hidden tabs with this userContextId
let containerTabsCount = 0;
await identityState.remapTabsIfMissing(cookieStoreId);
const containerState = await this.storageArea.get(cookieStoreId);
containerTabsCount += containerState.openTabs;
containerTabsCount += containerState.hiddenTabs.length;
return containerTabsCount;
},
async totalContainerTabsCount() {
// Returns the number of total open tabs across ALL containers
let totalContainerTabsCount = 0;
const containers = await browser.contextualIdentities.query({});
for (const id in containers) {
const container = containers[id];
const cookieStoreId = container.cookieStoreId;
await identityState.remapTabsIfMissing(cookieStoreId);
totalContainerTabsCount += await this.storageArea.get(cookieStoreId).openTabs;
}
return totalContainerTabsCount;
},
async totalNonContainerTabsCount() {
// Returns the number of open tabs NOT IN a container
let totalNonContainerTabsCount = 0;
const tabs = await browser.tabs.query({});
for (const tab of tabs) {
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(tab.cookieStoreId);
if (userContextId === 0) {
++totalNonContainerTabsCount;
}
}
return totalNonContainerTabsCount;
},
async remapTabsIfMissing(cookieStoreId) {
// We already know this cookieStoreId.
const containerState = await this.storageArea.get(cookieStoreId) || this._createIdentityState();
await this.storageArea.set(cookieStoreId, containerState);
await this.remapTabsFromUserContextId(cookieStoreId);
},
_matchTabsByContainer(cookieStoreId) {
return browser.tabs.query({cookieStoreId});
},
async remapTabsFromUserContextId(cookieStoreId) {
const tabsByContainer = await this._matchTabsByContainer(cookieStoreId);
const containerState = await this.storageArea.get(cookieStoreId);
containerState.openTabs = tabsByContainer.length;
await this.storageArea.set(cookieStoreId, containerState);
},
_createIdentityState() { _createIdentityState() {
return { return {
hiddenTabs: [], hiddenTabs: []
openTabs: 0
}; };
}, },
}; };
-4
View File
@@ -11,8 +11,6 @@
"js/background/badge.js", "js/background/badge.js",
"js/background/identityState.js", "js/background/identityState.js",
"js/background/messageHandler.js", "js/background/messageHandler.js",
"js/background/tabPageCounter.js",
"js/backdround/init.js"
] ]
--> -->
<script type="text/javascript" src="backgroundLogic.js"></script> <script type="text/javascript" src="backgroundLogic.js"></script>
@@ -20,7 +18,5 @@
<script type="text/javascript" src="badge.js"></script> <script type="text/javascript" src="badge.js"></script>
<script type="text/javascript" src="identityState.js"></script> <script type="text/javascript" src="identityState.js"></script>
<script type="text/javascript" src="messageHandler.js"></script> <script type="text/javascript" src="messageHandler.js"></script>
<script type="text/javascript" src="tabPageCounter.js"></script>
<script type="text/javascript" src="init.js"></script>
</body> </body>
</html> </html>
-27
View File
@@ -1,27 +0,0 @@
browser.runtime.sendMessage({
method: "getPreference",
pref: "browser.privatebrowsing.autostart"
}).then(pbAutoStart => {
// We don't want to disable the addon if we are in auto private-browsing.
if (!pbAutoStart) {
browser.tabs.onCreated.addListener(tab => {
if (tab.incognito) {
disableAddon(tab.id);
}
});
browser.tabs.query({}).then(tabs => {
for (let tab of tabs) { // eslint-disable-line prefer-const
if (tab.incognito) {
disableAddon(tab.id);
}
}
}).catch(() => {});
}
}).catch(() => {});
function disableAddon(tabId) {
browser.browserAction.disable(tabId);
browser.browserAction.setTitle({ tabId, title: "Containers disabled in Private Browsing Mode" });
}
+79 -69
View File
@@ -3,6 +3,7 @@ const messageHandler = {
// We use this to catch redirected tabs that have just opened // We use this to catch redirected tabs that have just opened
// If this were in platform we would change how the tab opens based on "new tab" link navigations such as ctrl+click // If this were in platform we would change how the tab opens based on "new tab" link navigations such as ctrl+click
LAST_CREATED_TAB_TIMER: 2000, LAST_CREATED_TAB_TIMER: 2000,
unhideQueue: [],
init() { init() {
// Handles messages from webextension code // Handles messages from webextension code
@@ -16,10 +17,6 @@ const messageHandler = {
case "createOrUpdateContainer": case "createOrUpdateContainer":
response = backgroundLogic.createOrUpdateContainer(m.message); response = backgroundLogic.createOrUpdateContainer(m.message);
break; break;
case "openTab":
// Same as open-tab for index.js
response = backgroundLogic.openTab(m.message);
break;
case "neverAsk": case "neverAsk":
assignManager._neverAsk(m); assignManager._neverAsk(m);
break; break;
@@ -42,26 +39,31 @@ const messageHandler = {
backgroundLogic.sortTabs(); backgroundLogic.sortTabs();
break; break;
case "showTabs": case "showTabs":
backgroundLogic.showTabs({cookieStoreId: m.cookieStoreId}); this.unhideContainer(m.cookieStoreId);
break; break;
case "hideTabs": case "hideTabs":
backgroundLogic.hideTabs({cookieStoreId: m.cookieStoreId}); backgroundLogic.hideTabs({
cookieStoreId: m.cookieStoreId,
windowId: m.windowId
});
break; break;
case "checkIncompatibleAddons": case "checkIncompatibleAddons":
// TODO // TODO
break; break;
case "moveTabsToWindow": case "moveTabsToWindow":
response = backgroundLogic.moveTabsToWindow({ response = backgroundLogic.moveTabsToWindow({
cookieStoreId: m.cookieStoreId cookieStoreId: m.cookieStoreId,
windowId: m.windowId
}); });
break; break;
case "getTabs": case "getTabs":
response = backgroundLogic.getTabs({ response = backgroundLogic.getTabs({
cookieStoreId: m.cookieStoreId cookieStoreId: m.cookieStoreId,
windowId: m.windowId
}); });
break; break;
case "queryIdentitiesState": case "queryIdentitiesState":
response = backgroundLogic.queryIdentitiesState(); response = backgroundLogic.queryIdentitiesState(m.message.windowId);
break; break;
case "exemptContainerAssignment": case "exemptContainerAssignment":
response = assignManager._exemptTab(m); response = assignManager._exemptTab(m);
@@ -70,38 +72,16 @@ const messageHandler = {
return response; return response;
}); });
// Handles messages from sdk code if (browser.contextualIdentities.onRemoved) {
const port = browser.runtime.connect(); browser.contextualIdentities.onRemoved.addListener(({contextualIdentity}) => {
port.onMessage.addListener(m => { const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(contextualIdentity.cookieStoreId);
switch (m.type) { backgroundLogic.deleteContainer(userContextId, true);
case "open-tab": });
backgroundLogic.openTab(m.message); }
break;
default:
throw new Error(`Unhandled message type: ${m.message}`);
}
});
browser.tabs.onCreated.addListener((tab) => {
// This works at capturing the tabs as they are created
// However we need onFocusChanged and onActivated to capture the initial tab
if (tab.id === -1) {
return {};
}
tabPageCounter.initTabCounter(tab);
});
browser.tabs.onRemoved.addListener((tabId) => {
if (tabId === -1) {
return {};
}
tabPageCounter.sendTabCountAndDelete(tabId);
});
browser.tabs.onActivated.addListener((info) => { browser.tabs.onActivated.addListener((info) => {
assignManager.removeContextMenu(); assignManager.removeContextMenu();
browser.tabs.get(info.tabId).then((tab) => { browser.tabs.get(info.tabId).then((tab) => {
tabPageCounter.initTabCounter(tab);
assignManager.calculateContextMenu(tab); assignManager.calculateContextMenu(tab);
}).catch((e) => { }).catch((e) => {
throw e; throw e;
@@ -109,34 +89,7 @@ const messageHandler = {
}); });
browser.windows.onFocusChanged.addListener((windowId) => { browser.windows.onFocusChanged.addListener((windowId) => {
assignManager.removeContextMenu(); this.onFocusChangedCallback(windowId);
// 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
badge.displayBrowserActionBadge();
browser.tabs.query({active: true, windowId}).then((tabs) => {
if (tabs && tabs[0]) {
tabPageCounter.initTabCounter(tabs[0]);
assignManager.calculateContextMenu(tabs[0]);
}
}).catch((e) => {
throw e;
});
});
browser.idle.onStateChanged.addListener((newState) => {
browser.tabs.query({}).then(tabs => {
for (let tab of tabs) { // eslint-disable-line prefer-const
if (newState === "idle") {
tabPageCounter.sendTabCountAndDelete(tab.id, "user-went-idle");
} else if (newState === "active" && tab.active) {
tabPageCounter.initTabCounter(tab);
}
}
}).catch(e => {
throw e;
});
}); });
browser.webRequest.onCompleted.addListener((details) => { browser.webRequest.onCompleted.addListener((details) => {
@@ -146,21 +99,78 @@ const messageHandler = {
assignManager.removeContextMenu(); assignManager.removeContextMenu();
browser.tabs.get(details.tabId).then((tab) => { browser.tabs.get(details.tabId).then((tab) => {
tabPageCounter.incrementTabCount(tab);
assignManager.calculateContextMenu(tab); assignManager.calculateContextMenu(tab);
}).catch((e) => { }).catch((e) => {
throw e; throw e;
}); });
}, {urls: ["<all_urls>"], types: ["main_frame"]}); }, {urls: ["<all_urls>"], types: ["main_frame"]});
// lets remember the last tab created so we can close it if it looks like a redirect browser.tabs.onCreated.addListener((tab) => {
browser.tabs.onCreated.addListener((details) => { if (tab.incognito) {
this.lastCreatedTab = details; badge.disableAddon(tab.id);
}
// 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(() => { setTimeout(() => {
this.lastCreatedTab = null; this.lastCreatedTab = null;
}, this.LAST_CREATED_TAB_TIMER); }, this.LAST_CREATED_TAB_TIMER);
}); });
},
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);
// Unhide all hidden tabs
await backgroundLogic.showTabs({
cookieStoreId
});
this.unhideQueue.splice(this.unhideQueue.indexOf(cookieStoreId), 1);
}
},
async onFocusChangedCallback(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
badge.displayBrowserActionBadge();
browser.tabs.query({active: true, windowId}).then((tabs) => {
if (tabs && tabs[0]) {
assignManager.calculateContextMenu(tabs[0]);
}
}).catch((e) => {
throw e;
});
} }
}; };
@@ -1,55 +0,0 @@
// eslint-disable-next-line no-unused-vars
const tabPageCounter = {
counters: {},
initTabCounter(tab) {
if (tab.id in this.counters) {
if (!("activity" in this.counters[tab.id])) {
this.counters[tab.id].activity = {
"cookieStoreId": tab.cookieStoreId,
"pageRequests": 0
};
}
if (!("tab" in this.counters[tab.id])) {
this.counters[tab.id].tab = {
"cookieStoreId": tab.cookieStoreId,
"pageRequests": 0
};
}
} else {
this.counters[tab.id] = {};
this.counters[tab.id].tab = {
"cookieStoreId": tab.cookieStoreId,
"pageRequests": 0
};
this.counters[tab.id].activity = {
"cookieStoreId": tab.cookieStoreId,
"pageRequests": 0
};
}
},
sendTabCountAndDelete(tabId, why = "user-closed-tab") {
if (!(this.counters[tabId])) {
return;
}
if (why === "user-closed-tab" && this.counters[tabId].tab) {
// When we send the ping because the user closed the tab,
// delete both the 'tab' and 'activity' counters
delete this.counters[tabId];
} else if (why === "user-went-idle" && this.counters[tabId].activity) {
// When we send the ping because the user went idle,
// only reset the 'activity' counter
this.counters[tabId].activity = {
"cookieStoreId": this.counters[tabId].tab.cookieStoreId,
"pageRequests": 0
};
}
},
incrementTabCount(tab) {
this.initTabCounter(tab);
this.counters[tab.id].tab.pageRequests++;
this.counters[tab.id].activity.pageRequests++;
}
};
+6 -2
View File
@@ -20,11 +20,15 @@ async function doAnimation(element, property, value) {
async function addMessage(message) { async function addMessage(message) {
const divElement = document.createElement("div"); const divElement = document.createElement("div");
divElement.classList.add("container-notification"); 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 // Ideally we would use https://bugzilla.mozilla.org/show_bug.cgi?id=1340930 when this is available
divElement.innerText = message.text; divElement.innerText = message.text;
const imageElement = document.createElement("img"); const imageElement = document.createElement("img");
imageElement.src = browser.extension.getURL("/img/container-site-d-24.png"); const imagePath = browser.extension.getURL("/img/container-site-d-24.png");
const response = await fetch(imagePath);
const blob = await response.blob();
const objectUrl = URL.createObjectURL(blob);
imageElement.src = objectUrl;
divElement.prepend(imageElement); divElement.prepend(imageElement);
document.body.appendChild(divElement); document.body.appendChild(divElement);
+74 -25
View File
@@ -22,6 +22,7 @@ const P_CONTAINERS_EDIT = "containersEdit";
const P_CONTAINER_INFO = "containerInfo"; const P_CONTAINER_INFO = "containerInfo";
const P_CONTAINER_EDIT = "containerEdit"; const P_CONTAINER_EDIT = "containerEdit";
const P_CONTAINER_DELETE = "containerDelete"; const P_CONTAINER_DELETE = "containerDelete";
const P_CONTAINERS_ACHIEVEMENT = "containersAchievement";
/** /**
* Escapes any occurances of &, ", <, > or / with XML entities. * Escapes any occurances of &, ", <, > or / with XML entities.
@@ -90,27 +91,16 @@ const Logic = {
// Routing to the correct panel. // Routing to the correct panel.
// If localStorage is disabled, we don't show the onboarding. // If localStorage is disabled, we don't show the onboarding.
const data = await browser.storage.local.get([ONBOARDING_STORAGE_KEY]); const onboardingData = await browser.storage.local.get([ONBOARDING_STORAGE_KEY]);
let onboarded = data[ONBOARDING_STORAGE_KEY]; let onboarded = onboardingData[ONBOARDING_STORAGE_KEY];
if (!onboarded) { if (!onboarded) {
// Legacy local storage used before panel 5 onboarded = 0;
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); this.setOnboardingStage(onboarded);
} }
switch (onboarded) { switch (onboarded) {
case 5: case 5:
this.showPanel(P_CONTAINERS_LIST); this.showAchievementOrContainersListPanel();
break; break;
case 4: case 4:
this.showPanel(P_ONBOARDING_5); this.showPanel(P_ONBOARDING_5);
@@ -132,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) { setOnboardingStage(stage) {
return browser.storage.local.set({ return browser.storage.local.set({
[ONBOARDING_STORAGE_KEY]: stage [ONBOARDING_STORAGE_KEY]: stage
@@ -144,7 +165,11 @@ const Logic = {
browser.browserAction.setBadgeBackgroundColor({color: ""}); browser.browserAction.setBadgeBackgroundColor({color: ""});
browser.browserAction.setBadgeText({text: ""}); browser.browserAction.setBadgeText({text: ""});
storage.browserActionBadgesClicked.push(extensionInfo.version); storage.browserActionBadgesClicked.push(extensionInfo.version);
browser.storage.local.set({browserActionBadgesClicked: storage.browserActionBadgesClicked}); // use set and spread to create a unique array
const browserActionBadgesClicked = [...new Set(storage.browserActionBadgesClicked)];
browser.storage.local.set({
browserActionBadgesClicked
});
}, },
async identity(cookieStoreId) { async identity(cookieStoreId) {
@@ -168,6 +193,7 @@ const Logic = {
}); });
element.addEventListener("keydown", (e) => { element.addEventListener("keydown", (e) => {
if (e.keyCode === 13) { if (e.keyCode === 13) {
e.preventDefault();
handler(e); handler(e);
} }
}); });
@@ -190,7 +216,10 @@ const Logic = {
const [identities, state] = await Promise.all([ const [identities, state] = await Promise.all([
browser.contextualIdentities.query({}), browser.contextualIdentities.query({}),
browser.runtime.sendMessage({ browser.runtime.sendMessage({
method: "queryIdentitiesState" method: "queryIdentitiesState",
message: {
windowId: browser.windows.WINDOW_ID_CURRENT
}
}) })
]); ]);
this._identities = identities.map((identity) => { this._identities = identities.map((identity) => {
@@ -451,7 +480,7 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
panelSelector: "#container-panel", panelSelector: "#container-panel",
// This method is called when the object is registered. // This method is called when the object is registered.
initialize() { async initialize() {
Logic.addEnterHandler(document.querySelector("#container-add-link"), () => { Logic.addEnterHandler(document.querySelector("#container-add-link"), () => {
Logic.showPanel(P_CONTAINER_EDIT, { name: Logic.generateIdentityName() }); Logic.showPanel(P_CONTAINER_EDIT, { name: Logic.generateIdentityName() });
}); });
@@ -599,12 +628,8 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
|| e.target.parentNode.matches(".open-newtab") || e.target.parentNode.matches(".open-newtab")
|| e.type === "keydown") { || e.type === "keydown") {
try { try {
await browser.runtime.sendMessage({ browser.tabs.create({
method: "openTab", cookieStoreId: identity.cookieStoreId
message: {
userContextId: Logic.userContextId(identity.cookieStoreId),
source: "pop-up"
}
}); });
window.close(); window.close();
} catch (e) { } catch (e) {
@@ -654,6 +679,7 @@ Logic.registerPanel(P_CONTAINER_INFO, {
try { try {
browser.runtime.sendMessage({ browser.runtime.sendMessage({
method: identity.hasHiddenTabs ? "showTabs" : "hideTabs", method: identity.hasHiddenTabs ? "showTabs" : "hideTabs",
windowId: browser.windows.WINDOW_ID_CURRENT,
cookieStoreId: Logic.currentCookieStoreId() cookieStoreId: Logic.currentCookieStoreId()
}); });
window.close(); window.close();
@@ -685,6 +711,7 @@ Logic.registerPanel(P_CONTAINER_INFO, {
Logic.addEnterHandler(moveTabsEl, async function () { Logic.addEnterHandler(moveTabsEl, async function () {
await browser.runtime.sendMessage({ await browser.runtime.sendMessage({
method: "moveTabsToWindow", method: "moveTabsToWindow",
windowId: browser.windows.WINDOW_ID_CURRENT,
cookieStoreId: Logic.currentIdentity().cookieStoreId, cookieStoreId: Logic.currentIdentity().cookieStoreId,
}); });
window.close(); window.close();
@@ -726,6 +753,7 @@ Logic.registerPanel(P_CONTAINER_INFO, {
// Let's retrieve the list of tabs. // Let's retrieve the list of tabs.
const tabs = await browser.runtime.sendMessage({ const tabs = await browser.runtime.sendMessage({
method: "getTabs", method: "getTabs",
windowId: browser.windows.WINDOW_ID_CURRENT,
cookieStoreId: Logic.currentIdentity().cookieStoreId cookieStoreId: Logic.currentIdentity().cookieStoreId
}); });
return this.buildInfoTable(tabs); return this.buildInfoTable(tabs);
@@ -800,7 +828,7 @@ Logic.registerPanel(P_CONTAINERS_EDIT, {
</td>`; </td>`;
tr.querySelector(".container-name").textContent = identity.name; tr.querySelector(".container-name").textContent = identity.name;
tr.querySelector(".edit-container").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`); tr.querySelector(".remove-container").setAttribute("title", `Remove ${identity.name} container`);
Logic.addEnterHandler(tr, e => { Logic.addEnterHandler(tr, e => {
@@ -1019,4 +1047,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(); Logic.init();
+5 -6
View File
@@ -1,9 +1,9 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "Containers Experiment", "name": "Firefox Multi-Account Containers",
"version": "3.1.0", "version": "4.1.0",
"description": "Containers works by isolating cookie jars using separate origin-attributes defined visually by colored Container Tabs. This add-on is a modified version of the containers feature for Firefox Test Pilot.", "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": { "icons": {
"48": "img/container-site-d-48.png", "48": "img/container-site-d-48.png",
"96": "img/container-site-d-96.png" "96": "img/container-site-d-96.png"
@@ -11,8 +11,7 @@
"applications": { "applications": {
"gecko": { "gecko": {
"strict_min_version": "51.0", "strict_min_version": "53.0"
"update_url": "https://testpilot.firefox.com/files/@testpilot-containers/updates.json"
} }
}, },
@@ -45,7 +44,7 @@
"browser_action": { "browser_action": {
"browser_style": true, "browser_style": true,
"default_icon": "img/container-site.svg", "default_icon": "img/container-site.svg",
"default_title": "Containers", "default_title": "Multi-Account Containers",
"default_popup": "popup.html" "default_popup": "popup.html"
}, },
+29 -1
View File
@@ -1,7 +1,7 @@
<html> <html>
<head> <head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"> <meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Containers browserAction Popup</title> <title>Multi-Account Containers</title>
<link rel="stylesheet" href="/css/popup.css"> <link rel="stylesheet" href="/css/popup.css">
</head> </head>
@@ -67,6 +67,34 @@
<a href="#" id="onboarding-longpress-button" class="onboarding-button">Done</a> <a href="#" id="onboarding-longpress-button" class="onboarding-button">Done</a>
</div> </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 class="panel container-panel hide" id="container-panel">
<div id="current-tab"> <div id="current-tab">
<h3>Current Tab</h3> <h3>Current Tab</h3>