Compare commits

...

124 Commits

Author SHA1 Message Date
shivangikakkar 7833a6a79f adding new version 2019-01-23 23:30:04 +05:30
Jonathan Kingston cc42beeb5a Merge branch 'ShivangiKakkar-fixes-168' 2019-01-12 23:30:02 +00:00
Jonathan Kingston ea5669911b Merge branch 'fixes-168' of https://github.com/ShivangiKakkar/multi-account-containers into ShivangiKakkar-fixes-168 2019-01-12 23:28:49 +00:00
Shivangi Kakkar b4c2da5474 Merge branch 'master' into fixes-168 2019-01-11 10:33:54 +09:00
shivangikakkar 6d7086d541 added functions and cosmetic changes 2019-01-11 09:25:00 +09:00
shivangikakkar db0dba66b2 dynamically added close image icon in the title column 2019-01-11 04:06:08 +09:00
shivangikakkar 5b58168999 Refactored the logic to form warning message 2019-01-07 23:08:59 +00:00
groovecoder 4ed453d58e save ajv at 6.6.2 2019-01-07 23:08:59 +00:00
mingchia-andy-liu 66a9116524 Fix delete warning message #1051
- Include the number of tabs that will be closed when container is deleted
- Insert the warning message to the `span` tag that was originally there
2019-01-07 23:08:59 +00:00
Jonathan Kingston cb7ac6ca5e Merge pull request #1298 from aabbi/duplicate-tabs-791
Fixes #1109 , rest of #791
2019-01-04 14:40:46 +00:00
luke crouch a2995b6c66 Merge pull request #1292 from jonathanKingston/fix-missing-favicon
Add in missing favicon. Fixes #1285
2018-12-05 12:41:43 -06:00
luke crouch ed383c8dfc Merge pull request #1294 from jonathanKingston/badge-background-error
Remove badge background error. Fixes #1293
2018-12-05 09:09:34 -06:00
luke crouch df9b900db6 Merge pull request #1297 from jonathanKingston/6.0.1
Bump release to 6.0.1
2018-12-05 08:48:50 -06:00
luke crouch 8e611de605 Merge pull request #1295 from jonathanKingston/fix-missing-check-image
Fix missing assignment check mark. Fixes #1271
2018-12-05 08:47:02 -06:00
shivangikakkar 8af4c36fd0 removing inline css 2018-10-29 16:32:10 +05:30
shivangikakkar abc4e0cdcf review changes 2018-10-23 15:13:41 +05:30
aabbi b6dd32f683 moved unhiding container to background logic 2018-10-22 20:05:03 -04:00
Jonathan Kingston 0a437ff303 Bump release to 6.0.1. Fixes #1296 2018-10-21 22:33:08 +01:00
Jonathan Kingston f7f4c320a6 Fix missing assignment check mark. Fixes #1271 2018-10-21 22:22:48 +01:00
Jonathan Kingston 5813621fb9 Remove badge background error. Fixes #1293 2018-10-21 21:25:55 +01:00
Jonathan Kingston 56fc7407da Add in missing favicon. Fixes #1285 2018-10-21 21:13:57 +01:00
Jonathan Kingston 220b902144 Merge pull request #1275 from ShivangiKakkar/fixes-256
#256 disables edit-container button when no container is present
2018-10-21 19:45:44 +01:00
shivangikakkar c15eee22c6 implemented remove individual tabs just for visible state 2018-10-21 23:24:18 +05:30
shivangikakkar dcc3b76cda simplifying the solution 2018-10-21 17:12:56 +05:30
shivangikakkar 752d18ffca prevent event handler when edit containers disabled 2018-10-18 21:33:48 +05:30
luke crouch 97559dd08a Merge pull request #1281 from hritvi/npm_fixes
Fixes vulnerabilities in npm
2018-10-03 07:00:14 -05:00
hritvi 0e7363a87f npm fixes 2018-10-03 16:16:44 +05:30
hritvi 6c62c2f599 fixes vulnerabilities in npm 2018-10-03 16:14:36 +05:30
Jonathan Kingston 884e419a7c Merge pull request #1265 from ShivangiKakkar/fixes-1028
#1028 resolves focus name field on opening new container sub-panel
2018-10-02 18:18:05 +01:00
shivangikakkar d7586dd4c2 resolves #256 disables edit-container button when no container is present
resolve lint errors

review changes
2018-10-01 00:01:50 +05:30
shivangikakkar aada0419eb removing event loop and using requestAnimationFrame 2018-09-30 18:17:01 +05:30
shivangikakkar 1ea04587d9 resolves lint errors 2018-09-25 17:47:17 +05:30
luke crouch 3d1dcd33d1 Merge pull request #1262 from ShivangiKakkar/fixes-885
#885 disables move tab to a new window when only one tab is opened
2018-09-24 14:05:27 -05:00
shivangikakkar bfdbd8199f fixes-168, added ability to close a tab individually 2018-09-25 00:21:46 +05:30
shivangikakkar fe0810b048 restructuring the code acc to the review 2018-09-23 00:42:43 +05:30
shivangikakkar e1c1ac4bd9 adds back the unnecessarily removed new line 2018-09-21 14:31:54 +05:30
shivangikakkar 7f7f221a79 fix-#1028 resolves focus name field on opening new container sub-panel 2018-09-21 14:04:43 +05:30
shivangikakkar e57c556427 review changes 2018-09-20 23:40:22 +05:30
shivangikakkar dd57158ab5 #885 disables move tab to a new window when only one tab is opened 2018-09-20 21:34:19 +05:30
stoically 99db192792 Merge pull request #1170 from real-or-random/master
Update GitHub URLs
2018-03-31 00:04:37 +02:00
stoically fcbee854d0 Merge pull request #1172 from stoically/add-issue-template
Add ISSUE_TEMPLATE
2018-03-30 16:46:22 +02:00
stoically fae1336467 Add ISSUE_TEMPLATE 2018-03-30 16:43:24 +02:00
Tim Ruffing 655d8f3791 Update GitHub URLs 2018-03-29 13:45:05 +02:00
luke crouch dcc852bf17 Merge pull request #1120 from stoically/cancel-redirects-tabid-url
Cancel redirects for the same requestIds and urls if originating from the same tabId
2018-03-01 14:23:29 -06:00
stoically dab3005c6f Cancel redirects for the same requestIds and urls if originating from the same tabId
Fixes #940
2018-02-23 15:33:32 +01:00
luke crouch ee6a54ffa2 Merge pull request #1116 from mozilla/testing-6.0.0
Version 6.0.0
2018-02-22 14:44:12 -06:00
Jonathan Kingston 601056406a Merge pull request #1118 from stoically/tests-6.0.0
Add and fix tests for 6.0.0
2018-02-22 16:57:08 +00:00
stoically fd72ce12b4 Fix assignment test to check for the new openerTabId property 2018-02-10 00:51:13 +01:00
stoically 6e45532f58 Add tests for external webextensions feature 2018-02-10 00:50:29 +01:00
Jonathan Kingston 61da6b5e99 Merge pull request #1117 from mozilla/jonathanKingston-patch-1
Adding an s://
2018-02-09 18:37:45 +00:00
Jonathan Kingston e0156388e8 Adding an s:// 2018-02-09 18:37:29 +00:00
Jonathan Kingston 16f1d47bf2 Merge pull request #1115 from jonathanKingston/6.0.0
Bumping version to 6.0.0 to account for latest fixes
2018-02-09 18:34:54 +00:00
Jonathan Kingston ee647344a1 Bumping version to 6.0.0 to account for latest fixes 2018-02-09 18:33:54 +00:00
Jonathan Kingston 40426ca936 Merge pull request #1107 from stoically/tests
Adding assignment feature tests
2018-02-09 18:07:20 +00:00
Jonathan Kingston d1e9c2d1e3 Merge pull request #1066 from LoveIsGrief/1065_-_Feature_Request]_Pass_openerTabId_when_creating_tabs_in_order_to_know_the_parent
Pass opener tab id when creating tabs in order to know the parent
2018-02-09 18:06:56 +00:00
Jonathan Kingston 3bd33cda99 Merge branch 'testing-6.0.0' into 1065_-_Feature_Request]_Pass_openerTabId_when_creating_tabs_in_order_to_know_the_parent 2018-02-09 18:04:58 +00:00
stoically 609f62ac7a Allow webextensions with contextualIdentities permission to get assignment
Closes #1095
2018-02-09 18:00:26 +00:00
stoically ce84665e3a Add management permission to manifest.json
Part of #1095
2018-02-09 18:00:26 +00:00
stoically 7dceaf6679 Cancel requests with the same requestId
Prevents potential redirects from opening two tabs
Closes #1114
2018-02-09 17:59:23 +00:00
stoically b6bcd99dc8 Add test for issue #940 2018-02-09 12:02:41 +01:00
stoically 9bc9509316 Added assignment feature test
Part of #1107
2018-02-02 19:05:40 +01:00
luke crouch 22ec01d565 Merge pull request #1106 from timendum/master
Fix for siteSettings is null
2018-01-30 14:03:06 -06:00
Timendum a16cae0342 Fix for siteSettings is null
siteSettings can be null, with this change we can avoid an exception.
2018-01-30 10:51:38 +01:00
luke crouch f17ff7168f Merge pull request #1055 from ericlathrop/fix-moving-pinned-tabs-to-new-window
Fix #1053.
2018-01-29 11:02:14 -06:00
luke crouch d3b22faf65 Merge pull request #1097 from crenwick/fix-unwanted-shortcut-bug
Fixes #1084.
2018-01-29 10:27:16 -06:00
Charles Renwick 30e5a27eb4 Fixes #1084.
Before opening a container with the `1-9` shortcut, it ensures that the current pannel is the "containersList"
2018-01-26 13:44:46 -05:00
luke crouch 0f720ec11d Merge pull request #1073 from tiagonbotelho/tb-fix-readme-code-wrapping
Fixes code markdown wrapping under Development in the README section
2018-01-18 14:19:57 -06:00
groovecoder 0ddee7f9d0 id:@testpilot-containers to manifest.json; 5.0.1 2018-01-08 22:59:52 +00:00
Tiago Botelho 1e16e203dc Fixes code markdown wrapping under Development in the README section 2018-01-08 20:37:35 +00:00
LoveIsGrief af986e8880 Set the openerTabId to a tab that won't be removed/closed
The tab might be removed before we can create the tab making the parent
 invalid.

#1065 - [Feature Request] Pass openerTabId when creating tabs in order to know the parent
2018-01-05 13:51:47 +01:00
LoveIsGrief 7e04c46070 Pass the openerTabId when automatically opening tabs in containers
the `openerTabId` can also be seen as the tab's parent.
This is useful for extensions like https://github.com/piroor/treestyletab

#1065 - [Feature Request] Pass openerTabId when creating tabs in order to know the parent
2018-01-05 13:05:32 +01:00
LoveIsGrief 166420dd86 Ignore JetBrains IDE files
#1065 - [Feature Request] Pass openerTabId when creating tabs in order to know the parent
2018-01-05 13:01:43 +01:00
Eric Lathrop d7a2b43b07 Fix #1053.
Create the new window with the default tab rather than an existing tab,
then pin the default tab. This allows any pinned tabs to be moved to the
new window because they're not allowed to be moved after a un-pinned
tab. The default tab is automatically closed later in `moveTabsToWindow`
because it's not a part of the container.
2017-12-22 11:51:11 -05:00
groovecoder bea201a389 update README & package.json to match web-ext flow 2017-12-05 19:06:13 +00:00
Jonathan Kingston d944116e3e Move to a Web Extension only. Fixes #1005 2017-12-05 19:06:13 +00:00
luke crouch 4a1597c87f Merge pull request #1021 from gaborluk/maintain-tab-active-state
Maintain the active state of the redirected tab
2017-12-04 09:20:02 -06:00
Gabor Luk f87bf2a861 Maintain the active state of the redirected tab 2017-12-02 15:25:32 +01:00
luke crouch ef45cde290 Merge pull request #992 from crenwick/open-containers-shortcut
Adds shortcut to open containers.
2017-11-28 15:59:19 -06:00
Charles Renwick 752b1c3b27 Removes shortcut for the tenth container. 2017-11-28 16:17:48 -05:00
Charles Renwick cf26d8547a Consolidates switch pattern for shortcut keys.
Applies @groovecoder suggestions to consolidate a long switch statement.
2017-11-28 11:40:10 -05:00
Charles Renwick 1d78febafc fixes keyCodes 2017-11-21 10:51:29 -05:00
Charles Renwick f483119a40 Adds shortcut to open containers.
Makes it more effective to open the first ten containers in the
containers list. Keys 1-0 will open containers 1-10, if that container
exists.

Note: the plugin's popup panel does not autofocus when opened. This
requires the user to focus on the panel by either clicking or pressing
TAB before using shortcut keys. This behavior is consistent with the previous shortcuts for
this addon.
2017-11-21 10:26:30 -05:00
groovecoder abd2b73fca ignore all *.sw* files for jpm and git 2017-11-17 09:59:29 -06:00
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
67 changed files with 1345 additions and 493 deletions
+28
View File
@@ -0,0 +1,28 @@
<!--
Feel free to ignore this Issue template if you just want to ask or suggest something. If you experience an Issue then please provide all asked informations.
Note: If "Firefox will: Never remember history" in the Firefox Preferences/Options under "Privacy & Security > History" is selected, then Multi-Account Containers will not work, since Containers aren't available in Private Windows.
-->
- Is "Firefox will: Never remember history" in the Firefox Preferences/Options under "Privacy & Security > History" selected? Yes/No:
- Are you using Firefox in a Private Window? Yes/No:
- Can you see a grayed out but ticked Checkbox with the description "Enable Container Tabs" in the Firefox Preferences/Options under "Tabs"? Yes/No:
- Multi-Account Containers Version:
- Operating System + Version:
- Firefox Version:
- Other installed Add-ons + Version + Enabled/Disabled-Status:
<!-- To be able to Copy&Paste the full list of your Add-ons navigate to "about:support" and scroll down to "Extensions" -->
### Actual behavior
..
### Expected behavior
..
### Steps to reproduce
1. ..
2. ..
3. ..
### Notes
..
+6 -2
View File
@@ -3,8 +3,12 @@ package-lock.json
node_modules
README.html
*.xpi
*.swp
*.swo
*.sw*
.vimrc
.env
addon.env
src/web-ext-artifacts/*
# JetBrains IDE files
.idea
+3
View File
@@ -3,6 +3,7 @@ docs/
test/
.npm/
node_modules/
bin/
.env
.eslintrc.js
@@ -14,6 +15,8 @@ node_modules/
.stylelintrc
.travis.yml
*.xpi
*.md
.vimrc
.DS_Store
.gdb_history
*.sw*
+1 -1
View File
@@ -5,7 +5,7 @@
"extends": "stylelint-config-standard",
"ignoreFiles": ["webextension/css/*.min.css"],
"ignoreFiles": ["src/css/*.min.css"],
"rules": {
"declaration-block-no-duplicate-properties": true,
+1 -1
View File
@@ -1,6 +1,6 @@
language: node_js
node_js:
- "6.1"
- "lts/*"
notifications:
irc:
+2 -2
View File
@@ -9,9 +9,9 @@ Everyone is welcome to contribute to containers. Reach out to team members if yo
If you find a bug with containers, please file a issue.
Check first if the bug might already exist: https://github.com/mozilla/testpilot-containers/issues
Check first if the bug might already exist: https://github.com/mozilla/multi-account-containers/issues
[Open an issue](https://github.com/mozilla/testpilot-containers/issues/new)
[Open an issue](https://github.com/mozilla/multi-account-containers/issues/new)
1. Visit about:support
2. Click "Copy raw data to clipboard" and paste into the bug. Alternatively copy the following sections into the issue:
+29 -73
View File
@@ -1,11 +1,8 @@
# Firefox Multi-Account Containers
# Multi-Account Containers
[![Available on Test Pilot](https://img.shields.io/badge/available_on-Test_Pilot-0996F8.svg)](https://testpilot.firefox.com/experiments/containers)
The Firefox Multi-Account Containers extension lets you carve out a separate box for each of your online lives no more opening a different browser just to check your work email! [Learn More Here](https://blog.mozilla.org/firefox/introducing-firefox-multi-account-containers/)
[Embedded Web Extension](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Embedded_WebExtensions) to build [Containers](https://blog.mozilla.org/tanvi/2016/06/16/contextual-identities-on-the-web/) as a Firefox [Test Pilot](https://testpilot.firefox.com/) Experiment and [Shield Study](https://wiki.mozilla.org/Firefox/Shield/Shield_Studies) to learn:
* Will a general Firefox audience understand the Containers feature?
* Is the UI as currently implemented in Nightly clear or discoverable?
[Available on addons.mozilla.org](https://addons.mozilla.org/en-GB/firefox/addon/multi-account-containers/)
For more info, see:
@@ -16,85 +13,44 @@ For more info, see:
## Requirements
* node 7+ (for jpm)
* Firefox 53+
* Firefox 57+
## Development
### Web Extension Development
Since Firefox 57, this extension can now be run without any of the legacy components that were previously needed.
1. Install web-ext with npm
2. cd webextension; web-ext run -f Nightly
This will work in other builds of Firefox however certain features won't work and you will need to manually flip preferences to enable containers. All other sections of this guide talk about using the legacy setup with jpm.
## Legacy Development
### Development Environment
Add-on development is better with [a particular environment](https://developer.mozilla.org/en-US/Add-ons/Setting_up_extension_development_environment). One simple way to get that environment set up is to install the [DevPrefs add-on](https://addons.mozilla.org/en-US/firefox/addon/devprefs/). You can make a custom Firefox profile that includes the DevPrefs add-on, and use that profile when you run the code in this repository.
1. Make a new profile by running `/path/to/firefox -P`, which launches the profile editor. "Create Profile" -- name it whatever you wish (e.g. 'addon_dev') and store it in the default location. It's probably best to deselect the option to "Use without asking," since you probably don't want to use this as your default profile.
2. Once you've created your profile, click "Start Firefox". A new instance of Firefox should launch. Go to Tools->Add-ons and search for "DevPrefs". Install it. Quit Firefox.
3. Now you have a new, vanilla Firefox profile with the DevPrefs add-on installed. You can use your new profile with the code in _this_ repository like so:
#### Run the `.xpi` file in an unbranded build
Release & Beta channels do not allow un-signed add-ons, even with the DevPrefs. So, you must run the add-on in an [unbranded build](https://wiki.mozilla.org/Add-ons/Extension_Signing#Unbranded_Builds):
1. Download and install an un-branded build of Firefox
2. Download the latest `.xpi` from this repository's releases
3. Run the un-branded build of Firefox with your DevPrefs profile
4. Go to `about:addons`
5. Click the gear, and select "Install Add-on From File..."
6. Select the `.xpi` file
#### Correct prefs
Whilst this is still using legacy code to test you will need the following in your profile:
Change the following prefs in about:config:
- extensions.legacy.enabled = true
- xpinstall.signatures.required = false
#### Run the TxP experiment with `jpm`
1. `git clone git@github.com:mozilla/testpilot-containers.git`
2. `cd testpilot-containers`
3. `npm install`
4. `./node_modules/.bin/jpm run -p /Path/To/Firefox/Profiles/{junk}.addon_dev -b FirefoxBeta` (where FirefoxBeta might be: ~/<reponame>/obj-x86_64-pc-linux-gnu/dist/bin/firefox or ~/<downloadedFirefoxBeta>/firefox)
Check out the [Browser Toolbox](https://developer.mozilla.org/en-US/docs/Tools/Browser_Toolbox) for more information about debugging add-on code.
### Building .xpi
To build a local testpilot-containers.xpi, use the plain [`jpm
xpi`](https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/jpm#jpm_xpi) command,
or run `npm run build`.
### Signing an .xpi
To sign an .xpi, use [`jpm
sign`](https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/jpm#jpm_sign)
command.
Note: You will need to be [an author on the AMO
add-on](https://addons.mozilla.org/en-US/developers/addon/containers-experiment/ownership).
1. `npm install`
2. `./node_modules/.bin/web-ext run -s src/`
### Testing
TBD
### Distributing
TBD
#### Make the new version
1. Bump the version number in `package.json` and `manifest.json`
2. Commit the version number bump
3. Create a git tag for the version: `git tag <version>`
4. Push the tag up to GitHub: `git push --tags`
#### Publish to AMO
1. `npm run-script build`
2. [Upload the `.zip` to AMO](https://addons.mozilla.org/en-US/developers/addon/multi-account-containers/versions/submit/)
#### Publish to GitHub
Finally, we also publish the release to GitHub for those followers.
1. Download the signed `.xpi` from [the addon versions page](https://addons.mozilla.org/en-US/developers/addon/multi-account-containers/versions)
2. [Make the new release on
GitHub](https://github.com/mozilla/multi-account-containers/releases/new)
* Use the version number for "Tag version" and "Release title"
* Release notes: copy the output of `git log --no-merges --pretty=format:"%h %s" <previous-version>..<new-version>`
* Attach binaries: select the signed `.xpi` file
### Links
Facebook & Twitter icons CC-Attrib https://fairheadcreative.com.
- [Licence](./LICENSE.txt)
- [Contributing](./CONTRIBUTING.md)
- [Code Of Conduct](./CODE_OF_CONDUCT.md)
-170
View File
@@ -1,170 +0,0 @@
"use strict";
const PREFS = [
{
name: "privacy.userContext.enabled",
value: true,
type: "bool",
default: false
},
{
name: "privacy.userContext.longPressBehavior",
value: 2,
type: "int",
default: 0
},
{
name: "privacy.userContext.ui.enabled",
value: true, // Post web ext we will be setting this true
type: "bool",
default: true
},
{
name: "privacy.usercontext.about_newtab_segregation.enabled",
value: true,
type: "bool",
default: false
},
];
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cc = Components.classes;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const { TextDecoder, TextEncoder } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
const JETPACK_DIR_BASENAME = "jetpack";
const EXTENSION_ID = "@testpilot-containers";
function loadStyles(resourceURI) {
const styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
.getService(Ci.nsIStyleSheetService);
const styleURI = styleSheet(resourceURI);
const sheetType = styleSheetService.AGENT_SHEET;
styleSheetService.loadAndRegisterSheet(styleURI, sheetType);
}
function styleSheet(resourceURI) {
return Services.io.newURI("data/usercontext.css", null, resourceURI);
}
function unloadStyles(resourceURI) {
const styleURI = styleSheet(resourceURI);
const styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
.getService(Ci.nsIStyleSheetService);
const sheetType = styleSheetService.AGENT_SHEET;
if (styleSheetService.sheetRegistered(styleURI, sheetType)) {
styleSheetService.unregisterSheet(styleURI, sheetType);
}
}
function filename() {
const storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
storeFile.append(JETPACK_DIR_BASENAME);
storeFile.append(EXTENSION_ID);
storeFile.append("simple-storage");
storeFile.append("store.json");
return storeFile.path;
}
async function makeFilepath() {
const storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
storeFile.append(JETPACK_DIR_BASENAME);
await OS.File.makeDir(storeFile.path, { ignoreExisting: true });
storeFile.append(EXTENSION_ID);
await OS.File.makeDir(storeFile.path, { ignoreExisting: true });
storeFile.append("simple-storage");
await OS.File.makeDir(storeFile.path, { ignoreExisting: true });
}
async function getConfig() {
let savedConfig = {savedConfiguration: {}};
try {
const bytes = await OS.File.read(filename());
const raw = new TextDecoder().decode(bytes) || "";
if (raw) {
savedConfig = JSON.parse(raw);
}
} catch (e) {
// ignore file read errors, sometimes they happen and I'm not sure if we can fix
}
return savedConfig;
}
async function initConfig() {
const savedConfig = await getConfig();
savedConfig.savedConfiguration.version = 2;
if (!("prefs" in savedConfig.savedConfiguration)) {
savedConfig.savedConfiguration.prefs = {};
PREFS.forEach((pref) => {
if ("int" === pref.type) {
savedConfig.savedConfiguration.prefs[pref.name] = Services.prefs.getIntPref(pref.name);
} else {
savedConfig.savedConfiguration.prefs[pref.name] = Services.prefs.getBoolPref(pref.name);
}
});
}
const serialized = JSON.stringify(savedConfig);
const bytes = new TextEncoder().encode(serialized) || "";
await makeFilepath();
await OS.File.writeAtomic(filename(), bytes, { });
}
function setPrefs() {
PREFS.forEach((pref) => {
if ("int" === pref.type) {
Services.prefs.setIntPref(pref.name, pref.value);
} else {
Services.prefs.setBoolPref(pref.name, pref.value);
}
});
}
// eslint-disable-next-line no-unused-vars
async function install() {
await initConfig();
setPrefs();
}
// eslint-disable-next-line no-unused-vars
async function uninstall(aData, aReason) {
if (aReason === ADDON_UNINSTALL
|| aReason === ADDON_DISABLE) {
const config = await getConfig();
const storedPrefs = config.savedConfiguration.prefs || {};
PREFS.forEach((pref) => {
let value = pref.default;
if (pref.name in storedPrefs) {
value = storedPrefs[pref.name];
}
if ("int" === pref.type) {
Services.prefs.setIntPref(pref.name, value);
} else {
Services.prefs.setBoolPref(pref.name, value);
}
});
}
}
// eslint-disable-next-line no-unused-vars
function startup({webExtension, resourceURI}) {
const version = Services.appinfo.version;
const versionMatch = version.match(/^([0-9]+)\./)[1];
if (versionMatch === "55"
|| versionMatch === "56") {
loadStyles(resourceURI);
}
// Reset prefs that may have changed, or are legacy
install();
// Start the embedded webextension.
webExtension.startup();
}
// eslint-disable-next-line no-unused-vars
function shutdown({resourceURI}) {
unloadStyles(resourceURI);
}
-23
View File
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>@testpilot-containers</em:id>
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>
<em:multiprocessCompatible>true</em:multiprocessCompatible>
<em:hasEmbeddedWebExtension>true</em:hasEmbeddedWebExtension>
<em:name>Firefox Multi-Account Containers</em:name>
<em:description>Containers helps you keep all the parts of your online life contained in different tabs. Custom labels and color-coded tabs help keep different activities — like online shopping, travel planning, or checking work email — separate.</em:description>
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!--Firefox-->
<em:minVersion>51.0a1</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
<em:version>4.0.0</em:version>
<em:unpack>false</em:unpack>
</Description>
</RDF>
+22 -17
View File
@@ -1,43 +1,48 @@
{
"name": "testpilot-containers",
"title": "Firefox Multi-Account Containers",
"title": "Multi-Account Containers",
"description": "Containers helps you keep all the parts of your online life contained in different tabs. Custom labels and color-coded tabs help keep different activities — like online shopping, travel planning, or checking work email — separate.",
"version": "4.0.0",
"version": "6.1.0",
"author": "Andrea Marchesini, Luke Crouch and Jonathan Kingston",
"bugs": {
"url": "https://github.com/mozilla/testpilot-containers/issues"
"url": "https://github.com/mozilla/multi-account-containers/issues"
},
"dependencies": {},
"devDependencies": {
"addons-linter": "^0.15.14",
"deploy-txp": "^1.0.7",
"ajv": "^6.6.2",
"addons-linter": "^1.3.2",
"chai": "^4.1.2",
"eslint": "^3.17.1",
"eslint-plugin-no-unsanitized": "^2.0.0",
"eslint-plugin-promise": "^3.4.0",
"htmllint-cli": "^0.0.5",
"jpm": "^1.2.2",
"htmllint-cli": "0.0.7",
"jsdom": "^11.6.2",
"json": "^9.0.6",
"mocha": "^5.0.0",
"npm-run-all": "^4.0.0",
"sinon": "^4.4.0",
"sinon-chai": "^2.14.0",
"stylelint": "^7.9.0",
"stylelint-config-standard": "^16.0.0",
"stylelint-order": "^0.3.0"
"stylelint-order": "^0.3.0",
"web-ext": "^2.2.2"
},
"homepage": "https://github.com/mozilla/testpilot-containers#readme",
"homepage": "https://github.com/mozilla/multi-account-containers#readme",
"license": "MPL-2.0",
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/mozilla/testpilot-containers.git"
"url": "git+https://github.com/mozilla/multi-account-containers.git"
},
"scripts": {
"build": "npm test && jpm xpi",
"deploy": "deploy-txp",
"build": "npm test && cd src && web-ext build --overwrite-dest",
"lint": "npm-run-all lint:*",
"lint:addon": "addons-linter webextension --self-hosted",
"lint:css": "stylelint webextension/css/*.css",
"lint:html": "htmllint webextension/*.html",
"lint:addon": "addons-linter src --self-hosted",
"lint:css": "stylelint src/css/*.css",
"lint:html": "htmllint *.html",
"lint:js": "eslint .",
"package": "npm run build && mv testpilot-containers.xpi addon.xpi",
"test": "npm run lint"
"package": "rm -rf src/web-ext-artifacts && npm run build && mv src/web-ext-artifacts/firefox_multi-account_containers-*.zip addon.xpi",
"test": "npm run lint && mocha ./test/setup.js test/**/*.test.js",
"test-watch": "mocha ./test/setup.js test/**/*.test.js --watch"
}
}
@@ -1,7 +1,7 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Firefox Multi-Account 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 rel="stylesheet" href="/css/confirm-page.css" />
</head>
@@ -45,6 +45,8 @@ body {
--small-text-size: 0.833rem; /* 10px */
--small-radius: 3px;
--icon-button-size: calc(calc(var(--block-line-separation-size) * 2) + 1.66rem); /* 20px */
--column-panel-inline-size: 268px;
--inactive-opacity: 0.3;
}
@media (min-resolution: 1dppx) {
@@ -242,7 +244,8 @@ table {
min-block-size: 400px;
}
.panel.onboarding {
.panel.onboarding,
.achievement-panel {
align-items: center;
block-size: 360px;
margin-block-end: 16px;
@@ -265,7 +268,7 @@ table {
.column-panel-content {
display: flex;
flex-direction: column;
inline-size: 268px;
inline-size: var(--column-panel-inline-size);
}
.column-panel-content .panel-footer {
@@ -536,7 +539,7 @@ span ~ .panel-header-text {
}
#current-tab > label > input:checked {
background-image: url("chrome://global/skin/in-content/check.svg#check-native");
background-image: url("/img/check.svg");
background-position: -1px -1px;
background-size: var(--icon-size);
}
@@ -577,6 +580,11 @@ span ~ .panel-header-text {
max-inline-size: 204px;
}
.disable-edit-containers {
opacity: var(--inactive-opacity);
pointer-events: none;
}
.userContext-wrapper {
align-items: center;
display: flex;
@@ -652,7 +660,11 @@ span ~ .panel-header-text {
/* Container info list */
.container-info-tab-title {
flex: 1;
display: flex;
}
.container-info-tab-row:hover .container-info-tab-title .truncate-text {
inline-size: calc(var(--column-panel-inline-size) - 58px);
}
#container-info-hideorshow {
@@ -669,6 +681,21 @@ span ~ .panel-header-text {
opacity: 0.3;
}
.container-close-tab {
transform: scale(0.7);
visibility: collapse;
}
.container-info-tab-row:hover .container-close-tab {
opacity: 0.5;
visibility: visible;
}
.container-info-tab-row .container-close-tab:hover {
opacity: 1;
visibility: visible;
}
.container-info-has-tabs,
.container-info-tab-row {
align-items: center;
@@ -695,10 +722,6 @@ span ~ .panel-header-text {
margin-inline-end: 0;
}
.container-info-tab-row td {
max-inline-size: 200px;
}
.container-info-list {
display: flex;
flex-direction: column;
@@ -887,3 +910,53 @@ span ~ .panel-header-text {
font-size: 14px !important;
padding-block-end: 6px;
}
/* Achievement panel elements */
.share-ctas {
padding-block-end: 0.5em;
padding-block-start: 0.5em;
padding-inline-end: 0.5em;
padding-inline-start: 0.5em;
text-align: center;
}
.cta-link {
text-decoration: none;
}
.cta {
color: #fff;
font-size: 0.7em;
font-weight: bold;
margin-block-end: 0.4em;
margin-block-start: 0.4em;
margin-inline-end: 0.4em;
margin-inline-start: 0.4em;
padding-block-end: 0.5em;
padding-block-start: 0.5em;
padding-inline-end: 0.5em;
padding-inline-start: 0.5em;
text-transform: uppercase;
}
.cta-icon {
height: 18px;
padding-right: 0.5em;
vertical-align: middle;
}
.fb-share-cta {
background: #375496;
}
.fb-share-cta .cta-icon {
margin-block-start: -5px;
}
.tweet-cta {
background: #37bae7;
}
.amo-rate-cta {
background: #0f1126;
}
+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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 61 KiB

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 307 B

+6
View File
@@ -0,0 +1,6 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="#3c3c3c" d="M6 14a1 1 0 0 1-.707-.293l-3-3a1 1 0 0 1 1.414-1.414l2.157 2.157 6.316-9.023a1 1 0 0 1 1.639 1.146l-7 10a1 1 0 0 1-.732.427A.863.863 0 0 1 6 14z"/>
</svg>

After

Width:  |  Height:  |  Size: 477 B

Before

Width:  |  Height:  |  Size: 595 B

After

Width:  |  Height:  |  Size: 595 B

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 520 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 7 7">
<polygon fill="#4c4c4c" points="5.8,0 3.5,2.4 1.2,0 0,1.2 2.4,3.5 0.1,5.8 1.2,7 3.5,4.7 5.8,7 7,5.8 4.7,3.5 7,1.2"/>
</svg>

After

Width:  |  Height:  |  Size: 183 B

Before

Width:  |  Height:  |  Size: 626 B

After

Width:  |  Height:  |  Size: 626 B

Before

Width:  |  Height:  |  Size: 603 B

After

Width:  |  Height:  |  Size: 603 B

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 883 B

After

Width:  |  Height:  |  Size: 883 B

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 342 B

After

Width:  |  Height:  |  Size: 342 B

Before

Width:  |  Height:  |  Size: 578 B

After

Width:  |  Height:  |  Size: 578 B

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 307 B

Before

Width:  |  Height:  |  Size: 534 B

After

Width:  |  Height:  |  Size: 534 B

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 755 B

After

Width:  |  Height:  |  Size: 755 B

Before

Width:  |  Height:  |  Size: 399 B

After

Width:  |  Height:  |  Size: 399 B

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

+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

@@ -113,55 +113,131 @@ const assignManager = {
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 (siteSettings && !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 {};
}
const removeTab = backgroundLogic.NEW_TAB_PAGES.has(tab.url)
|| (messageHandler.lastCreatedTab
&& messageHandler.lastCreatedTab.id === tab.id);
const openTabId = removeTab ? tab.openerTabId : tab.id;
if (!this.canceledRequests[tab.id]) {
// we decided to cancel the request at this point, register canceled request
this.canceledRequests[tab.id] = {
requestIds: {
[options.requestId]: true
},
urls: {
[options.url]: true
}
};
// since webRequest onCompleted and onErrorOccurred are not 100% reliable (see #1120)
// we register a timer here to cleanup canceled requests, just to make sure we don't
// end up in a situation where certain urls in a tab.id stay canceled
setTimeout(() => {
if (this.canceledRequests[tab.id]) {
delete this.canceledRequests[tab.id];
}
}, 2000);
} else {
let cancelEarly = false;
if (this.canceledRequests[tab.id].requestIds[options.requestId] ||
this.canceledRequests[tab.id].urls[options.url]) {
// same requestId or url from the same tab
// this is a redirect that we have to cancel early to prevent opening two tabs
cancelEarly = true;
}
// we decided to cancel the request at this point, register canceled request
this.canceledRequests[tab.id].requestIds[options.requestId] = true;
this.canceledRequests[tab.id].urls[options.url] = true;
if (cancelEarly) {
return {
cancel: true
};
}
}
this.reloadPageInContainer(
options.url,
userContextId,
siteSettings.userContextId,
tab.index + 1,
tab.active,
siteSettings.neverAsk,
openTabId
);
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 (removeTab) {
browser.tabs.remove(tab.id);
}
return {
cancel: true,
};
},
init() {
browser.contextMenus.onClicked.addListener((info, tab) => {
this._onClickedHandler(info, tab);
});
// Before a request is handled by the browser we decide if we should route through a different container
this.canceledRequests = {};
browser.webRequest.onBeforeRequest.addListener((options) => {
if (options.frameId !== 0 || options.tabId === -1) {
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;
});
return this.onBeforeRequest(options);
},{urls: ["<all_urls>"], types: ["main_frame"]}, ["blocking"]);
// Clean up canceled requests
browser.webRequest.onCompleted.addListener((options) => {
if (this.canceledRequests[options.tabId]) {
delete this.canceledRequests[options.tabId];
}
},{urls: ["<all_urls>"], types: ["main_frame"]});
browser.webRequest.onErrorOccurred.addListener((options) => {
if (this.canceledRequests[options.tabId]) {
delete this.canceledRequests[options.tabId];
}
},{urls: ["<all_urls>"], types: ["main_frame"]});
},
async _onClickedHandler(info, tab) {
@@ -328,15 +404,22 @@ const assignManager = {
});
},
reloadPageInContainer(url, currentUserContextId, userContextId, index, neverAsk = false) {
encodeURLProperty(url) {
return encodeURIComponent(url).replace(/[!'()*]/g, (c) => {
const charCode = c.charCodeAt(0).toString(16);
return `%${charCode}`;
});
},
reloadPageInContainer(url, currentUserContextId, userContextId, index, active, neverAsk = false, openerTabId = null) {
const cookieStoreId = backgroundLogic.cookieStoreId(userContextId);
const loadPage = browser.extension.getURL("confirm-page.html");
// False represents assignment is not permitted
// If the user has explicitly checked "Never Ask Again" on the warning page we will send them straight there
if (neverAsk) {
browser.tabs.create({url, cookieStoreId, index});
browser.tabs.create({url, cookieStoreId, index, active, openerTabId});
} else {
let confirmUrl = `${loadPage}?url=${encodeURIComponent(url)}&cookieStoreId=${cookieStoreId}`;
let confirmUrl = `${loadPage}?url=${this.encodeURLProperty(url)}&cookieStoreId=${cookieStoreId}`;
let currentCookieStoreId;
if (currentUserContextId) {
currentCookieStoreId = backgroundLogic.cookieStoreId(currentUserContextId);
@@ -345,7 +428,9 @@ const assignManager = {
browser.tabs.create({
url: confirmUrl,
cookieStoreId: currentCookieStoreId,
index
openerTabId,
index,
active
}).then(() => {
// We don't want to sync this URL ever nor clutter the users history
browser.history.deleteUrl({url: confirmUrl});
@@ -6,6 +6,7 @@ const backgroundLogic = {
"about:home",
"about:blank"
]),
unhideQueue: [],
async getExtensionInfo() {
const manifestPath = browser.extension.getURL("manifest.json");
@@ -63,8 +64,7 @@ const backgroundLogic = {
url = undefined;
}
// We can't open these we just have to throw them away
if (new URL(url).protocol === "about:") {
if (!this.isPermissibleURL(url)) {
return;
}
@@ -76,6 +76,17 @@ const backgroundLogic = {
});
},
isPermissibleURL(url) {
const protocol = new URL(url).protocol;
// We can't open these we just have to throw them away
if (protocol === "about:"
|| protocol === "chrome:"
|| protocol === "moz-extension:") {
return false;
}
return true;
},
checkArgs(requiredArguments, options, methodName) {
requiredArguments.forEach((argument) => {
if (!(argument in options)) {
@@ -102,6 +113,17 @@ const backgroundLogic = {
return list.concat(containerState.hiddenTabs);
},
async unhideContainer(cookieStoreId) {
if (!this.unhideQueue.includes(cookieStoreId)) {
this.unhideQueue.push(cookieStoreId);
await this.showTabs({
cookieStoreId
});
this.unhideQueue.splice(this.unhideQueue.indexOf(cookieStoreId), 1);
}
},
async moveTabsToWindow(options) {
const requiredArguments = ["cookieStoreId", "windowId"];
this.checkArgs(requiredArguments, options, "moveTabsToWindow");
@@ -113,25 +135,50 @@ const backgroundLogic = {
});
const containerState = await identityState.storageArea.get(cookieStoreId);
// Nothing to do
if (list.length === 0 &&
containerState.hiddenTabs.length === 0) {
return;
}
const newWindowObj = await browser.windows.create({
tabId: list.shift().id
});
browser.tabs.move(list.map((tab) => tab.id), {
windowId: newWindowObj.id,
index: -1
});
let newWindowObj;
let hiddenDefaultTabToClose;
if (list.length) {
newWindowObj = await browser.windows.create();
// Pin the default tab in the new window so existing pinned tabs can be moved after it.
// From the docs (https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/move):
// Note that you can't move pinned tabs to a position after any unpinned tabs in a window, or move any unpinned tabs to a position before any pinned tabs.
await browser.tabs.update(newWindowObj.tabs[0].id, { pinned: true });
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.
for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const
browser.tabs.create(object.url || DEFAULT_TAB, {
windowId: newWindowObj.id,
cookieStoreId
});
if (!this.unhideQueue.includes(cookieStoreId)) {
this.unhideQueue.push(cookieStoreId);
for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const
showHiddenPromises.push(browser.tabs.create({
url: object.url || DEFAULT_TAB,
windowId: newWindowObj.id,
cookieStoreId
}));
}
}
if (hiddenDefaultTabToClose) {
// Lets wait for hidden tabs to show before closing the others
await showHiddenPromises;
}
containerState.hiddenTabs = [];
@@ -139,13 +186,15 @@ const backgroundLogic = {
// 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
// crazy stuff.
const tabs = browser.tabs.query({windowId: newWindowObj.id});
const tabs = await browser.tabs.query({windowId: newWindowObj.id});
for (let tab of tabs) { // eslint-disable-line prefer-const
if (tabs.cookieStoreId !== cookieStoreId) {
if (tab.cookieStoreId !== cookieStoreId) {
browser.tabs.remove(tab.id);
}
}
return await identityState.storageArea.set(cookieStoreId, containerState);
const rv = await identityState.storageArea.set(cookieStoreId, containerState);
this.unhideQueue.splice(this.unhideQueue.indexOf(cookieStoreId), 1);
return rv;
},
async _closeTabs(userContextId, windowId = false) {
@@ -178,7 +227,9 @@ const backgroundLogic = {
});
identitiesOutput[cookieStoreId] = {
hasHiddenTabs: !!containerState.hiddenTabs.length,
hasOpenTabs: !!openTabs.length
hasOpenTabs: !!openTabs.length,
numberOfHiddenTabs: containerState.hiddenTabs.length,
numberOfOpenTabs: openTabs.length
};
return;
});
@@ -276,4 +327,3 @@ const backgroundLogic = {
return `firefox-container-${userContextId}`;
}
};
@@ -4,12 +4,13 @@ const badge = {
const currentWindow = await browser.windows.getCurrent();
this.displayBrowserActionBadge(currentWindow.incognito);
},
async displayBrowserActionBadge(disable) {
if (disable) {
browser.browserAction.disable();
} else {
browser.browserAction.enable();
}
disableAddon(tabId) {
browser.browserAction.disable(tabId);
browser.browserAction.setTitle({ tabId, title: "Containers disabled in Private Browsing Mode" });
},
async displayBrowserActionBadge() {
const extensionInfo = await backgroundLogic.getExtensionInfo();
const storage = await browser.storage.local.get({browserActionBadgesClicked: []});
@@ -41,6 +41,9 @@ const identityState = {
const tabsByContainer = await browser.tabs.query({cookieStoreId, windowId});
tabsByContainer.forEach((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
// non-active.
tabObject.active = false;
@@ -11,7 +11,6 @@
"js/background/badge.js",
"js/background/identityState.js",
"js/background/messageHandler.js",
"js/backdround/init.js"
]
-->
<script type="text/javascript" src="backgroundLogic.js"></script>
@@ -19,6 +18,5 @@
<script type="text/javascript" src="badge.js"></script>
<script type="text/javascript" src="identityState.js"></script>
<script type="text/javascript" src="messageHandler.js"></script>
<script type="text/javascript" src="init.js"></script>
</body>
</html>
@@ -3,7 +3,6 @@ const messageHandler = {
// 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
LAST_CREATED_TAB_TIMER: 2000,
unhideQueue: [],
init() {
// Handles messages from webextension code
@@ -39,7 +38,7 @@ const messageHandler = {
backgroundLogic.sortTabs();
break;
case "showTabs":
backgroundLogic.showTabs({cookieStoreId: m.cookieStoreId});
backgroundLogic.unhideContainer(m.cookieStoreId);
break;
case "hideTabs":
backgroundLogic.hideTabs({
@@ -72,6 +71,42 @@ const messageHandler = {
return response;
});
// Handles external messages from webextensions
const externalExtensionAllowed = {};
browser.runtime.onMessageExternal.addListener(async (message, sender) => {
if (!externalExtensionAllowed[sender.id]) {
const extensionInfo = await browser.management.get(sender.id);
if (!extensionInfo.permissions.includes("contextualIdentities")) {
throw new Error("Missing contextualIdentities permission");
}
externalExtensionAllowed[sender.id] = true;
}
let response;
switch (message.method) {
case "getAssignment":
if (typeof message.url === "undefined") {
throw new Error("Missing message.url");
}
response = assignManager.storageArea.get(message.url);
break;
default:
throw new Error("Unknown message.method");
}
return response;
});
// Delete externalExtensionAllowed if add-on installs/updates; permissions might change
browser.management.onInstalled.addListener(extensionInfo => {
if (externalExtensionAllowed[extensionInfo.id]) {
delete externalExtensionAllowed[extensionInfo.id];
}
});
// Delete externalExtensionAllowed if add-on uninstalls; not needed anymore
browser.management.onUninstalled.addListener(extensionInfo => {
if (externalExtensionAllowed[extensionInfo.id]) {
delete externalExtensionAllowed[extensionInfo.id];
}
});
if (browser.contextualIdentities.onRemoved) {
browser.contextualIdentities.onRemoved.addListener(({contextualIdentity}) => {
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(contextualIdentity.cookieStoreId);
@@ -106,10 +141,21 @@ const messageHandler = {
}, {urls: ["<all_urls>"], types: ["main_frame"]});
browser.tabs.onCreated.addListener((tab) => {
if (tab.incognito) {
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) {
this.unhideContainer(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();
}
backgroundLogic.unhideContainer(tab.cookieStoreId);
}
setTimeout(() => {
this.lastCreatedTab = null;
@@ -117,25 +163,31 @@ const messageHandler = {
});
},
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 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 onFocusChangedCallback(windowId) {
assignManager.removeContextMenu();
const currentWindow = await browser.windows.getCurrent();
// 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(currentWindow.incognito);
badge.displayBrowserActionBadge();
browser.tabs.query({active: true, windowId}).then((tabs) => {
if (tabs && tabs[0]) {
assignManager.calculateContextMenu(tabs[0]);
@@ -20,11 +20,15 @@ async function doAnimation(element, property, value) {
async function addMessage(message) {
const divElement = document.createElement("div");
divElement.classList.add("container-notification");
// For the eager eyed, this is an experiment. It is however likely that a website will know it is "contained" anyway
// Ideally we would use https://bugzilla.mozilla.org/show_bug.cgi?id=1340930 when this is available
divElement.innerText = message.text;
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);
document.body.appendChild(divElement);
+173 -52
View File
@@ -22,6 +22,7 @@ const P_CONTAINERS_EDIT = "containersEdit";
const P_CONTAINER_INFO = "containerInfo";
const P_CONTAINER_EDIT = "containerEdit";
const P_CONTAINER_DELETE = "containerDelete";
const P_CONTAINERS_ACHIEVEMENT = "containersAchievement";
/**
* Escapes any occurances of &, ", <, > or / with XML entities.
@@ -90,27 +91,16 @@ const Logic = {
// Routing to the correct panel.
// If localStorage is disabled, we don't show the onboarding.
const data = await browser.storage.local.get([ONBOARDING_STORAGE_KEY]);
let onboarded = data[ONBOARDING_STORAGE_KEY];
const onboardingData = await browser.storage.local.get([ONBOARDING_STORAGE_KEY]);
let onboarded = onboardingData[ONBOARDING_STORAGE_KEY];
if (!onboarded) {
// Legacy local storage used before panel 5
if (localStorage.getItem("onboarded4")) {
onboarded = 4;
} else if (localStorage.getItem("onboarded3")) {
onboarded = 3;
} else if (localStorage.getItem("onboarded2")) {
onboarded = 2;
} else if (localStorage.getItem("onboarded1")) {
onboarded = 1;
} else {
onboarded = 0;
}
onboarded = 0;
this.setOnboardingStage(onboarded);
}
switch (onboarded) {
case 5:
this.showPanel(P_CONTAINERS_LIST);
this.showAchievementOrContainersListPanel();
break;
case 4:
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) {
return browser.storage.local.set({
[ONBOARDING_STORAGE_KEY]: stage
@@ -141,10 +162,14 @@ const Logic = {
async clearBrowserActionBadge() {
const extensionInfo = await getExtensionInfo();
const storage = await browser.storage.local.get({browserActionBadgesClicked: []});
browser.browserAction.setBadgeBackgroundColor({color: ""});
browser.browserAction.setBadgeBackgroundColor({color: null});
browser.browserAction.setBadgeText({text: ""});
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) {
@@ -152,7 +177,9 @@ const Logic = {
name: "Default",
cookieStoreId,
icon: "default-tab",
color: "default-tab"
color: "default-tab",
numberOfHiddenTabs: 0,
numberOfOpenTabs: 0
};
// Handle old style rejection with null and also Promise.reject new style
try {
@@ -168,6 +195,7 @@ const Logic = {
});
element.addEventListener("keydown", (e) => {
if (e.keyCode === 13) {
e.preventDefault();
handler(e);
}
});
@@ -186,6 +214,27 @@ const Logic = {
return false;
},
async numTabs() {
const activeTabs = await browser.tabs.query({windowId: browser.windows.WINDOW_ID_CURRENT});
return activeTabs.length;
},
_disableMoveTabs(message) {
const moveTabsEl = document.querySelector("#container-info-movetabs");
const fragment = document.createDocumentFragment();
const incompatEl = document.createElement("div");
moveTabsEl.classList.remove("clickable");
moveTabsEl.setAttribute("title", message);
fragment.appendChild(incompatEl);
incompatEl.setAttribute("id", "container-info-movetabs-incompat");
incompatEl.textContent = message;
incompatEl.classList.add("container-info-tab-row");
moveTabsEl.parentNode.insertBefore(fragment, moveTabsEl.nextSibling);
},
async refreshIdentities() {
const [identities, state] = await Promise.all([
browser.contextualIdentities.query({}),
@@ -201,6 +250,8 @@ const Logic = {
if (stateObject) {
identity.hasOpenTabs = stateObject.hasOpenTabs;
identity.hasHiddenTabs = stateObject.hasHiddenTabs;
identity.numberOfHiddenTabs = stateObject.numberOfHiddenTabs;
identity.numberOfOpenTabs = stateObject.numberOfOpenTabs;
}
return identity;
});
@@ -459,8 +510,10 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
Logic.showPanel(P_CONTAINER_EDIT, { name: Logic.generateIdentityName() });
});
Logic.addEnterHandler(document.querySelector("#edit-containers-link"), () => {
Logic.showPanel(P_CONTAINERS_EDIT);
Logic.addEnterHandler(document.querySelector("#edit-containers-link"), (e) => {
if (!e.target.classList.contains("disable-edit-containers")){
Logic.showPanel(P_CONTAINERS_EDIT);
}
});
Logic.addEnterHandler(document.querySelector("#sort-containers-link"), async function () {
@@ -497,6 +550,15 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
case 38:
previous();
break;
default:
if ((e.keyCode >= 49 && e.keyCode <= 57) &&
Logic._currentPanel === "containersList") {
const element = selectables[e.keyCode - 48];
if (element) {
element.click();
}
}
break;
}
});
@@ -631,6 +693,13 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
document.addEventListener("mousedown", () => {
document.removeEventListener("focus", focusHandler);
});
/* If no container is present disable the Edit Containers button */
const editContainer = document.querySelector("#edit-containers-link");
if (Logic.identities().length === 0) {
editContainer.classList.add("disable-edit-containers");
} else {
editContainer.classList.remove("disable-edit-containers");
}
return Promise.resolve();
},
@@ -663,37 +732,31 @@ Logic.registerPanel(P_CONTAINER_INFO, {
});
// Check if the user has incompatible add-ons installed
let incompatible = false;
try {
const incompatible = await browser.runtime.sendMessage({
incompatible = await browser.runtime.sendMessage({
method: "checkIncompatibleAddons"
});
const moveTabsEl = document.querySelector("#container-info-movetabs");
if (incompatible) {
const fragment = document.createDocumentFragment();
const incompatEl = document.createElement("div");
moveTabsEl.classList.remove("clickable");
moveTabsEl.setAttribute("title", "Moving container tabs is incompatible with Pulse, PageShot, and SnoozeTabs.");
fragment.appendChild(incompatEl);
incompatEl.setAttribute("id", "container-info-movetabs-incompat");
incompatEl.textContent = "Incompatible with other Experiments.";
incompatEl.classList.add("container-info-tab-row");
moveTabsEl.parentNode.insertBefore(fragment, moveTabsEl.nextSibling);
} else {
Logic.addEnterHandler(moveTabsEl, async function () {
await browser.runtime.sendMessage({
method: "moveTabsToWindow",
windowId: browser.windows.WINDOW_ID_CURRENT,
cookieStoreId: Logic.currentIdentity().cookieStoreId,
});
window.close();
});
}
} catch (e) {
throw new Error("Could not check for incompatible add-ons.");
}
const moveTabsEl = document.querySelector("#container-info-movetabs");
const numTabs = await Logic.numTabs();
if (incompatible) {
Logic._disableMoveTabs("Moving container tabs is incompatible with Pulse, PageShot, and SnoozeTabs.");
return;
} else if (numTabs === 1) {
Logic._disableMoveTabs("Cannot move a tab from a single-tab window.");
return;
}
Logic.addEnterHandler(moveTabsEl, async function () {
await browser.runtime.sendMessage({
method: "moveTabsToWindow",
windowId: browser.windows.WINDOW_ID_CURRENT,
cookieStoreId: Logic.currentIdentity().cookieStoreId,
});
window.close();
});
},
// This method is called when the panel is shown.
@@ -742,20 +805,44 @@ Logic.registerPanel(P_CONTAINER_INFO, {
tr.classList.add("container-info-tab-row");
tr.innerHTML = escaped`
<td></td>
<td class="container-info-tab-title truncate-text" title="${tab.url}" >${tab.title}</td>`;
<td class="container-info-tab-title truncate-text" title="${tab.url}" ><div class="container-tab-title">${tab.title}</div></td>`;
tr.querySelector("td").appendChild(Utils.createFavIconElement(tab.favIconUrl));
document.getElementById("container-info-table").appendChild(fragment);
// On click, we activate this tab. But only if this tab is active.
if (!tab.hiddenState) {
const closeImage = document.createElement("img");
closeImage.src = "/img/container-close-tab.svg";
closeImage.className = "container-close-tab";
closeImage.title = "Close tab";
closeImage.id = tab.id;
const tabTitle = tr.querySelector(".container-info-tab-title");
tabTitle.appendChild(closeImage);
// On hover, we add truncate-text class to add close-tab-image after tab title truncates
const tabTitleHoverEvent = () => {
tabTitle.classList.toggle("truncate-text");
tr.querySelector(".container-tab-title").classList.toggle("truncate-text");
};
tr.addEventListener("mouseover", tabTitleHoverEvent);
tr.addEventListener("mouseout", tabTitleHoverEvent);
tr.classList.add("clickable");
Logic.addEnterHandler(tr, async function () {
await browser.tabs.update(tab.id, {active: true});
window.close();
});
}
}
document.getElementById("container-info-table").appendChild(fragment);
const closeTab = document.getElementById(tab.id);
if (closeTab) {
Logic.addEnterHandler(closeTab, async function(e) {
await browser.tabs.remove(Number(e.target.id));
window.close();
});
}
}
}
},
});
@@ -802,7 +889,7 @@ Logic.registerPanel(P_CONTAINERS_EDIT, {
</td>`;
tr.querySelector(".container-name").textContent = identity.name;
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 => {
@@ -966,6 +1053,11 @@ Logic.registerPanel(P_CONTAINER_EDIT, {
document.querySelector("#edit-container-panel-name-input").value = identity.name || "";
document.querySelector("#edit-container-panel-usercontext-input").value = userContextId || NEW_CONTAINER_ID;
const containerName = document.querySelector("#edit-container-panel-name-input");
window.requestAnimationFrame(() => {
containerName.select();
containerName.focus();
});
[...document.querySelectorAll("[name='container-color']")].forEach(colorInput => {
colorInput.checked = colorInput.value === identity.color;
});
@@ -1010,9 +1102,17 @@ Logic.registerPanel(P_CONTAINER_DELETE, {
prepare() {
const identity = Logic.currentIdentity();
// Populating the panel: name and icon
// Populating the panel: name, icon, and warning message
document.getElementById("delete-container-name").textContent = identity.name;
const totalNumberOfTabs = identity.numberOfHiddenTabs + identity.numberOfOpenTabs;
let warningMessage = "";
if (totalNumberOfTabs > 0) {
const grammaticalNumTabs = totalNumberOfTabs > 1 ? "tabs" : "tab";
warningMessage = `If you remove this container now, ${totalNumberOfTabs} container ${grammaticalNumTabs} will be closed.`;
}
document.getElementById("delete-container-tab-warning").textContent = warningMessage;
const icon = document.getElementById("delete-container-icon");
icon.setAttribute("data-identity-icon", identity.icon);
icon.setAttribute("data-identity-color", identity.color);
@@ -1021,4 +1121,25 @@ Logic.registerPanel(P_CONTAINER_DELETE, {
},
});
// P_CONTAINERS_ACHIEVEMENT: Page for achievement.
// ----------------------------------------------------------------------------
Logic.registerPanel(P_CONTAINERS_ACHIEVEMENT, {
panelSelector: ".achievement-panel",
// This method is called when the object is registered.
initialize() {
// Set done and move to the containers list panel.
Logic.addEnterHandler(document.querySelector("#achievement-done-button"), async function () {
await Logic.setAchievementDone("manyContainersOpened");
Logic.showPanel(P_CONTAINERS_LIST);
});
},
// This method is called when the panel is shown.
prepare() {
return Promise.resolve(null);
},
});
Logic.init();
+1 -1
View File
@@ -1,4 +1,4 @@
const DEFAULT_FAVICON = "moz-icon://goat?size=16";
const DEFAULT_FAVICON = "/img/blank-favicon.svg";
// TODO use export here instead of globals
window.Utils = {
@@ -1,9 +1,9 @@
{
"manifest_version": 2,
"name": "Firefox Multi-Account Containers",
"version": "4.0.0",
"version": "6.1.0",
"description": "Firefox 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.",
"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": {
"48": "img/container-site-d-48.png",
"96": "img/container-site-d-96.png"
@@ -11,12 +11,12 @@
"applications": {
"gecko": {
"strict_min_version": "51.0",
"update_url": "https://testpilot.firefox.com/files/@testpilot-containers/updates.json"
"id": "@testpilot-containers",
"strict_min_version": "57.0"
}
},
"homepage_url": "https://testpilot.firefox.com/",
"homepage_url": "https://github.com/mozilla/multi-account-containers#readme",
"permissions": [
"<all_urls>",
@@ -26,6 +26,7 @@
"contextualIdentities",
"history",
"idle",
"management",
"storage",
"tabs",
"webRequestBlocking",
@@ -45,7 +46,7 @@
"browser_action": {
"browser_style": true,
"default_icon": "img/container-site.svg",
"default_title": "Firefox Multi-Account Containers",
"default_title": "Multi-Account Containers",
"default_popup": "popup.html"
},
+31 -3
View File
@@ -1,8 +1,8 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Firefox Multi-Account Containers</title>
<link rel="stylesheet" href="/css/popup.css">
<title>Multi-Account Containers</title>
<link rel="stylesheet" href="css/popup.css">
</head>
<body>
@@ -67,6 +67,34 @@
<a href="#" id="onboarding-longpress-button" class="onboarding-button">Done</a>
</div>
<div class="panel achievement-panel hide" id="achievement-panel">
<img class="onboarding-img" alt="You achieved a Containers milestone!" src="/img/onboarding-3.png" />
<h3 class="onboarding-title">100 tabs!</h3>
<p>You've opened 100 Container tabs.</p>
<p>If you enjoy Containers, help us spread the word!</p>
<p class="share-ctas">
<a class="cta-link" href="https://mzl.la/2gJtIZ4" id="achievement-rate-button" target="_blank">
<span class="cta amo-rate-cta">
<img src="/img/amo-icon.svg" class="cta-icon" alt="addons.mozilla.org Icon">
Rate
</span>
</a>
<a class="cta-link" href="https://bit.ly/fb-share-mac-addon" target="_blank">
<span class="cta fb-share-cta">
<img src="/img/webicon-facebook.svg" class="cta-icon" alt="Facebook Icon">
Share
</span>
</a>
<a class="cta-link" href="http://bit.ly/tweet-100-tabs-mac-addon" target="_blank">
<span class="cta tweet-cta">
<img src="/img/webicon-twitter.svg" class="cta-icon" alt="Twitter Icon">
Tweet
</span>
</a>
</p>
<a href="#" id="achievement-done-button" class="onboarding-button">Done</a>
</div>
<div class="panel container-panel hide" id="container-panel">
<div id="current-tab">
<h3>Current Tab</h3>
@@ -176,7 +204,7 @@
</div>
<div class="panel-content delete-container-confirm">
<h4 class="delete-container-confirm-title">Remove This Container</h4>
<p>If you remove this container now, <span id="delete-container-tab-count"></span> container tabs will be closed. Are you sure you want to remove this Container?</p>
<p><span id="delete-container-tab-warning"></span> Are you sure you want to remove this Container?</p>
</div>
<div class="panel-footer">
<a href="#" class="button expanded secondary footer-button cancel-button" id="delete-container-cancel-link">Cancel</a>
+16
View File
@@ -0,0 +1,16 @@
module.exports = {
env: {
"node": true,
"mocha": true
},
globals: {
"sinon": false,
"expect": false,
"nextTick": false,
"buildBackgroundDom": false,
"background": false,
"buildPopupDom": false,
"popup": false,
"helper": false
}
}
+137
View File
@@ -0,0 +1,137 @@
module.exports = () => {
const _storage = {};
// could maybe be replaced by https://github.com/acvetkov/sinon-chrome
const browserMock = {
_storage,
runtime: {
onMessage: {
addListener: sinon.stub(),
},
onMessageExternal: {
addListener: sinon.stub(),
},
sendMessage: sinon.stub().resolves(),
},
webRequest: {
onBeforeRequest: {
addListener: sinon.stub()
},
onCompleted: {
addListener: sinon.stub()
},
onErrorOccurred: {
addListener: sinon.stub()
}
},
windows: {
getCurrent: sinon.stub().resolves({}),
onFocusChanged: {
addListener: sinon.stub(),
}
},
tabs: {
onActivated: {
addListener: sinon.stub()
},
onCreated: {
addListener: sinon.stub()
},
onUpdated: {
addListener: sinon.stub()
},
sendMessage: sinon.stub(),
query: sinon.stub().resolves([{}]),
get: sinon.stub(),
create: sinon.stub().resolves({}),
remove: sinon.stub().resolves()
},
history: {
deleteUrl: sinon.stub()
},
storage: {
local: {
get: sinon.stub(),
set: sinon.stub()
}
},
contextualIdentities: {
create: sinon.stub(),
get: sinon.stub(),
query: sinon.stub().resolves([])
},
contextMenus: {
create: sinon.stub(),
remove: sinon.stub(),
onClicked: {
addListener: sinon.stub()
}
},
browserAction: {
setBadgeBackgroundColor: sinon.stub(),
setBadgeText: sinon.stub()
},
management: {
get: sinon.stub(),
onInstalled: {
addListener: sinon.stub()
},
onUninstalled: {
addListener: sinon.stub()
}
},
extension: {
getURL: sinon.stub().returns("moz-extension://multi-account-containers/confirm-page.html")
}
};
// inmemory local storage
browserMock.storage.local = {
get: sinon.spy(async key => {
if (!key) {
return _storage;
}
let result = {};
if (Array.isArray(key)) {
key.map(akey => {
if (typeof _storage[akey] !== "undefined") {
result[akey] = _storage[akey];
}
});
} else if (typeof key === "object") {
// TODO support nested objects
Object.keys(key).map(oKey => {
if (typeof _storage[oKey] !== "undefined") {
result[oKey] = _storage[oKey];
} else {
result[oKey] = key[oKey];
}
});
} else {
result = _storage[key];
}
return result;
}),
set: sinon.spy(async (key, value) => {
if (typeof key === "object") {
// TODO support nested objects
Object.keys(key).map(oKey => {
_storage[oKey] = key[oKey];
});
} else {
_storage[key] = value;
}
}),
remove: sinon.spy(async (key) => {
if (Array.isArray(key)) {
key.map(aKey => {
delete _storage[aKey];
});
} else {
delete _storage[key];
}
}),
};
return browserMock;
};
+75
View File
@@ -0,0 +1,75 @@
describe("Assignment Feature", () => {
const activeTab = {
id: 1,
cookieStoreId: "firefox-container-1",
url: "http://example.com",
index: 0
};
beforeEach(async () => {
await helper.browser.initializeWithTab(activeTab);
});
describe("click the 'Always open in' checkbox in the popup", () => {
beforeEach(async () => {
// popup click to set assignment for activeTab.url
await helper.popup.clickElementById("container-page-assigned");
});
describe("open new Tab with the assigned URL in the default container", () => {
const newTab = {
id: 2,
cookieStoreId: "firefox-default",
url: activeTab.url,
index: 1,
active: true
};
beforeEach(async () => {
// new Tab opening activeTab.url in default container
await helper.browser.openNewTab(newTab);
});
it("should open the confirm page", async () => {
// should have created a new tab with the confirm page
background.browser.tabs.create.should.have.been.calledWith({
url: "moz-extension://multi-account-containers/confirm-page.html?" +
`url=${encodeURIComponent(activeTab.url)}` +
`&cookieStoreId=${activeTab.cookieStoreId}`,
cookieStoreId: undefined,
openerTabId: null,
index: 2,
active: true
});
});
it("should remove the new Tab that got opened in the default container", () => {
background.browser.tabs.remove.should.have.been.calledWith(newTab.id);
});
});
describe("click the 'Always open in' checkbox in the popup again", () => {
beforeEach(async () => {
// popup click to remove assignment for activeTab.url
await helper.popup.clickElementById("container-page-assigned");
});
describe("open new Tab with the no longer assigned URL in the default container", () => {
const newTab = {
id: 3,
cookieStoreId: "firefox-default",
url: activeTab.url,
index: 3,
active: true
};
beforeEach(async () => {
// new Tab opening activeTab.url in default container
await helper.browser.openNewTab(newTab);
});
it("should not open the confirm page", async () => {
// should not have created a new tab
background.browser.tabs.create.should.not.have.been.called;
});
});
});
});
});
@@ -0,0 +1,67 @@
describe("External Webextensions", () => {
const activeTab = {
id: 1,
cookieStoreId: "firefox-container-1",
url: "http://example.com",
index: 0
};
beforeEach(async () => {
await helper.browser.initializeWithTab(activeTab);
await helper.popup.clickElementById("container-page-assigned");
});
describe("with contextualIdentities permissions", () => {
it("should be able to get assignments", async () => {
background.browser.management.get.resolves({
permissions: ["contextualIdentities"]
});
const message = {
method: "getAssignment",
url: "http://example.com"
};
const sender = {
id: "external-webextension"
};
// currently not possible to get the return value of yielding with sinon
// so we expect that if no error is thrown and the storage was called, everything is ok
// maybe i get around to provide a PR https://github.com/sinonjs/sinon/issues/903
//
// the alternative would be to expose the actual messageHandler and call it directly
// but personally i think that goes against the black-box-ish nature of these feature tests
const rejectionStub = sinon.stub();
process.on("unhandledRejection", rejectionStub);
background.browser.runtime.onMessageExternal.addListener.yield(message, sender);
await nextTick();
process.removeListener("unhandledRejection", rejectionStub);
rejectionStub.should.not.have.been.called;
background.browser.storage.local.get.should.have.been.called;
});
});
describe("without contextualIdentities permissions", () => {
it("should throw an error", async () => {
background.browser.management.get.resolves({
permissions: []
});
const message = {
method: "getAssignment",
url: "http://example.com"
};
const sender = {
id: "external-webextension"
};
const rejectionStub = sinon.spy();
process.on("unhandledRejection", rejectionStub);
background.browser.runtime.onMessageExternal.addListener.yield(message, sender);
await nextTick();
process.removeListener("unhandledRejection", rejectionStub);
rejectionStub.should.have.been.calledWith(sinon.match({
message: "Missing contextualIdentities permission"
}));
});
});
});
+47
View File
@@ -0,0 +1,47 @@
module.exports = {
browser: {
async initializeWithTab(tab) {
await buildBackgroundDom({
beforeParse(window) {
window.browser.tabs.get.resolves(tab);
window.browser.tabs.query.resolves([tab]);
window.browser.contextualIdentities.get.resolves({
cookieStoreId: tab.cookieStoreId
});
}
});
await buildPopupDom({
beforeParse(window) {
window.browser.tabs.get.resolves(tab);
window.browser.tabs.query.resolves([tab]);
}
});
},
async openNewTab(tab, options = {}) {
if (options.resetHistory) {
background.browser.tabs.create.resetHistory();
background.browser.tabs.remove.resetHistory();
}
background.browser.tabs.get.resolves(tab);
background.browser.tabs.onCreated.addListener.yield(tab);
const [promise] = background.browser.webRequest.onBeforeRequest.addListener.yield({
frameId: 0,
tabId: tab.id,
url: tab.url,
requestId: options.requestId
});
return promise;
}
},
popup: {
async clickElementById(id) {
const clickEvent = popup.document.createEvent("HTMLEvents");
clickEvent.initEvent("click");
popup.document.getElementById(id).dispatchEvent(clickEvent);
await nextTick();
}
},
};
+180
View File
@@ -0,0 +1,180 @@
describe("#940", () => {
describe("when other onBeforeRequestHandlers are faster and redirect with the same requestId", () => {
it("should not open two confirm pages", async () => {
// init
const activeTab = {
id: 1,
cookieStoreId: "firefox-container-1",
url: "http://example.com",
index: 0
};
await helper.browser.initializeWithTab(activeTab);
// assign the activeTab.url
await helper.popup.clickElementById("container-page-assigned");
// start request and don't await the requests at all
// so the second request below is actually comparable to an actual redirect that also fires immediately
const newTab = {
id: 2,
cookieStoreId: "firefox-default",
url: activeTab.url,
index: 1,
active: true
};
helper.browser.openNewTab(newTab, {
requestId: 1
});
// other addon sees the same request
// and redirects to the https version of activeTab.url
// since it's a redirect the request has the same requestId
background.browser.webRequest.onBeforeRequest.addListener.yield({
frameId: 0,
tabId: newTab.id,
url: "https://example.com",
requestId: 1
});
await nextTick();
background.browser.tabs.create.should.have.been.calledOnce;
});
});
describe("when redirects change requestId midflight", () => {
let promiseResults;
beforeEach(async () => {
// init
const activeTab = {
id: 1,
cookieStoreId: "firefox-container-1",
url: "https://www.youtube.com",
index: 0
};
await helper.browser.initializeWithTab(activeTab);
// assign the activeTab.url
await helper.popup.clickElementById("container-page-assigned");
// http://youtube.com
const newTab = {
id: 2,
cookieStoreId: "firefox-default",
url: "http://youtube.com",
index: 1,
active: true
};
const promise1 = helper.browser.openNewTab(newTab, {
requestId: 1
});
// https://youtube.com
const [promise2] = background.browser.webRequest.onBeforeRequest.addListener.yield({
frameId: 0,
tabId: newTab.id,
url: "https://youtube.com",
requestId: 1
});
// https://www.youtube.com
const [promise3] = background.browser.webRequest.onBeforeRequest.addListener.yield({
frameId: 0,
tabId: newTab.id,
url: "https://www.youtube.com",
requestId: 1
});
// https://www.youtube.com
const [promise4] = background.browser.webRequest.onBeforeRequest.addListener.yield({
frameId: 0,
tabId: newTab.id,
url: "https://www.youtube.com",
requestId: 2
});
promiseResults = await Promise.all([promise1, promise2, promise3, promise4]);
});
it("should not open two confirm pages", async () => {
// http://youtube.com is not assigned, no cancel, no reopening
expect(promiseResults[0]).to.deep.equal({});
// https://youtube.com is not assigned, no cancel, no reopening
expect(promiseResults[1]).to.deep.equal({});
// https://www.youtube.com is assigned, this triggers reopening, cancel
expect(promiseResults[2]).to.deep.equal({
cancel: true
});
// https://www.youtube.com is assigned, this was a redirect, cancel early, no reopening
expect(promiseResults[3]).to.deep.equal({
cancel: true
});
background.browser.tabs.create.should.have.been.calledOnce;
});
it("should uncancel after webRequest.onCompleted", async () => {
const [promise1] = background.browser.webRequest.onCompleted.addListener.yield({
tabId: 2
});
await promise1;
const [promise2] = background.browser.webRequest.onBeforeRequest.addListener.yield({
frameId: 0,
tabId: 2,
url: "https://www.youtube.com",
requestId: 123
});
await promise2;
background.browser.tabs.create.should.have.been.calledTwice;
});
it("should uncancel after webRequest.onErrorOccurred", async () => {
const [promise1] = background.browser.webRequest.onErrorOccurred.addListener.yield({
tabId: 2
});
await promise1;
// request to assigned url in same tab
const [promise2] = background.browser.webRequest.onBeforeRequest.addListener.yield({
frameId: 0,
tabId: 2,
url: "https://www.youtube.com",
requestId: 123
});
await promise2;
background.browser.tabs.create.should.have.been.calledTwice;
});
it("should uncancel after 2 seconds", async () => {
await new Promise(resolve => setTimeout(resolve, 2000));
// request to assigned url in same tab
const [promise2] = background.browser.webRequest.onBeforeRequest.addListener.yield({
frameId: 0,
tabId: 2,
url: "https://www.youtube.com",
requestId: 123
});
await promise2;
background.browser.tabs.create.should.have.been.calledTwice;
}).timeout(2002);
it("should not influence the canceled url in other tabs", async () => {
const newTab = {
id: 123,
cookieStoreId: "firefox-default",
url: "https://www.youtube.com",
index: 10,
active: true
};
await helper.browser.openNewTab(newTab, {
requestId: 321
});
background.browser.tabs.create.should.have.been.calledTwice;
});
});
});
+101
View File
@@ -0,0 +1,101 @@
if (!process.listenerCount("unhandledRejection")) {
// eslint-disable-next-line no-console
process.on("unhandledRejection", r => console.log(r));
}
const jsdom = require("jsdom");
const path = require("path");
const chai = require("chai");
const sinonChai = require("sinon-chai");
global.sinon = require("sinon");
global.expect = chai.expect;
chai.should();
chai.use(sinonChai);
global.nextTick = () => {
return new Promise(resolve => {
setTimeout(() => {
process.nextTick(resolve);
});
});
};
global.helper = require("./helper");
const browserMock = require("./browser.mock");
const srcBasePath = path.resolve(path.join(__dirname, "..", "src"));
const srcJsBackgroundPath = path.join(srcBasePath, "js", "background");
global.buildBackgroundDom = async (options = {}) => {
const dom = await jsdom.JSDOM.fromFile(path.join(srcJsBackgroundPath, "index.html"), {
runScripts: "dangerously",
resources: "usable",
virtualConsole: (new jsdom.VirtualConsole).sendTo(console),
beforeParse(window) {
window.browser = browserMock();
window.fetch = sinon.stub().resolves({
json: sinon.stub().resolves({})
});
if (options.beforeParse) {
options.beforeParse(window);
}
}
});
await new Promise(resolve => {
dom.window.document.addEventListener("DOMContentLoaded", resolve);
});
await nextTick();
global.background = {
dom,
browser: dom.window.browser
};
};
global.buildPopupDom = async (options = {}) => {
const dom = await jsdom.JSDOM.fromFile(path.join(srcBasePath, "popup.html"), {
runScripts: "dangerously",
resources: "usable",
virtualConsole: (new jsdom.VirtualConsole).sendTo(console),
beforeParse(window) {
window.browser = browserMock();
window.browser.storage.local.set("browserActionBadgesClicked", []);
window.browser.storage.local.set("onboarding-stage", 5);
window.browser.storage.local.set("achievements", []);
window.browser.storage.local.set.resetHistory();
window.fetch = sinon.stub().resolves({
json: sinon.stub().resolves({})
});
if (options.beforeParse) {
options.beforeParse(window);
}
}
});
await new Promise(resolve => {
dom.window.document.addEventListener("DOMContentLoaded", resolve);
});
await nextTick();
dom.window.browser.runtime.sendMessage.resetHistory();
if (global.background) {
dom.window.browser.runtime.sendMessage = sinon.spy(function() {
global.background.browser.runtime.onMessage.addListener.yield(...arguments);
});
}
global.popup = {
dom,
document: dom.window.document,
browser: dom.window.browser
};
};
global.afterEach(() => {
if (global.background) {
global.background.dom.window.close();
delete global.background;
}
if (global.popup) {
global.popup.dom.window.close();
delete global.popup;
}
});
-19
View File
@@ -1,19 +0,0 @@
const main = require("../");
exports["test main"] = function(assert) {
assert.pass("Unit test running!");
};
exports["test main async"] = function(assert, done) {
assert.pass("async Unit test running!");
done();
};
exports["test dummy"] = function(assert, done) {
main.dummy("foo", function(text) {
assert.ok((text === "foo"), "Is the text actually 'foo'");
done();
});
};
require("sdk/test").run(exports);
-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" });
}