Compare commits

..

500 Commits

Author SHA1 Message Date
luke crouch 94885de85b Merge pull request #1750 from mozilla/1746-ux-ui-followup
UX/UI Followup Issues
2020-07-09 11:58:33 -05:00
Maxx Crawford bcd3aaeb92 Fixed icon clipping where container icons are displayed. Added override for website favicons (listed in active container) 2020-07-09 11:51:24 -05:00
Maxx Crawford d0f7fc35a4 Bumped version number for new release 2020-07-07 10:19:21 -05:00
Maxx Crawford 682d877960 Updated lint errors, updated Stylelint library to latest version
Re-added stylelint/order plugin, updated options to match new syntax .

Fixed stylelint error
2020-07-07 10:19:21 -05:00
Maxx Crawford d901ba9067 Fixed issue where items with too-long page titles wrapped in tab list of specific container inside the panel 2020-07-07 10:19:21 -05:00
Maxx Crawford b47844b43a Added dark mode support on the settings page
Updated p, label color for light mode, removed commented code
2020-07-07 10:19:21 -05:00
Maxx Crawford 765a0eb727 Linted Container Delete SVG 2020-07-07 10:19:21 -05:00
Maxx Crawford 3b97e2722f Merge pull request #1756 from nathanhinchey/show-fence-icon-in-popup
Show fence icon in popup
2020-06-24 09:27:16 -05:00
Nathan Hinchey 52749ee1f0 Show fence icon in popup
Fixes issue where Facebook container has no icon in popup. Also allows user to select fence icon for other containers.
2020-05-28 12:48:11 -04:00
Maxx Crawford 8ef0a6f9f1 Merge pull request #1654 from mozilla/UX-UI-update
UX/UI Update
2020-05-22 14:07:20 -05:00
luke crouch 532abbf032 Merge pull request #1706 from mozilla/version-6.2.5
bump version to 6.2.5
2020-04-13 11:23:44 -05:00
groovecoder d8f99ada77 bump version to 6.2.5 2020-04-13 07:49:31 -05:00
luke crouch 35cf2c95d4 Merge pull request #1686 from DeadlySurprise/fixCurrentContainerBtn
Fixes #1682 'open in current container button' not working
2020-04-11 15:05:42 -05:00
DeadlySurprise 19e694d8eb Fixed 'open in current container button' not working 2020-03-15 17:42:52 +01:00
Kendall Werts 8654aefd85 added keyboard shortcuts back (ctrl+. and then a number opens a new container in that tab) 2020-03-03 20:04:45 -06:00
Kendall Werts 7f91096311 Removed options from new container menu. Tweaked new container menu to remove
dupes from opening it twice.

Added focus highlighting for keyboard navigation
2020-02-28 15:47:43 -06:00
Kendall Werts ef66bee929 Implemented site isolation
Added feature to isolate (lock) assigned sites: When you are in
a container with site isolation enabled, navigating to a site
outside of the assignments will open that site in a new default
container tab.

Co-authored-by: Francis McKenzie <francis.mckenzie@gmail.com>
2020-02-28 15:47:43 -06:00
Kendall Werts 707cec56c5 keyboard shortcuts working 2020-02-28 15:47:43 -06:00
Kendall Werts 8ef5cbd81b Fixed Assignement Toast on page.
Now opens in the newly opened tab (after it has been reopened
in the correct container). It also does not provide a toast
when removing sites from the edit assignments panel, becuase
user will most likely not be on that site when editing the
panel, so it has been throwing an error. Error should go away.
2020-02-28 15:47:43 -06:00
Kendall Werts 0290eb1d56 css linting 2020-02-28 15:47:43 -06:00
Kendall Werts 50f5d92d41 added onboarding reset 2020-02-28 15:47:43 -06:00
Kendall Werts 807435ca4b code review and feedback changes 2020-02-28 15:47:43 -06:00
Kendall Werts 2679c4e15f removed svgs not used anymore and added license to svgs that were missing a license 2020-02-28 15:47:43 -06:00
Kendall Werts 6bb3acbe5a extraneous css removal 2020-02-28 15:47:43 -06:00
Kendall Werts 694b0f7b47 fixed mocha tests 2020-02-28 15:47:43 -06:00
Kendall Werts db46e71516 linting issues 2020-02-28 15:47:43 -06:00
Kendall Werts 486072bef8 keyboard shortcuts 2020-02-28 15:47:43 -06:00
Kendall Werts a33e358de8 Now can reopen in default container. Added an 'OK' button to new container panel. Fixed some css 2020-02-28 15:47:43 -06:00
Kendall Werts a7f62b13fa css linting 2020-02-28 15:47:43 -06:00
Kendall Werts 4f1e49bf69 pulled out the picker panels 2020-02-28 15:47:43 -06:00
Kendall Werts 55f940d372 some edits to panels 2020-02-28 15:47:43 -06:00
Kendall Werts 8d8d7f2a8e fixed info panel 2020-02-28 15:47:43 -06:00
Kendall Werts 9d151f0033 working on info panel 2020-02-28 15:47:43 -06:00
Kendall Werts fe62ee3abb updated browserAction icons to match default themes 2020-02-28 15:47:43 -06:00
Kendall Werts 18ed7237f0 added info icon as ling to options page 2020-02-28 15:47:43 -06:00
Kendall Werts b9084a9990 updates to all panels 2020-02-28 15:47:43 -06:00
Kendall Werts 6abeb976c6 container info panel complete 2020-02-28 15:47:43 -06:00
Kendall Werts 33b40ce938 fixed picker panel 2020-02-28 15:47:43 -06:00
Kendall Werts d050701343 ReOpenIn Picker Panel functional 2020-02-28 15:47:43 -06:00
Kendall Werts 1870ce08bc Page action assigns site to a container and simultaneously reopens site in container. 2020-02-28 15:47:43 -06:00
Kendall Werts fbba6beee2 testing keyboard shortcuts 2020-02-28 15:47:43 -06:00
Kendall Werts 97d4c46a4e Added Facebook Container Fence icon. Fixes #1425 2020-02-28 15:47:43 -06:00
Kendall Werts 0cbc9879bb Implemented UI/UX for container list Panel (first panel). 2020-02-28 15:47:43 -06:00
luke crouch ae1f80b8b5 Merge pull request #1667 from mozilla/more-bugfixes
Made selector for keyboard shortcuts more specific to fix #1666
2020-02-28 10:39:28 -06:00
Kendall Werts 358551feda Made selector for keyboard shortcuts more specific to fix #1666
Bumped version for release
2020-02-28 10:19:42 -06:00
luke crouch ca98b56ad7 Merge pull request #1663 from mozilla/bugfixes
Removed Enter Key Handler (fix for #1656, #1643, and #1637)
2020-02-27 14:44:34 -06:00
Kendall Werts 5bbf902b47 fix for #1656, #1643, and #1637
removes enter key listener in favor of focus highlighting to notify user that there
is keyboard navigation available for the onboarding panels. (enter key listener was added
 as keyboard navication for onboarding panels).

bumped version
2020-02-26 17:16:45 -06:00
luke crouch d654810d41 Merge pull request #1635 from mozilla/pinned-tabs
Fix for #1632: Pinned tabs cannot be loaded as 'discarded'
2020-02-14 09:05:15 -06:00
Kendall Werts 8172f291dd bumped version 2020-02-13 17:26:31 -06:00
Kendall Werts 9e12014c07 Fix for #1632: Pinned tabs cannot be loaded as 'discarded' 2020-02-13 16:54:18 -06:00
luke crouch da79841201 Merge pull request #1611 from kendallcorner/new-sync-file
Sync Feature
2020-02-11 08:36:02 -06:00
Kendall Werts 00ace352e8 upped version and added action badge 2020-02-10 17:09:20 -06:00
Kendall Werts 8cdfe0191f fixed url for syn setup 2020-02-10 16:55:48 -06:00
Kendall Werts f56f9b0ea8 fixed tests 2020-02-10 16:00:47 -06:00
Kendall Werts 4bc6fad83a added second onboarding panel 2020-02-10 15:19:41 -06:00
Kendall Werts beb027f1a1 updated readme 2020-02-10 12:38:57 -06:00
Kendall Werts 06b43c47c0 added new oboarding panel 2020-02-10 12:38:45 -06:00
Kendall Werts 7ed5085f10 changed window attachment of sync to be more clear 2020-02-10 12:38:17 -06:00
Kendall Werts c300751958 moved test to mocha tests 2020-02-06 11:58:53 -06:00
Kendall Werts e6f5bf76c2 Merge pull request #1 from stoically/sync-tests
Add sync tests to mocha suite
2020-02-03 11:25:31 -06:00
Kendall Werts 88d8edce7e fixed initial container sync entry dupe 2020-01-29 21:54:04 -06:00
stoically 419be23c9c Add sync tests to mocha suite 2020-01-28 11:33:20 +01:00
stoically 405e605ba3 Refactor tests to not rely on globals 2020-01-28 11:33:17 +01:00
Kendall Werts d7b1e7274f verison bump for beta 2020-01-24 17:18:31 -06:00
Kendall Werts 53baee1d5c swapped cookieStoreId and storekey 2020-01-24 17:02:29 -06:00
Kendall Werts 0447e54b1c review fedback 2020-01-23 13:47:56 -06:00
Kendall Werts 5b8cfa14ae Update src/js/background/sync.js
Co-Authored-By: Jonathan Kingston <jonathan@jooped.co.uk>
2020-01-23 13:47:56 -06:00
Kendall Werts d35712e9f9 Update src/js/background/sync.js
Co-Authored-By: Jonathan Kingston <jonathan@jooped.co.uk>
2020-01-23 13:47:56 -06:00
Kendall Werts f981200921 Update src/js/background/identityState.js
Co-Authored-By: Jonathan Kingston <jonathan@jooped.co.uk>
2020-01-23 13:47:56 -06:00
Kendall Werts 85c403bef5 made some suggested changes 2020-01-23 13:47:45 -06:00
Kendall Werts 268c638508 added onboarding panel 2020-01-23 00:27:05 -06:00
Kendall Werts 61b5c2e4b2 updated tests to match new formatting" 2020-01-22 23:50:19 -06:00
Kendall Werts 3aa2902cde added on/off switch and expiration date on instanceKeys in sync
on/off is in settings.
when instanceKey date is 30 days old, it is deleted

fixed bug
2020-01-22 22:46:36 -06:00
Kendall Werts d7b66eca52 added key to sync for each install of MAC.
Keeps a list of installed identities and site assignments of reach MAC install
2020-01-22 10:57:27 -06:00
Kendall Werts 361e49d553 bumped version for beta 2020-01-16 10:04:38 -06:00
Kendall Werts 60bfdffdd6 fixed race condition while upgrading containers and site assignments. 2020-01-15 16:54:24 -06:00
Kendall Werts d7c0a3d9e1 Fixed infinite loop in updating uuid for identities. 2020-01-15 10:03:40 -06:00
Kendall Werts a7d3d6d848 fixed linting 2020-01-14 11:21:19 -06:00
Kendall Werts 26028cac20 added UUIDs of containers to site assignments 2020-01-14 11:21:09 -06:00
Kendall Werts 33909d147a fixed cookiestoreIDmap 2020-01-14 11:21:09 -06:00
Kendall Werts 10d08f2ac9 changed confusing language on sync data check 2020-01-14 11:21:09 -06:00
Kendall Werts 5ae7a395a1 added some checks on sync and some error handling for bad site assignment info 2020-01-14 11:21:00 -06:00
Kendall Werts 9de6df6157 fixed bug that was showing all assigned site in the create new container panel 2020-01-14 11:20:51 -06:00
Kendall Werts 6184dbb656 fixed bug that was showing all assigned site in the create new container panel 2020-01-09 13:52:40 -06:00
Kendall Werts 34180aa2d5 worked on the duplicate bug 2020-01-08 23:38:01 -06:00
Kendall Werts 3a7744b41b wrote tests tests for browser use 2020-01-08 18:33:02 -06:00
Kendall Werts 53f7dc4915 added some tests in the browser html 2020-01-08 15:37:08 -06:00
Kendall Werts 53a901d023 saving to do some work 2020-01-08 13:32:02 -06:00
Kendall Werts c522d36a66 error handling 2020-01-08 13:23:35 -06:00
Kendall Werts f7b20f97b8 working with tests 2020-01-07 14:14:19 -06:00
Kendall Werts d033292784 pulled in sync tests 2020-01-06 13:27:57 -06:00
stoically 26cd3c3cc8 Feature test container sync initialization 2020-01-06 10:58:27 -06:00
Kendall Werts be3904cee8 package.json testing scripts 2020-01-06 10:50:00 -06:00
Kendall Werts 0252f9d1c3 refactored runSync and following functions 2020-01-05 14:32:11 -06:00
Kendall Werts 19dce3ba45 refactor and fixed site assignment reconcilding 2020-01-05 14:32:11 -06:00
Kendall Werts 0bdf8558f6 Syncs deletion of site assignments and reassignments 2020-01-05 14:32:11 -06:00
Kendall Werts 0e45f06338 deletes containers, and adds and deletes site assignments. 2020-01-05 14:32:11 -06:00
Kendall Werts 063b7509bd removed awaits that were causing issues with error handling 2020-01-05 14:32:11 -06:00
Kendall Werts 136aa3ce0e wip: cleaning up storage before sync 2020-01-05 14:32:11 -06:00
Kendall Werts a7f6659204 added check for uuid in updateUUID 2020-01-05 14:32:11 -06:00
Kendall Werts f4024bba66 re-enabled listeners 2020-01-05 14:32:11 -06:00
Kendall Werts e65c88cde2 updated with comments fixes from review 2020-01-05 14:32:11 -06:00
Kendall Werts 27225df281 added restore on previously synced browser 2020-01-05 14:32:11 -06:00
Kendall Werts 88e32fc72f added second run restore 2020-01-05 14:32:11 -06:00
Kendall Werts d61ea16db1 broke up restoreFristRun 2020-01-05 14:32:11 -06:00
Kendall Werts adc1f5ffe6 made some requested changes 2020-01-05 14:32:11 -06:00
Kendall Werts e8f8123c4c combined getByContainer and getAllAssignedSites 2020-01-05 14:29:53 -06:00
Kendall Werts 9788e159ae cleanup of old code 2020-01-05 14:29:53 -06:00
Kendall Werts d98d0f1697 a little linting, but not all the linting 2020-01-05 14:29:53 -06:00
Kendall Werts efb83255fd syncs on first run 2020-01-05 14:29:53 -06:00
Kendall Werts f5993add6f added uuids to contextualIdentities within MAC for comparison with other browsers 2020-01-05 14:29:53 -06:00
Kendall Werts 24c960bed0 first pass at container sync 2020-01-05 14:29:53 -06:00
stoically dc9e8f6399 Add test for unresolved issue
See: https://github.com/mozilla/multi-account-containers/issues/1168#issuecomment-378394091
2020-01-02 23:25:07 +00:00
stoically e37440720f Add container feature test 2020-01-02 23:25:07 +00:00
stoically 703a7cd3c2 Add test coverage with nyc 2020-01-02 23:25:07 +00:00
stoically 5eb79949e9 Adapt existing tests to use webextensions-jsdom 2020-01-02 23:25:07 +00:00
stoically 77fd843b3d Replace manual mock/jsdom with webextensions-jsdom 2020-01-02 23:25:07 +00:00
stoically 96ab4685e9 Bump test dependencies 2020-01-02 23:25:07 +00:00
stoically 426e81b88b Fix eslint errors after bump to 6.6.0 (#1595)
* Bump eslint from 3.19.0 to 6.6.0

Bumps [eslint](https://github.com/eslint/eslint) from 3.19.0 to 6.6.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v3.19.0...v6.6.0)

Signed-off-by: dependabot[bot] <support@github.com>

* Fix (and suppress) eslint errors
2019-12-18 09:02:02 +01:00
Kendall Werts 3c2dda5100 Merge pull request #1565 from kendallcorner/bookmark-menu-updates
fixed #1564: cleanup updates requested for bookmark context menu
2019-12-17 15:44:58 -06:00
luke crouch fca5caaa00 Merge pull request #1601 from mozilla/fix-odd-edit-arrow-alignment
fix odd edit arrow alignment
2019-12-09 15:59:15 -06:00
revealtheweb b0661f79aa fix odd edit arrow alignment 2019-12-09 15:55:57 -06:00
stoically e44fcdf606 Merge pull request #1574 from dnahol/readme-typo-fix
Fix README typo
2019-12-06 06:29:30 +01:00
dnahol 645fb6832c fix 'License' typo 2019-11-02 22:49:31 -07:00
Kendall Werts f653b5420f fixed #1564: cleanup updates requested for bookmark context menu 2019-10-31 14:16:23 -05:00
luke crouch f159e42029 Merge pull request #1539 from dnahol/1538-onboarding
added keyboard nav to onboarding popups
2019-10-25 13:34:56 -05:00
luke crouch a8aafc7064 Merge pull request #1540 from oksmelnik/duplicated-tabs
#950: don't unhide duplicates of already opened tab
2019-10-25 13:30:24 -05:00
Dalia Nahol 3284bfd7f3 Merge branch 'master' into 1538-onboarding 2019-10-25 10:56:06 -07:00
luke crouch da24febe6e Merge branch 'master' into duplicated-tabs 2019-10-25 10:49:11 -05:00
luke crouch 3e5334e9b0 Merge pull request #1524 from dnahol/1009-arrows
added right and left arrow shortcuts for container submenu tabs
2019-10-25 10:42:57 -05:00
luke crouch 8dd9927539 Merge pull request #1537 from kendallcorner/bookmark-context
Adds a bookmark context menu for opening bookmarks in container tabs #323
2019-10-25 10:14:01 -05:00
Oksana Melnik c3da573722 cleanup 2019-10-25 08:08:04 +02:00
Dalia Nahol f40fd82518 Merge branch 'master' into 1009-arrows 2019-10-24 20:32:09 -07:00
dnahol ef894d847e merge upstream commit 'remove HTML entities from tooltip' 2019-10-24 20:25:47 -07:00
Kendall Werts 7c08cf3190 Merge branch 'bookmark-context' of github.com:kendallcorner/multi-account-containers into bookmark-context 2019-10-24 16:46:54 -05:00
Kendall Werts 6f94ae1500 updated testing setup to understand my api calls 2019-10-24 16:42:19 -05:00
Kendall Werts bd579fe907 added changes requested 2019-10-24 16:32:49 -05:00
Kendall Werts 87a2aa5f1e added url checks for edge case urls 2019-10-24 16:16:02 -05:00
dnahol 1133d48103 left arrow now works without changing focus in right arrow case 2019-10-24 14:02:28 -07:00
Maxx Crawford dda5c9eeff Merge branch 'master' into bookmark-context 2019-10-24 14:33:15 -05:00
Kendall Werts e7824f367b added bookmarks as optional_permission and added checkbox in preferences menu. 2019-10-24 14:14:30 -05:00
dnahol 29a0277398 change getCurrentPanel to getCurrentPanelElement 2019-10-24 10:17:32 -07:00
luke crouch 510a188dee Merge pull request #1541 from Karskaya/iss1041
Fix #1041- [Feature request] Open previously hidden tabs in the background
2019-10-24 08:17:33 -05:00
luke crouch 7fa2d494c4 Merge pull request #1512 from romanrodriguez/patch-1
Fix #930 - Disable extension in incognito mode
2019-10-24 06:39:11 -05:00
Kendall Werts 7e4950b184 menu updates when containers are changed, added, or deleted. Tests need to be updated 2019-10-23 15:48:43 -05:00
Kendall Werts c832cf73aa edits from input 2019-10-19 22:31:12 -05:00
Karskaja 9ff1a14e0e Fix issue #1041- [Feature request] Open previously hidden tabs in the background
Add 'noload' property to openNewTab options, add 'discarded' parameter to browser.tabs.create. New tabs opened from showTabs will be discarded.
2019-10-19 10:04:48 -04:00
Oksana Melnik 384ac486d9 duplitated-tabs: don't unhide duplicates of already opened tab 2019-10-19 14:43:03 +02:00
dnahol 2cddbefb63 added keyboard nav to onboarding popups 2019-10-18 13:47:54 -07:00
dnahol dae376500b added right and left arrow shortcuts for container submenu tabs
change focus to whole panel element after right arrow press, allows navigation without pressing tab
2019-10-18 00:08:51 -07:00
Kendall Werts c4650d12bd added ability to open folders as well. issue #323 2019-10-17 15:25:58 -05:00
Kendall Werts 18bc8eb5aa added bookmark context menu ref: issue #323 2019-10-17 14:15:45 -05:00
Roman Rodriguez 8eceb0d298 Fix #930 - Disable extension in incognito mode 2019-10-16 13:07:59 -05:00
luke crouch 27d51f89c7 Merge pull request #1532 from scar45/fix/dark-mode-redirect-url-color
Explicitly set foreground color of #redirect-url in dark mode
2019-10-16 11:25:43 -05:00
luke crouch d04252a5ad Merge pull request #1517 from Jo-IE/edit-tooltip-html
remove HTML entities from tooltip
2019-10-16 10:42:38 -05:00
luke crouch 6b02258295 Merge pull request #1530 from kendallcorner/patch-1
Updated test instructions
2019-10-16 10:28:15 -05:00
luke crouch 97169d6f17 Merge pull request #1535 from kendallcorner/urldecode
Fix for double decoding on confirm page issue #1361
2019-10-16 07:13:24 -05:00
Kendall Werts a874c9c628 fix for double decoding on confirm page issue #1361 2019-10-15 14:19:18 -05:00
George Merlocco c28d77e7cd Explicitly set foreground color of #redirect-url in dark mode 2019-10-12 18:54:54 -04:00
Kendall Werts 397a3e4970 updated test info 2019-10-11 10:38:33 -05:00
luke crouch a4c578adde Merge pull request #1519 from jonathanKingston/updated-icons
Updating to sharper icon. Fixes #1518
2019-10-08 14:34:10 -05:00
Jonathan Kingston db8b7ea05f Updating to sharper icon. Fixes #1518 2019-10-08 17:06:05 +01:00
Jo IE f33b3b39c0 remove HTML entities from tooltip 2019-10-07 18:02:40 +01:00
luke crouch a328e5bf91 Merge pull request #1509 from mozilla/october-2019-dot-release
Updated add-on to version 6.1.1
2019-10-02 16:35:19 -05:00
Maxx Crawford 46a55da277 Updated to version 6.1.1 2019-10-02 16:22:54 -05:00
Francis McKenzie 1746d8379b Improved comments in CSS 2019-09-23 23:09:05 +01:00
Francis McKenzie f57cf92f41 Bugfix: incorrect scrollbars/spacing when editing container with many assignments 2019-09-23 23:09:05 +01:00
Harry Twyford 54659f5c77 #1375 - Correct colors on confirmation page 2019-09-23 17:34:46 +01:00
Maxx Crawford 2badd22f41 Merge pull request #1485 from kendallcorner/master
Fixed #766 — Popup display expands to fit overflow menu
2019-09-20 16:54:47 -05:00
Kendall Werts 21b7386a94 probably a better solution than using a timeout 2019-08-28 08:39:53 -05:00
Kendall Werts 529b9bb482 forgot a semicolon 2019-08-27 12:22:25 -05:00
Kendall Werts 3adb333022 solving linter issue without breaking the fix. 2019-08-27 11:54:16 -05:00
Kendall Werts cc988a303e lack of px on the zero appears to break it. 2019-08-27 11:06:40 -05:00
Kendall Werts 46349e1a70 linted 2019-08-27 10:42:58 -05:00
Kendall Werts acff80a234 Merge branch 'master' of github.com:mozilla/multi-account-containers 2019-08-26 17:12:22 -05:00
Kendall Werts bbe655d879 fixed first panel issue 2019-08-26 17:06:48 -05:00
Kendall Werts 7cbace9cc9 fix for #766 issues with display in overflow menu 2019-08-26 14:47:02 -05:00
luke crouch 9372899bfa Merge pull request #1470 from kendallcorner/master
Default favicon now displays for Edit Containers panel (issue #1314)
2019-08-07 10:41:30 -05:00
Kendall Werts 4d42a74e66 Added class to empty div as requested 2019-08-06 20:39:46 -05:00
Kendall Werts 76f7a64cb8 fixed favicon url 2019-07-26 08:50:22 -05:00
Kendall Werts 1af8cf8222 fix for issue #1314 2019-07-25 13:38:17 -05:00
stoically 11a3b2facd Merge pull request #1435 from silverwind/dark-mode-icon
Support dark themes on icon - Fixes #1424
2019-07-17 16:47:41 +02:00
stoically bd993d2f84 Merge pull request #1451 from Arthaey/patch-1
Fix typo in a comment.
2019-07-17 16:42:28 +02:00
Arthaey Angosii cf9683174d Fix typo in a comment. 2019-07-07 18:01:33 -07:00
silverwind f617ca26bb Support dark themes on icon - Fixes #1424 2019-06-09 18:45:41 +02:00
ctgardner 41686fdf6c Revert "Increase timeout on flakey async test"
This reverts commit 154df62687.
2019-05-07 02:23:00 +01:00
ctgardner c67a985847 Increase timeout on flakey async test 2019-05-07 02:23:00 +01:00
ctgardner 639399925c Refactor async anonymous functions into arrow functions
- fixes #972
2019-05-07 02:23:00 +01:00
stoically 2ded900188 Reduce noise in issue template 2019-05-07 01:36:02 +01:00
Jonathan Kingston 3ae1803420 Bug 1539507 - Fix targeting of button click when button element isn't clicked. 2019-05-07 01:32:28 +01:00
luke crouch 17b2d8c773 Merge pull request #1371 from gliptak/patch-1
Bring CODE_OF_CONDUCT.md current
2019-04-23 12:14:45 -05:00
Gábor Lipták 7025c98e7b Bring CODE_OF_CONDUCT.md current 2019-04-15 14:24:16 -04:00
luke crouch 6f4d3c4327 Merge pull request #1329 from ShivangiKakkar/new-release
Fixes #1328: Adding new version
2019-01-23 13:59:19 -06:00
shivangikakkar 7833a6a79f adding new version 2019-01-23 23:30:04 +05:30
luke crouch c2f2d69ba1 Merge pull request #1327 from ShivangiKakkar/fixes-985
exempting `always open in` functionality for non-standard ports
2019-01-22 20:21:57 -06:00
shivangikakkar 42b0312790 exempting always open in functionality for non-standard ports 2019-01-19 02:02:27 +09:00
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
groovecoder da3fc2ede2 bump version to 4.0.0 2017-09-05 13:33:34 -05:00
luke crouch 385c585888 Merge pull request #784 from jonathanKingston/fix-new-tab
Simplify new tab creation in the popup fixing issues. Fixes #781
2017-09-05 13:30:34 -05:00
luke crouch 734b97beb0 Merge pull request #786 from jonathanKingston/name-change
Name change. Fixes #763
2017-09-05 10:15:53 -05:00
Jonathan Kingston 3aa311a3c1 Name change. Fixes #763 2017-09-01 15:16:56 -07:00
Jonathan Kingston df7d7f9c38 Simplify new tab creation in the popup fixing issues. Fixes #781 2017-09-01 12:27:40 -07:00
luke crouch 65be77665a Merge pull request #783 from jonathanKingston/reset-prefs-on-uninstall
Reset prefs on uninstall
2017-09-01 11:08:06 -05:00
luke crouch 44548659db Merge pull request #780 from jonathanKingston/apply-styles-to-legacy
Add styles for 55+56 versions of Firefox so everyone gets a consisten…
2017-09-01 11:01:02 -05:00
Jonathan Kingston 10c4395efd Reset prefs for new users on uninstall. Fixes #782 2017-08-31 15:00:40 -07:00
Jonathan Kingston 27b2a4b5f2 Add styles for 55+56 versions of Firefox so everyone gets a consistent underline. Fixes #779 2017-08-31 11:58:03 -07:00
luke crouch 6f54e7ff7f Merge pull request #776 from jonathanKingston/context-menu-neaten
Add move and hide to context menu and neaten using checkboxes. Fixes …
2017-08-30 15:48:38 -05:00
Jonathan Kingston cb6726b667 Add move and hide to context menu and neaten using checkboxes. Fixes #711 2017-08-30 13:20:57 -07:00
luke crouch 0964311fa1 Merge pull request #778 from jonathanKingston/web-ext-advice
Add web extension specific advice on building. Fixes #751
2017-08-30 15:13:44 -05:00
luke crouch 2831d019f5 Merge pull request #777 from jonathanKingston/container-removal-fixes
Handle removing containers to refresh menus and remove assignments. F…
2017-08-30 15:13:03 -05:00
luke crouch 1cc58cad9b Merge pull request #775 from jonathanKingston/remove-unusable-paths-on-show
Remove about: paths from showTabs as it prevents the tabs being creat…
2017-08-30 15:00:26 -05:00
luke crouch bc9660f76e Merge pull request #774 from jonathanKingston/hidden-tab-open
Add a tab observer to show hidden tabs as there are many tab creation…
2017-08-30 14:55:06 -05:00
Jonathan Kingston f526caca50 Add web extension specific advice on building. Fixes #751 2017-08-30 11:12:55 -07:00
Jonathan Kingston b6a98fb83e Handle removing containers to refresh menus and remove assignments. Fixes: #761, Fixes: #752 2017-08-28 21:55:29 -07:00
Jonathan Kingston af2b4b79a9 Remove about: paths from showTabs as it prevents the tabs being created. Fixes #773 2017-08-28 17:46:55 -07:00
Jonathan Kingston a762b5eca2 Add a tab observer to show hidden tabs as there are many tab creation routes. Uses a queue to prevent multiple triggers. Fixes #765 2017-08-28 17:07:46 -07:00
luke crouch 3cc40344af Merge pull request #760 from jonathanKingston/removalOfTabPageCounter
Removal of tab page counter code. Fixes #759
2017-08-25 11:44:51 -05:00
luke crouch 278cdb7f69 Merge pull request #762 from jonathanKingston/change-to-window-only
Remove tab counting code as also not needed, change tab counting to b…
2017-08-25 11:27:45 -05:00
Jonathan Kingston 0be03ebeb7 Remove tab counting code as also not needed, change tab counting to be per window. Prevent reopening of hidden tabs doesn't call itself. Fixes #750, Fixes #753, Fixes #756 2017-08-24 16:05:14 +01:00
Jonathan Kingston c69f37a2de Removal of tab page counter code. Fixes #759 2017-08-24 15:04:47 +01:00
Jonathan Kingston b20ac8169a Merge pull request #758 from mozilla/move-to-AMO-665
for #665: update title, desc, version for AMO
2017-08-24 15:02:43 +01:00
groovecoder 9e98d35b45 for #665: update title, desc, version for AMO 2017-08-23 12:53:23 -05:00
luke crouch 17f2781e07 Merge pull request #757 from jonathanKingston/remove-pb-mode
Extension shouldn't be enabled in pb mode as it's currently not worki…
2017-08-23 12:48:38 -05:00
luke crouch 0acf9cc0e6 Merge pull request #754 from jonathanKingston/move-containers-fix
Fix moving of more than one tab to a new window. Fixes #746
2017-08-23 12:40:11 -05:00
Jonathan Kingston 70573d0559 Extension shouldn't be enabled in pb mode as it's currently not working in many places. Fixes #756 2017-08-23 15:23:49 +01:00
Jonathan Kingston da239237f7 Fix moving of more than one tab to a new window. Fixes #746 2017-08-22 18:54:32 +01:00
luke crouch 29f078d2c9 Merge pull request #726 from jonathanKingston/bootstrapify
Bootstrapify
2017-08-15 16:31:13 -05:00
Jonathan Kingston 5704a21c97 Merge remote-tracking branch 'upstream/master' into bootstrapify 2017-08-15 22:24:32 +01:00
Jonathan Kingston 57a31f7f97 Remove legacy telemetry code as non functional now 2017-08-15 19:30:57 +01:00
Jonathan Kingston d685a58d74 Bootstrapify extension to work for Firefox Nightly 57 SDK removal. Fixes #725 2017-08-15 16:53:43 +01:00
Jonathan Kingston a44bf21582 Merge pull request #713 from jonathanKingston/svg-icon-context-change
Svg icon context change
2017-08-15 10:50:44 +01:00
luke crouch 8f8fc322eb Merge pull request #715 from jonathanKingston/remove-legacy-css
Removal of legacy CSS for builds after 2015-08-06. Fixes #714
2017-08-07 14:05:51 -05:00
Jonathan Kingston f03404ad9e Removal of legacy CSS for builds after 2015-08-06. Fixes #714 2017-08-07 19:50:37 +01:00
Jonathan Kingston 78b5de3b44 Adding in SVG icon for context-fill colours. Fixes #710 2017-08-05 10:28:27 +01:00
Jonathan Kingston e78f49bec5 Making icons sharper since hand crafting your own svg was cool. 2017-08-05 09:59:45 +01:00
luke crouch ee8c69b73e Merge pull request #605 from jonathanKingston/sdk-mamoth-breakage
Shirk my SDK code
2017-08-04 20:13:20 +00:00
Jonathan Kingston 4dfffb8c34 Merge remote-tracking branch 'upstream/master' into sdk-mamoth-breakage 2017-08-04 18:21:48 +01:00
Jonathan Kingston fe77c891cd Adding back in legacy code without message passing code. 2017-08-04 16:28:53 +01:00
luke crouch e3ed4582d2 Merge pull request #707 from jonathanKingston/icon-reset-fix
Fix on update icons being reset to central defaults. Fixes #703
2017-08-03 16:05:54 +00:00
Jonathan Kingston 38c098edb6 Fix on update icons being reset to central defaults. Fixes #703 2017-08-03 11:26:00 +01:00
Jonathan Kingston 12a6bb3b9b Break web extensions code 2017-08-02 14:20:59 +01:00
Jonathan Kingston 175cdc1a6b Break my SDK code 2017-08-02 14:20:59 +01:00
Jonathan Kingston 0ec7e4aee3 Use new theme_icons manifest for specifying light and dark icons. Fixes #603 2017-08-02 14:20:59 +01:00
Jonathan Kingston ee63f72f3e Merge pull request #698 from mozilla/bump-version-to-2.4.1
bump version to 2.4.1
2017-07-31 19:51:15 +01:00
groovecoder 4cbcfac0f4 bump version to 2.4.1 2017-07-31 11:40:18 -05:00
luke crouch 641c95e64e Merge pull request #681 from jonathanKingston/set-default-background
Set default background to fix dark linux themes. Fixes #677
2017-07-18 18:53:01 +00:00
Jonathan Kingston b646a9183c Set default background to fix dark linux themes. Fixes #677 2017-07-17 15:22:45 +01:00
luke crouch 6e2ed6393e Merge pull request #669 from jonathanKingston/assignment-index
Fix assignment index loading at the end of the tab strip.
2017-07-14 10:53:10 -05:00
Jonathan Kingston af98174a19 Fix assignment index loading at the end of the tab strip. Fixes #672. 2017-07-14 10:44:53 +01:00
luke crouch 1c8530ef02 Merge pull request #668 from jonathanKingston/mac-keybord-shortcut
Adding a keyboard shortcut that will work on MacOS. Ctrl+.
2017-07-13 16:12:56 -05:00
Jonathan Kingston 366f9ec047 Adding a keyboard shortcut that will work on MacOS. Ctrl+. 2017-07-13 21:42:07 +01:00
luke crouch 63343f18eb Merge pull request #664 from mozilla/add-surveyurl-for-shield
add surveyUrl for shield study participants
2017-07-11 11:39:50 -05:00
groovecoder 080e9dd22d add surveyUrl for shield study participants 2017-07-11 11:08:38 -05:00
luke crouch 268da1350a Merge pull request #660 from mozilla/new-tab-long-press-onboarding
add onboarding panel for long-press
2017-07-06 14:47:02 -05:00
Jonathan Kingston e0abaa67e2 Using browser.storage.local for onboarding stage 2017-07-05 16:09:59 -07:00
groovecoder 69f06f96cc add onboarding panel for long-press 2017-07-05 16:00:19 -05:00
luke crouch 62d479a3f3 Merge pull request #659 from jonathanKingston/fix-non-nightly-menu
Fix non nightly long press menu. Fixes #658
2017-07-05 15:17:14 -05:00
Jonathan Kingston 315c75f2ac Fix non nightly long press menu. Fixes #658 2017-07-05 13:08:33 -07:00
luke crouch 9fcf822140 Merge pull request #643 from jonathanKingston/pre-shield
Pre shield
2017-06-29 17:06:32 -07:00
Jonathan Kingston e7ac72a6a2 Add new telemetry to the old plus button menu 2017-06-29 13:47:20 -07:00
Jonathan Kingston da39d18ce0 Increasing notification timeout 2017-06-29 10:00:54 -07:00
Jonathan Kingston 63025ab3d6 Removal of hover menu for plus menu 2017-06-29 09:59:53 -07:00
Jonathan Kingston 1c38e09dcc Changing the notification colour to be less alien 2017-06-29 09:42:41 -07:00
luke crouch 2178e26220 Merge pull request #637 from jonathanKingston/cleanup-exemption-code
Cleanup exemption code. On assignment set/remove clear exemptions and…
2017-06-27 15:54:59 -07:00
luke crouch 310e2fa503 Merge pull request #638 from jonathanKingston/tidy-bits
Add a code of conduct and contributing file.
2017-06-27 15:37:17 -07:00
Jonathan Kingston 30c55e093c Add a code of conduct and contributing file. 2017-06-23 14:22:26 +01:00
Jonathan Kingston d0037d1377 Cleanup exemption code. On assignment set/remove clear exemptions and set based on existing tabs. Fixes #635 2017-06-23 12:50:47 +01:00
luke crouch 2a3aa296c0 Merge pull request #633 from jonathanKingston/different-display-of-notice-b
Aligning items to center for container notification.
2017-06-22 16:28:27 -05:00
Jonathan Kingston 10e83d3795 Aligning items to center for container notification. 2017-06-22 22:22:45 +01:00
luke crouch 29b9590878 Merge pull request #631 from jonathanKingston/different-display-of-notice
Fix different display in content warning notice. Fixes #630
2017-06-22 16:09:13 -05:00
Jonathan Kingston a86bcf7983 Fix different display in content warning notice. Fixes #630 2017-06-22 22:05:05 +01:00
groovecoder e28b25e04c fix #626: cast message userContextId to string 2017-06-22 22:04:00 +01:00
luke crouch 1cc3ab83b9 Merge pull request #627 from jonathanKingston/correct-assignment-toggle
Fixing the assignment toggle for when a different container is open. …
2017-06-22 10:11:44 -05:00
Jonathan Kingston 0566c9f962 Fixing the assignment toggle for when a different container is open. Fixes #611 2017-06-22 16:09:10 +01:00
luke crouch 8c92d8ef5d Merge pull request #629 from jonathanKingston/assignment-confirm-alignment
Fixing alignment of checkbox on confirm screen. Fixes #607
2017-06-22 09:49:49 -05:00
Jonathan Kingston a8cac47125 Fixing alignment of checkbox on confirm screen. Fixes #607 2017-06-22 15:43:56 +01:00
groovecoder e191255c47 fix #608: re-"render" badge on window focus change 2017-06-22 15:35:55 +01:00
groovecoder 7eb752c2f7 fix #614: only call thisStudy.shutdown in shield 2017-06-22 14:50:31 +01:00
luke crouch 40f2f1af5e Merge pull request #620 from jonathanKingston/tooltips
Adding tooltips. Fixes #615. Fixes #609.  Fixes #112
2017-06-22 08:43:16 -05:00
Jonathan Kingston 214a83deda Adding tooltips. Fixes #615. Fixes #609. Fixes #112 2017-06-22 14:40:01 +01:00
luke crouch 51b804f96d Merge pull request #621 from jonathanKingston/close-correct-assignment
Close correct assignment window on confirmation page. Fixes #606
2017-06-22 08:16:03 -05:00
Jonathan Kingston 83e8340a70 Close correct assignment window on confirmation page. Fixes #606 2017-06-22 13:45:31 +01:00
groovecoder 6292d9b25d fix #498: final copy for security onboarding panels 2017-06-22 11:38:23 +01:00
groovecoder 2f5e195c91 for #498: use local node_modules/.bin/json 2017-06-22 11:38:23 +01:00
groovecoder af966d6d29 for #498 add onboarding-3-security.png asset
remove tab.create orientation
2017-06-22 11:38:23 +01:00
groovecoder 4ed136299b replace a .then() with await Promise.all() 2017-06-22 11:38:23 +01:00
groovecoder 5237e67fa6 for #498: security onboarding panels and logic 2017-06-22 11:38:23 +01:00
luke crouch 13cd601212 Merge pull request #602 from jonathanKingston/edit-restyle
Edit restyle
2017-06-20 10:18:46 -05:00
Jonathan Kingston bc847b53f5 Making create screen have buttons again 2017-06-19 15:51:29 +01:00
Jonathan Kingston 4e0180d521 Improve assignment styles part of #561 2017-06-19 14:33:57 +01:00
Jonathan Kingston 13e4b4e7f7 WIP styles improving radio styles 2017-06-19 14:33:57 +01:00
Jonathan Kingston 2278498b06 WIP edit-containers restyle 2017-06-19 14:33:57 +01:00
luke crouch 6533c74d0a Merge pull request #597 from jonathanKingston/sheild-study-fixes
Fix icons in shield study. Fixes #586
2017-06-16 10:30:17 -05:00
Jonathan Kingston 9b0fe826de Fix icons in shield study. Fixes #586 2017-06-16 15:14:21 +01:00
groovecoder 5d75d4525d document "alltabs-menu" as an open tab source 2017-06-15 20:35:55 +01:00
luke crouch 59f2b8a764 Merge pull request #593 from jonathanKingston/current-tab-window
Fix for current tab showing the wrong window. Fixes #592
2017-06-15 10:30:06 -05:00
Jonathan Kingston 68c21624e2 Reset context menu when assignment changes. Fixes #589 2017-06-15 13:39:22 +01:00
Jonathan Kingston bfc6f68978 Fix for current tab showing the wrong window. Fixes #592 2017-06-14 23:59:54 +01:00
groovecoder 78ef2e8304 update README for shield per @kjozwiak 2017-06-14 23:10:50 +01:00
luke crouch 5b85fc1690 Merge pull request #584 from jonathanKingston/assignment-context-issues
Fixing showing of assignment menu. Fixes #579
2017-06-14 11:39:13 -05:00
luke crouch be8f6bbe7c Merge pull request #581 from jonathanKingston/keyboard-focus
Fixing keyboard focus issues to new layout
2017-06-14 11:27:37 -05:00
Jonathan Kingston dfd420d1a5 Fix which assignment is being changed. Fixes #580 2017-06-14 17:01:14 +01:00
luke crouch 4030b6eeec Merge pull request #582 from jonathanKingston/fix-reload-telemetry
Fix reload telemetry payload.
2017-06-14 10:38:55 -05:00
Jonathan Kingston d2b4d972e1 Fixing showing of assignment menu. Fixes #579 2017-06-14 13:49:13 +01:00
Jonathan Kingston 06d381b931 Fix reload telemetry payload. 2017-06-14 12:09:18 +01:00
Jonathan Kingston c2ed5420a4 Fixing keyboard focus issues to new layout 2017-06-14 11:41:28 +01:00
luke crouch fc789a49ac Merge pull request #576 from jonathanKingston/shield-study
Shield study work
2017-06-13 11:41:28 -05:00
Jonathan Kingston bf75f52a52 Fix linting 2017-06-13 16:36:00 +01:00
Jonathan Kingston 090ae1f139 Fixing popup to use tabId for messaging assignment change 2017-06-13 15:02:47 +01:00
Jonathan Kingston cd2e110c17 Merge branch 'ux-fiddles' into shield-study 2017-06-13 14:09:27 +01:00
Jonathan Kingston d63e887ef7 Merge branch 'assignment-manage' into shield-study 2017-06-13 14:07:21 +01:00
Jonathan Kingston ea0c9d4306 Merge branch 'comma-shortcut' into shield-study 2017-06-13 14:05:29 +01:00
Jonathan Kingston e5a87ab535 Merge branch 'assign-menu-for-wrong-tab' into shield-study 2017-06-13 14:05:10 +01:00
Jonathan Kingston 3b9da05e67 Merge branch 'bakulf-colors' 2017-06-12 15:04:13 +01:00
baku 5c5cf02249 Reset color and icon when disabled - issue #398 2017-06-12 15:03:49 +01:00
Jonathan Kingston 8503e9c9c5 Adding manage assignment from edit container panel. Fixes #501 2017-06-06 16:48:01 +01:00
Jonathan Kingston 7f37ed906f Exchanging Ctrl+Y for Ctrl+Period due to text field collisions. Fixes #546 2017-06-06 14:22:48 +01:00
Jonathan Kingston 15477dc384 Start fixing styles 2017-06-05 17:48:18 +01:00
Jonathan Kingston 06d35e65ce Adding in content notification to look more browser like. 2017-06-05 15:37:01 +01:00
Jonathan Kingston 49e8afaf9a Fixing truncation for info screen tabs. 2017-06-05 15:37:01 +01:00
Jonathan Kingston 9903e811c2 Fix first focus issue on opening browser action. Fixes #564 2017-06-05 15:37:01 +01:00
Jonathan Kingston e467988a71 Fixing favicon loading for all icons. Part of #561 2017-06-05 15:37:01 +01:00
Jonathan Kingston 094a0e2391 Update README.md to bump Firefox version number 2017-06-04 22:22:42 +01:00
Jonathan Kingston df8bf4e5e4 Force removal of assign context menu entries before anything async happens to prevent the wrong tabs assign preference showing. Fixes #539 2017-05-31 11:10:45 +01:00
groovecoder 45f34a586a only start study for @shield-study-privacy 2017-05-30 21:24:29 +01:00
Jonathan Kingston ab2b9a48c7 Changing current tab truncation to prevent container overflowing. Fixes #552 2017-05-30 21:24:18 +01:00
luke crouch 82c9cac34c Merge pull request #540 from jonathanKingston/current-tab-layout
Cleaning up layout issues for current tab panel.
2017-05-30 12:37:15 -05:00
Jonathan Kingston 5cd2ac0187 Cleaning up layout issues for current tab panel. 2017-05-26 16:42:58 +01:00
luke crouch 0f9dd77687 Merge pull request #535 from jonathanKingston/assignment-controls_reject-assignment
Assignment controls reject assignment
2017-05-25 11:48:45 -05:00
Jonathan Kingston fb845cce12 Fixing linting errors 2017-05-25 17:16:46 +01:00
Jonathan Kingston a29fae0893 Fixing exemption to be stored in memory rather than storage (prevents exemption from being remembered on restart). 2017-05-25 17:02:16 +01:00
Jonathan Kingston bd72b4e759 Adding new exemption pings to metrics.md 2017-05-25 17:02:16 +01:00
Jonathan Kingston 4f6e91336f WIP assignment controls. Fixes #499 2017-05-25 17:02:16 +01:00
Jonathan Kingston 69d497bacd Adding container assignment exemption on confirm prompt. Fixes #500 2017-05-25 17:02:16 +01:00
luke crouch d3413c7afc Merge pull request #478 from mozilla/shield
Add lib/shield to enable shield study
2017-05-25 09:16:15 -05:00
luke crouch bb96ab9c37 Merge pull request #531 from mozilla/version-2.3.0
bump version to 2.3.0
2017-05-24 15:10:29 -05:00
groovecoder 6a10c1c970 bump version to 2.3.0 2017-05-24 12:11:24 -05:00
luke crouch 1d385d2aaa Merge pull request #530 from jonathanKingston/removal-of-tab-state-context
Removal of tab state context menu as unable to detect tab assignment …
2017-05-24 11:14:30 -05:00
Jonathan Kingston fd918408f4 Removal of tab state context menu as unable to detect tab assignment state. Fixes #520 2017-05-24 02:35:48 +01:00
Jonathan Kingston 08ba094748 add npm run build-shield command 2017-05-19 14:31:58 -05:00
groovecoder 5916bd2871 only use our experimentPing outside of Test Pilot 2017-05-19 13:38:04 -05:00
groovecoder 3700e6f461 experiment.js from testpilot addon
Remove variants functionality and javascript-flow

.eslintignore experiment.js
2017-05-18 10:32:27 -05:00
luke crouch a99cd9c8ef Merge pull request #515 from jonathanKingston/fix-underline
Remove underline from buttons. Fixes: #514
2017-05-18 09:08:40 -05:00
groovecoder dad3214986 actually start the study 2017-05-17 14:41:04 -05:00
Jonathan Kingston 099d07bf1f Update shield install path 2017-05-17 12:37:24 -05:00
groovecoder 93b6378b22 fix npm test/lint failures 2017-05-17 12:37:24 -05:00
groovecoder 84dd73bff5 update README with shield run instructions 2017-05-17 12:37:24 -05:00
groovecoder b0c53063d2 start shield study AFTER SDK starts the webext 2017-05-17 12:37:24 -05:00
Jonathan Kingston 1819e6cde9 Remove underline from buttons. Fixes: #514 2017-05-17 17:38:55 +01:00
groovecoder 54c598e22e startup the study 2017-05-17 11:34:03 -05:00
groovecoder e499ff5711 include lib/shield to make it work 2017-05-17 11:34:03 -05:00
groovecoder cd03ea7a59 start study.js 2017-05-17 11:34:03 -05:00
luke crouch 46b155c90f Merge pull request #487 from jonathanKingston/remove-sdk-further
Interim WIP patch of more removal
2017-05-17 11:25:46 -05:00
Jonathan Kingston 31f3a76a1c Merge branch 'asyncify' into remove-sdk-further 2017-05-17 16:09:33 +01:00
Jonathan Kingston 4ffb587d9e Removal of more SDK code 2017-05-17 16:08:29 +01:00
Jonathan Kingston 4d61fa190c Removal of more SDK code 2017-05-17 14:24:50 +01:00
Jonathan Kingston 4a97e07d43 Adding in shortcut and keyboard controls 2017-05-17 11:24:17 +01:00
luke crouch 41df7a10dc Merge pull request #510 from jonathanKingston/change-check-for-storage
Undefined storage area. Fixes #508
2017-05-16 13:29:07 -05:00
luke crouch 5089091617 Merge pull request #506 from mozilla/assignment-onboarding
Assignment onboarding panel and icon badge
2017-05-16 13:07:48 -05:00
groovecoder 54ccf5b9ec async/await in popup code too 2017-05-16 13:01:59 -05:00
groovecoder ee98034572 ecmaVersion 8 for eslint fixes 2017-05-16 12:29:45 -05:00
groovecoder d8fd47a353 move all badge logic into WebExtension code 2017-05-16 12:20:10 -05:00
Jonathan Kingston 02300630f6 Undefined storage area. Fixes #508 2017-05-16 11:04:23 +01:00
groovecoder 3805f12e17 Logic.clearBrowserActionBadge method 2017-05-15 16:22:35 -05:00
groovecoder 4a48a7debb show 'NEW' icon badge for major upgrades 2017-05-11 11:56:25 -05:00
luke crouch 26e6efd611 Merge pull request #488 from jonathanKingston/move-to-no-unescaped-eslint
Move to the Mozilla backed no-unsanitized ESLint plugin
2017-05-09 11:07:40 -05:00
Jonathan Kingston 92ab56448c Move to the Mozilla backed no-unsanitized ESLint plugin 2017-05-09 13:29:08 +01:00
luke crouch ac02c45e81 Merge pull request #485 from jonathanKingston/remove-sdk
Remove sdk
2017-05-08 14:06:42 -05:00
Jonathan Kingston 1ec86c7fd2 Moving remove, add and update code into the web extension background for stability 2017-05-08 14:32:35 +01:00
groovecoder 8f80b527f5 start onboarding-panel-4 for site assignments 2017-05-05 13:46:58 -05:00
groovecoder 337dee2061 more descriptive _containerTabs method name 2017-05-04 09:44:49 -05:00
Jonathan Kingston f2ddc7fd84 Moving create and update containers into popup.js 2017-05-04 09:44:49 -05:00
Jonathan Kingston f4597eae84 Moving bulk of removeIdentity code into popup.js 2017-05-04 09:44:49 -05:00
luke crouch 5887f991b3 Merge pull request #470 from bakulf/crash
Recovering after a crash - issue #463
2017-05-04 09:24:13 -05:00
baku 1de3f42385 Recovering after a crash - issue #463 2017-04-28 16:56:55 +02:00
baku 91ec0c4a6d Pinned tabs should be stored as such - issue #456 2017-04-28 14:44:20 +01:00
luke crouch 0b1d6e9087 Merge pull request #447 from jonathanKingston/tab-menu-item
Adding in context menu to tab context menu. Fixes #424.
2017-04-24 15:45:57 -05:00
Jonathan Kingston 2ee9682950 Adding in context menu to tab context menu. Fixes #424. 2017-04-12 17:57:23 +01:00
99 changed files with 7125 additions and 4530 deletions
+2 -1
View File
@@ -1 +1,2 @@
testpilot-metrics.js
lib/testpilot/*.js
coverage
+26 -7
View File
@@ -1,22 +1,31 @@
module.exports = {
"parserOptions": {
"ecmaVersion": 8
},
"env": {
"browser": true,
"es6": true,
"node": true,
"webextensions": true
},
"extends": [
"eslint:recommended"
],
"globals": {
"Utils": true,
"CustomizableUI": true,
"CustomizableWidgets": true,
"SessionStore": true,
"Services": true
"Services": true,
"Components": true,
"XPCOMUtils": true,
"OS": true,
"ADDON_UNINSTALL": true,
"ADDON_DISABLE": true
},
"plugins": [
"promise",
"no-unescaped"
"no-unsanitized"
],
"extends": [
"eslint:recommended"
],
"root": true,
"rules": {
@@ -29,8 +38,18 @@ module.exports = {
"promise/no-promise-in-callback": "warn",
"promise/no-return-wrap": "error",
"promise/param-names": "error",
"no-unescaped/no-key-assignment": "error",
"no-unescaped/enforce": "error",
"no-unsanitized/method": [
"error"
],
"no-unsanitized/property": [
"error",
{
"escape": {
"taggedTemplates": ["Utils.escaped"]
}
}
],
"eqeqeq": "error",
"indent": ["error", 2],
+27
View File
@@ -0,0 +1,27 @@
<!--
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 information.
Also please make sure that:
- "Firefox will: Never remember history" in the Firefox Preferences/Options under "Privacy & Security > History" is NOT selected
- You are NOT using Firefox in a Private Window
- You can see a grayed out but ticked Checkbox with the description "Enable Container Tabs" in the Firefox Preferences/Options under "Tabs"
-->
- 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
+11 -1
View File
@@ -1,8 +1,18 @@
.DS_Store
package-lock.json
node_modules
README.html
*.xpi
*.swp
*.sw*
.vimrc
.env
addon.env
src/web-ext-artifacts/*
# JetBrains IDE files
.idea
# IstanbulJS
.nyc_output
coverage
+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*
+8 -3
View File
@@ -5,13 +5,18 @@
"extends": "stylelint-config-standard",
"ignoreFiles": ["webextension/css/*.min.css"],
"ignoreFiles": ["src/css/*.min.css"],
"rules": {
"declaration-block-no-duplicate-properties": true,
"order/declaration-block-properties-alphabetical-order": true,
"order/properties-alphabetical-order": true,
"property-no-unknown": [
true, {
ignoreProperties:
["inset-block-end", "inset-block-start"]
}],
"property-blacklist": [
"/height/",
"/(min[-]|max[-])height/",
"/width/",
"/top/",
"/bottom/",
+1 -1
View File
@@ -1,6 +1,6 @@
language: node_js
node_js:
- "6.1"
- "lts/*"
notifications:
irc:
+15
View File
@@ -0,0 +1,15 @@
# Community Participation Guidelines
This repository is governed by Mozilla's code of conduct and etiquette guidelines.
For more details, please read the
[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
## How to Report
For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page.
<!--
## Project Specific Etiquette
In some cases, there will be additional project etiquette i.e.: (https://bugzilla.mozilla.org/page.cgi?id=etiquette.html).
Please update for your project.
-->
+35
View File
@@ -0,0 +1,35 @@
# Contributing
Everyone is welcome to contribute to containers. Reach out to team members if you have questions:
- IRC: #containers on irc.mozilla.org
- Email: containers@mozilla.com
## Filing bugs
If you find a bug with containers, please file a issue.
Check first if the bug might already exist: https://github.com/mozilla/multi-account-containers/issues
[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:
- Application Basics
- Nightly Features (if you are in nightly)
- Extensions
- Experimental Features
3. Include clear steps to reproduce the issue you have experienced.
4. Include screenshots if possible.
## Sending Pull Requests
Patches should be submitted as pull requests. When submitting patches as PRs:
- You agree to license your code under the project's open source license (MPL 2.0).
- Base your branch off the current master (see below for an example workflow).
- Add both your code and new tests if relevant.
- Run npm test to make sure all tests still pass.
- Please do not include merge commits in pull requests; include only commits with the new relevant code.
See the main [README](./README.md) for information on prerequisites, installing, running and testing.
+46 -62
View File
@@ -1,80 +1,64 @@
# Containers: Test Pilot Experiment
# 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 experiment with [Containers](https://blog.mozilla.org/tanvi/2016/06/16/contextual-identities-on-the-web/) in [Firefox Test Pilot](https://testpilot.firefox.com/) to learn:
[Available on addons.mozilla.org](https://addons.mozilla.org/en-GB/firefox/addon/multi-account-containers/)
* Will a general Firefox audience understand the Containers feature?
* Is the UI as currently implemented in Nightly clear or discoverable?
For more info, see:
See [the Product Hypothesis Document for more
details](https://docs.google.com/document/d/1WQdHTVXROk7dYkSFluc6_hS44tqZjIrG9I-uPyzevE8/edit?ts=5824ba12#).
* [Test Pilot Product Hypothesis Document](https://docs.google.com/document/d/1WQdHTVXROk7dYkSFluc6_hS44tqZjIrG9I-uPyzevE8/edit#)
* [Shield Product Hypothesis Document](https://docs.google.com/document/d/1vMD-fH_5hGDDqNvpRZk12_RhCN2WAe4_yaBamaNdtik/edit#)
## Requirements
* node 7+ (for jpm)
* Firefox 51+
## Run it
See Development
* Firefox 57+
## Development
### Development Environment
Add-on development is better with [a particular environment](https://developer.mozilla.org/en-US/Add-ons/Setting_up_extension_development_environment). One simple way to get that environment set up is to install the [DevPrefs add-on](https://addons.mozilla.org/en-US/firefox/addon/devprefs/). You can make a custom Firefox profile that includes the DevPrefs add-on, and use that profile when you run the code in this repository.
1. Make a new profile by running `/path/to/firefox -P`, which launches the profile editor. "Create Profile" -- name it whatever you wish (e.g. 'addon_dev') and store it in the default location. It's probably best to deselect the option to "Use without asking," since you probably don't want to use this as your default profile.
2. Once you've created your profile, click "Start Firefox". A new instance of Firefox should launch. Go to Tools->Add-ons and search for "DevPrefs". Install it. Quit Firefox.
3. Now you have a new, vanilla Firefox profile with the DevPrefs add-on installed. You can use your new profile with the code in _this_ repository like so:
**Beta building**
To build this for 51 beta just using the downloaded version of beta will not work as XPI signature checking is disabled fully.
The only way to run the experiment is using an [unbranded version build](https://wiki.mozilla.org/Add-ons/Extension_Signing#Unbranded_Builds) or to build beta yourself:
1. [Download the mozilla-beta repo](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Source_Code/Mercurial#mozilla-beta_(prerelease_development_tree))
2. [Create a mozconfig file](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Configuring_Build_Options) - probably optional
3. `cd <reponame>`
3. `./mach bootstrap`
4. `./mach build`
5. Follow the above instructions by creating the new profile via: `~/<reponame>/obj-x86_64-pc-linux-gnu/dist/bin/firefox -P` (Where "obj-x86_64-pc-linux-gnu" may be different depending on platform obj-...)
### Run with jpm
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 .xpi, use the plain [`jpm
xpi`](https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/jpm#jpm_xpi) command.
### 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/web-ext/bin/web-ext run -s src/`
### Testing
TBD
`npm run test`
or
`npm run lint`
for just the linter
There is a timeout test that sometimes fails on certain machines, so make sure to run the tests on your clone before you make any changes to see if you have this problem.
### 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.
- [License](./LICENSE.txt)
- [Contributing](./CONTRIBUTING.md)
- [Code Of Conduct](./CODE_OF_CONDUCT.md)
+3 -121
View File
@@ -1,108 +1,3 @@
/* HACK: Custom Container vars do not propigate correctly
until the container tab is blurred and refocused,
adding the data-identity-color with the default hex
value, or chrome url path as an alternate selector mitiages this bug.*/
[data-identity-color="blue"],
[data-identity-color="#00a7e0"] {
--identity-tab-color: #37adff;
--identity-icon-color: #37adff;
}
[data-identity-color="turquoise"],
[data-identity-color="#01bdad"] {
--identity-tab-color: #00c79a;
--identity-icon-color: #00c79a;
}
[data-identity-color="green"],
[data-identity-color="#7dc14c"] {
--identity-tab-color: #51cd00;
--identity-icon-color: #51cd00;
}
[data-identity-color="yellow"],
[data-identity-color="#ffcb00"] {
--identity-tab-color: #ffcb00;
--identity-icon-color: #ffcb00;
}
[data-identity-color="orange"],
[data-identity-color="#f89c24"] {
--identity-tab-color: #ff9f00;
--identity-icon-color: #ff9f00;
}
[data-identity-color="red"],
[data-identity-color="#d92215"] {
--identity-tab-color: #ff613d;
--identity-icon-color: #ff613d;
}
[data-identity-color="pink"],
[data-identity-color="#ee5195"] {
--identity-tab-color: #ff4bda;
--identity-icon-color: #ff4bda;
}
[data-identity-color="purple"],
[data-identity-color="#7a2f7a"] {
--identity-tab-color: #af51f5;
--identity-icon-color: #af51f5;
}
[data-identity-icon="fingerprint"],
[data-identity-icon="chrome://browser/skin/usercontext/personal.svg"] {
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#fingerprint");
}
[data-identity-icon="briefcase"],
[data-identity-icon="chrome://browser/skin/usercontext/work.svg"] {
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#briefcase");
}
[data-identity-icon="dollar"],
[data-identity-icon="chrome://browser/skin/usercontext/banking.svg"] {
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#dollar");
}
[data-identity-icon="cart"],
[data-identity-icon="chrome://browser/skin/usercontext/cart.svg"],
[data-identity-icon="chrome://browser/skin/usercontext/shopping.svg"] {
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#cart");
}
[data-identity-icon="circle"] {
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#circle");
}
[data-identity-icon="gift"] {
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#gift");
}
[data-identity-icon="vacation"] {
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#vacation");
}
[data-identity-icon="food"] {
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#food");
}
[data-identity-icon="fruit"] {
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#fruit");
}
[data-identity-icon="pet"] {
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#pet");
}
[data-identity-icon="tree"] {
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#tree");
}
[data-identity-icon="chill"] {
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#chill");
}
#userContext-indicator {
height: 16px;
list-style-image: none !important;
@@ -129,19 +24,6 @@ value, or chrome url path as an alternate selector mitiages this bug.*/
display: none;
}
.userContext-icon,
.menuitem-iconic[data-usercontextid] > .menu-iconic-left > .menu-iconic-icon,
.subviewbutton[usercontextid] > .toolbarbutton-icon,
#userContext-indicator {
background-image: var(--identity-icon) !important;
background-position: center center;
background-repeat: no-repeat;
background-size: contain;
fill: var(--identity-icon-color) !important;
filter: url(/img/filters.svg#fill);
filter: url(resource://testpilot-containers/data/filters.svg#fill);
}
/* containers experiment */
/* reset nightly containers */
@@ -200,7 +82,7 @@ special cases are addressed below */
}
#new-tab-overlay {
--icon-size: 26px;
--icon-size: 16px;
-moz-appearance: none;
background: transparent;
font-style: -moz-use-system-font;
@@ -252,8 +134,8 @@ special cases are addressed below */
}
#new-tab-overlay .menuitem-iconic[data-usercontextid] > .menu-iconic-left > .menu-iconic-icon {
block-height: var(--icon-size);
block-width: var(--icon-size);
block-size: var(--icon-size);
inline-size: var(--icon-size);
}
.menuitem-iconic[data-usercontextid] > .menu-iconic-left {
-266
View File
@@ -1,266 +0,0 @@
# METRICS
## Data Analysis
The collected data will primarily be used to answer the following questions.
Images are used for visualization and are not composed of actual data.
### Do users install and run this?
What is the overall engagement of the Containers experiment?
**This is the standard Daily Active User (DAU) and Monthly Active User (MAU) analysis.**
This captures data from the users who have the add-on installed, regardless of
whether they are actively interacting with it.
![](kpi-1.png)
### Immediate Questions
* Do people use the containers feature & how do people create new container tabs?
* Click to create new container tab
* \+ `entry-point` value: "tab-bar" or "pop-up"
* Do people who use the containers feature continue to use it?
* Retention: opening a second container tab (second tab in the same container, or a tab in a second container?)
* What containers do people use?
* userContextId
* \+ Number of tabs in the container (when should we measure this? on every tab open?)
* Do people edit their containers?
* Click on "Edit Containers"
* Click to edit a single container
* Click "OK"
* Click to delete a single container
* Click "OK"
* Click to add a container
* Click "OK"
* Do people sort the tabs?
* Click sort
* \+ Number of tabs when clicked
* Average number of container tabs when sort was clicked
* Do users show and hide container tabs?
* Click hide
* \+ Number of tabs when clicked
* \+ Number of hidden containers when clicked
* Click show
* \+ Number of tabs when clicked
* \+ Number of shown containers when clicked
* Do users move container tabs to new windows?
* Click move
* \+ Number of tabs when clicked
* Average number of container tabs when new window was clicked
* How many containers do users have hidden at the same time? (when should we measure this? each time a container is hidden?)
* Do users pin container tabs? (do we have existing Telemetry for pinning?)
* Do users visit more pages in container tabs than non-container tabs?
### Follow-up Questions
What are some follow-up questions we anticipate we will ask based on any of the
above answers/data?
* What is the average lifespan of a container tab? Is that longer or shorter than a regular tab? (if we don't have data on the latter, the former probably isn't worth gathering data on since we will have nothing to compare it to).
## Data Collection
### Server Side
There is currently no server side component to Containers.
### Client Side
Containers will use Test Pilot Telemetry with no batching of data. Details
of when pings are sent are below, along with examples of the `payload` portion
of a `testpilottest` telemetry ping for each scenario.
* The user clicks on a container name to open a tab in that container
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"clickedContainerTabCount": <number-of-tabs-in-the-container>,
"event": "open-tab",
"eventSource": ["tab-bar"|"pop-up"|"file-menu"]
}
```
* The user clicks "Edit Containers" in the pop-up
```js
{
"uuid": <uuid>,
"event": "edit-containers"
}
```
* The user clicks OK after clicking on a container edit icon in the pop-up
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"event": "edit-container"
}
```
* The user clicks OK after clicking on a container delete icon in the pop-up
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"event": "delete-container"
}
```
* The user clicks OK after clicking to add a container in the pop-up
```js
{
"uuid": <uuid>,
"event": "add-container"
}
```
* The user clicks the sort button/icon in the pop-up
```js
{
"uuid": <uuid>,
"event": "sort-tabs",
"shownContainersCount": <number-of-containers-with-tabs-shown>,
"totalContainerTabsCount": <number-of-all-container-tabs>,
"totalNonContainerTabsCount": <number-of-all-non-container-tabs>
}
```
* The user clicks "Hide these container tabs" in the popup
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"clickedContainerTabCount": <number-of-tabs-in-the-container>,
"event": "hide-tabs",
"hiddenContainersCount": <number-of-containers-with-tabs-hidden>,
"shownContainersCount": <number-of-containers-with-tabs-shown>,
"totalContainersCount": <number-of-containers-with-tabs-hidden-or-shown>
}
```
* The user clicks "Show these container tabs" in the popup
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"clickedContainerTabCount": <number-of-tabs-in-the-container>,
"event": "show-tabs",
"hiddenContainersCount": <number-of-containers-with-tabs-hidden>,
"shownContainersCount": <number-of-containers-with-tabs-shown>,
"totalContainersCount": <number-of-containers-with-tabs-hidden-or-shown>
}
```
* The user clicks "Move tabs to a new window" in the popup
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"clickedContainerTabCount": <number-of-tabs-in-the-container>,
"event": "move-tabs-to-window"
}
```
* When a user encounters the disabled "move" feature because of incompatible add-ons
```js
{
"uuid": <uuid>,
"event": "incompatible-addons-detected"
}
```
* The user closes a tab
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"event": "page-requests-completed-per-tab",
"pageRequestCount": <pageRequestCount>
}
```
* The user goes idle
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"event": "page-requests-completed-per-activity",
"pageRequestCount": <pageRequestCount>
}
```
* The user chooses "Always Open in this Container" context menu option. (Note: We send two separate event names: one for assigning a site to a container, one for removing a site from a container.)
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"event": "[added|removed]-container-assignment"
}
```
* Firefox prompts the user to reload a site into a container after the user picked "Always Open in this Container".
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"event": "prompt-reload-page-in-container"
}
```
* The user clicks "Take me there" to reload a site into a container after the user picked "Always Open in this Container".
```js
{
"uuid": <uuid>,
"event": "click-to-reload-page-in-container"
}
```
* Firefox automatically reloads a site into a container after the user picked "Always Open in this Container".
```js
{
"uuid": <uuid>,
"userContextId": <userContextId>,
"event": "auto-reload-page-in-container"
}
```
### A Redshift schema for the payload:
```lua
local schema = {
-- column name field type length attributes field name
{"uuid", "VARCHAR", 255, nil, "Fields[payload.uuid]"},
{"userContextId", "INTEGER", 255, nil, "Fields[payload.userContextId]"},
{"clickedContainerTabCount", "INTEGER", 255, nil, "Fields[payload.clickedContainerTabCount]"},
{"eventSource", "VARCHAR", 255, nil, "Fields[payload.eventSource]"},
{"event", "VARCHAR", 255, nil, "Fields[payload.event]"},
{"pageRequestCount", "INTEGER", 255, nil, "Fields[payload.pageRequestCount]"}
{"hiddenContainersCount", "INTEGER", 255, nil, "Fields[payload.hiddenContainersCount]"},
{"shownContainersCount", "INTEGER", 255, nil, "Fields[payload.shownContainersCount]"},
{"totalContainersCount", "INTEGER", 255, nil, "Fields[payload.totalContainersCount]"},
{"totalContainerTabsCount", "INTEGER", 255, nil, "Fields[payload.totalContainerTabsCount]"},
{"totalNonContainerTabsCount", "INTEGER", 255, nil, "Fields[payload.totalNonContainerTabsCount]"}
}
```
### Valid data should be enforced on the server side:
* `eventSource` should be one of `tab-bar`, `pop-up`, or `file-menu`.
All Mozilla data is kept by default for 180 days and in accordance with our
privacy policies.
-1600
View File
File diff suppressed because it is too large Load Diff
+33 -35
View File
@@ -1,54 +1,52 @@
{
"name": "testpilot-containers",
"title": "Containers Experiment",
"description": "Containers works by isolating cookie jars using separate origin-attributes defined visually by colored Container Tabs. This add-on is a modified version of the containers feature for Firefox Test Pilot.",
"version": "2.2.0",
"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": "7.0.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",
"eslint": "^3.17.1",
"eslint-plugin-no-unescaped": "^1.1.0",
"addons-linter": "^1.3.2",
"ajv": "^6.6.3",
"chai": "^4.2.0",
"eslint": "^6.6.0",
"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",
"json": "^9.0.6",
"mocha": "^6.2.2",
"npm-run-all": "^4.0.0",
"stylelint": "^7.9.0",
"stylelint-config-standard": "^16.0.0",
"stylelint-order": "^0.3.0",
"testpilot-metrics": "^2.1.0"
"nyc": "^15.0.0",
"sinon": "^7.5.0",
"sinon-chai": "^3.3.0",
"stylelint-order": "^4.0.0",
"stylelint": "^13.5.0",
"stylelint-config-standard": "^20.0.0",
"web-ext": "^2.9.3",
"webextensions-jsdom": "^1.2.1"
},
"engines": {
"firefox": ">=51.0"
},
"permissions": {
"multiprocess": true
},
"hasEmbeddedWebExtension": true,
"homepage": "https://github.com/mozilla/testpilot-containers#readme",
"keywords": [
"jetpack"
],
"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",
"webext": "web-ext run -s src/",
"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"
},
"updateURL": "https://testpilot.firefox.com/files/@testpilot-containers/updates.json"
"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 && npm run coverage",
"test:once": "mocha test/**/*.test.js",
"test:watch": "npm run test:once -- --watch",
"coverage": "nyc --reporter=html --reporter=text mocha test/**/*.test.js --timeout 60000"
}
}
+36
View File
@@ -0,0 +1,36 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<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>
<body>
<main>
<div class="title">
<h1 class="title-text">Open this site in your assigned container?</h1>
</div>
<form id="redirect-form">
<p>
You asked <dfn id="browser-name" title="Thanks for trying out Containers. Sorry we may have got your browser name wrong. #FxNightly" >Firefox</dfn> to always open <dfn class="container-name"></dfn> for this site:<br />
</p>
<div id="redirect-url"></div>
<p>Would you still like to open in this current container?</p>
<br />
<br />
<label for="never-ask" class="check-label">
<input id="never-ask" type="checkbox" />
Remember my decision for this site
</label>
<br />
<div class="button-container">
<button id="deny" class="button">Open in <dfn id="current-container-name">Current</dfn> Container</button>
<button id="confirm" class="button primary" autofocus>Open in <dfn class="container-name"></dfn> Container</button>
</div>
</form>
</main>
<script src="js/utils.js"></script>
<script src="js/confirm-page.js"></script>
</body>
</html>
+87
View File
@@ -0,0 +1,87 @@
/* General Rules and Resets */
.title {
background-image: none;
}
main {
background: url(/img/onboarding-4.png) no-repeat;
background-position: -10px -15px;
background-size: 300px;
margin-inline-start: -350px;
padding-inline-start: 350px;
}
.container-name {
font-weight: bold;
}
button .container-name,
#current-container-name {
font-weight: bold;
text-transform: capitalize;
}
@media only screen and (max-width: 1300px) {
main {
background: none;
}
/* for a mid sized window we have enough for this but not our image */
.title {
background-image: url("chrome://global/skin/icons/info.svg");
}
}
html {
box-sizing: border-box;
font: message-box;
}
#redirect-url,
#redirect-origin {
font-weight: bold;
/* max-inline-size is needed to force this text smaller than the layout at a mid-sized window */
max-inline-size: 40rem;
word-break: break-all;
}
#redirect-url {
background: #efedf0; /* Grey 20 */
border-radius: 2px;
line-height: 1.5;
padding-block-end: 0.5rem;
padding-block-start: 0.5rem;
padding-inline-end: 0.5rem;
padding-inline-start: 0.5rem;
}
/* stylelint-disable media-feature-name-no-unknown */
@media (prefers-color-scheme: dark) {
#redirect-url {
background: #38383d; /* Grey 70 */
color: #eee; /* White 20 */
}
}
/* stylelint-enable */
#redirect-url img {
block-size: 16px;
inline-size: 16px;
margin-inline-end: 6px;
offset-block-start: 3px;
position: relative;
}
dfn {
font-style: normal;
}
.button-container > button {
min-inline-size: 240px;
}
.check-label {
align-items: center;
display: flex;
}
+27
View File
@@ -0,0 +1,27 @@
.container-notification {
align-items: center;
background: #efefef;
color: #003f07;
display: flex;
font: 12px sans-serif;
inline-size: 100vw;
justify-content: start;
offset-block-start: 0;
offset-inline-start: 0;
padding-block-end: 8px;
padding-block-start: 8px;
padding-inline-end: 8px;
padding-inline-start: 8px;
position: fixed;
text-align: start;
transform: translateY(-100%);
transition: transform 0.3s cubic-bezier(0.07, 0.95, 0, 1) 0.3s;
z-index: 999999999999;
}
.container-notification img {
block-size: 16px;
display: inline-block;
inline-size: 16px;
margin-inline-end: 3px;
}
+29
View File
@@ -0,0 +1,29 @@
body {
background: #fff;
color: #202023;
}
h3 {
margin-block-start: 2.5rem;
}
h3:first-of-type {
margin-block-start: 1rem;
}
p,
label {
color: rgb(74, 74, 79);
}
@media (prefers-color-scheme: dark) {
body {
background: #202023;
color: #fff;
}
p,
label {
color: rgb(177, 177, 179);
}
}
+928
View File
@@ -0,0 +1,928 @@
/* General Rules and Resets */
* {
font-size: inherit;
margin-block-end: 0;
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
padding-block-end: 0;
padding-block-start: 0;
padding-inline-end: 0;
padding-inline-start: 0;
}
html {
background-color: #fefefe;
box-sizing: border-box;
font-size: 12px;
}
body {
color: #000;
font-family: Roboto, Noto, "San Francisco", Ubuntu, "Segoe UI", "Fira Sans", message-box, Arial, sans-serif;
font-size: 13px;
inline-size: calc(var(--overflow-size) + 299px);
/* inline-size: 320px; */
letter-spacing: -0.1px;
max-inline-size: calc(var(--overflow-size) + 299px);
--highlight-blue: #1296f8;
--hr-grey: #e3e3e3;
--text-grey: #737373;
}
html,
body {
block-size: 100%; /* Bugfix: issue 948 */
}
:root {
--primary-action-color: #248aeb;
--title-text-color: #000;
--text-normal-color: #4a4a4a;
--text-heading-color: #000;
/* calculated from 12px */
--font-size-heading: 1.33rem; /* 16px */
--block-line-space-size: 0.5rem; /* 6px */
--inline-item-space-size: 0.5rem; /* 6px */
--block-line-separation-size: 0.33rem; /* 10px */
--inline-icon-space-size: 0.833rem; /* 10px */
/* Use for url and icon size */
--block-url-label-size: 2rem; /* 24px */
--inline-start-size: 1.66rem; /* 20px */
--inline-button-size: 5.833rem; /* 70px */
--icon-size: 1.166rem; /* 14px */
--small-text-size: 0.833rem; /* 10px */
--small-radius: 3px;
--icon-button-size: calc(calc(var(--block-line-separation-size) * 2) + 1.66rem); /* 20px */
--column-panel-inline-size: calc(var(--overflow-size) + 267px);
--inactive-opacity: 0.3;
--overflow-size: 1px;
--icon-fit: 8;
}
@media (min-resolution: 1dppx) {
html {
font-size: 14px;
}
}
*,
*::before,
*::after {
box-sizing: inherit;
}
form {
margin-block-end: 0;
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
}
table {
border: 0;
border-spacing: 0;
inline-size: 100%;
margin-block-end: 0;
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
}
/* Helper Classes */
.hide {
display: none !important;
}
.scrollable {
flex: 1;
inline-size: 100%;
max-block-size: 400px;
overflow: auto;
}
.offpage {
opacity: 0;
}
[hidden] {
display: none !important;
}
/* Effect borrowed from tabs in Firefox, ensure that the element flexes to the full width */
.truncate-text {
inline-size: 100%;
overflow: hidden;
position: relative;
white-space: nowrap;
}
.truncate-text::after {
background: white;
content: " ";
height: 100%;
inline-size: 50px;
inset-inline-end: 0;
mask-image: linear-gradient(to right, transparent, white 70%);
position: absolute;
}
.hover-highlight:hover .truncate-text::after,
.hover-highlight:focus .truncate-text::after {
background: var(--highlight-blue);
mask-image: linear-gradient(to right, transparent, var(--highlight-blue) 50%);
}
/* Color and icon helpers */
[data-identity-color="blue"] {
--identity-tab-color: #37adff;
--identity-icon-color: #37adff;
}
[data-identity-color="turquoise"] {
--identity-tab-color: #00c79a;
--identity-icon-color: #00c79a;
}
[data-identity-color="green"] {
--identity-tab-color: #51cd00;
--identity-icon-color: #51cd00;
}
[data-identity-color="grey"] {
/* Only used for the edit panel */
--identity-icon-color: #616161;
}
[data-identity-color="yellow"] {
--identity-tab-color: #ffcb00;
--identity-icon-color: #ffcb00;
}
[data-identity-color="orange"] {
--identity-tab-color: #ff9f00;
--identity-icon-color: #ff9f00;
}
[data-identity-color="red"] {
--identity-tab-color: #ff613d;
--identity-icon-color: #ff613d;
}
[data-identity-color="pink"] {
--identity-tab-color: #ff4bda;
--identity-icon-color: #ff4bda;
}
[data-identity-color="purple"] {
--identity-tab-color: #af51f5;
--identity-icon-color: #af51f5;
}
[data-identity-icon="fingerprint"] {
--identity-icon: url("/img/usercontext.svg#fingerprint");
}
[data-identity-icon="briefcase"] {
--identity-icon: url("/img/usercontext.svg#briefcase");
}
[data-identity-icon="dollar"] {
--identity-icon: url("/img/usercontext.svg#dollar");
}
[data-identity-icon="cart"] {
--identity-icon: url("/img/usercontext.svg#cart");
}
[data-identity-icon="circle"] {
--identity-icon: url("/img/usercontext.svg#circle");
}
[data-identity-icon="food"] {
--identity-icon: url("/img/usercontext.svg#food");
}
[data-identity-icon="gift"] {
--identity-icon: url("/img/usercontext.svg#gift");
}
[data-identity-icon="vacation"] {
--identity-icon: url("/img/usercontext.svg#vacation");
}
[data-identity-icon="fruit"] {
--identity-icon: url("/img/usercontext.svg#fruit");
}
[data-identity-icon="pet"] {
--identity-icon: url("/img/usercontext.svg#pet");
}
[data-identity-icon="tree"] {
--identity-icon: url("/img/usercontext.svg#tree");
}
[data-identity-icon="chill"] {
--identity-icon: url("/img/usercontext.svg#chill");
}
[data-identity-icon="fence"] {
--identity-icon: url("/img/usercontext.svg#fence");
}
#current-tab [data-identity-icon="default-tab"] {
background: center center no-repeat url("/img/blank-tab.svg");
fill: currentColor;
}
/* Buttons */
.button {
color: black;
}
.button.primary {
background-color: #0996f8;
color: white;
}
.button.primary:hover,
.button.primary:focus {
background-color: #0675d3;
}
.button.secondary:hover,
.button.secondary:focus {
background-color: rgba(0, 0, 0, 0.05);
}
/* Panels keep everything together */
.panel {
display: flex;
flex-direction: column;
justify-content: space-between;
min-block-size: 400px;
}
.panel.onboarding,
.achievement-panel {
align-items: center;
block-size: 360px;
margin-block-end: 16px;
margin-block-start: 16px;
margin-inline-end: 16px;
margin-inline-start: 16px;
min-block-size: 360px;
}
.panel-content {
flex: 1;
padding-block-start: 16px;
}
.panel-footer .button {
align-items: center;
block-size: 100%;
display: flex;
flex: 1;
justify-content: center;
}
/* Onboarding styles */
.onboarding * {
text-align: center;
}
.onboarding-img {
block-size: 132px;
inline-size: 180px;
}
.onboarding-title {
color: #43484e;
font-size: var(--font-size-heading);
margin-block-end: 0;
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
max-inline-size: 80%;
}
.onboarding p {
color: var(--text-normal-color);
font-size: 14px;
margin-block-end: 16px;
max-inline-size: 84%;
}
.onboarding-button {
align-items: center;
background-color: #0996f8;
border-radius: 3px;
color: white;
display: flex;
flex: 0 0 44px;
font-size: 14px;
inline-size: 100%;
justify-content: center;
text-decoration: none;
transition: background-color 75ms;
}
.half-button-wrapper {
align-items: center;
display: flex;
flex-direction: row;
height: 44px;
inline-size: 100%;
}
.half-onboarding-button {
align-items: center;
background-color: #0996f8;
border-radius: 3px;
color: white;
display: flex;
flex: 1 0 auto;
font-size: 14px;
height: 44px;
inline-size: 50%;
justify-content: center;
margin-inline-end: 4px;
text-decoration: none;
transition: background-color 75ms;
}
.grey-button {
background-color: #e3e3e3;
color: #000;
}
.onboarding-button:hover,
.onboarding-button:active {
background-color: #0675d3;
}
.onboarding-button:focus,
.half-onboarding-button:focus {
box-shadow: 0 0 0 1px #0a84ff inset, 0 0 0 1px #0a84ff, 0 0 0 4px rgba(10, 132, 255, 0.3);
}
/* Pop buttons are the square shaped buttons used to
manage things like container crud */
.pop-button {
align-items: center;
block-size: var(--icon-button-size);
cursor: pointer;
display: flex;
flex: 0 0 var(--icon-button-size);
justify-content: center;
}
.panel-footer a {
text-decoration: none;
}
.userContext-wrapper {
align-items: center;
display: flex;
flex: 1 1;
transition: background-color 75ms;
}
.edit-containers-panel .userContext-wrapper {
max-inline-size: calc(var(--overflow-size) + 203px);
}
.disable-edit-containers {
opacity: var(--inactive-opacity);
pointer-events: none;
}
.userContext-icon-wrapper {
block-size: var(--icon-button-size);
flex: 0 0 var(--icon-button-size);
margin-inline-start: var(--inline-icon-space-size);
}
/* .userContext-icon is used natively, Bug 1333811 was raised to fix */
.usercontext-icon {
background-image: var(--identity-icon);
background-position: center center;
background-repeat: no-repeat;
background-size: 16px;
block-size: 100%;
fill: var(--identity-icon-color);
filter: url('/img/filters.svg#fill');
}
.mac-icon {
background-image: url('/img/multiaccountcontainer-16.svg');
background-position: center center;
background-repeat: no-repeat;
background-size: 16px;
block-size: 100%;
}
.container-panel-row:hover .clickable .usercontext-icon,
.container-panel-row:focus .clickable .usercontext-icon,
.container-panel-row .clickable:focus .usercontext-icon {
background-image: url('/img/container-newtab.svg');
fill: #979797;
filter: url('/img/filters.svg#fill');
}
.container-panel-row .clickable:hover .usercontext-icon,
.container-panel-row .clickable:focus .usercontext-icon {
fill: #0094fb;
}
/* Panel Footer */
.panel-footer {
align-items: center;
background: #efefef;
block-size: var(--icon-button-size);
border-block-end: 1px solid #d8d8d8;
color: #000;
display: flex;
font-size: 13px;
inline-size: 100%;
justify-content: space-between;
}
.container-info-has-tabs,
.container-info-tab-row {
align-items: center;
display: flex;
flex: 0 0 28px;
font-size: 14px;
justify-content: flex-start;
margin-block-end: 0;
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
padding-inline-end: 16px;
padding-inline-start: 16px;
}
.container-info-has-tabs img,
.container-info-tab-row img {
block-size: 16px;
flex: 0 0 16px;
margin-inline-end: 4px;
}
.container-info-tab-row img[src=""] {
margin-inline-end: 0;
}
.delete-container-confirm {
padding-inline-end: 20px;
padding-inline-start: 20px;
}
.delete-container-confirm-title {
color: #000;
font-size: var(--font-size-heading);
}
#edit-sites-assigned h3 {
font-size: 14px;
font-weight: normal;
padding-block-end: 6px;
padding-block-start: 6px;
padding-inline-end: 16px;
padding-inline-start: 16px;
}
.assigned-sites-list > div {
display: flex;
padding-block-end: 6px;
padding-block-start: 6px;
}
.assigned-sites-list > div > .icon {
margin-inline-end: 10px;
}
.assigned-sites-list > div > .hostname {
flex: 1;
}
.radio-choice > .radio-container {
align-items: center;
block-size: 25px;
display: flex;
flex: 0 0 calc(100% / var(--icon-fit));
}
.radio-choice > .radio-container > label {
background: none;
block-size: 23px;
border: 0;
filter: none;
inline-size: 23px;
margin-block-end: 0;
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
padding-block-end: 0;
padding-block-start: 0;
padding-inline-end: 0;
padding-inline-start: 0;
}
.radio-choice > .radio-container > label::before {
background-color: unset;
background-image: var(--identity-icon);
background-position: center;
background-repeat: no-repeat;
background-size: 16px;
block-size: 23px;
border: none;
content: "";
display: block;
fill: var(--identity-icon-color);
filter: url('/img/filters.svg#fill');
inline-size: 23px;
position: relative;
}
.radio-choice > .radio-container > [type="radio"] {
-moz-appearance: none;
display: inline;
opacity: 0;
}
.radio-choice > .radio-container > [type="radio"]:checked + label {
background: #d3d3d3;
border-radius: 100%;
}
/* When focusing the element add a thin blue highlight to match input fields. This gives a distinction to other selected radio items */
.radio-choice > .radio-container > [type="radio"]:focus + label {
outline: 1px solid #1f9ffc;
-moz-outline-radius: 100%;
}
.edit-container-panel fieldset {
background: none;
border: none;
display: flex;
flex-direction: row;
flex-wrap: wrap;
inline-size: 80%;
margin-block-end: 10px;
margin-inline-end: 0;
margin-inline-start: 0;
padding-block-end: 0;
padding-block-start: 0;
padding-inline-end: 0;
padding-inline-start: 0;
}
.edit-container-panel fieldset:last-of-type {
margin-block-end: 0;
}
.edit-container-panel input[type="text"] {
block-size: 36px;
border-radius: 3px;
font-size: 14px;
inline-size: 100%;
padding-block-end: 5px;
padding-block-start: 5px;
padding-inline-end: 5px;
padding-inline-start: 5px;
}
.edit-container-panel legend,
.options-header {
flex: 1 0;
font-size: 14px !important;
margin-block-end: 4px;
margin-block-start: -6px;
}
.options-header {
margin-block-end: 8px;
margin-block-start: 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;
}
h3.title {
block-size: 40px;
color: #000;
font-size: 13px;
font-weight: bold;
inline-size: 100%;
letter-spacing: -0.1px;
line-height: 40px;
text-align: center;
}
.menu {
border-style: none;
inline-size: 100%;
}
.menu-item {
cursor: pointer;
height: 24px;
inline-size: 100%;
line-height: 24px;
}
.menu-item td {
display: flex;
max-inline-size: 300px;
}
.disabled-menu-item {
color: grey;
cursor: default;
font-style: italic;
}
.hover-highlight:hover,
.hover-highlight:focus {
background: var(--highlight-blue);
color: #fff;
}
.menu-text {
inline-size: calc(100% - 40px);
line-height: 24px;
max-inline-size: 260px;
}
.menu-icon {
display: block;
height: 16px;
inline-size: 23px;
margin-block-end: 4px;
margin-block-start: 4px;
margin-inline-end: 8px;
margin-inline-start: 16px;
text-align: center;
}
/* Maintain 1:1 square ratio for Favicons of websites added to a specific container */
#container-info-table .menu-icon {
inline-size: 16px;
}
.menu-right-float {
height: 24px;
inline-size: 60px;
text-align: right;
}
.container-count {
opacity: 0.6;
padding-block-end: 0;
padding-block-start: 0;
padding-inline-end: 6px;
padding-inline-start: 0;
text-align: right;
}
.menu-arrow {
display: inline-block;
float: right;
height: 24px;
inline-size: 18px;
padding-block-end: 6px;
padding-block-start: 6px;
padding-inline-end: 12px;
padding-inline-start: 0;
text-align: center;
}
.menu-arrow img {
height: 12px;
inline-size: 12px;
padding-block-end: 2px;
padding-block-start: 2px;
padding-inline-end: 2px;
padding-inline-start: 2px;
}
hr {
border: 0;
border-block-start: 1px solid var(--hr-grey);
display: block;
margin-block-end: 0;
margin-block-start: 6px;
margin-inline-end: 0;
margin-inline-start: 0;
padding-block-end: 6px;
padding-block-start: 0;
padding-inline-end: 0;
padding-inline-start: 0;
}
.sub-header {
color: var(--text-grey);
height: 24px;
line-height: 24px;
padding-block-end: 0;
padding-block-start: 0;
padding-inline-end: 16px;
padding-inline-start: 16px;
}
.edit-form {
color: var(--text-grey);
flex: 1;
padding-block-end: 0;
padding-block-start: 0;
padding-inline-end: 16px;
padding-inline-start: 16px;
}
.identities-list {
margin-block-end: 41px;
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
}
.bottom-btn {
background-color: var(--hr-grey);
border: solid 1px #d9d9d9;
cursor: pointer;
height: 41px;
inline-size: 100%;
inset-block-end: 0;
line-height: 41px;
padding-block-end: 0;
padding-block-start: 0;
padding-inline-end: 16px;
padding-inline-start: 16px;
position: fixed;
}
.delete-container {
background-color: #fff;
border-block-start: solid 1px var(--hr-grey);
cursor: default;
display: flex;
height: 65px;
inline-size: 100%;
justify-content: space-between;
padding-block-end: 27px;
padding-block-start: 9px;
padding-inline-end: 18px;
padding-inline-start: 17px;
}
.delete-btn {
background-color: rgba(12, 12, 13, 0.1);
border: 0;
border-radius: 2px;
cursor: pointer;
height: 30px;
inline-size: 100%;
line-height: 30px;
text-align: center;
}
.btn-return.arrow-left {
background-color: rgba(255, 255, 255, 1);
background-image: url("/img/arrow-icon-left.svg");
border: 0;
cursor: pointer;
height: 1.2rem;
inline-size: 1.2rem;
inset-block-start: 15px;
left: 15px;
position: absolute;
}
input {
border: solid 1px #bebebe;
border-radius: 2px;
}
.form-header {
height: 23px;
line-height: 23px;
padding-block-end: 0;
padding-block-start: 0;
padding-inline-end: 0;
padding-inline-start: 0;
}
.edit-container-panel-name-input {
height: 29px;
}
.container-options {
height: 23px;
}
.site-isolation {
inset-block-end: auto;
position: fixed;
}
.options-label {
cursor: pointer;
padding-inline-start: 25px;
}
.manage-assigned-sites-list {
color: var(--highlight-blue);
}
.info-icon {
cursor: pointer;
height: 16px;
inline-size: 16px;
inset-block-start: 13px;
position: absolute;
right: 13px;
text-align: center;
text-decoration: none;
}
.delete-warning {
padding-block-end: 8px;
padding-block-start: 8px;
padding-inline-end: 0;
padding-inline-start: 0;
}
.trash-button {
display: inline-block;
float: right;
height: 16px;
inline-size: 16px;
margin-block-end: 4px;
margin-block-start: 4px;
margin-inline-end: 10px;
margin-inline-start: 0;
text-align: center;
}
tr > td > .trash-button {
display: none;
}
tr:hover > td > .trash-button {
display: block;
}
+3
View File
@@ -0,0 +1,3 @@
<!-- 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 id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 160"><defs><style>.cls-1{fill:#6a57a5;}.cls-2{fill:#5a4a9e;}.cls-3{fill:#e7dfff;}</style></defs><title>account</title><path class="cls-1" d="M110,138.89A58.89,58.89,0,1,1,168.89,80,59,59,0,0,1,110,138.89Z"/><path class="cls-2" d="M110,130.27A50.27,50.27,0,1,1,160.27,80,50.33,50.33,0,0,1,110,130.27Z"/><circle class="cls-3" cx="110.39" cy="65.12" r="23.27" transform="translate(-12.01 27.1) rotate(-13.28)"/><path class="cls-3" d="M141.78,92.87c-8.2-9.46-19.58,3.28-31.39,3.28S87.2,83.41,79,92.87a7.83,7.83,0,0,0-.53,9.53,38.43,38.43,0,0,0,63.83,0A7.83,7.83,0,0,0,141.78,92.87Z"/></svg>

After

Width:  |  Height:  |  Size: 887 B

+3
View File
@@ -0,0 +1,3 @@
<!-- 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 id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 160"><defs><style>.cls-1{fill:#9f9fad;}.cls-2{fill:#5a4a9e;}.cls-3{fill:#6a57a5;}.cls-4{fill:#8f8f9d;}.cls-5{fill:none;stroke:#80808e;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.6px;}.cls-6{fill:#231f20;opacity:0.4;}.cls-7{fill:#ee3389;}.cls-8{fill:#7661aa;}</style></defs><title>Sync</title><path class="cls-1" d="M119.16,122.69v4.81H19.76v-4.81l12.83-3.21h72.15Z"/><rect class="cls-1" x="24.57" y="55.35" width="89.79" height="67.34" rx="3"/><path class="cls-2" d="M79.08,65l-49.7,49.7a1.61,1.61,0,0,0,1.6,1.61h77a1.62,1.62,0,0,0,1.61-1.61V65Z"/><polygon class="cls-3" points="29.38 64.97 29.38 114.67 79.08 64.97 29.38 64.97"/><path class="cls-2" d="M107.94,60.16H31a1.6,1.6,0,0,0-1.6,1.6V65h80.17V61.76A1.61,1.61,0,0,0,107.94,60.16Z"/><path class="cls-4" d="M108.74,121.09H30.18a.81.81,0,0,1,0-1.61h78.56a.81.81,0,1,1,0,1.61Z"/><line class="cls-5" x1="63.61" y1="124.18" x2="74.83" y2="124.18"/><path class="cls-6" d="M114.35,127.35H102.2V71.64a5.53,5.53,0,0,1,5.52-5.53h6.63Z"/><path class="cls-1" d="M200.24,134.72v4.81h-99.4v-4.81l12.82-3.21h72.15Z"/><rect class="cls-1" x="105.65" y="67.38" width="89.79" height="67.34" rx="3"/><path class="cls-2" d="M160.16,77l-49.71,49.7a1.61,1.61,0,0,0,1.61,1.6h77a1.6,1.6,0,0,0,1.6-1.6V77Z"/><polygon class="cls-3" points="110.45 77 110.45 126.7 160.16 77 110.45 77"/><path class="cls-2" d="M189,72.19h-77a1.61,1.61,0,0,0-1.61,1.6V77h80.17V73.79A1.6,1.6,0,0,0,189,72.19Z"/><path class="cls-4" d="M189.82,133.11H111.26a.8.8,0,1,1,0-1.6h78.56a.8.8,0,0,1,0,1.6Z"/><line class="cls-5" x1="144.69" y1="136.2" x2="155.91" y2="136.2"/><path class="cls-7" d="M136.85,50l-3-.55a3,3,0,0,0-3.51,2.37l-.27,1.45c-1.59,8.36-9.86,14.42-19.66,14.42a21,21,0,0,1-15.93-6.89H103a3,3,0,0,0,3-3v-3a3,3,0,0,0-3-3H84.86a3,3,0,0,0-3,3V73.64a3,3,0,0,0,3,3h3a3,3,0,0,0,3-3V69.72a30.8,30.8,0,0,0,19.57,6.87c14.15,0,26.15-9.11,28.54-21.66l.27-1.45A2.94,2.94,0,0,0,136.85,50Z"/><path class="cls-8" d="M84.06,47l3,.54a3.41,3.41,0,0,0,.55,0,3,3,0,0,0,3-2.41l.27-1.45h0c1.59-8.36,9.86-14.42,19.65-14.42a21,21,0,0,1,15.94,6.89H117.9a3,3,0,0,0-3,3v3a3,3,0,0,0,3,3h18.15a3,3,0,0,0,3-3V23.43a3,3,0,0,0-3-3h-3a3,3,0,0,0-3,3v3.92a30.82,30.82,0,0,0-19.58-6.88c-14.14,0-26.14,9.11-28.53,21.67l-.27,1.45A3,3,0,0,0,84.06,47Z"/></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

+3
View File
@@ -0,0 +1,3 @@
<!-- 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 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: 3.0 KiB

+3
View File
@@ -0,0 +1,3 @@
<!-- 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="context-fill" d="M6.414 8l4.293-4.293a1 1 0 0 0-1.414-1.414l-5 5a1 1 0 0 0 0 1.414l5 5a1 1 0 0 0 1.414-1.414z"></path></svg>

After

Width:  |  Height:  |  Size: 431 B

+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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 width="5px" height="8px" viewBox="0 0 5 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
<title>Arrow</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M5.00090934,9.5006316 C4.79840046,9.50099392 4.61567086,9.37916873 4.53812503,9.19209489 C4.4605792,9.00502105 4.5035325,8.78964579 4.64690934,8.6466316 L7.29490934,6.0006316 L4.64690934,3.3546316 C4.45140054,3.1591228 4.45140054,2.8421404 4.64690934,2.6466316 C4.84241814,2.4511228 5.15940054,2.4511228 5.35490934,2.6466316 L8.35490934,5.6466316 C8.44895104,5.74043586 8.50180313,5.86780434 8.50180313,6.0006316 C8.50180313,6.13345886 8.44895104,6.26082734 8.35490934,6.3546316 L5.35490934,9.3546316 C5.26095861,9.44834555 5.13360821,9.5008686 5.00090934,9.5006316 Z" id="path-1"></path>
</defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.6">
<g id="Icons-/-12-/-Arrowhead-Right-12---Thin" transform="translate(-4.000000, -2.000000)">
<rect id="bouding-box" x="0" y="0" width="12" height="12"></rect>
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="Shape" fill-rule="nonzero"></g>
<g id="Color-/-Photon-/-Primary---Grey-90-80%" mask="url(#mask-2)" fill="#0C0C0D" fill-opacity="0.8" fill-rule="evenodd">
<rect id="Rectangle" x="0" y="0" width="12" height="12"></rect>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 61 KiB

+5
View File
@@ -0,0 +1,5 @@
<!-- 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="18" height="18" viewBox="0 0 18 18">
<path d="M17,12v2a1,1,0,0,1-1,1H2a1,1,0,0,1-1-1V12a1,1,0,0,1,1-1H1.142c2.3,0,2.536-1.773,2.874-4,0.351-2.316.083-4,3.13-4h3.707C13.917,3,13.647,4.684,14,7c0.34,2.228.582,4,2.89,4H16A1,1,0,0,1,17,12Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 518 B

+5
View File
@@ -0,0 +1,5 @@
<!-- 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" viewBox="0 0 7 7">
<polygon fill="#FFFFFF" 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: 394 B

+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 12 12" style="enable-background:new 0 0 12 12;" xml:space="preserve">
<path fill="#FFFFFF" d="M4.6,0.3h2.7c0.1,0,0.2,0.1,0.2,0.2v1H4.4v-1C4.4,0.4,4.5,0.3,4.6,0.3z M1.7,1.5h8.6c0.1,0,0.2,0.1,0.2,0.2
l0.2,1.4H1.3l0.2-1.4C1.5,1.6,1.6,1.5,1.7,1.5z M6,11.7H3.2L2.1,3.9H6h3.9l-1.1,7.8H6L6,11.7z"/>
</svg>

After

Width:  |  Height:  |  Size: 779 B

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">

Before

Width:  |  Height:  |  Size: 883 B

After

Width:  |  Height:  |  Size: 1.1 KiB

+9
View File
@@ -0,0 +1,9 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg data-name="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<rect x="1" y="1" width="6" height="6" rx="1"/>
<rect x="1" y="9" width="6" height="6" rx="1"/>
<rect x="9" y="9" width="6" height="6" rx="1"/>
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M14.92 1.62a1 1 0 0 0-0.54-0.54A1 1 0 0 0 14 1h-4a1 1 0 0 0 0 2h1.59l-2.3 2.29a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0L13 4.41V6a1 1 0 0 0 2 0V2a1 1 0 0 0-0.08-0.38z"/>
</svg>

After

Width:  |  Height:  |  Size: 677 B

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: 399 B

After

Width:  |  Height:  |  Size: 399 B

+13
View File
@@ -0,0 +1,13 @@
<!-- 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 id="Flat_For_Export_" data-name="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs>
<style>
.cls-1{fill-rule:evenodd}
</style>
</defs>
<path fill="context-fill" fill-opacity="context-fill-opacity" class="cls-1" d="M8 1a7 7 0 1 0 7 7 7 7 0 0 0-7-7zm0 13a6 6 0 1 1 6-6 6 6 0 0 1-6 6z"/>
<path fill="context-fill" fill-opacity="context-fill-opacity" class="cls-1" d="M8 7a1 1 0 0 0-1 1v3a1 1 0 0 0 2 0V8a1 1 0 0 0-1-1z"/>
<circle cx="8" cy="5" r="1.19"/>
</svg>

After

Width:  |  Height:  |  Size: 718 B

+7
View File
@@ -0,0 +1,7 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg data-name="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M15.85 12.15l-3-3a0.49 0.49 0 0 0-0.7 0.7L14.29 12H9.5a0.5 0.5 0 0 0 0 1h4.79l-2.14 2.15a0.48 0.48 0 0 0 0 0.7 0.48 0.48 0 0 0 0.7 0l3-3a0.36 0.36 0 0 0 0.11-0.16 0.5 0.5 0 0 0 0-0.38 0.36 0.36 0 0 0-0.11-0.16z"/>
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M13 1H3a3 3 0 0 0-3 3v8a3 3 0 0 0 3 3h4a1 1 0 0 0 0-2H3a1 1 0 0 1-1-1V6h12v2a1 1 0 0 0 2 0V4a3 3 0 0 0-3-3zM2 5V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1z"/>
</svg>

After

Width:  |  Height:  |  Size: 796 B

@@ -0,0 +1,9 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. --><svg data-name="Flat (For Export)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<style>rect,path {fill: rgba(249, 249, 250, 0.8);}</style>
<rect x="1" y="1" width="6" height="6" rx="1"/>
<path d="M14.75 3H13V1.25A0.25 0.25 0 0 0 12.75 1h-1.5A0.25 0.25 0 0 0 11 1.25V3H9.25A0.25 0.25 0 0 0 9 3.25v1.5A0.25 0.25 0 0 0 9.25 5H11v1.75A0.25 0.25 0 0 0 11.25 7h1.5A0.25 0.25 0 0 0 13 6.75V5h1.75A0.25 0.25 0 0 0 15 4.75v-1.5A0.25 0.25 0 0 0 14.75 3z" fill-rule="evenodd"/>
<rect x="1" y="9" width="6" height="6" rx="1"/>
<rect x="9" y="9" width="6" height="6" rx="1"/>
</svg>

After

Width:  |  Height:  |  Size: 801 B

+7
View File
@@ -0,0 +1,7 @@
<svg data-name="Flat (For Export)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<style>rect,path {fill: rgba(24, 25, 26, 01);}</style>
<rect x="1" y="1" width="6" height="6" rx="1"/>
<path d="M14.75 3H13V1.25A0.25 0.25 0 0 0 12.75 1h-1.5A0.25 0.25 0 0 0 11 1.25V3H9.25A0.25 0.25 0 0 0 9 3.25v1.5A0.25 0.25 0 0 0 9.25 5H11v1.75A0.25 0.25 0 0 0 11.25 7h1.5A0.25 0.25 0 0 0 13 6.75V5h1.75A0.25 0.25 0 0 0 15 4.75v-1.5A0.25 0.25 0 0 0 14.75 3z" fill-rule="evenodd"/>
<rect x="1" y="9" width="6" height="6" rx="1"/>
<rect x="9" y="9" width="6" height="6" rx="1"/>
</svg>

After

Width:  |  Height:  |  Size: 586 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="context-fill" d="M14 7H9V2a1 1 0 0 0-2 0v5H2a1 1 0 1 0 0 2h5v5a1 1 0 0 0 2 0V9h5a1 1 0 0 0 0-2z"/>
</svg>

After

Width:  |  Height:  |  Size: 416 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

+9
View File
@@ -0,0 +1,9 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg data-name="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M12 7l-4 4a4 4 0 0 0 4-4z"/>
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M15.66 7.71a7.78 7.78 0 0 0-1.55-2.82L12.7 6.3a5.9 5.9 0 0 1 1 1.7A6 6 0 0 1 8 12a7.28 7.28 0 0 1-.93-.07l-1.64 1.64A7.92 7.92 0 0 0 8 14a8 8 0 0 0 7.66-5.71 1 1 0 0 0 0-.58zM14.71 1.29a1 1 0 0 0-1.42 0l-1.63 1.64A7.8 7.8 0 0 0 8 2a8 8 0 0 0-7.66 5.71 1 1 0 0 0 0 .58 7.8 7.8 0 0 0 2.34 3.62l-1.39 1.38a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l12-12a1 1 0 0 0 0-1.42zM8.5 5a1.43 1.43 0 0 1 .82.26L7.26 7.32A1.43 1.43 0 0 1 7 6.5 1.5 1.5 0 0 1 8.5 5zM2.35 8a6 6 0 0 1 2.11-2.82A3.91 3.91 0 0 0 5 9.61l-.9.9A5.91 5.91 0 0 1 2.35 8z"/>
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M12 7l-4 4a4 4 0 0 0 4-4z"/>
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M16 7.7a8.06 8.06 0 0 0-1.72-2.94l-1.45 1.41A5.91 5.91 0 0 1 13.94 8 6.33 6.33 0 0 1 8 12a7.28 7.28 0 0 1-.93-.07l-1.66 1.66A8.56 8.56 0 0 0 8 14a8.34 8.34 0 0 0 8-5.7 1.22 1.22 0 0 0 0-.6zM14.71 1.29a1 1 0 0 0-1.42 0L11.7 2.88A8.43 8.43 0 0 0 8 2a8.34 8.34 0 0 0-8 5.7 1.22 1.22 0 0 0 0 .6A7.87 7.87 0 0 0 2.58 12l-1.29 1.29a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l12-12a1 1 0 0 0 0-1.42zM8.5 5a1.43 1.43 0 0 1 .82.26L7.26 7.32A1.43 1.43 0 0 1 7 6.5 1.5 1.5 0 0 1 8.5 5zM2.06 8a6 6 0 0 1 2.49-3A4 4 0 0 0 4 7a4 4 0 0 0 1 2.61l-1 1A5.94 5.94 0 0 1 2.06 8z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+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 data-name="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M15 1a1 1 0 0 0-1 1v2.42A7 7 0 1 0 13 13a1 1 0 0 0-1.41-1.41 5 5 0 1 1 1-5.54H10a1 1 0 0 0 0 2h5a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1z"/>
</svg>

After

Width:  |  Height:  |  Size: 495 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 data-name="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M14.71 4.29l-3-3a1 1 0 0 0-1.42 1.42L11.59 4H4a1 1 0 0 0 0 2h7.59l-1.3 1.29a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3-3a1 1 0 0 0 0-1.42zM12 10H4.41l1.3-1.29a1 1 0 1 0-1.42-1.42l-3 3a1 1 0 0 0 0 1.42l3 3a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42L4.41 12H12a1 1 0 0 0 0-2z"/>
</svg>

After

Width:  |  Height:  |  Size: 625 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 data-name="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M11 11V9a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1V5a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v6H1a1 1 0 0 0 0 2h7v-1a1 1 0 0 1 1-1zm4.5 1H13V9.5a0.5 0.5 0 0 0-1 0V12H9.5a0.5 0.5 0 0 0 0 1H12v2.5a0.5 0.5 0 0 0 1 0V13h2.5a0.5 0.5 0 0 0 0-1z"/>
</svg>

After

Width:  |  Height:  |  Size: 583 B

@@ -13,6 +13,7 @@
display: none;
}
</style>
<path id="fence" d="M28 4l-2 2v4h-4V6l-2-2-2 2v4h-4V6l-2-2-2 2v4H6V6L4 4 2 6v22h4v-4h4v4h4v-4h4v4h4v-4h4v4h4V6l-2-2zM6 22V12h4v10H6zm8 0V12h4v10h-4zm8 0V12h4v10h-4z"/>
<path id="dollar" d="M16.2,0c-8.9,0-16,7.3-16,16c0,8.9,7.1,16,15.8,16s15.8-7.1,15.8-16C32,7.3,24.9,0,16.2,0z M17.1,25.1v1.6
c0,0.4-0.4,0.5-0.7,0.5c-0.4,0-0.7-0.2-0.7-0.5v-1.6c-3.2-0.2-5-1.8-5.5-4.3c0-0.2,0-0.2,0-0.4c0-0.5,0.4-0.9,0.9-0.9
c0.2,0,0.2,0,0.4,0c0.5,0,0.9,0.2,1.1,0.7c0.4,1.8,1.2,2.7,3.4,2.8v-6.8c-3.6-0.4-5.3-1.8-5.3-4.6c0-3,2.5-4.6,5.2-4.8V5.7
@@ -69,4 +70,12 @@
s1.6,0.5,1.8,1.4v0.2c0,0.2,0,0.2,0,0.4c0,2,0.7,3.7,2.1,5c1.4,1.4,3,2.1,5,2.1l0,0c2,0,3.6-0.7,5-2.1c1.4-1.2,2.1-3.2,2.1-5V9.2
C32,5.2,28.8,2,24.7,2"/>
<circle id="circle" r="16" cx="16" cy="16" fill-rule="evenodd"/>
<path id="bullhorn" d="M1.5 5A.5.5 0 0 0 1 5.5v5a.5.5 0 0 0 1 0v-5A.5.5 0 0 0 1.5 5zM14.6 2.2A1 1 0 0 0 13.71 2l-10 3A1 1 0 0 0 3 6v4a1 1 0 0 0 .71 1L5 11.35a.4.4 0 0 0 0 .15v1a2.5 2.5 0 0 0 5 .33L13.71 14A1 1 0 0 0 14 14a1 1 0 0 0 .6-.2A1 1 0 0 0 15 13V3a1 1 0 0 0-.4-.8zM7.5 14A1.5 1.5 0 0 1 6 12.5v-.86l3 .9A1.51 1.51 0 0 1 7.5 14zm5.5-2.34l-8-2.4V6.74l8-2.4z"/>
<path id="folder" d="M13 4H8.41L7 2.59A2 2 0 0 0 5.59 2H3a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z"/>
<path id="hat" d="M15 10a1 1 0 0 0-1 1 1 1 0 0 1-1 1V6a4 4 0 0 0-4-4H7a4 4 0 0 0-4 4v6a1 1 0 0 1-1-1 1 1 0 0 0-2 0 3 3 0 0 0 3 3h10a3 3 0 0 0 3-3 1 1 0 0 0-1-1zM7 4h2a2 2 0 0 1 2 2v3H5V6a2 2 0 0 1 2-2z"/>
<path id="wallet" d="M14 2H2C1 2 0.06 3 0 4.86v6.34A1.94 1.94 0 0 0 1.22 13l7 2.86A2 2 0 0 0 9 16a2 2 0 0 0 1.14-0.35A1.9 1.9 0 0 0 11 14.07V7.8A1.94 1.94 0 0 0 9.78 6L4.85 4h8.65a0.5 0.5 0 0 1 0 1H9.92l0.24 0.1a2.93 2.93 0 0 1 1.2 0.9h2.14A0.5 0.5 0 0 1 14 6.5v3a0.51 0.51 0 0 1-0.5 0.5H12v2h2a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2zm-6 8.5a0.5 0.5 0 0 1 1 0v1a0.5 0.5 0 0 1-1 0z"/>
</svg>

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 11 KiB

+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<!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.3 KiB

+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<!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.5 KiB

+14
View File
@@ -0,0 +1,14 @@
module.exports = {
"extends": [
"../../.eslintrc.js"
],
"globals": {
"assignManager": true,
"badge": true,
"backgroundLogic": true,
"identityState": true,
"messageHandler": true,
"sync": true,
"Utils": true
}
};
+748
View File
@@ -0,0 +1,748 @@
window.assignManager = {
MENU_ASSIGN_ID: "open-in-this-container",
MENU_REMOVE_ID: "remove-open-in-this-container",
MENU_SEPARATOR_ID: "separator",
MENU_HIDE_ID: "hide-container",
MENU_MOVE_ID: "move-to-new-window-container",
OPEN_IN_CONTAINER: "open-bookmark-in-container-tab",
storageArea: {
area: browser.storage.local,
exemptedTabs: {},
getSiteStoreKey(pageUrlorUrlKey) {
if (pageUrlorUrlKey.includes("siteContainerMap@@_")) return pageUrlorUrlKey;
const url = new window.URL(pageUrlorUrlKey);
const storagePrefix = "siteContainerMap@@_";
if (url.port === "80" || url.port === "443") {
return `${storagePrefix}${url.hostname}`;
} else {
return `${storagePrefix}${url.hostname}${url.port}`;
}
},
setExempted(pageUrlorUrlKey, tabId) {
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
if (!(siteStoreKey in this.exemptedTabs)) {
this.exemptedTabs[siteStoreKey] = [];
}
this.exemptedTabs[siteStoreKey].push(tabId);
},
removeExempted(pageUrlorUrlKey) {
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
this.exemptedTabs[siteStoreKey] = [];
},
isExempted(pageUrlorUrlKey, tabId) {
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
if (!(siteStoreKey in this.exemptedTabs)) {
return false;
}
return this.exemptedTabs[siteStoreKey].includes(tabId);
},
get(pageUrlorUrlKey) {
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
return this.getByUrlKey(siteStoreKey);
},
async getSyncEnabled() {
const { syncEnabled } = await browser.storage.local.get("syncEnabled");
return !!syncEnabled;
},
getByUrlKey(siteStoreKey) {
return new Promise((resolve, reject) => {
this.area.get([siteStoreKey]).then((storageResponse) => {
if (storageResponse && siteStoreKey in storageResponse) {
resolve(storageResponse[siteStoreKey]);
}
resolve(null);
}).catch((e) => {
reject(e);
});
});
},
async set(pageUrlorUrlKey, data, exemptedTabIds, backup = true) {
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
if (exemptedTabIds) {
exemptedTabIds.forEach((tabId) => {
this.setExempted(pageUrlorUrlKey, tabId);
});
}
// eslint-disable-next-line require-atomic-updates
data.identityMacAddonUUID =
await identityState.lookupMACaddonUUID(data.userContextId);
await this.area.set({
[siteStoreKey]: data
});
const syncEnabled = await this.getSyncEnabled();
if (backup && syncEnabled) {
await sync.storageArea.backup({undeleteSiteStoreKey: siteStoreKey});
}
return;
},
async remove(pageUrlorUrlKey) {
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
// When we remove an assignment we should clear all the exemptions
this.removeExempted(pageUrlorUrlKey);
await this.area.remove([siteStoreKey]);
const syncEnabled = await this.getSyncEnabled();
if (syncEnabled) await sync.storageArea.backup({siteStoreKey});
return;
},
async deleteContainer(userContextId) {
const sitesByContainer = await this.getAssignedSites(userContextId);
this.area.remove(Object.keys(sitesByContainer));
},
async getAssignedSites(userContextId = null) {
const sites = {};
const siteConfigs = await this.area.get();
for(const urlKey of Object.keys(siteConfigs)) {
if (urlKey.includes("siteContainerMap@@_")) {
// For some reason this is stored as string... lets check
// them both as that
if (!!userContextId &&
String(siteConfigs[urlKey].userContextId)
!== String(userContextId)) {
continue;
}
const site = siteConfigs[urlKey];
// In hindsight we should have stored this
// TODO file a follow up to clean the storage onLoad
site.hostname = urlKey.replace(/^siteContainerMap@@_/, "");
sites[urlKey] = site;
}
}
return sites;
},
/*
* Looks for abandoned site assignments. If there is no identity with
* the site assignment's userContextId (cookieStoreId), then the assignment
* is removed.
*/
async upgradeData() {
const identitiesList = await browser.contextualIdentities.query({});
const macConfigs = await this.area.get();
for(const configKey of Object.keys(macConfigs)) {
if (configKey.includes("siteContainerMap@@_")) {
const cookieStoreId =
"firefox-container-" + macConfigs[configKey].userContextId;
const match = identitiesList.find(
localIdentity => localIdentity.cookieStoreId === cookieStoreId
);
if (!match) {
await this.remove(configKey);
continue;
}
const updatedSiteAssignment = macConfigs[configKey];
updatedSiteAssignment.identityMacAddonUUID =
await identityState.lookupMACaddonUUID(match.cookieStoreId);
await this.set(
configKey,
updatedSiteAssignment,
false,
false
);
}
}
}
},
_neverAsk(m) {
const pageUrl = m.pageUrl;
if (m.neverAsk === true) {
// If we have existing data and for some reason it hasn't been
// deleted etc lets update it
this.storageArea.get(pageUrl).then((siteSettings) => {
if (siteSettings) {
siteSettings.neverAsk = true;
this.storageArea.set(pageUrl, siteSettings);
}
}).catch((e) => {
throw e;
});
}
},
// We return here so the confirm page can load the tab when exempted
async _exemptTab(m) {
const pageUrl = m.pageUrl;
await this.storageArea.setExempted(pageUrl, m.tabId);
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);
// https://github.com/mozilla/multi-account-containers/issues/847
//
// Handle the case where this request's URL is not assigned to any particular
// container. We must do the following check:
//
// If the current tab's container is "unlocked", we can just go ahead
// and open the URL in the current tab, since an "unlocked" container accepts
// any-and-all sites.
//
// But if the current tab's container has been "locked" by the user, then we must
// re-open the page in the default container, because the user doesn't want random
// sites polluting their locked container.
//
// For example:
// - the current tab's container is locked and only allows "www.google.com"
// - the incoming request is for "www.amazon.com", which has no specific container assignment
// - in this case, we must re-open "www.amazon.com" in a new tab in the default container
const siteIsolatedReloadInDefault =
await this._maybeSiteIsolatedReloadInDefault(siteSettings, tab);
if (!siteIsolatedReloadInDefault) {
if (!siteSettings
|| userContextId === siteSettings.userContextId
|| 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
};
}
}
if (siteIsolatedReloadInDefault) {
this.reloadPageInDefaultContainer(
options.url,
tab.index + 1,
tab.active,
openTabId
);
} else {
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,
};
},
async _maybeSiteIsolatedReloadInDefault(siteSettings, tab) {
// Tab doesn't support cookies, so containers not supported either.
if (!("cookieStoreId" in tab)) {
return false;
}
// Requested page has been assigned to a specific container.
// I.e. it will be opened in that container anyway, so we don't need to check if the
// current tab's container is locked or not.
if (siteSettings) {
return false;
}
//tab is alredy reopening in the default container
if (tab.cookieStoreId === "firefox-default") {
return false;
}
// Requested page is not assigned to a specific container. If the current tab's container
// is locked, then the page must be reloaded in the default container.
const currentContainerState = await identityState.storageArea.get(tab.cookieStoreId);
return currentContainerState && currentContainerState.isIsolated;
},
init() {
browser.contextMenus.onClicked.addListener((info, tab) => {
info.bookmarkId ?
this._onClickedBookmark(info) :
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) => {
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"]});
this.resetBookmarksMenuItem();
},
async resetBookmarksMenuItem() {
const hasPermission = await browser.permissions.contains({
permissions: ["bookmarks"]
});
if (this.hadBookmark === hasPermission) {
return;
}
this.hadBookmark = hasPermission;
if (hasPermission) {
this.initBookmarksMenu();
browser.contextualIdentities.onCreated
.addListener(this.contextualIdentityCreated);
browser.contextualIdentities.onUpdated
.addListener(this.contextualIdentityUpdated);
browser.contextualIdentities.onRemoved
.addListener(this.contextualIdentityRemoved);
} else {
this.removeBookmarksMenu();
browser.contextualIdentities.onCreated
.removeListener(this.contextualIdentityCreated);
browser.contextualIdentities.onUpdated
.removeListener(this.contextualIdentityUpdated);
browser.contextualIdentities.onRemoved
.removeListener(this.contextualIdentityRemoved);
}
},
contextualIdentityCreated(changeInfo) {
browser.contextMenus.create({
parentId: assignManager.OPEN_IN_CONTAINER,
id: changeInfo.contextualIdentity.cookieStoreId,
title: changeInfo.contextualIdentity.name,
icons: { "16": `img/usercontext.svg#${
changeInfo.contextualIdentity.icon
}` }
});
},
contextualIdentityUpdated(changeInfo) {
browser.contextMenus.update(
changeInfo.contextualIdentity.cookieStoreId, {
title: changeInfo.contextualIdentity.name,
icons: { "16": `img/usercontext.svg#${
changeInfo.contextualIdentity.icon}` }
});
},
contextualIdentityRemoved(changeInfo) {
browser.contextMenus.remove(
changeInfo.contextualIdentity.cookieStoreId
);
},
async _onClickedHandler(info, tab) {
const userContextId = this.getUserContextIdFromCookieStore(tab);
// Mapping ${URL(info.pageUrl).hostname} to ${userContextId}
let remove;
if (userContextId) {
switch (info.menuItemId) {
case this.MENU_ASSIGN_ID:
case this.MENU_REMOVE_ID:
if (info.menuItemId === this.MENU_ASSIGN_ID) {
remove = false;
} else {
remove = true;
}
await this._setOrRemoveAssignment(
tab.id, info.pageUrl, userContextId, remove
);
break;
case this.MENU_MOVE_ID:
backgroundLogic.moveTabsToWindow({
cookieStoreId: tab.cookieStoreId,
windowId: tab.windowId,
});
break;
case this.MENU_HIDE_ID:
backgroundLogic.hideTabs({
cookieStoreId: tab.cookieStoreId,
windowId: tab.windowId,
});
break;
}
}
},
async _onClickedBookmark(info) {
async function _getBookmarksFromInfo(info) {
const [bookmarkTreeNode] =
await browser.bookmarks.get(info.bookmarkId);
if (bookmarkTreeNode.type === "folder") {
return browser.bookmarks.getChildren(bookmarkTreeNode.id);
}
return [bookmarkTreeNode];
}
const bookmarks = await _getBookmarksFromInfo(info);
for (const bookmark of bookmarks) {
// Some checks on the urls from
// https://github.com/Rob--W/bookmark-container-tab/ thanks!
if ( !/^(javascript|place):/i.test(bookmark.url) &&
bookmark.type !== "folder") {
const openInReaderMode = bookmark.url.startsWith("about:reader");
if(openInReaderMode) {
try {
const parsed = new URL(bookmark.url);
bookmark.url = parsed.searchParams.get("url") + parsed.hash;
} catch (err) {
return err.message;
}
}
browser.tabs.create({
cookieStoreId: info.menuItemId,
url: bookmark.url,
openInReaderMode: openInReaderMode
});
}
}
},
deleteContainer(userContextId) {
this.storageArea.deleteContainer(userContextId);
},
getUserContextIdFromCookieStore(tab) {
if (!("cookieStoreId" in tab)) {
return false;
}
return backgroundLogic.getUserContextIdFromCookieStoreId(
tab.cookieStoreId
);
},
isTabPermittedAssign(tab) {
// Ensure we are not an important about url
const url = new URL(tab.url);
if (url.protocol === "about:"
|| url.protocol === "moz-extension:") {
return false;
}
return true;
},
async _setOrRemoveAssignment(tabId, pageUrl, userContextId, remove) {
let actionName;
// https://github.com/mozilla/testpilot-containers/issues/626
// Context menu has stored context IDs as strings, so we need to coerce
// the value to a string for accurate checking
userContextId = String(userContextId);
if (!remove) {
const tabs = await browser.tabs.query({});
const assignmentStoreKey = this.storageArea.getSiteStoreKey(pageUrl);
const exemptedTabIds = tabs.filter((tab) => {
const tabStoreKey = this.storageArea.getSiteStoreKey(tab.url);
/* Auto exempt all tabs that exist for this hostname that are not in the same container */
if (tabStoreKey === assignmentStoreKey &&
this.getUserContextIdFromCookieStore(tab) !== userContextId) {
return true;
}
return false;
}).map((tab) => {
return tab.id;
});
await this.storageArea.set(pageUrl, {
userContextId,
neverAsk: false
}, exemptedTabIds);
actionName = "assigned site to always open in this container";
} else {
// Remove assignment
await this.storageArea.remove(pageUrl);
actionName = "removed from assigned sites list";
// remove site isolation if now empty
await this._maybeRemoveSiteIsolation(userContextId);
}
if (tabId) {
const tab = await browser.tabs.get(tabId);
setTimeout(function(){
browser.tabs.sendMessage(tabId, {
text: `Successfully ${actionName}`
});
}, 1000);
this.calculateContextMenu(tab);
}
},
async _maybeRemoveSiteIsolation(userContextId) {
const assignments = await this.storageArea.getByContainer(userContextId);
const hasAssignments = assignments && Object.keys(assignments).length > 0;
if (hasAssignments) {
return;
}
await backgroundLogic.addRemoveSiteIsolation(
backgroundLogic.cookieStoreId(userContextId),
true
);
},
async _getAssignment(tab) {
const cookieStore = this.getUserContextIdFromCookieStore(tab);
// Ensure we have a cookieStore to assign to
if (cookieStore
&& this.isTabPermittedAssign(tab)) {
return this.storageArea.get(tab.url);
}
return false;
},
_getByContainer(userContextId) {
return this.storageArea.getAssignedSites(userContextId);
},
removeContextMenu() {
// There is a focus issue in this menu where if you change window with a context menu click
// you get the wrong menu display because of async
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1215376#c16
// We also can't change for always private mode
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1352102
browser.contextMenus.remove(this.MENU_ASSIGN_ID);
browser.contextMenus.remove(this.MENU_REMOVE_ID);
browser.contextMenus.remove(this.MENU_SEPARATOR_ID);
browser.contextMenus.remove(this.MENU_HIDE_ID);
browser.contextMenus.remove(this.MENU_MOVE_ID);
},
async calculateContextMenu(tab) {
this.removeContextMenu();
const siteSettings = await this._getAssignment(tab);
// Return early and not add an item if we have false
// False represents assignment is not permitted
if (siteSettings === false) {
return false;
}
let checked = false;
let menuId = this.MENU_ASSIGN_ID;
const tabUserContextId = this.getUserContextIdFromCookieStore(tab);
if (siteSettings &&
Number(siteSettings.userContextId) === Number(tabUserContextId)) {
checked = true;
menuId = this.MENU_REMOVE_ID;
}
browser.contextMenus.create({
id: menuId,
title: "Always Open in This Container",
checked,
type: "checkbox",
contexts: ["all"],
});
browser.contextMenus.create({
id: this.MENU_SEPARATOR_ID,
type: "separator",
contexts: ["all"],
});
browser.contextMenus.create({
id: this.MENU_HIDE_ID,
title: "Hide This Container",
contexts: ["all"],
});
browser.contextMenus.create({
id: this.MENU_MOVE_ID,
title: "Move Tabs to a New Window",
contexts: ["all"],
});
},
encodeURLProperty(url) {
return encodeURIComponent(url).replace(/[!'()*]/g, (c) => {
const charCode = c.charCodeAt(0).toString(16);
return `%${charCode}`;
});
},
reloadPageInDefaultContainer(url, index, active, openerTabId) {
// To create a new tab in the default container, it is easiest just to omit the
// cookieStoreId entirely.
//
// Unfortunately, if you create a new tab WITHOUT a cookieStoreId but WITH an openerTabId,
// then the new tab automatically inherits the opener tab's cookieStoreId.
// I.e. it opens in the wrong container!
//
// So we have to explicitly pass in a cookieStoreId when creating the tab, since we
// are specifying the openerTabId. There doesn't seem to be any way
// to look up the default container's cookieStoreId programatically, so sadly
// we have to hardcode it here as "firefox-default". This is potentially
// not cross-browser compatible.
//
// Note that we could have just omitted BOTH cookieStoreId and openerTabId. But the
// drawback then is that if the user later closes the newly-created tab, the browser
// does not automatically return to the original opener tab. To get this desired behaviour,
// we MUST specify the openerTabId when creating the new tab.
const cookieStoreId = "firefox-default";
browser.tabs.create({url, cookieStoreId, index, active, openerTabId});
},
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) {
return browser.tabs.create({url, cookieStoreId, index, active, openerTabId});
} else {
let confirmUrl = `${loadPage}?url=${this.encodeURLProperty(url)}&cookieStoreId=${cookieStoreId}`;
let currentCookieStoreId;
if (currentUserContextId) {
currentCookieStoreId = backgroundLogic.cookieStoreId(currentUserContextId);
confirmUrl += `&currentCookieStoreId=${currentCookieStoreId}`;
}
return browser.tabs.create({
url: confirmUrl,
cookieStoreId: currentCookieStoreId,
openerTabId,
index,
active
}).then(() => {
// We don't want to sync this URL ever nor clutter the users history
browser.history.deleteUrl({url: confirmUrl});
}).catch((e) => {
throw e;
});
}
},
async initBookmarksMenu() {
browser.contextMenus.create({
id: this.OPEN_IN_CONTAINER,
title: "Open Bookmark in Container Tab",
contexts: ["bookmark"],
});
const identities = await browser.contextualIdentities.query({});
for (const identity of identities) {
browser.contextMenus.create({
parentId: this.OPEN_IN_CONTAINER,
id: identity.cookieStoreId,
title: identity.name,
icons: { "16": `img/usercontext.svg#${identity.icon}` }
});
}
},
async removeBookmarksMenu() {
browser.contextMenus.remove(this.OPEN_IN_CONTAINER);
const identities = await browser.contextualIdentities.query({});
for (const identity of identities) {
browser.contextMenus.remove(identity.cookieStoreId);
}
},
};
assignManager.init();
+366
View File
@@ -0,0 +1,366 @@
const DEFAULT_TAB = "about:newtab";
const backgroundLogic = {
NEW_TAB_PAGES: new Set([
"about:startpage",
"about:newtab",
"about:home",
"about:blank"
]),
NUMBER_OF_KEYBOARD_SHORTCUTS: 10,
unhideQueue: [],
init() {
browser.commands.onCommand.addListener(function (command) {
for (let i=0; i < backgroundLogic.NUMBER_OF_KEYBOARD_SHORTCUTS; i++) {
const key = "open_container_" + i;
const cookieStoreId = identityState.keyboardShortcut[key];
if (command === key) {
if (cookieStoreId === "none") return;
browser.tabs.create({cookieStoreId});
}
}
});
},
async getExtensionInfo() {
const manifestPath = browser.extension.getURL("manifest.json");
const response = await fetch(manifestPath);
const extensionInfo = await response.json();
return extensionInfo;
},
getUserContextIdFromCookieStoreId(cookieStoreId) {
if (!cookieStoreId) {
return false;
}
const container = cookieStoreId.replace("firefox-container-", "");
if (container !== cookieStoreId) {
return container;
}
return false;
},
async deleteContainer(userContextId, removed = false) {
await this._closeTabs(userContextId);
if (!removed) {
await browser.contextualIdentities.remove(this.cookieStoreId(userContextId));
}
assignManager.deleteContainer(userContextId);
return {done: true, userContextId};
},
async createOrUpdateContainer(options) {
let donePromise;
if (options.userContextId !== "new") {
donePromise = browser.contextualIdentities.update(
this.cookieStoreId(options.userContextId),
options.params
);
} else {
donePromise = browser.contextualIdentities.create(options.params);
}
await donePromise;
},
async openNewTab(options) {
let url = options.url || undefined;
const userContextId = ("userContextId" in options) ? options.userContextId : 0;
const active = ("nofocus" in options) ? options.nofocus : true;
const discarded = ("noload" in options) ? options.noload : false;
const cookieStoreId = backgroundLogic.cookieStoreId(userContextId);
// Autofocus url bar will happen in 54: https://bugzilla.mozilla.org/show_bug.cgi?id=1295072
// We can't open new tab pages, so open a blank tab. Used in tab un-hide
if (this.NEW_TAB_PAGES.has(url)) {
url = undefined;
}
if (!this.isPermissibleURL(url)) {
return;
}
return browser.tabs.create({
url,
active,
discarded,
pinned: options.pinned || false,
cookieStoreId
});
},
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)) {
return new Error(`${methodName} must be called with ${argument} argument.`);
}
});
},
async getTabs(options) {
const requiredArguments = ["cookieStoreId", "windowId"];
this.checkArgs(requiredArguments, options, "getTabs");
const { cookieStoreId, windowId } = options;
const list = [];
const tabs = await browser.tabs.query({
cookieStoreId,
windowId
});
tabs.forEach((tab) => {
list.push(identityState._createTabObject(tab));
});
const containerState = await identityState.storageArea.get(cookieStoreId);
return list.concat(containerState.hiddenTabs);
},
async unhideContainer(cookieStoreId, alreadyShowingUrl) {
if (!this.unhideQueue.includes(cookieStoreId)) {
this.unhideQueue.push(cookieStoreId);
await this.showTabs({
cookieStoreId,
alreadyShowingUrl
});
this.unhideQueue.splice(this.unhideQueue.indexOf(cookieStoreId), 1);
}
},
// https://github.com/mozilla/multi-account-containers/issues/847
async addRemoveSiteIsolation(cookieStoreId, remove = false) {
const containerState = await identityState.storageArea.get(cookieStoreId);
try {
if ("isIsolated" in containerState || remove) {
delete containerState.isIsolated;
} else {
containerState.isIsolated = "locked";
}
return await identityState.storageArea.set(cookieStoreId, containerState);
} catch (error) {
console.error(`No container: ${cookieStoreId}`);
}
},
async moveTabsToWindow(options) {
const requiredArguments = ["cookieStoreId", "windowId"];
this.checkArgs(requiredArguments, options, "moveTabsToWindow");
const { cookieStoreId, windowId } = options;
const list = await browser.tabs.query({
cookieStoreId,
windowId
});
const containerState = await identityState.storageArea.get(cookieStoreId);
// Nothing to do
if (list.length === 0 &&
containerState.hiddenTabs.length === 0) {
return;
}
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.
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 = [];
// 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 = await browser.tabs.query({windowId: newWindowObj.id});
for (let tab of tabs) { // eslint-disable-line prefer-const
if (tab.cookieStoreId !== cookieStoreId) {
browser.tabs.remove(tab.id);
}
}
const rv = await identityState.storageArea.set(cookieStoreId, containerState);
this.unhideQueue.splice(this.unhideQueue.indexOf(cookieStoreId), 1);
return rv;
},
async _closeTabs(userContextId, windowId = false) {
const cookieStoreId = this.cookieStoreId(userContextId);
let tabs;
/* if we have no windowId we are going to close all this container (used for deleting) */
if (windowId !== false) {
tabs = await browser.tabs.query({
cookieStoreId,
windowId
});
} else {
tabs = await browser.tabs.query({
cookieStoreId
});
}
const tabIds = tabs.map((tab) => tab.id);
return browser.tabs.remove(tabIds);
},
async queryIdentitiesState(windowId) {
const identities = await browser.contextualIdentities.query({});
const identitiesOutput = {};
const identitiesPromise = identities.map(async (identity) => {
const { cookieStoreId } = identity;
const containerState = await identityState.storageArea.get(cookieStoreId);
const openTabs = await browser.tabs.query({
cookieStoreId,
windowId
});
identitiesOutput[cookieStoreId] = {
hasHiddenTabs: !!containerState.hiddenTabs.length,
hasOpenTabs: !!openTabs.length,
numberOfHiddenTabs: containerState.hiddenTabs.length,
numberOfOpenTabs: openTabs.length,
isIsolated: !!containerState.isIsolated
};
return;
});
await Promise.all(identitiesPromise);
return identitiesOutput;
},
async sortTabs() {
const windows = await browser.windows.getAll();
for (let windowObj of windows) { // eslint-disable-line prefer-const
// First the pinned tabs, then the normal ones.
await this._sortTabsInternal(windowObj, true);
await this._sortTabsInternal(windowObj, false);
}
},
async _sortTabsInternal(windowObj, pinnedTabs) {
const tabs = await browser.tabs.query({windowId: windowObj.id});
let pos = 0;
// Let's collect UCIs/tabs for this window.
const map = new Map;
for (const tab of tabs) {
if (pinnedTabs && !tab.pinned) {
// We don't have, or we already handled all the pinned tabs.
break;
}
if (!pinnedTabs && tab.pinned) {
// pinned tabs must be consider as taken positions.
++pos;
continue;
}
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(tab.cookieStoreId);
if (!map.has(userContextId)) {
map.set(userContextId, []);
}
map.get(userContextId).push(tab);
}
// Let's sort the map.
const sortMap = new Map([...map.entries()].sort((a, b) => a[0] > b[0]));
// Let's move tabs.
sortMap.forEach(tabs => {
for (const tab of tabs) {
++pos;
browser.tabs.move(tab.id, {
windowId: windowObj.id,
index: pos
});
}
});
},
async hideTabs(options) {
const requiredArguments = ["cookieStoreId", "windowId"];
this.checkArgs(requiredArguments, options, "hideTabs");
const { cookieStoreId, windowId } = options;
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(cookieStoreId);
const containerState = await identityState.storeHidden(cookieStoreId, windowId);
await this._closeTabs(userContextId, windowId);
return containerState;
},
async showTabs(options) {
if (!("cookieStoreId" in options)) {
return Promise.reject("showTabs must be called with cookieStoreId argument.");
}
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId);
const promises = [];
const containerState = await identityState.storageArea.get(options.cookieStoreId);
for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const
// do not show already opened url
const noload = !object.pinned;
if (object.url !== options.alreadyShowingUrl) {
promises.push(this.openNewTab({
userContextId: userContextId,
url: object.url,
nofocus: options.nofocus || false,
noload: noload,
pinned: object.pinned,
}));
}
}
containerState.hiddenTabs = [];
await Promise.all(promises);
return identityState.storageArea.set(options.cookieStoreId, containerState);
},
cookieStoreId(userContextId) {
if(userContextId === 0) return "firefox-default";
return `firefox-container-${userContextId}`;
}
};
backgroundLogic.init();
+20
View File
@@ -0,0 +1,20 @@
const MAJOR_VERSIONS = ["2.3.0", "2.4.0", "6.2.0"];
const badge = {
async init() {
const currentWindow = await browser.windows.getCurrent();
this.displayBrowserActionBadge(currentWindow);
},
async displayBrowserActionBadge() {
const extensionInfo = await backgroundLogic.getExtensionInfo();
const storage = await browser.storage.local.get({ browserActionBadgesClicked: [] });
if (MAJOR_VERSIONS.indexOf(extensionInfo.version) > -1 &&
storage.browserActionBadgesClicked.indexOf(extensionInfo.version) < 0) {
browser.browserAction.setBadgeBackgroundColor({ color: "rgba(0,217,0,255)" });
browser.browserAction.setBadgeText({ text: "NEW" });
}
}
};
badge.init();
+194
View File
@@ -0,0 +1,194 @@
window.identityState = {
keyboardShortcut: {},
storageArea: {
area: browser.storage.local,
getContainerStoreKey(cookieStoreId) {
const storagePrefix = "identitiesState@@_";
return `${storagePrefix}${cookieStoreId}`;
},
async get(cookieStoreId) {
const storeKey = this.getContainerStoreKey(cookieStoreId);
const storageResponse = await this.area.get([storeKey]);
if (storageResponse && storeKey in storageResponse) {
if (!storageResponse[storeKey].macAddonUUID){
storageResponse[storeKey].macAddonUUID = uuidv4();
await this.set(cookieStoreId, storageResponse[storeKey]);
}
return storageResponse[storeKey];
}
// If local storage doesn't have an entry, look it up to make sure it's
// an in-use identity.
const identities = await browser.contextualIdentities.query({});
const match = identities.find(
(identity) => identity.cookieStoreId === cookieStoreId);
if (match) {
const defaultContainerState = identityState._createIdentityState();
await this.set(cookieStoreId, defaultContainerState);
return defaultContainerState;
}
return false;
},
set(cookieStoreId, data) {
const storeKey = this.getContainerStoreKey(cookieStoreId);
return this.area.set({
[storeKey]: data
});
},
async remove(cookieStoreId) {
const storeKey = this.getContainerStoreKey(cookieStoreId);
return this.area.remove([storeKey]);
},
async setKeyboardShortcut(shortcutId, cookieStoreId) {
identityState.keyboardShortcut[shortcutId] = cookieStoreId;
return this.area.set({[shortcutId]: cookieStoreId});
},
async loadKeyboardShortcuts () {
const identities = await browser.contextualIdentities.query({});
for (let i=0; i < backgroundLogic.NUMBER_OF_KEYBOARD_SHORTCUTS; i++) {
const key = "open_container_" + i;
const storageObject = await this.area.get(key);
if (storageObject[key]){
identityState.keyboardShortcut[key] = storageObject[key];
continue;
}
if (identities[i]) {
identityState.keyboardShortcut[key] = identities[i].cookieStoreId;
continue;
}
identityState.keyboardShortcut[key] = "none";
}
return identityState.keyboardShortcut;
},
/*
* Looks for abandoned identity keys in local storage, and makes sure all
* identities registered in the browser are also in local storage. (this
* appears to not always be the case based on how this.get() is written)
*/
async upgradeData() {
const identitiesList = await browser.contextualIdentities.query({});
for (const identity of identitiesList) {
// ensure all identities have an entry in local storage
await identityState.addUUID(identity.cookieStoreId);
}
const macConfigs = await this.area.get();
for(const configKey of Object.keys(macConfigs)) {
if (configKey.includes("identitiesState@@_")) {
const cookieStoreId = String(configKey).replace(/^identitiesState@@_/, "");
const match = identitiesList.find(
localIdentity => localIdentity.cookieStoreId === cookieStoreId
);
if (cookieStoreId === "firefox-default") continue;
if (!match) {
await this.remove(cookieStoreId);
continue;
}
if (!macConfigs[configKey].macAddonUUID) {
await identityState.storageArea.get(cookieStoreId);
}
}
}
},
},
_createTabObject(tab) {
return Object.assign({}, tab);
},
async getCookieStoreIDuuidMap() {
const containers = {};
const identities = await browser.contextualIdentities.query({});
for(const identity of identities) {
const containerInfo = await this.storageArea.get(identity.cookieStoreId);
containers[identity.cookieStoreId] = containerInfo.macAddonUUID;
}
return containers;
},
async storeHidden(cookieStoreId, windowId) {
const containerState = await this.storageArea.get(cookieStoreId);
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;
tabObject.hiddenState = true;
containerState.hiddenTabs.push(tabObject);
});
return this.storageArea.set(cookieStoreId, containerState);
},
async updateUUID(cookieStoreId, uuid) {
if (!cookieStoreId || !uuid) {
throw new Error ("cookieStoreId or uuid missing");
}
const containerState = await this.storageArea.get(cookieStoreId);
containerState.macAddonUUID = uuid;
await this.storageArea.set(cookieStoreId, containerState);
return uuid;
},
async addUUID(cookieStoreId) {
await this.storageArea.get(cookieStoreId);
},
async lookupMACaddonUUID(cookieStoreId) {
// This stays a lookup, because if the cookieStoreId doesn't
// exist, this.get() will create it, which is not what we want.
const cookieStoreIdKey = cookieStoreId.includes("firefox-container-") ?
cookieStoreId : "firefox-container-" + cookieStoreId;
const macConfigs = await this.storageArea.area.get();
for(const configKey of Object.keys(macConfigs)) {
if (configKey === this.storageArea.getContainerStoreKey(cookieStoreIdKey)) {
return macConfigs[configKey].macAddonUUID;
}
}
return false;
},
async lookupCookieStoreId(macAddonUUID) {
const macConfigs = await this.storageArea.area.get();
for(const configKey of Object.keys(macConfigs)) {
if (configKey.includes("identitiesState@@_")) {
if(macConfigs[configKey].macAddonUUID === macAddonUUID) {
return String(configKey).replace(/^identitiesState@@_/, "");
}
}
}
return false;
},
_createIdentityState() {
return {
hiddenTabs: [],
macAddonUUID: uuidv4()
};
},
init() {
this.storageArea.loadKeyboardShortcuts();
}
};
identityState.init();
function uuidv4() {
// https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
+23
View File
@@ -0,0 +1,23 @@
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<!--
This didn't work for debugging in the manifest.
"scripts": [
"js/background/backgroundLogic.js",
"js/background/assignManager.js",
"js/background/badge.js",
"js/background/identityState.js",
"js/background/messageHandler.js",
]
-->
<script type="text/javascript" src="backgroundLogic.js"></script>
<script type="text/javascript" src="assignManager.js"></script>
<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="sync.js"></script>
</body>
</html>
+256
View File
@@ -0,0 +1,256 @@
const messageHandler = {
// After the timer completes we assume it's a tab the user meant to keep open
// 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,
init() {
// Handles messages from webextension code
browser.runtime.onMessage.addListener(async (m) => {
let response;
let tab;
switch (m.method) {
case "getShortcuts":
response = identityState.storageArea.loadKeyboardShortcuts();
break;
case "setShortcut":
identityState.storageArea.setKeyboardShortcut(m.shortcut, m.cookieStoreId);
break;
case "resetSync":
response = sync.resetSync();
break;
case "resetBookmarksContext":
response = assignManager.resetBookmarksMenuItem();
break;
case "deleteContainer":
response = backgroundLogic.deleteContainer(m.message.userContextId);
break;
case "createOrUpdateContainer":
response = backgroundLogic.createOrUpdateContainer(m.message);
break;
case "neverAsk":
assignManager._neverAsk(m);
break;
case "addRemoveSiteIsolation":
response = backgroundLogic.addRemoveSiteIsolation(m.cookieStoreId);
break;
case "getAssignment":
response = browser.tabs.get(m.tabId).then((tab) => {
return assignManager._getAssignment(tab);
});
break;
case "getAssignmentObjectByContainer":
response = assignManager._getByContainer(m.message.userContextId);
break;
case "setOrRemoveAssignment":
// m.tabId is used for where to place the in content message
// m.url is the assignment to be removed/added
response = assignManager._setOrRemoveAssignment(m.tabId, m.url, m.userContextId, m.value);
break;
case "sortTabs":
backgroundLogic.sortTabs();
break;
case "showTabs":
backgroundLogic.unhideContainer(m.cookieStoreId);
break;
case "hideTabs":
backgroundLogic.hideTabs({
cookieStoreId: m.cookieStoreId,
windowId: m.windowId
});
break;
case "checkIncompatibleAddons":
// TODO
break;
case "moveTabsToWindow":
response = backgroundLogic.moveTabsToWindow({
cookieStoreId: m.cookieStoreId,
windowId: m.windowId
});
break;
case "getTabs":
response = backgroundLogic.getTabs({
cookieStoreId: m.cookieStoreId,
windowId: m.windowId
});
break;
case "queryIdentitiesState":
response = backgroundLogic.queryIdentitiesState(m.message.windowId);
break;
case "exemptContainerAssignment":
response = assignManager._exemptTab(m);
break;
case "reloadInContainer":
response = assignManager.reloadPageInContainer(
m.url,
m.currentUserContextId,
m.newUserContextId,
m.tabIndex,
m.active,
true
);
break;
case "assignAndReloadInContainer":
tab = await assignManager.reloadPageInContainer(
m.url,
m.currentUserContextId,
m.newUserContextId,
m.tabIndex,
m.active,
true
);
// m.tabId is used for where to place the in content message
// m.url is the assignment to be removed/added
response = browser.tabs.get(tab.id).then((tab) => {
return assignManager._setOrRemoveAssignment(tab.id, m.url, m.newUserContextId, m.value);
});
break;
}
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");
}
// eslint-disable-next-line require-atomic-updates
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);
backgroundLogic.deleteContainer(userContextId, true);
});
}
browser.tabs.onActivated.addListener((info) => {
assignManager.removeContextMenu();
browser.tabs.get(info.tabId).then((tab) => {
assignManager.calculateContextMenu(tab);
}).catch((e) => {
throw e;
});
});
browser.windows.onFocusChanged.addListener((windowId) => {
this.onFocusChangedCallback(windowId);
});
browser.webRequest.onCompleted.addListener((details) => {
if (details.frameId !== 0 || details.tabId === -1) {
return {};
}
assignManager.removeContextMenu();
browser.tabs.get(details.tabId).then((tab) => {
assignManager.calculateContextMenu(tab);
}).catch((e) => {
throw e;
});
}, {urls: ["<all_urls>"], types: ["main_frame"]});
browser.tabs.onCreated.addListener((tab) => {
// lets remember the last tab created so we can close it if it looks like a redirect
this.lastCreatedTab = tab;
if (tab.cookieStoreId) {
// Don't count firefox-default, firefox-private, nor our own confirm page loads
if (tab.cookieStoreId !== "firefox-default" &&
tab.cookieStoreId !== "firefox-private" &&
!tab.url.startsWith("moz-extension")) {
// increment the counter of container tabs opened
this.incrementCountOfContainerTabsOpened();
this.tabUpdateHandler = (tabId, changeInfo) => {
if (tabId === tab.id && changeInfo.status === "complete") {
// get current tab's url to not open the same one from hidden tabs
browser.tabs.get(tabId).then(loadedTab => {
backgroundLogic.unhideContainer(tab.cookieStoreId, loadedTab.url);
}).catch((e) => {
throw e;
});
browser.tabs.onUpdated.removeListener(this.tabUpdateHandler);
}
};
// if it's a container tab wait for it to complete and
// unhide other tabs from this container
if (tab.cookieStoreId.startsWith("firefox-container")) {
browser.tabs.onUpdated.addListener(this.tabUpdateHandler);
}
}
}
setTimeout(() => {
this.lastCreatedTab = null;
}, this.LAST_CREATED_TAB_TIMER);
});
},
async incrementCountOfContainerTabsOpened() {
const key = "containerTabsOpened";
const count = await browser.storage.local.get({[key]: 0});
const countOfContainerTabsOpened = ++count[key];
browser.storage.local.set({[key]: countOfContainerTabsOpened});
// When the user opens their _ tab, give them the achievement
if (countOfContainerTabsOpened === 100) {
const storage = await browser.storage.local.get({achievements: []});
storage.achievements.push({"name": "manyContainersOpened", "done": false});
// use set and spread to create a unique array
const achievements = [...new Set(storage.achievements)];
browser.storage.local.set({achievements});
browser.browserAction.setBadgeBackgroundColor({color: "rgba(0,217,0,255)"});
browser.browserAction.setBadgeText({text: "NEW"});
}
},
async onFocusChangedCallback(windowId) {
assignManager.removeContextMenu();
// browserAction loses background color in new windows ...
// https://bugzil.la/1314674
// https://github.com/mozilla/testpilot-containers/issues/608
// ... so re-call displayBrowserActionBadge on window changes
badge.displayBrowserActionBadge();
browser.tabs.query({active: true, windowId}).then((tabs) => {
if (tabs && tabs[0]) {
assignManager.calculateContextMenu(tabs[0]);
}
}).catch((e) => {
throw e;
});
}
};
// Lets do this last as theme manager did a check before connecting before
messageHandler.init();
+580
View File
@@ -0,0 +1,580 @@
const SYNC_DEBUG = false;
const sync = {
storageArea: {
area: browser.storage.sync,
async get(){
return this.area.get();
},
async set(options) {
return this.area.set(options);
},
async deleteIdentity(deletedIdentityUUID) {
const deletedIdentityList =
await sync.storageArea.getDeletedIdentityList();
if (
! deletedIdentityList.find(element => element === deletedIdentityUUID)
) {
deletedIdentityList.push(deletedIdentityUUID);
await sync.storageArea.set({ deletedIdentityList });
}
await this.removeIdentityKeyFromSync(deletedIdentityUUID);
},
async removeIdentityKeyFromSync(deletedIdentityUUID) {
await sync.storageArea.area.remove( "identity@@_" + deletedIdentityUUID);
},
async deleteSite(siteStoreKey) {
const deletedSiteList =
await sync.storageArea.getDeletedSiteList();
if (deletedSiteList.find(element => element === siteStoreKey)) return;
deletedSiteList.push(siteStoreKey);
await sync.storageArea.set({ deletedSiteList });
await sync.storageArea.area.remove(siteStoreKey);
},
async getDeletedIdentityList() {
const storedArray = await this.getStoredItem("deletedIdentityList");
return storedArray || [];
},
async getIdentities() {
const allSyncStorage = await this.get();
const identities = [];
for (const storageKey of Object.keys(allSyncStorage)) {
if (storageKey.includes("identity@@_")) {
identities.push(allSyncStorage[storageKey]);
}
}
return identities;
},
async getDeletedSiteList() {
const storedArray = await this.getStoredItem("deletedSiteList");
return (storedArray) ? storedArray : [];
},
async getAssignedSites() {
const allSyncStorage = await this.get();
const sites = {};
for (const storageKey of Object.keys(allSyncStorage)) {
if (storageKey.includes("siteContainerMap@@_")) {
sites[storageKey] = allSyncStorage[storageKey];
}
}
return sites;
},
async getStoredItem(objectKey) {
const outputObject = await this.get(objectKey);
if (outputObject && outputObject[objectKey])
return outputObject[objectKey];
return false;
},
async getAllInstanceInfo() {
const instanceList = {};
const allSyncInfo = await this.get();
for (const objectKey of Object.keys(allSyncInfo)) {
if (objectKey.includes("MACinstance")) {
instanceList[objectKey] = allSyncInfo[objectKey]; }
}
return instanceList;
},
getInstanceKey() {
return browser.runtime.getURL("")
.replace(/moz-extension:\/\//, "MACinstance:")
.replace(/\//, "");
},
async removeInstance(installUUID) {
if (SYNC_DEBUG) console.log("removing", installUUID);
await this.area.remove(installUUID);
return;
},
async removeThisInstanceFromSync() {
const installUUID = this.getInstanceKey();
await this.removeInstance(installUUID);
return;
},
async hasSyncStorage(){
const inSync = await this.get();
return !(Object.entries(inSync).length === 0);
},
async backup(options) {
// remove listeners to avoid an infinite loop!
await sync.checkForListenersMaybeRemove();
const identities = await updateSyncIdentities();
const siteAssignments = await updateSyncSiteAssignments();
await updateInstanceInfo(identities, siteAssignments);
if (options && options.uuid)
await this.deleteIdentity(options.uuid);
if (options && options.undeleteUUID)
await removeFromDeletedIdentityList(options.undeleteUUID);
if (options && options.siteStoreKey)
await this.deleteSite(options.siteStoreKey);
if (options && options.undeleteSiteStoreKey)
await removeFromDeletedSitesList(options.undeleteSiteStoreKey);
if (SYNC_DEBUG) console.log("Backed up!");
await sync.checkForListenersMaybeAdd();
async function updateSyncIdentities() {
const identities = await browser.contextualIdentities.query({});
for (const identity of identities) {
delete identity.colorCode;
delete identity.iconUrl;
identity.macAddonUUID = await identityState.lookupMACaddonUUID(identity.cookieStoreId);
if(identity.macAddonUUID) {
const storageKey = "identity@@_" + identity.macAddonUUID;
await sync.storageArea.set({ [storageKey]: identity });
}
}
//await sync.storageArea.set({ identities });
return identities;
}
async function updateSyncSiteAssignments() {
const assignedSites =
await assignManager.storageArea.getAssignedSites();
for (const siteKey of Object.keys(assignedSites)) {
await sync.storageArea.set({ [siteKey]: assignedSites[siteKey] });
}
return assignedSites;
}
async function updateInstanceInfo(identitiesInput, siteAssignmentsInput) {
const date = new Date();
const timestamp = date.getTime();
const installUUID = sync.storageArea.getInstanceKey();
if (SYNC_DEBUG) console.log("adding", installUUID);
const identities = [];
const siteAssignments = [];
for (const identity of identitiesInput) {
identities.push(identity.macAddonUUID);
}
for (const siteAssignmentKey of Object.keys(siteAssignmentsInput)) {
siteAssignments.push(siteAssignmentKey);
}
await sync.storageArea.set({ [installUUID]: { timestamp, identities, siteAssignments } });
}
async function removeFromDeletedIdentityList(identityUUID) {
const deletedIdentityList =
await sync.storageArea.getDeletedIdentityList();
const newDeletedIdentityList = deletedIdentityList
.filter(element => element !== identityUUID);
await sync.storageArea.set({ deletedIdentityList: newDeletedIdentityList });
}
async function removeFromDeletedSitesList(siteStoreKey) {
const deletedSiteList =
await sync.storageArea.getDeletedSiteList();
const newDeletedSiteList = deletedSiteList
.filter(element => element !== siteStoreKey);
await sync.storageArea.set({ deletedSiteList: newDeletedSiteList });
}
},
onChangedListener(changes, areaName) {
if (areaName === "sync") sync.errorHandledRunSync();
},
async addToDeletedList(changeInfo) {
const identity = changeInfo.contextualIdentity;
const deletedUUID =
await identityState.lookupMACaddonUUID(identity.cookieStoreId);
await identityState.storageArea.remove(identity.cookieStoreId);
sync.storageArea.backup({uuid: deletedUUID});
}
},
async init() {
const syncEnabled = await assignManager.storageArea.getSyncEnabled();
if (syncEnabled) {
// Add listener to sync storage and containers.
// Works for all installs that have any sync storage.
// Waits for sync storage change before kicking off the restore/backup
// initial sync must be kicked off by user.
this.checkForListenersMaybeAdd();
return;
}
this.checkForListenersMaybeRemove();
},
async errorHandledRunSync () {
await sync.runSync().catch( async (error)=> {
if (SYNC_DEBUG) console.error("Error from runSync", error);
await sync.checkForListenersMaybeAdd();
});
},
async checkForListenersMaybeAdd() {
const hasStorageListener =
await browser.storage.onChanged.hasListener(
sync.storageArea.onChangedListener
);
const hasCIListener = await sync.hasContextualIdentityListeners();
if (!hasCIListener) {
await sync.addContextualIdentityListeners();
}
if (!hasStorageListener) {
await browser.storage.onChanged.addListener(
sync.storageArea.onChangedListener);
}
},
async checkForListenersMaybeRemove() {
const hasStorageListener =
await browser.storage.onChanged.hasListener(
sync.storageArea.onChangedListener
);
const hasCIListener = await sync.hasContextualIdentityListeners();
if (hasCIListener) {
await sync.removeContextualIdentityListeners();
}
if (hasStorageListener) {
await browser.storage.onChanged.removeListener(
sync.storageArea.onChangedListener);
}
},
async runSync() {
if (SYNC_DEBUG) {
const syncInfo = await sync.storageArea.get();
const localInfo = await browser.storage.local.get();
const idents = await browser.contextualIdentities.query({});
console.log("Initial State:", {syncInfo, localInfo, idents});
}
await sync.checkForListenersMaybeRemove();
if (SYNC_DEBUG) console.log("runSync");
await identityState.storageArea.upgradeData();
await assignManager.storageArea.upgradeData();
const hasSyncStorage = await sync.storageArea.hasSyncStorage();
if (hasSyncStorage) await restore();
await sync.storageArea.backup();
await removeOldDeletedItems();
return;
},
async addContextualIdentityListeners() {
await browser.contextualIdentities.onCreated.addListener(sync.storageArea.backup);
await browser.contextualIdentities.onRemoved.addListener(sync.storageArea.addToDeletedList);
await browser.contextualIdentities.onUpdated.addListener(sync.storageArea.backup);
},
async removeContextualIdentityListeners() {
await browser.contextualIdentities.onCreated.removeListener(sync.storageArea.backup);
await browser.contextualIdentities.onRemoved.removeListener(sync.storageArea.addToDeletedList);
await browser.contextualIdentities.onUpdated.removeListener(sync.storageArea.backup);
},
async hasContextualIdentityListeners() {
return (
await browser.contextualIdentities.onCreated.hasListener(sync.storageArea.backup) &&
await browser.contextualIdentities.onRemoved.hasListener(sync.storageArea.addToDeletedList) &&
await browser.contextualIdentities.onUpdated.hasListener(sync.storageArea.backup)
);
},
async resetSync() {
const syncEnabled = await assignManager.storageArea.getSyncEnabled();
if (syncEnabled) {
this.errorHandledRunSync();
return;
}
await this.checkForListenersMaybeRemove();
await this.storageArea.removeThisInstanceFromSync();
}
};
// attaching to window for use in mocha tests
window.sync = sync;
sync.init();
async function restore() {
if (SYNC_DEBUG) console.log("restore");
await reconcileIdentities();
await reconcileSiteAssignments();
return;
}
/*
* Checks for the container name. If it exists, they are assumed to be the
* same container, and the color and icon are overwritten from sync, if
* different.
*/
async function reconcileIdentities(){
if (SYNC_DEBUG) console.log("reconcileIdentities");
// first delete any from the deleted list
const deletedIdentityList =
await sync.storageArea.getDeletedIdentityList();
// first remove any deleted identities
for (const deletedUUID of deletedIdentityList) {
const deletedCookieStoreId =
await identityState.lookupCookieStoreId(deletedUUID);
if (deletedCookieStoreId){
try{
await browser.contextualIdentities.remove(deletedCookieStoreId);
} catch (error) {
// if the identity we are deleting is not there, that's fine.
console.error("Error deleting contextualIdentity", deletedCookieStoreId);
continue;
}
}
}
const localIdentities = await browser.contextualIdentities.query({});
const syncIdentitiesRemoveDupes =
await sync.storageArea.getIdentities();
// find any local dupes created on sync storage and delete from sync storage
for (const localIdentity of localIdentities) {
const syncIdentitiesOfName = syncIdentitiesRemoveDupes
.filter(identity => identity.name === localIdentity.name);
if (syncIdentitiesOfName.length > 1) {
const identityMatchingContextId = syncIdentitiesOfName
.find(identity => identity.cookieStoreId === localIdentity.cookieStoreId);
if (identityMatchingContextId)
await sync.storageArea.removeIdentityKeyFromSync(identityMatchingContextId.macAddonUUID);
}
}
const syncIdentities =
await sync.storageArea.getIdentities();
// now compare all containers for matching names.
for (const syncIdentity of syncIdentities) {
if (syncIdentity.macAddonUUID){
const localMatch = localIdentities.find(
localIdentity => localIdentity.name === syncIdentity.name
);
if (!localMatch) {
// if there's no name match found, check on uuid,
const localCookieStoreID =
await identityState.lookupCookieStoreId(syncIdentity.macAddonUUID);
if (localCookieStoreID) {
await ifUUIDMatch(syncIdentity, localCookieStoreID);
continue;
}
await ifNoMatch(syncIdentity);
continue;
}
// Names match, so use the info from Sync
await updateIdentityWithSyncInfo(syncIdentity, localMatch);
continue;
}
// if no macAddonUUID, there is a problem with the sync info and it needs to be ignored.
}
await updateSiteAssignmentUUIDs();
async function updateSiteAssignmentUUIDs(){
const sites = assignManager.storageArea.getAssignedSites();
for (const siteKey of Object.keys(sites)) {
await assignManager.storageArea.set(siteKey, sites[siteKey]);
}
}
}
async function updateIdentityWithSyncInfo(syncIdentity, localMatch) {
// Sync is truth. if there is a match, compare data and update as needed
if (syncIdentity.color !== localMatch.color
|| syncIdentity.icon !== localMatch.icon) {
await browser.contextualIdentities.update(
localMatch.cookieStoreId, {
name: syncIdentity.name,
color: syncIdentity.color,
icon: syncIdentity.icon
});
if (SYNC_DEBUG) {
if (localMatch.color !== syncIdentity.color) {
console.log(localMatch.name, "Change color: ", syncIdentity.color);
}
if (localMatch.icon !== syncIdentity.icon) {
console.log(localMatch.name, "Change icon: ", syncIdentity.icon);
}
}
}
// Sync is truth. If all is the same, update the local uuid to match sync
if (localMatch.macAddonUUID !== syncIdentity.macAddonUUID) {
await identityState.updateUUID(
localMatch.cookieStoreId,
syncIdentity.macAddonUUID
);
}
// TODOkmw: update any site assignment UUIDs
}
async function ifUUIDMatch(syncIdentity, localCookieStoreID) {
// if there's an identical local uuid, it's the same container. Sync is truth
const identityInfo = {
name: syncIdentity.name,
color: syncIdentity.color,
icon: syncIdentity.icon
};
if (SYNC_DEBUG) {
try {
const getIdent =
await browser.contextualIdentities.get(localCookieStoreID);
if (getIdent.name !== identityInfo.name) {
console.log(getIdent.name, "Change name: ", identityInfo.name);
}
if (getIdent.color !== identityInfo.color) {
console.log(getIdent.name, "Change color: ", identityInfo.color);
}
if (getIdent.icon !== identityInfo.icon) {
console.log(getIdent.name, "Change icon: ", identityInfo.icon);
}
} catch (error) {
//if this fails, there is probably differing sync info.
console.error("Error getting info on CI", error);
}
}
try {
// update the local container with the sync data
await browser.contextualIdentities
.update(localCookieStoreID, identityInfo);
return;
} catch (error) {
// If this fails, sync info is off.
console.error("Error udpating CI", error);
}
}
async function ifNoMatch(syncIdentity){
// if no uuid match either, make new identity
if (SYNC_DEBUG) console.log("create new ident: ", syncIdentity.name);
const newIdentity =
await browser.contextualIdentities.create({
name: syncIdentity.name,
color: syncIdentity.color,
icon: syncIdentity.icon
});
await identityState.updateUUID(
newIdentity.cookieStoreId,
syncIdentity.macAddonUUID
);
return;
}
/*
* Checks for site previously assigned. If it exists, and has the same
* container assignment, the assignment is kept. If it exists, but has
* a different assignment, the user is prompted (not yet implemented).
* If it does not exist, it is created.
*/
async function reconcileSiteAssignments() {
if (SYNC_DEBUG) console.log("reconcileSiteAssignments");
const assignedSitesLocal =
await assignManager.storageArea.getAssignedSites();
const assignedSitesFromSync =
await sync.storageArea.getAssignedSites();
const deletedSiteList =
await sync.storageArea.getDeletedSiteList();
for(const siteStoreKey of deletedSiteList) {
if (Object.prototype.hasOwnProperty.call(assignedSitesLocal,siteStoreKey)) {
assignManager
.storageArea
.remove(siteStoreKey);
}
}
for(const urlKey of Object.keys(assignedSitesFromSync)) {
const assignedSite = assignedSitesFromSync[urlKey];
try{
if (assignedSite.identityMacAddonUUID) {
// Sync is truth.
// Not even looking it up. Just overwrite
if (SYNC_DEBUG){
const isInStorage = await assignManager.storageArea.getByUrlKey(urlKey);
if (!isInStorage)
console.log("new assignment ", assignedSite);
}
await setAssignmentWithUUID(assignedSite, urlKey);
continue;
}
} catch (error) {
// this is probably old or incorrect site info in Sync
// skip and move on.
}
}
}
const MILISECONDS_IN_THIRTY_DAYS = 2592000000;
async function removeOldDeletedItems() {
const instanceList = await sync.storageArea.getAllInstanceInfo();
const deletedSiteList = await sync.storageArea.getDeletedSiteList();
const deletedIdentityList = await sync.storageArea.getDeletedIdentityList();
for (const instanceKey of Object.keys(instanceList)) {
const date = new Date();
const currentTimestamp = date.getTime();
if (instanceList[instanceKey].timestamp < currentTimestamp - MILISECONDS_IN_THIRTY_DAYS) {
delete instanceList[instanceKey];
sync.storageArea.removeInstance(instanceKey);
continue;
}
}
for (const siteStoreKey of deletedSiteList) {
let hasMatch = false;
for (const instance of Object.values(instanceList)) {
const match = instance.siteAssignments.find(element => element === siteStoreKey);
if (!match) continue;
hasMatch = true;
}
if (!hasMatch) {
await sync.storageArea.backup({undeleteSiteStoreKey: siteStoreKey});
}
}
for (const identityUUID of deletedIdentityList) {
let hasMatch = false;
for (const instance of Object.values(instanceList)) {
const match = instance.identities.find(element => element === identityUUID);
if (!match) continue;
hasMatch = true;
}
if (!hasMatch) {
await sync.storageArea.backup({undeleteUUID: identityUUID});
}
}
}
async function setAssignmentWithUUID(assignedSite, urlKey) {
const uuid = assignedSite.identityMacAddonUUID;
const cookieStoreId = await identityState.lookupCookieStoreId(uuid);
if (cookieStoreId) {
// eslint-disable-next-line require-atomic-updates
assignedSite.userContextId = cookieStoreId
.replace(/^firefox-container-/, "");
await assignManager.storageArea.set(
urlKey,
assignedSite,
false,
false
);
return;
}
throw new Error (`No cookieStoreId found for: ${uuid}, ${urlKey}`);
}
+80
View File
@@ -0,0 +1,80 @@
async function load() {
const searchParams = new URL(window.location).searchParams;
const redirectUrl = searchParams.get("url");
const cookieStoreId = searchParams.get("cookieStoreId");
const currentCookieStoreId = searchParams.get("currentCookieStoreId");
const redirectUrlElement = document.getElementById("redirect-url");
redirectUrlElement.textContent = redirectUrl;
appendFavicon(redirectUrl, redirectUrlElement);
const container = await browser.contextualIdentities.get(cookieStoreId);
[...document.querySelectorAll(".container-name")].forEach((containerNameElement) => {
containerNameElement.textContent = container.name;
});
// If default container, button will default to normal HTML content
if (currentCookieStoreId) {
const currentContainer = await browser.contextualIdentities.get(currentCookieStoreId);
document.getElementById("current-container-name").textContent = currentContainer.name;
}
document.getElementById("deny").addEventListener("click", (e) => {
e.preventDefault();
denySubmit(redirectUrl);
});
document.getElementById("confirm").addEventListener("click", (e) => {
e.preventDefault();
confirmSubmit(redirectUrl, cookieStoreId);
});
}
function appendFavicon(pageUrl, redirectUrlElement) {
const origin = new URL(pageUrl).origin;
const favIconElement = Utils.createFavIconElement(`${origin}/favicon.ico`);
redirectUrlElement.prepend(favIconElement);
}
function confirmSubmit(redirectUrl, cookieStoreId) {
const neverAsk = document.getElementById("never-ask").checked;
// Sending neverAsk message to background to store for next time we see this process
if (neverAsk) {
browser.runtime.sendMessage({
method: "neverAsk",
neverAsk: true,
pageUrl: redirectUrl
});
}
openInContainer(redirectUrl, cookieStoreId);
}
function getCurrentTab() {
return browser.tabs.query({
active: true,
windowId: browser.windows.WINDOW_ID_CURRENT
});
}
async function denySubmit(redirectUrl) {
const tab = await getCurrentTab();
await browser.runtime.sendMessage({
method: "exemptContainerAssignment",
tabId: tab[0].id,
pageUrl: redirectUrl
});
document.location.replace(redirectUrl);
}
load();
async function openInContainer(redirectUrl, cookieStoreId) {
const tab = await getCurrentTab();
await browser.tabs.create({
index: tab[0].index + 1,
cookieStoreId,
url: redirectUrl
});
if (tab.length > 0) {
browser.tabs.remove(tab[0].id);
}
}
+46
View File
@@ -0,0 +1,46 @@
async function delayAnimation(delay = 350) {
return new Promise((resolve) => {
setTimeout(resolve, delay);
});
}
async function doAnimation(element, property, value) {
return new Promise((resolve) => {
const handler = () => {
resolve();
element.removeEventListener("transitionend", handler);
};
element.addEventListener("transitionend", handler);
window.requestAnimationFrame(() => {
element.style[property] = value;
});
});
}
async function addMessage(message) {
const divElement = document.createElement("div");
divElement.classList.add("container-notification");
// 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");
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);
await delayAnimation(100);
await doAnimation(divElement, "transform", "translateY(0)");
await delayAnimation(3000);
await doAnimation(divElement, "transform", "translateY(-100%)");
divElement.remove();
}
browser.runtime.onMessage.addListener((message) => {
addMessage(message);
});
+90
View File
@@ -0,0 +1,90 @@
const NUMBER_OF_KEYBOARD_SHORTCUTS = 10;
async function requestPermissions() {
const checkbox = document.querySelector("#bookmarksPermissions");
if (checkbox.checked) {
const granted = await browser.permissions.request({permissions: ["bookmarks"]});
if (!granted) {
checkbox.checked = false;
return;
}
} else {
await browser.permissions.remove({permissions: ["bookmarks"]});
}
browser.runtime.sendMessage({ method: "resetBookmarksContext" });
}
async function enableDisableSync() {
const checkbox = document.querySelector("#syncCheck");
if (checkbox.checked) {
await browser.storage.local.set({syncEnabled: true});
} else {
await browser.storage.local.set({syncEnabled: false});
}
browser.runtime.sendMessage({ method: "resetSync" });
}
async function setupOptions() {
const hasPermission = await browser.permissions.contains({permissions: ["bookmarks"]});
const { syncEnabled } = await browser.storage.local.get("syncEnabled");
if (hasPermission) {
document.querySelector("#bookmarksPermissions").checked = true;
}
if (syncEnabled) {
document.querySelector("#syncCheck").checked = true;
} else {
document.querySelector("#syncCheck").checked = false;
}
setupContainerShortcutSelects();
}
async function setupContainerShortcutSelects () {
const keyboardShortcut = await browser.runtime.sendMessage({method: "getShortcuts"});
const identities = await browser.contextualIdentities.query({});
const fragment = document.createDocumentFragment();
const noneOption = document.createElement("option");
noneOption.value = "none";
noneOption.id = "none";
noneOption.textContent = "None";
fragment.append(noneOption);
for (const identity of identities) {
const option = document.createElement("option");
option.value = identity.cookieStoreId;
option.id = identity.cookieStoreId;
option.textContent = identity.name;
fragment.append(option);
}
for (let i=0; i < NUMBER_OF_KEYBOARD_SHORTCUTS; i++) {
const shortcutKey = "open_container_"+i;
const shortcutSelect = document.getElementById(shortcutKey);
shortcutSelect.appendChild(fragment.cloneNode(true));
if (keyboardShortcut && keyboardShortcut[shortcutKey]) {
const cookieStoreId = keyboardShortcut[shortcutKey];
shortcutSelect.querySelector("#" + cookieStoreId).selected = true;
}
}
}
function storeShortcutChoice (event) {
browser.runtime.sendMessage({
method: "setShortcut",
shortcut: event.target.id,
cookieStoreId: event.target.value
});
}
function resetOnboarding() {
browser.storage.local.set({"onboarding-stage": 0});
}
document.addEventListener("DOMContentLoaded", setupOptions);
document.querySelector("#bookmarksPermissions").addEventListener( "change", requestPermissions);
document.querySelector("#syncCheck").addEventListener( "change", enableDisableSync);
document.querySelector("button").addEventListener("click", resetOnboarding);
for (let i=0; i < NUMBER_OF_KEYBOARD_SHORTCUTS; i++) {
document.querySelector("#open_container_"+i)
.addEventListener("change", storeShortcutChoice);
}
+35
View File
@@ -0,0 +1,35 @@
async function init() {
const fragment = document.createDocumentFragment();
const identities = await browser.contextualIdentities.query({});
identities.forEach(identity => {
const tr = document.createElement("tr");
tr.classList.add("menu-item", "hover-highlight");
const td = document.createElement("td");
td.innerHTML = Utils.escaped`
<div class="menu-icon">
<div class="usercontext-icon"
data-identity-icon="${identity.icon}"
data-identity-color="${identity.color}">
</div>
</div>
<span class="menu-text">${identity.name}</span>`;
tr.appendChild(td);
fragment.appendChild(tr);
Utils.addEnterHandler(tr, async () => {
Utils.alwaysOpenInContainer(identity);
window.close();
});
});
const list = document.querySelector("#picker-identities-list");
list.innerHTML = "";
list.appendChild(fragment);
}
init();
+1416
View File
File diff suppressed because it is too large Load Diff
+128
View File
@@ -0,0 +1,128 @@
const DEFAULT_FAVICON = "/img/blank-favicon.svg";
// TODO use export here instead of globals
const Utils = {
createFavIconElement(url) {
const imageElement = document.createElement("img");
imageElement.classList.add("icon", "offpage", "menu-icon");
imageElement.src = url;
const loadListener = (e) => {
e.target.classList.remove("offpage");
e.target.removeEventListener("load", loadListener);
e.target.removeEventListener("error", errorListener);
};
const errorListener = (e) => {
e.target.src = DEFAULT_FAVICON;
};
imageElement.addEventListener("error", errorListener);
imageElement.addEventListener("load", loadListener);
return imageElement;
},
/**
* Escapes any occurances of &, ", <, > or / with XML entities.
*
* @param {string} str
* The string to escape.
* @return {string} The escaped string.
*/
escapeXML(str) {
const replacements = { "&": "&amp;", "\"": "&quot;", "'": "&apos;", "<": "&lt;", ">": "&gt;", "/": "&#x2F;" };
return String(str).replace(/[&"'<>/]/g, m => replacements[m]);
},
/**
* A tagged template function which escapes any XML metacharacters in
* interpolated values.
*
* @param {Array<string>} strings
* An array of literal strings extracted from the templates.
* @param {Array} values
* An array of interpolated values extracted from the template.
* @returns {string}
* The result of the escaped values interpolated with the literal
* strings.
*/
escaped(strings, ...values) {
const result = [];
for (const [i, string] of strings.entries()) {
result.push(string);
if (i < values.length)
result.push(this.escapeXML(values[i]));
}
return result.join("");
},
async currentTab() {
const activeTabs = await browser.tabs.query({ active: true, windowId: browser.windows.WINDOW_ID_CURRENT });
if (activeTabs.length > 0) {
return activeTabs[0];
}
return false;
},
addEnterHandler(element, handler) {
element.addEventListener("click", (e) => {
handler(e);
});
element.addEventListener("keydown", (e) => {
if (e.keyCode === 13) {
e.preventDefault();
handler(e);
}
});
},
userContextId(cookieStoreId = "") {
const userContextId = cookieStoreId.replace("firefox-container-", "");
return (userContextId !== cookieStoreId) ? Number(userContextId) : false;
},
setOrRemoveAssignment(tabId, url, userContextId, value) {
return browser.runtime.sendMessage({
method: "setOrRemoveAssignment",
tabId,
url,
userContextId,
value
});
},
async reloadInContainer(url, currentUserContextId, newUserContextId, tabIndex, active) {
return await browser.runtime.sendMessage({
method: "reloadInContainer",
url,
currentUserContextId,
newUserContextId,
tabIndex,
active
});
},
async alwaysOpenInContainer(identity) {
const currentTab = await this.currentTab();
const assignedUserContextId = this.userContextId(identity.cookieStoreId);
if (currentTab.cookieStoreId !== identity.cookieStoreId) {
return await browser.runtime.sendMessage({
method: "assignAndReloadInContainer",
url: currentTab.url,
currentUserContextId: false,
newUserContextId: assignedUserContextId,
tabIndex: currentTab.index +1,
active:currentTab.active
});
}
await Utils.setOrRemoveAssignment(
currentTab.id,
currentTab.url,
assignedUserContextId,
false
);
}
};
window.Utils = Utils;
+149
View File
@@ -0,0 +1,149 @@
{
"manifest_version": 2,
"name": "Firefox Multi-Account Containers",
"version": "7.0.0",
"incognito": "not_allowed",
"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"
},
"applications": {
"gecko": {
"id": "@testpilot-containers",
"strict_min_version": "67.0"
}
},
"homepage_url": "https://github.com/mozilla/multi-account-containers#readme",
"permissions": [
"<all_urls>",
"activeTab",
"cookies",
"contextMenus",
"contextualIdentities",
"history",
"idle",
"management",
"storage",
"tabs",
"webRequestBlocking",
"webRequest"
],
"optional_permissions": [
"bookmarks"
],
"commands": {
"_execute_browser_action": {
"suggested_key": {
"default": "Ctrl+Period",
"mac": "MacCtrl+Period"
},
"description": "Open containers panel"
},
"open_container_0": {
"suggested_key": {
"default": "Ctrl+Shift+1"
},
"description": "Container Shortcut 1"
},
"open_container_1": {
"suggested_key": {
"default": "Ctrl+Shift+2"
},
"description": "Container Shortcut 2"
},
"open_container_2": {
"suggested_key": {
"default": "Ctrl+Shift+3"
},
"description": "Container Shortcut 3"
},
"open_container_3": {
"suggested_key": {
"default": "Ctrl+Shift+4"
},
"description": "Container Shortcut 4"
},
"open_container_4": {
"suggested_key": {
"default": "Ctrl+Shift+5"
},
"description": "Container Shortcut 5"
},
"open_container_5": {
"suggested_key": {
"default": "Ctrl+Shift+6"
},
"description": "Container Shortcut 6"
},
"open_container_6": {
"suggested_key": {
"default": "Ctrl+Shift+7"
},
"description": "Container Shortcut 7"
},
"open_container_7": {
"suggested_key": {
"default": "Ctrl+Shift+8"
},
"description": "Container Shortcut 8"
},
"open_container_8": {
"suggested_key": {
"default": "Ctrl+Shift+9"
},
"description": "Container Shortcut 9"
},
"open_container_9": {
"suggested_key": {
"default": "Ctrl+Shift+0"
},
"description": "Container Shortcut 10"
}
},
"browser_action": {
"browser_style": true,
"default_icon": "img/multiaccountcontainer-16.svg",
"default_title": "Multi-Account Containers",
"default_popup": "popup.html",
"theme_icons": [
{
"light": "img/multiaccountcontainer-16-dark.svg",
"dark": "img/multiaccountcontainer-16.svg",
"size": 32
}
]
},
"page_action": {
"browser_style": true,
"default_icon": "img/container-openin-16.svg",
"default_title": "Always open this in a Container",
"default_popup": "pageActionPopup.html",
"pinned": false,
"show_matches": ["*://*/*"]
},
"background": {
"page": "js/background/index.html"
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"js/content-script.js"
],
"css": [
"css/content.css"
],
"run_at": "document_start"
}
],
"web_accessible_resources": [
"/img/container-site-d-24.png"
],
"options_ui": {
"page": "options.html",
"browser_style": true
}
}
+81
View File
@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/options.css">
</head>
<body>
<form>
<h3>Optional Permissions:</h3>
<label>
<input type="checkbox" id="bookmarksPermissions">
Enable Bookmark Menus
</label>
<p><em>This setting allows you to open a bookmark or folder of bookmarks in a container.</em></p>
<h3>Firefox Accounts Sync:</h3>
<label>
<input type="checkbox" id="syncCheck">
Enable Sync
</label>
<p><em>This setting allows you to sync your containers and site assignments across devices.</em></p>
<h3>Keyboard Shortcuts:</h3>
<p><em>Edit which container is opened when using the numbered shortcuts.</em></p>
<p><label>
Container to open with Keyboard Shortcut 1
<select id="open_container_0">
</select>
</label></p>
<p><label>
Container to open with Keyboard Shortcut 2
<select id="open_container_1">
</select>
</label></p>
<p><label>
Container to open with Keyboard Shortcut 3
<select id="open_container_2">
</select>
</label></p>
<p><label>
Container to open with Keyboard Shortcut 4
<select id="open_container_3">
</select>
</label></p>
<p><label>
Container to open with Keyboard Shortcut 5
<select id="open_container_4">
</select>
</label></p>
<p><label>
Container to open with Keyboard Shortcut 6
<select id="open_container_5">
</select>
</label></p>
<p><label>
Container to open with Keyboard Shortcut 7
<select id="open_container_6">
</select>
</label></p>
<p><label>
Container to open with Keyboard Shortcut 8
<select id="open_container_7">
</select>
</label></p>
<p><label>
Container to open with Keyboard Shortcut 9
<select id="open_container_8">
</select>
</label></p>
<p><label>
Container to open with Keyboard Shortcut 10
<select id="open_container_9">
</select>
</label></p>
<h3>Onboarding:</h3>
<button>Reset Onboarding Panels</button>
<p><em>Toggle this to see the onboarding panels again.</em></p>
</form>
<script src="js/options.js"></script>
</body>
</html>
+34
View File
@@ -0,0 +1,34 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Multi-Account Containers</title>
<link rel="stylesheet" type="text/css" href="css/popup.css">
</head>
<body>
<div class="page-action-container-picker" id="container-picker-panel">
<h3 class="title">
Always Open this Site in...
</h3>
<hr>
<div class="scrollable identities-list">
<table class="menu" id="picker-identities-list">
<tr class="menu-item hover-highlight">
<td>
<div class="menu-icon">
<div class="usercontext-icon"
data-identity-icon="pet"
data-identity-color="blue">
</div>
</div>
<span class="menu-text">Default</span>
</td>
</tr>
</table>
</div>
</div>
<script src="js/utils.js"></script>
<script src="js/pageAction.js"></script>
</body>
</html>
+361
View File
@@ -0,0 +1,361 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Multi-Account Containers</title>
<link rel="stylesheet" href="css/popup.css">
</head>
<body>
<div class="hide panel onboarding onboarding-panel-1">
<img class="onboarding-img" alt="Container Tabs Overview" src="/img/onboarding-1.png" />
<h3 class="onboarding-title">A better way to manage all the things you do online</h3>
<p>
Use containers to organize tasks, manage accounts, and keep your focus where you want it.
</p>
<a href="#" class="onboarding-button onboarding-start-button" tabindex="0">Get Started</a>
</div>
<div class="hide panel onboarding security-onboarding-panel-1">
<img class="onboarding-img" alt="Container Tabs Overview" src="/img/onboarding-1.png" />
<h3 class="onboarding-title">A simple and secure way to manage your online life</h3>
<p>
Use containers to organize tasks, manage accounts, and store sensitive data.
</p>
<a href="#" class="onboarding-button onboarding-start-button" tabindex="0">Get Started</a>
</div>
<div class="panel onboarding onboarding-panel-2 hide">
<img class="onboarding-img" alt="How Containers Work" src="/img/onboarding-2.png" />
<h3 class="onboarding-title">Put containers to work for you.</h3>
<p>Features like color-coding and separate container tabs help you find things easily, focus your attention, and minimize distractions.</p>
<a href="#" class="onboarding-button onboarding-next-button" tabindex="0">Next</a>
</div>
<div class="panel onboarding security-onboarding-panel-2 hide">
<img class="onboarding-img" alt="How Containers Work" src="/img/onboarding-2.png" />
<h3 class="onboarding-title">Put containers to work for you.</h3>
<p>Color-coding helps you categorize your online life, find things easily, and minimize distractions.</p>
<a href="#" class="onboarding-button onboarding-next-button" tabindex="0">Next</a>
</div>
<div class="panel onboarding onboarding-panel-3 hide">
<img class="onboarding-img" alt="How Containers Work" src="/img/onboarding-3.png" />
<h3 class="onboarding-title">A place for everything, and everything in its place.</h3>
<p>Start with the containers we've created, or create your own.</p>
<a href="#" class="onboarding-button onboarding-almost-done-button" tabindex="0">Next</a>
</div>
<div class="panel onboarding security-onboarding-panel-3 hide">
<img class="onboarding-img" alt="How Containers Work" src="/img/onboarding-3-security.png" />
<h3 class="onboarding-title">Set boundaries for your browsing.</h3>
<p>Cookies are stored within a container, so you can segment sensitive data and browsing history to stay organized and to limit the impact of online trackers.</p>
<a href="#" class="onboarding-button onboarding-almost-done-button" tabindex="0">Next</a>
</div>
<div class="panel onboarding onboarding-panel-4 hide" id="onboarding-panel-4">
<img class="onboarding-img" alt="How to assign sites to containers" src="/img/onboarding-4.png" />
<h3 class="onboarding-title">Always open sites in the containers you want.</h3>
<p>Right-click inside a container tab to assign the site to always open in the container.</p>
<a href="#" id="onboarding-done-button" class="onboarding-button" tabindex="0">Next</a>
</div>
<div class="panel onboarding onboarding-panel-5 hide" id="onboarding-panel-5">
<img class="onboarding-img" alt="Long-press the New Tab button to create a new container tab." src="/img/onboarding-3.png" />
<h3 class="onboarding-title">Container tabs when you need them.</h3>
<p>Long-press the New Tab button to create a new container tab.</p>
<a href="#" id="onboarding-longpress-button" class="onboarding-button" tabindex="0">Next</a>
</div>
<div class="panel onboarding onboarding-panel-6 hide" id="onboarding-panel-6">
<img class="onboarding-img" alt="Syncing Containers is now Available!" src="/img/Sync.svg" />
<h3 class="onboarding-title">Syncing Containers is now Available!</h3>
<p>Turn on Sync to share container and site assignments with any computer connected to your Firefox Account.</p>
<div class="half-button-wrapper">
<a herf="#" id="no-sync" class="half-onboarding-button grey-button" tabindex="0">Not Now</a>
<a href="#" id="start-sync-button" class="half-onboarding-button" tabindex="0">Start Syncing</a>
</div>
</div>
<div class="panel onboarding onboarding-panel-7 hide" id="onboarding-panel-7">
<img class="onboarding-img" alt="Firefox Account is required to sync" src="/img/Account.svg" />
<h3 class="onboarding-title">Firefox Account is required to sync.</h3>
<p>Click Sign In to confirm that your Firefox Account is active.</p>
<div class="half-button-wrapper">
<a herf="#" id="no-sign-in" class="half-onboarding-button grey-button" tabindex="0">Not Now</a>
<a href="#" id="sign-in" class="half-onboarding-button" tabindex="0">Sign In</a>
</div>
</div>
<div class="panel achievement-panel hide" id="achievement-panel">
<img class="onboarding-img" alt="You achieved a Containers milestone!" src="/img/onboarding-3.png" />
<h3 class="onboarding-title">100 tabs!</h3>
<p>You've opened 100 Container tabs.</p>
<p>If you enjoy Containers, help us spread the word!</p>
<p class="share-ctas">
<a class="cta-link" href="https://mzl.la/2gJtIZ4" id="achievement-rate-button" target="_blank">
<span class="cta amo-rate-cta">
<img src="/img/amo-icon.svg" class="cta-icon" alt="addons.mozilla.org Icon">
Rate
</span>
</a>
<a class="cta-link" href="https://bit.ly/fb-share-mac-addon" target="_blank">
<span class="cta fb-share-cta">
<img src="/img/webicon-facebook.svg" class="cta-icon" alt="Facebook Icon">
Share
</span>
</a>
<a class="cta-link" href="http://bit.ly/tweet-100-tabs-mac-addon" target="_blank">
<span class="cta tweet-cta">
<img src="/img/webicon-twitter.svg" class="cta-icon" alt="Twitter Icon">
Tweet
</span>
</a>
</p>
<a href="#" id="achievement-done-button" class="onboarding-button">Done</a>
</div>
<div class="panel menu-panel container-panel hide" id="container-panel">
<h3 class="title">
Multi-Account Containers
</h3>
<a href="#" class="info-icon" id="info-icon" tabindex="10">
<img alt="info" src="/img/info-thin-16.svg" / >
</a>
<hr>
<table class="menu">
<tr class="menu-item hover-highlight" id="open-new-tab-in" tabindex="0">
<td>
<img class="menu-icon" alt="Open in New Tab" src="/img/tab-new-16.svg" />
<span class="menu-text">Open New Tab in...</span>
<span class="menu-arrow">
<img alt="Container Info" src="/img/arrow-icon-right.svg" />
</span>
</td>
</tr>
<tr class="menu-item hover-highlight" id="reopen-site-in" tabindex="0">
<td>
<img class="menu-icon" alt="Open in New Tab" src="/img/refresh-16.svg" />
<span class="menu-text">Reopen This Site in...</span>
<span class="menu-arrow">
<img alt="Container Info" src="/img/arrow-icon-right.svg" />
</span>
</td>
</tr>
</table>
<hr>
<table class="menu">
<tr class="menu-item hover-highlight" id="sort-containers-link" tabindex="0">
<td>
<img class="menu-icon" alt="Open in New Tab" src="/img/sort-16_1.svg" />
<span class="menu-text">Sort Tabs by Container</span>
<span class="menu-arrow">
</span>
</td>
</tr>
<tr class="menu-item hover-highlight" id="always-open-in" tabindex="0">
<td>
<img class="menu-icon" alt="Open in New Tab" src="/img/container-openin-16.svg" />
<span class="menu-text">Always Open This Site in...</span>
<span class="menu-arrow">
<img alt="Container Info" src="/img/arrow-icon-right.svg" />
</span>
</td>
</tr>
</table>
<hr>
<div class="sub-header">
Containers
</div>
<div class="scrollable identities-list">
<table class="menu" id="identities-list">
<tr class="menu-item hover-highlight">
<td>
<div class="menu-icon">
<div class="usercontext-icon"
data-identity-icon="pet"
data-identity-color="blue">
</div>
</div>
<span class="menu-text">Default</span>
<span class="menu-right-float">
<span class="container-count">22</span>
<span class="menu-arrow">
<img alt="Container Info" src="/img/arrow-icon-right.svg" />
</span>
</span>
</td>
</tr>
</table>
</div>
<div class="bottom-btn" id="manage-containers-link" tabindex="0">
Manage Containers
</div>
</div>
<div class="hide panel menu-panel container-info-panel" id="container-info-panel" tabindex="-1">
<h3 class="title" id="container-info-title">
Personal
</h3>
<button class="btn-return arrow-left" id="close-container-info-panel" tabindex="0"></button>
<hr>
<table class="menu">
<tr class="menu-item hover-highlight" id="open-new-tab-in-info" tabindex="0">
<td>
<img class="menu-icon" alt="Open in New Tab" src="/img/tab-new-16.svg" />
<span class="menu-text">Open New Tab in this Container</span>
<span class="menu-arrow">
</span>
</td>
</tr>
<tr class="menu-item hover-highlight" id="hideorshow-container" tabindex="0">
<td>
<img id="container-info-hideorshow-icon" class="menu-icon" alt="Hide This Container" src="img/password-hide.svg" />
<span id="container-info-hideorshow-label" class="menu-text">Hide This Container</span>
<span class="menu-arrow">
</span>
</td>
</tr>
<tr class="menu-item hover-highlight" id="move-to-new-window" tabindex="0">
<td>
<img class="menu-icon" alt="Move Tabs to a New Window" src="/img/movetowindow-16.svg" />
<span class="menu-text">Move Tabs to a New Window</span>
<span class="menu-arrow">
</span>
</td>
</tr>
<tr class="menu-item hover-highlight hover-highlight" id="always-open" tabindex="0">
<td>
<img class="menu-icon" alt="Always Open Site in Container" src="/img/container-openin-16.svg" />
<span class="menu-text" id="always-open-in-info-panel">Always Open Site in Container</span>
<span class="menu-arrow">
</span>
</td>
</tr>
</table>
<hr>
<div class="sub-header">
Open Tabs
</div>
<div class="scrollable">
<table class="menu" id="container-info-table">
<tr class="menu-item hover-highlight" tabindex="0">
<td>
<div class="favicon"><img class="menu-icon" src="https://www.mozilla.org/favicon.ico" /></div>
<span class="menu-text truncate-text">www.mozillllllllllllllllllllllllllllllllllllla.org</span>
<img class="trash-button" src="/img/container-close-tab.svg" />
</td>
</tr>
</table>
</div>
<div class="bottom-btn" id="manage-container-link">
Manage This Container
</div>
</div>
<div class="panel menu-panel container-picker-panel hide" id="container-picker-panel">
<h3 class="title" id="picker-title">
Multi-Account Containers
</h3>
<button class="btn-return arrow-left" id="close-container-picker-panel" tabindex="0"></button>
<hr>
<div id="new-container-div"></div>
<div class="scrollable identities-list">
<table class="menu" id="picker-identities-list">
<tr class="menu-item hover-highlight">
<td>
<div class="menu-icon">
<div class="usercontext-icon"
data-identity-icon="pet"
data-identity-color="blue">
</div>
</div>
<span class="menu-text">Default</span>
</td>
</tr>
</table>
</div>
</div>
<div class="panel menu-panel edit-container-panel hide" id="edit-container-panel">
<h3 class="title" id="container-edit-title">
Default
</h3>
<button class="btn-return arrow-left" id="close-container-edit-panel"></button>
<hr>
<div class="scrollable edit-form">
<form id="edit-container-panel-form">
<input type="hidden" name="container-id" id="edit-container-panel-usercontext-input" />
<fieldset>
<legend class="form-header">Name</legend>
<input type="text" name="container-name" id="edit-container-panel-name-input" class="edit-container-panel-name-input" maxlength="25"/>
</fieldset>
<fieldset id="edit-container-panel-choose-color" class="radio-choice">
<legend class="form-header">Color</legend>
</fieldset>
<fieldset id="edit-container-panel-choose-icon" class="radio-choice">
<legend class="form-header">Icon</legend>
</fieldset>
</form>
<div id="edit-container-options">
<div class="options-header">Options</div>
<div class="container-options">
<input type="checkbox" class="site-isolation" id="site-isolation" name="site-isolation">
<label for="site-isolation" class="options-label">Limit to Designated Sites</label>
</div>
<div class="container-options options-label manage-assigned-sites-list" id="manage-assigned-sites-list">Manage Site List...
</div>
</div>
</div>
<div class="delete-container">
<button class="delete-btn" id="delete-container-button">Delete This Container</button>
</div>
<div class="panel-footer">
<a href="#" class="button expanded secondary footer-button cancel-button" id="create-container-cancel-link">Cancel</a>
<a href="#" class="button expanded primary footer-button" id="create-container-ok-link">OK</a>
</div>
</div>
<div class="panel menu-panel edit-container-assignments hide" id="edit-container-assignments">
<h3 class="title" id="edit-assignments-title">
Default
</h3>
<button class="btn-return arrow-left" id="close-container-assignment-panel"></button>
<hr>
<div class="scrollable edit-sites-assigned">
<div class="sub-header">Sites assigned to this container</div>
<table class="menu scrollable" id="edit-sites-assigned">
<tr class="menu-item hover-highlight" tabindex="0">
<td>
<div class="favicon"><img class="menu-icon" src="https://www.mozilla.org/favicon.ico" /></div>
<span class="menu-text truncate-text">www.mozillllllllllllllllllllllllllllla.org</span>
<img class="trash-button" src="/img/container-delete.svg" />
</td>
</tr>
</table>
</div>
</div>
<div class="hide panel delete-container-panel" id="delete-container-panel">
<h3 class="title" id="container-delete-title">
Default
</h3>
<button class="btn-return arrow-left" id="close-container-delete-panel"></button>
<hr>
<div class="panel-content delete-container-confirm">
<h4 class="delete-container-confirm-title">Remove This Container</h4>
<p class="delete-warning" id="delete-container-tab-warning"></p>
<p class="delete-warning">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>
<a href="#" class="button expanded primary footer-button" id="delete-container-ok-link">OK</a>
</div>
</div>
<script src="js/utils.js"></script>
<script src="js/popup.js"></script>
</body>
</html>
+12
View File
@@ -0,0 +1,12 @@
module.exports = {
env: {
"node": true,
"mocha": true
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"no-restricted-globals": ["error", "browser"]
}
}
+111
View File
@@ -0,0 +1,111 @@
if (!process.listenerCount("unhandledRejection")) {
// eslint-disable-next-line no-console
process.on("unhandledRejection", r => console.log(r));
}
const path = require("path");
const chai = require("chai");
const sinonChai = require("sinon-chai");
const crypto = require("crypto");
const sinon = require("sinon");
const expect = chai.expect;
chai.should();
chai.use(sinonChai);
const nextTick = () => {
return new Promise(resolve => {
setTimeout(() => {
process.nextTick(resolve);
});
});
};
const webExtensionsJSDOM = require("webextensions-jsdom");
const manifestPath = path.resolve(path.join(__dirname, "../src/manifest.json"));
const buildDom = async ({background = {}, popup = {}}) => {
background = {
...background,
jsdom: {
...background.jsom,
beforeParse(window) {
window.browser.permissions.getAll.resolves({permissions: ["bookmarks"]});
window.crypto = {
getRandomValues: arr => crypto.randomBytes(arr.length),
};
}
}
};
popup = {
...popup,
jsdom: {
...popup.jsdom,
pretendToBeVisual: true
}
};
const webExtension = await webExtensionsJSDOM.fromManifest(manifestPath, {
apiFake: true,
wiring: true,
sinon: global.sinon,
background,
popup
});
webExtension.browser = webExtension.background.browser;
return webExtension;
};
const buildBackgroundDom = background => {
return buildDom({
background,
popup: false
});
};
const buildPopupDom = popup => {
return buildDom({
popup,
background: false
});
};
const initializeWithTab = async (details = {
cookieStoreId: "firefox-default"
}) => {
let tab;
const webExtension = await buildDom({
background: {
async afterBuild(background) {
tab = await background.browser.tabs._create(details);
}
},
popup: {
jsdom: {
beforeParse(window) {
window.browser.storage.local.set({
"browserActionBadgesClicked": [],
"onboarding-stage": 7,
"achievements": [],
"syncEnabled": true
});
window.browser.storage.local.set.resetHistory();
window.browser.storage.sync.clear();
}
}
}
});
webExtension.tab = tab;
return webExtension;
};
module.exports = {
buildDom,
buildBackgroundDom,
buildPopupDom,
initializeWithTab,
sinon,
expect,
nextTick,
};
+87
View File
@@ -0,0 +1,87 @@
const {initializeWithTab} = require("../common");
describe("Assignment Reopen Feature", function () {
const url = "http://example.com";
beforeEach(async function () {
this.webExt = await initializeWithTab({
cookieStoreId: "firefox-default",
url
});
});
afterEach(function () {
this.webExt.destroy();
});
describe("set to 'Always open in' firefox-container-4", function () {
beforeEach(async function () {
// popup click to set assignment for activeTab.url
await this.webExt.popup.helper.clickElementById("always-open-in");
await this.webExt.popup.helper.clickElementByQuerySelectorAll("#picker-identities-list > .menu-item");
});
it("should open the page in the assigned container", async function () {
// should have created a new tab with the confirm page
this.webExt.background.browser.tabs.create.should.have.been.calledWithMatch({
active: true,
cookieStoreId: "firefox-container-4",
index: 1,
openerTabId: null,
url: "http://example.com"
});
});
});
});
describe("Assignment Comfirm Page Feature", function () {
const url = "http://example.com";
beforeEach(async function () {
this.webExt = await initializeWithTab({
cookieStoreId: "firefox-container-4",
url
});
});
afterEach(function () {
this.webExt.destroy();
});
describe("open new Tab with the assigned URL in the default container", function () {
let newTab;
beforeEach(async function () {
await this.webExt.popup.helper.clickElementById("always-open-in");
await this.webExt.popup.helper.clickElementByQuerySelectorAll("#picker-identities-list > .menu-item");
// new Tab opening activeTab.url in default container
newTab = await this.webExt.background.browser.tabs._create({
cookieStoreId: "firefox-default",
url
}, {
options: {
webRequestError: true // because request is canceled due to reopening
}
});
});
it("should open the confirm page", async function () {
// should have created a new tab with the confirm page
this.webExt.background.browser.tabs.create.should.have.been.calledWithMatch({
url: "moz-extension://fake/confirm-page.html?" +
`url=${encodeURIComponent(url)}` +
`&cookieStoreId=${this.webExt.tab.cookieStoreId}`,
cookieStoreId: undefined,
openerTabId: null,
index: 2,
active: true
});
});
it("should remove the new Tab that got opened in the default container", function () {
this.webExt.background.browser.tabs.remove.should.have.been.calledWith(newTab.id);
});
});
});
+36
View File
@@ -0,0 +1,36 @@
const {initializeWithTab} = require("../common");
describe("Containers Management", function () {
beforeEach(async function () {
this.webExt = await initializeWithTab();
});
afterEach(function () {
this.webExt.destroy();
});
describe("creating a new container", function () {
beforeEach(async function () {
await this.webExt.popup.helper.clickElementById("manage-containers-link");
await this.webExt.popup.helper.clickElementById("new-container");
await this.webExt.popup.helper.clickElementById("create-container-ok-link");
});
it("should create it in the browser as well", function () {
this.webExt.background.browser.contextualIdentities.create.should.have.been.calledOnce;
});
describe("removing it afterwards", function () {
beforeEach(async function () {
await this.webExt.popup.helper.clickElementById("manage-containers-link");
await this.webExt.popup.helper.clickElementByQuerySelectorAll("#picker-identities-list > .menu-item", "last");
await this.webExt.popup.helper.clickElementById("delete-container-button");
await this.webExt.popup.helper.clickElementById("delete-container-ok-link");
});
it("should remove it in the browser as well", function () {
this.webExt.background.browser.contextualIdentities.remove.should.have.been.calledOnce;
});
});
});
});
@@ -0,0 +1,64 @@
const {expect, initializeWithTab} = require("../common");
describe("External Webextensions", function () {
const url = "http://example.com";
beforeEach(async function () {
this.webExt = await initializeWithTab({
cookieStoreId: "firefox-container-4",
url
});
await this.webExt.popup.helper.clickElementById("always-open-in");
await this.webExt.popup.helper.clickElementByQuerySelectorAll("#picker-identities-list > .menu-item", "last");
});
afterEach(function () {
this.webExt.destroy();
});
describe("with contextualIdentities permissions", function () {
it("should be able to get assignments", async function () {
this.webExt.background.browser.management.get.resolves({
permissions: ["contextualIdentities"]
});
const message = {
method: "getAssignment",
url
};
const sender = {
id: "external-webextension"
};
const [promise] = this.webExt.background.browser.runtime.onMessageExternal.addListener.yield(message, sender);
const answer = await promise;
expect(answer.userContextId === "4").to.be.true;
expect(answer.neverAsk === false).to.be.true;
expect(
Object.prototype.hasOwnProperty.call(
answer, "identityMacAddonUUID")).to.be.true;
});
});
describe("without contextualIdentities permissions", function () {
it("should throw an error", async function () {
this.webExt.background.browser.management.get.resolves({
permissions: []
});
const message = {
method: "getAssignment",
url
};
const sender = {
id: "external-webextension"
};
const [promise] = this.webExt.background.browser.runtime.onMessageExternal.addListener.yield(message, sender);
return promise.catch(error => {
expect(error.message).to.equal("Missing contextualIdentities permission");
});
});
});
});
+465
View File
@@ -0,0 +1,465 @@
const {initializeWithTab} = require("../common");
describe("Sync", function() {
beforeEach(async function() {
this.webExt = await initializeWithTab();
this.syncHelper = new SyncTestHelper(this.webExt);
});
afterEach(function() {
this.webExt.destroy();
delete this.syncHelper;
});
it("testIdentityStateCleanup", async function() {
await this.syncHelper.stopSyncListeners();
await this.syncHelper.setState({}, LOCAL_DATA, TEST_CONTAINERS, []);
await this.webExt.browser.storage.local.set({
"identitiesState@@_firefox-container-5": {
"hiddenTabs": []
}
});
await this.webExt.background.window.identityState.storageArea.upgradeData();
const macConfigs = await this.webExt.browser.storage.local.get();
const identities = [];
for(const configKey of Object.keys(macConfigs)) {
if (configKey.includes("identitiesState@@_") && !configKey.includes("default")) {
identities.push(macConfigs[configKey]);
}
}
identities.should.have.lengthOf(5, "There should be 5 identity entries");
for (const identity of identities) {
(!!identity.macAddonUUID).should.be.true;
}
});
it("testAssignManagerCleanup", async function() {
await this.syncHelper.stopSyncListeners();
await this.syncHelper.setState({}, LOCAL_DATA, TEST_CONTAINERS, TEST_ASSIGNMENTS);
await this.webExt.browser.storage.local.set({
"siteContainerMap@@_www.goop.com": {
"userContextId": "999",
"neverAsk": true
}
});
await this.webExt.background.window.identityState.storageArea.upgradeData();
await this.webExt.background.window.assignManager.storageArea.upgradeData();
const macConfigs = await this.webExt.browser.storage.local.get();
const assignments = [];
for(const configKey of Object.keys(macConfigs)) {
if (configKey.includes("siteContainerMap@@_")) {
macConfigs[configKey].configKey = configKey;
assignments.push(macConfigs[configKey]);
}
}
assignments.should.have.lengthOf(5, "There should be 5 site assignments");
for (const assignment of assignments) {
(!!assignment.identityMacAddonUUID).should.be.true;
}
});
it("testReconcileSiteAssignments", async function() {
await this.syncHelper.stopSyncListeners();
await this.syncHelper.setState(
DUPE_TEST_SYNC,
LOCAL_DATA,
TEST_CONTAINERS,
SITE_ASSIGNMENT_TEST
);
// add 200ok (bad data).
const testSites = {
"siteContainerMap@@_developer.mozilla.org": {
"userContextId": "588",
"neverAsk": true,
"identityMacAddonUUID": "d20d7af2-9866-468e-bb43-541efe8c2c2e",
"hostname": "developer.mozilla.org"
},
"siteContainerMap@@_reddit.com": {
"userContextId": "592",
"neverAsk": true,
"identityMacAddonUUID": "3dc916fb-8c0a-4538-9758-73ef819a45f7",
"hostname": "reddit.com"
},
"siteContainerMap@@_twitter.com": {
"userContextId": "589",
"neverAsk": true,
"identityMacAddonUUID": "cdd73c20-c26a-4c06-9b17-735c1f5e9187",
"hostname": "twitter.com"
},
"siteContainerMap@@_www.facebook.com": {
"userContextId": "590",
"neverAsk": true,
"identityMacAddonUUID": "32cc4a9b-05ed-4e54-8e11-732468de62f4",
"hostname": "www.facebook.com"
},
"siteContainerMap@@_www.linkedin.com": {
"userContextId": "591",
"neverAsk": true,
"identityMacAddonUUID": "9ff381e3-4c11-420d-8e12-e352a3318be1",
"hostname": "www.linkedin.com"
},
"siteContainerMap@@_200ok.us": {
"userContextId": "1",
"neverAsk": true,
"identityMacAddonUUID": "b5f5f794-b37e-4cec-9f4e-6490df620336",
"hostname": "www.linkedin.com"
}
};
for (const site of Object.keys(testSites)) {
await this.webExt.browser.storage.sync.set({[site]:testSites[site]});
}
await this.webExt.browser.storage.sync.set({
deletedSiteList: ["siteContainerMap@@_www.google.com"]
});
await this.webExt.background.window.sync.runSync();
const assignedSites = await this.webExt.background.window.assignManager.storageArea.getAssignedSites();
Object.keys(assignedSites).should.have.lengthOf(6);
});
it("testInitialSync", async function() {
await this.syncHelper.stopSyncListeners();
await this.syncHelper.setState({}, LOCAL_DATA, TEST_CONTAINERS, []);
await this.webExt.background.window.sync.runSync();
const getAssignedSites =
await this.webExt.background.window.assignManager.storageArea.getAssignedSites();
const identities = await this.webExt.browser.contextualIdentities.query({});
identities.should.have.lengthOf(5, "There should be 5 identity entries");
Object.keys(getAssignedSites).should.have.lengthOf(0, "There should be no site assignments");
});
it("test2", async function() {
await this.syncHelper.stopSyncListeners();
await this.syncHelper.setState(SYNC_DATA, LOCAL_DATA, TEST_CONTAINERS, TEST_ASSIGNMENTS);
await this.webExt.background.window.sync.runSync();
const getAssignedSites =
await this.webExt.background.window.assignManager.storageArea.getAssignedSites();
const identities = await this.webExt.browser.contextualIdentities.query({});
identities.should.have.lengthOf(6, "There should be 6 identity entries");
Object.keys(getAssignedSites).should.have.lengthOf(5, "There should be 5 site assignments");
});
it("dupeTest", async function() {
await this.syncHelper.stopSyncListeners();
await this.syncHelper.setState(
DUPE_TEST_SYNC,
DUPE_TEST_LOCAL,
DUPE_TEST_IDENTS,
DUPE_TEST_ASSIGNMENTS
);
await this.webExt.background.window.sync.runSync();
const getAssignedSites =
await this.webExt.background.window.assignManager.storageArea.getAssignedSites();
const identities = await this.webExt.browser.contextualIdentities.query({});
identities.should.have.lengthOf(7, "There should be 7 identity entries");
Object.keys(getAssignedSites).should.have.lengthOf(5, "There should be 5 identity entries");
const personalContainer =
this.syncHelper.lookupIdentityBy(identities, {name: "Personal"});
(personalContainer.color === "red").should.be.true;
const mozillaContainer =
this.syncHelper.lookupIdentityBy(identities, {name: "Mozilla"});
(mozillaContainer.icon === "pet").should.be.true;
});
});
class SyncTestHelper {
constructor(webExt) {
this.webExt = webExt;
}
async stopSyncListeners() {
await this.webExt.browser.storage.onChanged.removeListener(this.webExt.background.window.sync.storageArea.onChangedListener);
await this.webExt.background.window.sync.removeContextualIdentityListeners();
}
async setState(syncData, localData, identityData, assignmentData){
await this.removeAllContainers();
await this.webExt.browser.storage.sync.clear();
await this.webExt.browser.storage.sync.set(syncData);
await this.webExt.browser.storage.local.clear();
await this.webExt.browser.storage.local.set(localData);
for (let i=0; i < identityData.length; i++) {
//build identities
const newIdentity =
await this.webExt.browser.contextualIdentities.create(identityData[i]);
// fill identies with site assignments
if (assignmentData && assignmentData[i]) {
const data = {
"userContextId":
String(
newIdentity.cookieStoreId.replace(/^firefox-container-/, "")
),
"neverAsk": true
};
await this.webExt.browser.storage.local.set({[assignmentData[i]]: data});
}
}
return;
}
async removeAllContainers() {
const identities = await this.webExt.browser.contextualIdentities.query({});
for (const identity of identities) {
await this.webExt.browser.contextualIdentities.remove(identity.cookieStoreId);
}
}
lookupIdentityBy(identities, options) {
for (const identity of identities) {
if (options && options.name) {
if (identity.name === options.name) return identity;
}
if (options && options.color) {
if (identity.color === options.color) return identity;
}
if (options && options.color) {
if (identity.color === options.color) return identity;
}
}
return false;
}
}
const TEST_CONTAINERS = [
{
name: "Personal",
color: "blue",
icon: "fingerprint"
},
{
name: "Banking",
color: "green",
icon: "dollar"
},
{
name: "Mozilla",
color: "red",
icon: "briefcase"
},
{
name: "Groceries, obviously",
color: "yellow",
icon: "cart"
},
{
name: "Facebook",
color: "toolbar",
icon: "fence"
},
];
const TEST_ASSIGNMENTS = [
"siteContainerMap@@_developer.mozilla.org",
"siteContainerMap@@_twitter.com",
"siteContainerMap@@_www.facebook.com",
"siteContainerMap@@_www.linkedin.com",
"siteContainerMap@@_reddit.com"
];
const LOCAL_DATA = {
"browserActionBadgesClicked": [ "6.2.0" ],
"containerTabsOpened": 7,
"identitiesState@@_firefox-default": { "hiddenTabs": [] },
"onboarding-stage": 5
};
const SYNC_DATA = {
"identity@@_22ded543-5173-44a5-a47a-8813535945ca": {
"name": "Personal",
"icon": "fingerprint",
"color": "red",
"cookieStoreId": "firefox-container-146",
"macAddonUUID": "22ded543-5173-44a5-a47a-8813535945ca"
},
"identity@@_63e5212f-0858-418e-b5a3-09c2dea61fcd": {
"name": "Oscar",
"icon": "dollar",
"color": "green",
"cookieStoreId": "firefox-container-147",
"macAddonUUID": "3e5212f-0858-418e-b5a3-09c2dea61fcd"
},
"identity@@_71335417-158e-4d74-a55b-e9e9081601ec": {
"name": "Mozilla",
"icon": "pet",
"color": "red",
"cookieStoreId": "firefox-container-148",
"macAddonUUID": "71335417-158e-4d74-a55b-e9e9081601ec"
},
"identity@@_59c4e5f7-fe3b-435a-ae60-1340db31a91b": {
"name": "Groceries, obviously",
"icon": "cart",
"color": "pink",
"cookieStoreId": "firefox-container-149",
"macAddonUUID": "59c4e5f7-fe3b-435a-ae60-1340db31a91b"
},
"identity@@_3dc916fb-8c0a-4538-9758-73ef819a45f7": {
"name": "Facebook",
"icon": "fence",
"color": "toolbar",
"cookieStoreId": "firefox-container-150",
"macAddonUUID": "3dc916fb-8c0a-4538-9758-73ef819a45f7"
}
};
const DUPE_TEST_SYNC = {
"identity@@_d20d7af2-9866-468e-bb43-541efe8c2c2e": {
"name": "Personal",
"icon": "fingerprint",
"color": "red",
"cookieStoreId": "firefox-container-588",
"macAddonUUID": "d20d7af2-9866-468e-bb43-541efe8c2c2e"
},
"identity@@_cdd73c20-c26a-4c06-9b17-735c1f5e9187": {
"name": "Big Bird",
"icon": "pet",
"color": "yellow",
"cookieStoreId": "firefox-container-589",
"macAddonUUID": "cdd73c20-c26a-4c06-9b17-735c1f5e9187"
},
"identity@@_32cc4a9b-05ed-4e54-8e11-732468de62f4": {
"name": "Mozilla",
"icon": "pet",
"color": "red",
"cookieStoreId": "firefox-container-590",
"macAddonUUID": "32cc4a9b-05ed-4e54-8e11-732468de62f4"
},
"identity@@_9ff381e3-4c11-420d-8e12-e352a3318be1": {
"name": "Groceries, obviously",
"icon": "cart",
"color": "pink",
"cookieStoreId": "firefox-container-591",
"macAddonUUID": "9ff381e3-4c11-420d-8e12-e352a3318be1"
},
"identity@@_3dc916fb-8c0a-4538-9758-73ef819a45f7": {
"name": "Facebook",
"icon": "fence",
"color": "toolbar",
"cookieStoreId": "firefox-container-592",
"macAddonUUID": "3dc916fb-8c0a-4538-9758-73ef819a45f7"
},
"identity@@_63e5212f-0858-418e-b5a3-09c2dea61fcd": {
"name": "Oscar",
"icon": "dollar",
"color": "green",
"cookieStoreId": "firefox-container-593",
"macAddonUUID": "63e5212f-0858-418e-b5a3-09c2dea61fcd"
},
"siteContainerMap@@_developer.mozilla.org": {
"userContextId": "588",
"neverAsk": true,
"identityMacAddonUUID": "d20d7af2-9866-468e-bb43-541efe8c2c2e",
"hostname": "developer.mozilla.org"
},
"siteContainerMap@@_reddit.com": {
"userContextId": "592",
"neverAsk": true,
"identityMacAddonUUID": "3dc916fb-8c0a-4538-9758-73ef819a45f7",
"hostname": "reddit.com"
},
"siteContainerMap@@_twitter.com": {
"userContextId": "589",
"neverAsk": true,
"identityMacAddonUUID": "cdd73c20-c26a-4c06-9b17-735c1f5e9187",
"hostname": "twitter.com"
},
"siteContainerMap@@_www.facebook.com": {
"userContextId": "590",
"neverAsk": true,
"identityMacAddonUUID": "32cc4a9b-05ed-4e54-8e11-732468de62f4",
"hostname": "www.facebook.com"
},
"siteContainerMap@@_www.linkedin.com": {
"userContextId": "591",
"neverAsk": true,
"identityMacAddonUUID": "9ff381e3-4c11-420d-8e12-e352a3318be1",
"hostname": "www.linkedin.com"
}
};
const DUPE_TEST_LOCAL = {
"beenSynced": true,
"browserActionBadgesClicked": [
"6.2.0"
],
"containerTabsOpened": 7,
"identitiesState@@_firefox-default": {
"hiddenTabs": []
},
"onboarding-stage": 5,
};
const DUPE_TEST_ASSIGNMENTS = [
"siteContainerMap@@_developer.mozilla.org",
"siteContainerMap@@_reddit.com",
"siteContainerMap@@_twitter.com",
"siteContainerMap@@_www.facebook.com",
"siteContainerMap@@_www.linkedin.com"
];
const SITE_ASSIGNMENT_TEST = [
"siteContainerMap@@_developer.mozilla.org",
"siteContainerMap@@_www.facebook.com",
"siteContainerMap@@_www.google.com",
"siteContainerMap@@_bugzilla.mozilla.org"
];
const DUPE_TEST_IDENTS = [
{
"name": "Personal",
"icon": "fingerprint",
"color": "blue",
},
{
"name": "Banking",
"icon": "pet",
"color": "green",
},
{
"name": "Mozilla",
"icon": "briefcase",
"color": "red",
},
{
"name": "Groceries, obviously",
"icon": "cart",
"color": "orange",
},
{
"name": "Facebook",
"icon": "fence",
"color": "toolbar",
},
{
"name": "Big Bird",
"icon": "dollar",
"color": "yellow",
}
];
+38
View File
@@ -0,0 +1,38 @@
const {expect, sinon, initializeWithTab} = require("../common");
describe("#1168", function () {
describe("when navigation happens too slow after opening new tab to a page which then redirects", function () {
let clock, tab, background;
beforeEach(async function () {
this.webExt = await initializeWithTab({
cookieStoreId: "firefox-container-1",
url: "https://bugzilla.mozilla.org"
});
await this.webExt.popup.helper.clickElementById("container-page-assigned");
clock = sinon.useFakeTimers();
tab = await this.webExt.browser.tabs._create({});
clock.tick(2000);
await background.browser.tabs._navigate(tab.id, "https://duckduckgo.com/?q=%21bugzilla+thing&t=ffab");
await background.browser.tabs._redirect(tab.id, [
"https://bugzilla.mozilla.org"
]);
});
afterEach(function () {
this.webExt.destroy();
clock.restore();
});
// Not solved yet
// See: https://github.com/mozilla/multi-account-containers/issues/1168#issuecomment-378394091
it.skip("should remove the old tab", async function () {
expect(background.browser.tabs.create).to.have.been.calledOnce;
expect(background.browser.tabs.remove).to.have.been.calledWith(tab.id);
});
});
});
+179
View File
@@ -0,0 +1,179 @@
const {expect, sinon, initializeWithTab} = require("../common");
describe("#940", function () {
describe("when other onBeforeRequestHandlers are faster and redirect with the same requestId", function () {
it("should not open two confirm pages", async function () {
const webExtension = await initializeWithTab({
cookieStoreId: "firefox-container-4",
url: "http://example.com"
});
await webExtension.popup.helper.clickElementById("always-open-in");
await webExtension.popup.helper.clickElementByQuerySelectorAll("#picker-identities-list > .menu-item");
const responses = {};
await webExtension.background.browser.tabs._create({
url: "https://example.com"
}, {
options: {
webRequestRedirects: ["https://example.com"],
webRequestError: true,
instantRedirects: true
},
responses
});
const result = await responses.webRequest.onBeforeRequest[1];
expect(result).to.deep.equal({
cancel: true
});
webExtension.browser.tabs.create.should.have.been.calledOnce;
webExtension.destroy();
});
});
describe("when redirects change requestId midflight", function () {
beforeEach(async function () {
this.webExt = await initializeWithTab({
cookieStoreId: "firefox-container-4",
url: "https://www.youtube.com"
});
await this.webExt.popup.helper.clickElementById("always-open-in");
await this.webExt.popup.helper.clickElementByQuerySelectorAll("#picker-identities-list > .menu-item");
global.clock = sinon.useFakeTimers();
this.redirectedRequest = async (options = {}) => {
const newTabResponses = {};
const newTab = await this.webExt.browser.tabs._create({
url: "http://youtube.com"
}, {
options: Object.assign({
webRequestRedirects: [
"https://youtube.com",
"https://www.youtube.com",
{
url: "https://www.youtube.com",
webRequest: {
requestId: 2
}
}
],
webRequestError: true,
instantRedirects: true
}, options),
responses: newTabResponses
});
return [newTabResponses, newTab];
};
});
afterEach(function () {
this.webExt.destroy();
global.clock.restore();
});
it("should not open two confirm pages", async function () {
const [newTabResponses] = await this.redirectedRequest();
// http://youtube.com is not assigned, no cancel, no reopening
expect(await newTabResponses.webRequest.onBeforeRequest[0]).to.deep.equal({});
// https://youtube.com is not assigned, no cancel, no reopening
expect(await newTabResponses.webRequest.onBeforeRequest[1]).to.deep.equal({});
// https://www.youtube.com is assigned, this triggers reopening, cancel
expect(await newTabResponses.webRequest.onBeforeRequest[2]).to.deep.equal({
cancel: true
});
// https://www.youtube.com is assigned, this was a redirect, cancel early, no reopening
expect(await newTabResponses.webRequest.onBeforeRequest[3]).to.deep.equal({
cancel: true
});
this.webExt.background.browser.tabs.create.should.have.been.calledOnce;
});
it("should uncancel after webRequest.onCompleted", async function () {
const [newTabResponses, newTab] = await this.redirectedRequest();
// remove onCompleted listeners because in the real world this request would never complete
// and thus might trigger unexpected behavior because the tab gets removed when reopening
this.webExt.background.browser.webRequest.onCompleted.addListener = sinon.stub();
this.webExt.background.browser.tabs.create.resetHistory();
// we create a tab with the same id and use the same request id to see if uncanceled
await this.webExt.browser.tabs._create({
id: newTab.id,
url: "https://www.youtube.com"
}, {
options: {
webRequest: {
requestId: newTabResponses.webRequest.request.requestId
}
}
});
this.webExt.background.browser.tabs.create.should.have.been.calledOnce;
});
it("should uncancel after webRequest.onErrorOccurred", async function () {
const [newTabResponses, newTab] = await this.redirectedRequest();
this.webExt.background.browser.tabs.create.resetHistory();
// we create a tab with the same id and use the same request id to see if uncanceled
await this.webExt.browser.tabs._create({
id: newTab.id,
url: "https://www.youtube.com"
}, {
options: {
webRequest: {
requestId: newTabResponses.webRequest.request.requestId
},
webRequestError: true
}
});
this.webExt.background.browser.tabs.create.should.have.been.calledOnce;
});
it("should uncancel after 2 seconds", async function () {
const [newTabResponses, newTab] = await this.redirectedRequest({
webRequestDontYield: ["onCompleted", "onErrorOccurred"]
});
global.clock.tick(2000);
this.webExt.background.browser.tabs.create.resetHistory();
// we create a tab with the same id and use the same request id to see if uncanceled
await this.webExt.browser.tabs._create({
id: newTab.id,
url: "https://www.youtube.com"
}, {
options: {
webRequest: {
requestId: newTabResponses.webRequest.request.requestId
},
webRequestError: true
}
});
this.webExt.background.browser.tabs.create.should.have.been.calledOnce;
});
it("should not influence the canceled url in other tabs", async function () {
await this.redirectedRequest();
this.webExt.background.browser.tabs.create.resetHistory();
await this.webExt.browser.tabs._create({
cookieStoreId: "firefox-default",
url: "https://www.youtube.com"
}, {
options: {
webRequestError: true
}
});
this.webExt.background.browser.tabs.create.should.have.been.calledOnce;
});
});
});
-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);
-333
View File
@@ -1,333 +0,0 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/**
* Class that represents a metrics event broker. Events are sent to Google
* Analytics if the `tid` parameter is set. Events are sent to Mozilla's
* data pipeline via the Test Pilot add-on. No metrics code changes are
* needed when the experiment is added to or removed from Test Pilot.
* @constructor
* @param {string} $0.id - addon ID, e.g. '@testpilot-addon'. See https://mdn.io/add_on_id.
* @param {string} $0.version - addon version, e.g. '1.0.2'.
* @param {string} [$0.uid] - unique identifier for a specific instance of an addon.
* Optional, but required to send events to Google Analytics. Sent to Google Analytics
* but not Mozilla services.
* @param {string} [$0.tid] - Google Analytics tracking ID. Optional, but required
* to send events to Google Analytics.
* @param {string} [$0.type=webextension] - addon type. one of: 'webextension',
* 'sdk', 'bootstrapped'.
* @param {boolean} [$0.debug=false] - if true, enables logging. Note that this
* value can be changed on a running instance, by modifying its `debug` property.
* @throws {SyntaxError} If the required properties are missing, or if the
* 'type' property is unrecognized.
* @throws {Error} if initializing the transports fails.
*/
function Metrics({id, version, uid, tid = null, type = 'webextension', debug = false}) {
if (!id) {
throw new SyntaxError(`'id' property is required.`);
} else if (!version) {
throw new SyntaxError(`'version' property is required.`);
} else if (tid && !uid) {
throw new SyntaxError(`'uid' property is required to send events to Google Analytics.`);
}
if (!['webextension', 'sdk', 'bootstrapped'].includes(type)) {
throw new SyntaxError(`'type' property must be one of: 'webextension', 'sdk', or 'bootstrapped'`);
}
Object.assign(this, {id, uid, version, tid, type, debug});
// The test pilot add-on uses its own nsIObserverService topic for sending
// pings to Telemetry. Otherwise, the topic is based on add-on type.
if (id === '@testpilot-addon') {
this.topic = 'testpilot';
} else if (type === 'webextension') {
this.topic = 'testpilot-telemetry';
} else {
this.topic = 'testpilottest';
}
// NOTE: order is important here. _initTransports uses console.log, which may
// not be available before _initConsole has run.
this._initConsole();
this._initTransports();
this.sendEvent = this.sendEvent.bind(this);
this._log(`Initialized topic to ${this.topic}`);
if (!tid) {
this._log(`Google Analytics disabled: 'tid' value not passed to constructor.`);
} else {
this._log(`Google Analytics enabled for Tracking ID ${tid}.`);
}
this._log('Constructor finished successfully.');
}
Metrics.prototype = {
/**
* Sends an event to the Mozilla data pipeline (and Google Analytics, if
* a `tid` was passed to the constructor). Note: to avoid breaking callers,
* if sending the event fails, no Errors will be thrown. Instead, the message
* will be silently dropped, and, if debug mode is enabled, an error will be
* logged to the Browser Console.
*
* If you want to pass extra fields to GA, or use a GA hit type other than
* `Event`, you can transform the output data object yourself using the
* `transform` parameter. You will need to add Custom Dimensions to GA for any
* extra fields: https://support.google.com/analytics/answer/2709828. Note
* that, by convention, the `variant` argument is mapped to the first Custom
* Dimension (`cd1`) when constructing the GA Event hit.
*
* Note: the data object format is currently different for each experiment,
* and should be defined based on the result of conversations with the Mozilla
* data team.
*
* A suggested default format is:
* @param {string} [$0.method] - What is happening? e.g. `click`
* @param {string} [$0.object] - What is being affected? e.g. `home-button-1`
* @param {string} [$0.category=interactions] - If you want to add a category
* for easy reporting later. e.g. `mainmenu`
* @param {string} [$0.variant=null] - An identifying string if you're running
* different variants. e.g. `cohort-A`
* @param {function} [transform] - Transform function used to alter the
* parameters sent to GA. The `transform` function signature is
* `transform(input, output)`, where `input` is the object passed to
* `sendEvent` (excluding `transform`), and `output` is the default GA
* object generated by the `_gaTransform` method. The `transform` function
* should return an object whose keys are GA Measurement Protocol parameters.
* The returned object will be form encoded and sent to GA.
*/
sendEvent: function(params = {}, transform) {
const args = this._clone(params);
args.object = params.object || null;
args.category = params.category || 'interactions';
args.variant = params.variant || null;
this._log(`sendEvent called with method = ${args.method}, object = ${args.object}, category = ${args.category}, variant = ${args.variant}.`);
const clientData = this._clone(args);
const gaData = this._clone(args);
if (!clientData) {
this._error(`Unable to process data object. Dropping packet.`);
return;
}
this._sendToClient(clientData);
if (this.tid && this.uid) {
const defaultEvent = this._gaTransform(gaData);
let userEvent;
if (transform) {
userEvent = transform.call(null, gaData, defaultEvent);
}
this._gaSend(userEvent || defaultEvent);
}
},
/**
* Clone a data object by serializing / deserializing it.
* @private
* @param {object} o - Object to be cloned.
* @returns A clone of the object, or `null` if cloning failed.
*/
_clone: function(o) {
let cloned;
try {
cloned = JSON.parse(JSON.stringify(o));
} catch (ex) {
this._error(`Unable to clone object: ${ex}.`);
return null;
}
return cloned;
},
/**
* Sends an event to the Mozilla data pipeline via the Test Pilot add-on.
* Uses BroadcastChannel for WebExtensions, and nsIObserverService for other
* add-on types.
* @private
* @param {object} params - Entire object sent to `sendEvent`.
*/
_sendToClient: function(params) {
if (this.type === 'webextension') {
this._channel.postMessage(params);
this._log(`Sent client message via postMessage: ${params}`);
} else {
let stringified;
try {
stringified = JSON.stringify(params);
} catch(ex) {
this._error(`Unable to serialize metrics event: ${ex}`);
return;
}
const subject = {
wrappedJSObject: {
observersModuleSubjectWrapper: true,
object: this.id
}
};
try {
Services.obs.notifyObservers(subject, 'testpilot::send-metric', stringified);
this._log(`Sent client message via nsIObserverService: ${stringified}`);
} catch (ex) {
this._error(`Failed to send nsIObserver client ping: ${ex}`);
return;
}
}
},
/**
* Transforms `sendEvent()` arguments into a Google Analytics `Event` hit.
* @private
* @param {string} method - see `sendEvent` docs
* @param {string} [object] - see `sendEvent` docs
* @param {string} category - see `sendEvent` docs. Note that `category` is
* required here, assuming the default value was filled in by `sendEvent()`.
* @param {string} variant - see `sendEvent` docs. Note that `variant` is
* required here, assuming the default value was filled in by `sendEvent()`.
*/
_gaTransform: function({method, object, category, variant}) {
const data = {
v: 1,
an: this.id,
av: this.version,
tid: this.tid,
uid: this.uid,
t: 'event',
ec: category,
ea: method
};
if (object) {
data.el = object;
}
if (variant) {
data.cd1 = variant;
}
return data;
},
/**
* Encodes and sends an event message to Google Analytics.
* @private
* @param {object} msg - An object whose keys correspond to parameters in the
* Google Analytics Measurement Protocol.
*/
_gaSend: function(msg) {
const encoded = this._formEncode(msg);
const GA_URL = 'https://ssl.google-analytics.com/collect';
if (this.type === 'webextension') {
navigator.sendBeacon(GA_URL, encoded);
} else {
// SDK and bootstrapped types might not have a window reference, so get
// the sendBeacon DOM API from the hidden window.
Services.appShell.hiddenDOMWindow.navigator.sendBeacon(GA_URL, encoded);
}
this._log(`Sent GA message: ${encoded}`);
},
/**
* URL encodes an object. Encodes spaces as '%20', not '+', following the
* GA docs.
*
* @example
* // returns 'a=b&foo=b%20ar'
* metrics._formEncode({a: 'b', foo: 'b ar'});
* @private
* @param {Object} obj - Any JS object
* @returns {string}
*/
_formEncode: function(obj) {
const params = [];
if (!obj) { return ''; }
Object.keys(obj).forEach(item => {
const encoded = encodeURIComponent(item) + '=' + encodeURIComponent(obj[item]);
params.push(encoded);
});
return params.join('&');
},
/**
* Initializes transports used for sending messages. For WebExtensions,
* creates a `BroadcastChannel` (transport for client pings). WebExtensions
* use navigator.sendBeacon for GA transport, and they always have access
* to DOM APIs, so there's no setup work required. For other types, loads
* `Services.jsm`, which exposes the nsIObserverService (transport for client
* pings), and exposes the navigator.sendBeacon API (GA transport) via the
* appShell service's hidden window.
* @private
* @throws {Error} if transport setup unexpectedly fails
*/
_initTransports: function() {
if (this.type === 'webextension') {
try {
this._channel = new BroadcastChannel(this.topic);
} catch(ex) {
throw new Error(`Unable to create BroadcastChannel: ${ex}`);
}
} else if (this.type === 'sdk') {
try {
const { Cu } = require('chrome');
Cu.import('resource://gre/modules/Services.jsm');
} catch(ex) {
throw new Error(`Unable to load Services.jsm: ${ex}`);
}
} else { /* this.type === 'bootstrapped' */
try {
Components.utils.import('resource://gre/modules/Services.jsm');
} catch(ex) {
throw new Error(`Unable to load Services.jsm: ${ex}`);
}
}
this._log('Successfully initialized transports.');
},
/**
* Initializes a console for 'bootstrapped' add-ons.
* @private
*/
_initConsole: function() {
if (this.type === 'bootstrapped') {
try {
Components.utils.import('resource://gre/modules/Console.jsm');
this._log('Successfully initialized console.');
} catch(ex) {
throw new Error(`Unable to initialize console: ${ex}`);
}
}
},
/**
* Logs messages to the console. Only enabled if `this.debug` is truthy.
* @private
* @param {string} msg - A message
*/
_log: function(msg) {
if (this.debug) {
console.log(msg); // eslint-disable-line no-console
}
},
/**
* Logs errors to the console. Only enabled if `this.debug` is truthy.
* @private
* @param {string} msg - An error message
*/
_error: function(msg) {
if (this.debug) {
console.error(msg); // eslint-disable-line no-console
}
}
};
// WebExtensions don't support CommonJS module style, so 'module' might not be
// defined.
if (typeof module !== 'undefined') {
module.exports = Metrics;
}
// Export the Metrics constructor in Gecko JSM style, for legacy addons
// that use the JSM loader. See also: https://mdn.io/jsm/using
const EXPORTED_SYMBOLS = ['Metrics']; // eslint-disable-line no-unused-vars
-473
View File
@@ -1,473 +0,0 @@
const assignManager = {
CLOSEABLE_WINDOWS: new Set([
"about:startpage",
"about:newtab",
"about:home",
"about:blank"
]),
MENU_ASSIGN_ID: "open-in-this-container",
MENU_REMOVE_ID: "remove-open-in-this-container",
storageArea: {
area: browser.storage.local,
getSiteStoreKey(pageUrl) {
const url = new window.URL(pageUrl);
const storagePrefix = "siteContainerMap@@_";
return `${storagePrefix}${url.hostname}`;
},
get(pageUrl) {
const siteStoreKey = this.getSiteStoreKey(pageUrl);
return new Promise((resolve, reject) => {
this.area.get([siteStoreKey]).then((storageResponse) => {
if (storageResponse && siteStoreKey in storageResponse) {
resolve(storageResponse[siteStoreKey]);
}
resolve(null);
}).catch((e) => {
reject(e);
});
});
},
set(pageUrl, data) {
const siteStoreKey = this.getSiteStoreKey(pageUrl);
return this.area.set({
[siteStoreKey]: data
});
},
remove(pageUrl) {
const siteStoreKey = this.getSiteStoreKey(pageUrl);
return this.area.remove([siteStoreKey]);
},
deleteContainer(userContextId) {
const removeKeys = [];
this.area.get().then((siteConfigs) => {
Object.keys(siteConfigs).forEach((key) => {
// For some reason this is stored as string... lets check them both as that
if (String(siteConfigs[key].userContextId) === String(userContextId)) {
removeKeys.push(key);
}
});
this.area.remove(removeKeys);
}).catch((e) => {
throw e;
});
}
},
init() {
browser.runtime.onMessage.addListener((neverAskMessage) => {
const pageUrl = neverAskMessage.pageUrl;
if (neverAskMessage.neverAsk === true) {
// If we have existing data and for some reason it hasn't been deleted etc lets update it
this.storageArea.get(pageUrl).then((siteSettings) => {
if (siteSettings) {
siteSettings.neverAsk = true;
this.storageArea.set(pageUrl, siteSettings);
}
}).catch((e) => {
throw e;
});
}
});
browser.contextMenus.onClicked.addListener((info, tab) => {
const userContextId = this.getUserContextIdFromCookieStore(tab);
// Mapping ${URL(info.pageUrl).hostname} to ${userContextId}
if (userContextId) {
let actionName;
let storageAction;
if (info.menuItemId === this.MENU_ASSIGN_ID) {
actionName = "added";
storageAction = this.storageArea.set(info.pageUrl, {
userContextId,
neverAsk: false
});
} else {
actionName = "removed";
storageAction = this.storageArea.remove(info.pageUrl);
}
storageAction.then(() => {
browser.notifications.create({
type: "basic",
title: "Containers",
message: `Successfully ${actionName} site to always open in this container`,
iconUrl: browser.extension.getURL("/img/onboarding-1.png")
});
browser.runtime.sendMessage({
method: "sendTelemetryPayload",
event: `${actionName}-container-assignment`,
userContextId: userContextId,
});
this.calculateContextMenu(tab);
}).catch((e) => {
throw e;
});
}
});
// Before a request is handled by the browser we decide if we should route through a different container
browser.webRequest.onBeforeRequest.addListener((options) => {
if (options.frameId !== 0 || options.tabId === -1) {
return {};
}
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) {
return {};
}
this.reloadPageInContainer(options.url, 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 (CLOSEABLE_WINDOWS), 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 (this.CLOSEABLE_WINDOWS.has(tab.url)
|| (messageHandler.lastCreatedTab
&& messageHandler.lastCreatedTab.id === tab.id)) {
browser.tabs.remove(tab.id);
}
return {
cancel: true,
};
}).catch((e) => {
throw e;
});
},{urls: ["<all_urls>"], types: ["main_frame"]}, ["blocking"]);
},
deleteContainer(userContextId) {
this.storageArea.deleteContainer(userContextId);
},
getUserContextIdFromCookieStore(tab) {
if (!("cookieStoreId" in tab)) {
return false;
}
const cookieStore = tab.cookieStoreId;
const container = cookieStore.replace("firefox-container-", "");
if (container !== cookieStore) {
return container;
}
return false;
},
isTabPermittedAssign(tab) {
// Ensure we are not an important about url
// Ensure we are not in incognito mode
const url = new URL(tab.url);
if (url.protocol === "about:"
|| tab.incognito) {
return false;
}
return true;
},
calculateContextMenu(tab) {
// There is a focus issue in this menu where if you change window with a context menu click
// you get the wrong menu display because of async
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1215376#c16
// We also can't change for always private mode
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1352102
const cookieStore = this.getUserContextIdFromCookieStore(tab);
browser.contextMenus.remove(this.MENU_ASSIGN_ID);
browser.contextMenus.remove(this.MENU_REMOVE_ID);
// Ensure we have a cookieStore to assign to
if (cookieStore
&& this.isTabPermittedAssign(tab)) {
this.storageArea.get(tab.url).then((siteSettings) => {
// ✓ This is to mitigate https://bugzilla.mozilla.org/show_bug.cgi?id=1351418
let prefix = " "; // Alignment of non breaking space, unknown why this requires so many spaces to align with the tick
let menuId = this.MENU_ASSIGN_ID;
if (siteSettings) {
prefix = "✓";
menuId = this.MENU_REMOVE_ID;
}
browser.contextMenus.create({
id: menuId,
title: `${prefix} Always Open in This Container`,
checked: true,
contexts: ["all"],
});
}).catch((e) => {
throw e;
});
}
},
reloadPageInContainer(url, userContextId, index, neverAsk = false) {
const loadPage = browser.extension.getURL("confirm-page.html");
// If the user has explicitly checked "Never Ask Again" on the warning page we will send them straight there
if (neverAsk) {
browser.tabs.create({url, cookieStoreId: `firefox-container-${userContextId}`, index});
browser.runtime.sendMessage({
method: "sendTelemetryPayload",
event: "auto-reload-page-in-container",
userContextId: userContextId,
});
} else {
browser.runtime.sendMessage({
method: "sendTelemetryPayload",
event: "prompt-to-reload-page-in-container",
userContextId: userContextId,
});
const confirmUrl = `${loadPage}?url=${url}`;
browser.tabs.create({url: confirmUrl, cookieStoreId: `firefox-container-${userContextId}`, index}).then(() => {
// We don't want to sync this URL ever nor clutter the users history
browser.history.deleteUrl({url: confirmUrl});
}).catch((e) => {
throw e;
});
}
}
};
const messageHandler = {
// After the timer completes we assume it's a tab the user meant to keep open
// 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,
init() {
// Handles messages from index.js
const port = browser.runtime.connect();
port.onMessage.addListener(m => {
switch (m.type) {
case "lightweight-theme-changed":
themeManager.update(m.message);
break;
case "delete-container":
assignManager.deleteContainer(m.message.userContextId);
break;
default:
throw new Error(`Unhandled message type: ${m.message}`);
}
});
browser.tabs.onCreated.addListener((tab) => {
// This works at capturing the tabs as they are created
// However we need onFocusChanged and onActivated to capture the initial tab
if (tab.id === -1) {
return {};
}
tabPageCounter.initTabCounter(tab);
});
browser.tabs.onRemoved.addListener((tabId) => {
if (tabId === -1) {
return {};
}
tabPageCounter.sendTabCountAndDelete(tabId);
});
browser.tabs.onActivated.addListener((info) => {
browser.tabs.get(info.tabId).then((tab) => {
tabPageCounter.initTabCounter(tab);
assignManager.calculateContextMenu(tab);
}).catch((e) => {
throw e;
});
});
browser.windows.onFocusChanged.addListener((windowId) => {
browser.tabs.query({active: true, windowId}).then((tabs) => {
if (tabs && tabs[0]) {
tabPageCounter.initTabCounter(tabs[0]);
assignManager.calculateContextMenu(tabs[0]);
}
}).catch((e) => {
throw e;
});
});
browser.idle.onStateChanged.addListener((newState) => {
browser.tabs.query({}).then(tabs => {
for (let tab of tabs) { // eslint-disable-line prefer-const
if (newState === "idle") {
tabPageCounter.sendTabCountAndDelete(tab.id, "user-went-idle");
} else if (newState === "active" && tab.active) {
tabPageCounter.initTabCounter(tab);
}
}
}).catch(e => {
throw e;
});
});
browser.webRequest.onCompleted.addListener((details) => {
if (details.frameId !== 0 || details.tabId === -1) {
return {};
}
browser.tabs.get(details.tabId).then((tab) => {
tabPageCounter.incrementTabCount(tab);
assignManager.calculateContextMenu(tab);
}).catch((e) => {
throw e;
});
}, {urls: ["<all_urls>"], types: ["main_frame"]});
// lets remember the last tab created so we can close it if it looks like a redirect
browser.tabs.onCreated.addListener((details) => {
this.lastCreatedTab = details;
setTimeout(() => {
this.lastCreatedTab = null;
}, this.LAST_CREATED_TAB_TIMER);
});
}
};
const themeManager = {
existingTheme: null,
init() {
this.check();
},
setPopupIcon(theme) {
let icons = {
16: "img/container-site-d-24.png",
32: "img/container-site-d-48.png"
};
if (theme === "firefox-compact-dark@mozilla.org") {
icons = {
16: "img/container-site-w-24.png",
32: "img/container-site-w-48.png"
};
}
browser.browserAction.setIcon({
path: icons
});
},
check() {
browser.runtime.sendMessage({
method: "getTheme"
}).then((theme) => {
this.update(theme);
}).catch(() => {
throw new Error("Unable to get theme");
});
},
update(theme) {
if (this.existingTheme !== theme) {
this.setPopupIcon(theme);
this.existingTheme = theme;
}
}
};
const tabPageCounter = {
counters: {},
initTabCounter(tab) {
if (tab.id in this.counters) {
if (!("activity" in this.counters[tab.id])) {
this.counters[tab.id].activity = {
"cookieStoreId": tab.cookieStoreId,
"pageRequests": 0
};
}
if (!("tab" in this.counters[tab.id])) {
this.counters[tab.id].tab = {
"cookieStoreId": tab.cookieStoreId,
"pageRequests": 0
};
}
} else {
this.counters[tab.id] = {};
this.counters[tab.id].tab = {
"cookieStoreId": tab.cookieStoreId,
"pageRequests": 0
};
this.counters[tab.id].activity = {
"cookieStoreId": tab.cookieStoreId,
"pageRequests": 0
};
}
},
sendTabCountAndDelete(tabId, why = "user-closed-tab") {
if (!(this.counters[tabId])) {
return;
}
if (why === "user-closed-tab" && this.counters[tabId].tab) {
browser.runtime.sendMessage({
method: "sendTelemetryPayload",
event: "page-requests-completed-per-tab",
userContextId: this.counters[tabId].tab.cookieStoreId,
pageRequestCount: this.counters[tabId].tab.pageRequests
});
// When we send the ping because the user closed the tab,
// delete both the 'tab' and 'activity' counters
delete this.counters[tabId];
} else if (why === "user-went-idle" && this.counters[tabId].activity) {
browser.runtime.sendMessage({
method: "sendTelemetryPayload",
event: "page-requests-completed-per-activity",
userContextId: this.counters[tabId].activity.cookieStoreId,
pageRequestCount: this.counters[tabId].activity.pageRequests
});
// When we send the ping because the user went idle,
// only reset the 'activity' counter
this.counters[tabId].activity = {
"cookieStoreId": this.counters[tabId].tab.cookieStoreId,
"pageRequests": 0
};
}
},
incrementTabCount(tab) {
this.counters[tab.id].tab.pageRequests++;
this.counters[tab.id].activity.pageRequests++;
}
};
assignManager.init();
themeManager.init();
// Lets do this last as theme manager did a check before connecting before
messageHandler.init();
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" });
}
-33
View File
@@ -1,33 +0,0 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>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>
<body>
<main>
<div class="title">
<h1 class="title-text">Should we open this in your container?</h1>
</div>
<form id="redirect-form">
<p>
Looks like you requested:
</p>
<div id="redirect-url"></div>
<p>
You asked <dfn id="browser-name" title="Thanks for trying out Containers. Sorry we may have got your browser name wrong. #FxNightly" >Firefox</dfn> to always open <dfn id="redirect-site"></dfn> in <dfn>this</dfn> type of container. Would you like to proceed?<br />
</p>
<br />
<br />
<input id="never-ask" type="checkbox" /><label for="never-ask">Remember my decision for this site</label>
<br />
<div class="button-container">
<button id="confirm" class="button primary" autofocus>Take me there</button>
</div>
</form>
</main>
<script src="js/confirm-page.js"></script>
</body>
</html>
-41
View File
@@ -1,41 +0,0 @@
/* General Rules and Resets */
.title {
background-image: none;
}
main {
background: url(/img/onboarding-1.png) no-repeat;
background-position: -10px -15px;
background-size: 285px;
margin-inline-start: -285px;
padding-inline-start: 285px;
}
@media only screen and (max-width: 1300px) {
main {
background: none;
}
/* for a mid sized window we have enough for this but not our image */
.title {
background-image: url("chrome://global/skin/icons/info.svg");
}
}
html {
box-sizing: border-box;
font: message-box;
}
#redirect-url,
#redirect-origin {
font-weight: bold;
/* max-inline-size is needed to force this text smaller than the layout at a mid-sized window */
max-inline-size: 40rem;
word-break: break-all;
}
dfn {
font-style: normal;
}
-593
View File
@@ -1,593 +0,0 @@
/* General Rules and Resets */
body {
inline-size: 300px;
max-inline-size: 300px;
}
html {
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
table {
border: 0;
border-spacing: 0;
inline-size: 100%;
margin-block-end: 0;
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
}
/* Helper Classes */
.hide {
display: none !important;
}
.scrollable {
inline-size: 100%;
max-block-size: 400px;
overflow: auto;
}
/* Color and icon helpers */
[data-identity-color="blue"] {
--identity-tab-color: #37adff;
--identity-icon-color: #37adff;
}
[data-identity-color="turquoise"] {
--identity-tab-color: #00c79a;
--identity-icon-color: #00c79a;
}
[data-identity-color="green"] {
--identity-tab-color: #51cd00;
--identity-icon-color: #51cd00;
}
[data-identity-color="yellow"] {
--identity-tab-color: #ffcb00;
--identity-icon-color: #ffcb00;
}
[data-identity-color="orange"] {
--identity-tab-color: #ff9f00;
--identity-icon-color: #ff9f00;
}
[data-identity-color="red"] {
--identity-tab-color: #ff613d;
--identity-icon-color: #ff613d;
}
[data-identity-color="pink"] {
--identity-tab-color: #ff4bda;
--identity-icon-color: #ff4bda;
}
[data-identity-color="purple"] {
--identity-tab-color: #af51f5;
--identity-icon-color: #af51f5;
}
[data-identity-icon="fingerprint"] {
--identity-icon: url("/img/usercontext.svg#fingerprint");
}
[data-identity-icon="briefcase"] {
--identity-icon: url("/img/usercontext.svg#briefcase");
}
[data-identity-icon="dollar"] {
--identity-icon: url("/img/usercontext.svg#dollar");
}
[data-identity-icon="cart"] {
--identity-icon: url("/img/usercontext.svg#cart");
}
[data-identity-icon="circle"] {
--identity-icon: url("/img/usercontext.svg#circle");
}
[data-identity-icon="food"] {
--identity-icon: url("/img/usercontext.svg#food");
}
[data-identity-icon="gift"] {
--identity-icon: url("/img/usercontext.svg#gift");
}
[data-identity-icon="vacation"] {
--identity-icon: url("/img/usercontext.svg#vacation");
}
[data-identity-icon="fruit"] {
--identity-icon: url("/img/usercontext.svg#fruit");
}
[data-identity-icon="pet"] {
--identity-icon: url("/img/usercontext.svg#pet");
}
[data-identity-icon="tree"] {
--identity-icon: url("/img/usercontext.svg#tree");
}
[data-identity-icon="chill"] {
--identity-icon: url("/img/usercontext.svg#chill");
}
/* Buttons */
.button.primary {
background-color: #0996f8;
color: white;
}
.button.primary:hover {
background-color: #0675d3;
}
.button.secondary:hover {
background-color: rgba(0, 0, 0, 0.05);
}
/* Panels keep everything togethert */
.panel {
display: flex;
flex-direction: column;
justify-content: space-between;
min-block-size: 400px;
}
.panel.onboarding {
align-items: center;
block-size: 360px;
margin-block-end: 16px;
margin-block-start: 16px;
margin-inline-end: 16px;
margin-inline-start: 16px;
min-block-size: 360px;
}
.panel .columns {
display: flex;
flex: 1;
}
.panel-content {
flex: 1;
}
/* Column panels for edit screens */
.column-panel-content {
display: flex;
flex-direction: column;
inline-size: 268px;
}
.column-panel-content .panel-footer {
align-items: center;
display: flex;
justify-content: center;
}
.column-panel-content .button,
.panel-footer .button {
align-items: center;
block-size: 54px;
display: flex;
flex: 1;
justify-content: center;
}
/* Column panels have a special back arrow */
.panel-back-arrow {
align-items: center;
background: #ebebeb;
box-shadow: inset -2px 0 4px -2px rgba(0, 0, 0, 0.4);
display: flex;
flex: 0 0 32px;
flex-direction: column;
justify-content: center;
}
.panel-back-arrow:hover {
background: #dedede;
}
.back-arrow-img {
block-size: 16px;
inline-size: 16px;
transform: rotate(180deg);
}
/* Onboarding styles */
.onboarding * {
text-align: center;
}
.onboarding-img {
block-size: 132px;
inline-size: 180px;
}
.onboarding-title {
color: #43484e;
font-size: 16px;
margin-block-end: 0;
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
max-inline-size: 80%;
}
.onboarding p {
color: #4a4a4a;
font-size: 14px;
margin-block-end: 16px;
max-inline-size: 84%;
}
.onboarding-button {
align-items: center;
background-color: #0996f8;
border-radius: 3px;
color: white;
display: flex;
flex: 0 0 44px;
font-size: 14px;
inline-size: 100%;
justify-content: center;
text-decoration: none;
transition: background-color 75ms;
}
.onboarding-button:hover {
background-color: #0675d3;
}
/* Pop buttons are the square shaped buttons used to
manage things like container crud */
.pop-button {
align-items: center;
block-size: 48px;
display: flex;
flex: 0 0 48px;
justify-content: center;
}
.pop-button:hover,
.panel-footer-secondary:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.pop-button:active,
.panel-footer-secondary:active {
background-color: rgba(0, 0, 0, 0.08);
}
.pop-button-image {
block-size: 20px;
flex: 0 0 20px;
}
.pop-button-image-small {
block-size: 12px;
flex: 0 0 12px;
}
/* Panel Header */
.panel-header {
align-items: center;
block-size: 48px;
border-block-end: 1px solid #ebebeb;
display: flex;
justify-content: space-between;
}
.column-panel-content .panel-header {
flex: 0 0 48px;
inline-size: 100%;
}
.panel-header-text {
color: #4a4a4a;
flex: 1;
font-size: 16px;
font-weight: normal;
margin-block-end: 0;
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
padding-block-end: 16px;
padding-block-start: 16px;
padding-inline-end: 16px;
padding-inline-start: 16px;
}
span ~ .panel-header-text {
padding-block-end: 0;
padding-block-start: 0;
padding-inline-end: 0;
padding-inline-start: 0;
}
/* Rows used when iterating over panels */
.container-panel-row {
align-items: center;
background-color: #fefefe !important;
block-size: 48px;
border-block-end: 1px solid #f1f1f1;
box-sizing: border-box;
display: flex;
justify-content: space-between;
}
.container-panel-row .container-name {
max-inline-size: 160px;
overflow: hidden;
padding-inline-end: 4px;
padding-inline-start: 4px;
text-overflow: ellipsis;
white-space: nowrap;
}
.edit-containers-panel .userContext-wrapper {
max-inline-size: 204px;
}
.userContext-wrapper {
align-items: center;
display: flex;
flex: 1 1;
transition: background-color 75ms;
}
.clickable.userContext-wrapper:hover {
background: #f2f2f2;
}
.userContext-icon-wrapper {
block-size: 48px;
flex: 0 0 48px;
}
/* .userContext-icon is used natively, Bug 1333811 was raised to fix */
.userContext-icon,
.usercontext-icon {
background-image: var(--identity-icon);
background-position: center center;
background-repeat: no-repeat;
background-size: 20px 20px;
block-size: 48px;
fill: var(--identity-icon-color);
filter: url('/img/filters.svg#fill');
flex: 0 0 48px;
}
.clickable:hover .userContext-icon,
.clickable:hover .usercontext-icon {
background-image: url('/img/container-newtab.svg');
fill: 'gray';
filter: url('/img/filters.svg#fill');
}
/* Panel Footer */
.panel-footer {
align-items: center;
background: #efefef;
block-size: 54px;
border-block-end: 1px solid #d8d8d8;
color: #000;
display: flex;
font-size: 13px;
inline-size: 100%;
justify-content: space-between;
}
.panel-footer .pop-button {
block-size: 54px;
flex: 0 0 54px;
}
.edit-containers-text {
align-items: center;
block-size: 54px;
border-inline-end: solid 1px #d8d8d8;
display: flex;
flex: 1;
justify-content: center;
}
.edit-containers-text a {
align-items: center;
block-size: 54px;
color: #0a0a0a;
display: flex;
flex: 1;
justify-content: center;
}
/* Container info list */
#container-info-name {
margin-inline-end: 0.5rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#container-info-hideorshow {
margin-block-start: 4px;
}
#container-info-movetabs-incompat {
font-size: 10px;
opacity: 0.3;
}
.container-info-tab-row:not(.clickable),
.select-row:not(.clickable) {
opacity: 0.3;
}
.container-info-has-tabs,
.container-info-tab-row {
align-items: center;
display: flex;
flex: 0 0 28px;
font-size: 14px;
justify-content: flex-start;
margin-block-end: 0;
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
padding-inline-end: 16px;
padding-inline-start: 16px;
}
.container-info-has-tabs img,
.container-info-tab-row img {
block-size: 16px;
flex: 0 0 16px;
margin-inline-end: 4px;
}
.container-info-tab-row img[src=""] {
margin-inline-end: 0;
}
.container-info-tab-row td {
max-inline-size: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.container-info-list {
border-block-start: 1px solid #ebebeb;
display: flex;
flex-direction: column;
margin-block-start: 4px;
padding-block-start: 4px;
}
.clickable {
cursor: pointer;
}
.clickable:hover {
background-color: #ebebeb;
}
.edit-containers-exit-text {
align-items: center;
background: #248aeb;
block-size: 100%;
color: #fff;
display: flex;
flex: 1;
justify-content: center;
}
.exit-edit-mode-link::before {
background: url('/img/container-arrow.svg') no-repeat;
block-size: 16px;
content: "";
display: block;
filter: grayscale(100%) brightness(5);
float: left;
inline-size: 16px;
margin-inline-end: 5px;
transform: scaleX(-1);
}
.delete-container-confirm {
padding-inline-end: 20px;
padding-inline-start: 20px;
}
.delete-container-confirm-title {
color: #000;
font-size: 16px;
}
/* Form info */
.column-panel-content form {
flex: 1;
padding-block-end: 16px;
padding-block-start: 16px;
padding-inline-end: 16px;
padding-inline-start: 16px;
}
.column-panel-content form span {
align-items: center;
block-size: 44px;
display: flex;
flex: 0 0 25%;
justify-content: center;
}
.edit-container-panel label {
background-image: var(--identity-icon);
background-size: 26px 26px;
block-size: 34px;
fill: var(--identity-icon-color);
filter: url('/img/filters.svg#fill');
flex: 0 0 34px;
position: relative;
}
.edit-container-panel label::before {
opacity: 0 !important;
}
.edit-container-panel [type="radio"] {
display: inline;
opacity: 0;
}
.edit-container-panel [type="radio"]:checked + label {
outline: 2px solid grey;
-moz-outline-radius: 50px;
}
.edit-container-panel fieldset {
background: none;
border: none;
display: flex;
flex-direction: row;
flex-wrap: wrap;
inline-size: 100%;
margin-block-end: 10px;
margin-inline-end: 0;
margin-inline-start: 0;
padding-block-end: 0;
padding-block-start: 0;
padding-inline-end: 0;
padding-inline-start: 0;
}
.edit-container-panel input[type="text"] {
block-size: 36px;
border-radius: 3px;
font-size: 14px;
inline-size: 100%;
padding-block-end: 5px;
padding-block-start: 5px;
padding-inline-end: 5px;
padding-inline-start: 5px;
}
.edit-container-panel legend {
flex: 1 0;
font-size: 14px !important;
padding-block-end: 5px;
}
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 11 11" style="enable-background:new 0 0 11 11;" xml:space="preserve">
<style type="text/css">
.st0{fill:#858585;}
</style>
<title>firefox</title>
<g id="General-icons">
<polygon class="st0" points="10.8,4.4 6.4,4.4 6.4,0.2 4.6,0.2 4.6,4.4 0.2,4.4 0.2,6.4 4.6,6.4 4.6,10.8 6.4,10.8 6.4,6.4
10.8,6.4 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 595 B

-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 6 8" style="enable-background:new 0 0 6 8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#4C4C4C;}
</style>
<polygon id="Arrow---Disclosure---Collapsed-Copy" class="st0" points="5.5,4 1.5,7.7 0.5,6.8 3.5,4 0.5,1.2 1.5,0.3 "/>
</svg>

Before

Width:  |  Height:  |  Size: 520 B

-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 12 12" style="enable-background:new 0 0 12 12;" xml:space="preserve">
<style type="text/css">
.st0{fill:#858585;}
</style>
<path class="st0" d="M4.6,0.3h2.7c0.1,0,0.2,0.1,0.2,0.2v1H4.4v-1C4.4,0.4,4.5,0.3,4.6,0.3z M1.7,1.5h8.6c0.1,0,0.2,0.1,0.2,0.2
l0.2,1.4H1.3l0.2-1.4C1.5,1.6,1.6,1.5,1.7,1.5z M6,11.7H3.2L2.1,3.9H6h3.9l-1.1,7.8H6L6,11.7z"/>
</svg>

Before

Width:  |  Height:  |  Size: 626 B

-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 12 12" style="enable-background:new 0 0 12 12;" xml:space="preserve">
<style type="text/css">
.st0{fill:#858585;}
</style>
<g>
<path class="st0" d="M11.4,2.6L9.6,0.9c-0.1-0.1-0.2-0.1-0.3,0l-7,7l2,2l7-7C11.4,2.8,11.4,2.7,11.4,2.6z"/>
<path class="st0" d="M0.8,10.9c-0.1,0.3,0,0.4,0.4,0.4l2.3-0.6l-2-2L0.8,10.9z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 603 B

-19
View File
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 12 12" style="enable-background:new 0 0 12 12;" xml:space="preserve">
<style type="text/css">
.st0{fill:#858585;}
</style>
<polygon class="st0" points="10.3,0.5 8.9,2 2,8.9 0.5,10.3 1.3,11.1 11.1,1.3 "/>
<g>
<path class="st0" d="M4.8,7.5l2.8-2.8C7.2,4.3,6.7,4,6.1,4c-1.1,0-2,0.9-2,2C4.1,6.6,4.4,7.1,4.8,7.5z"/>
<path class="st0" d="M5.9,7.9c0,0,0.1,0,0.2,0c1.1,0,2-0.9,2-2c0,0,0-0.1,0-0.2L5.9,7.9z"/>
</g>
<g>
<path class="st0" d="M4.1,8.2C2.6,7.4,1.3,6.1,1.2,6C1.3,5.8,3.8,3.1,6,3.1c0.8,0,1.6,0.4,2.4,0.8L9,3.3C8.1,2.7,7,2.2,6,2.2
c-2.6,0-5.4,2.9-5.5,3.1C0.3,5.6,0.2,5.7,0.2,6v0c0,0.2,0.1,0.4,0.2,0.6c0.1,0.1,1.3,1.4,2.9,2.3L4.1,8.2z"/>
<path class="st0" d="M9.9,3.9L9.2,4.5c0.9,0.7,1.5,1.3,1.6,1.4C10.6,6.2,8.1,8.8,6,8.8c-0.3,0-0.6,0-0.9-0.1L4.4,9.4
C4.9,9.6,5.5,9.7,6,9.7c2.6,0,5.4-2.9,5.5-3.1c0.2-0.2,0.2-0.4,0.2-0.6v0c0-0.2,0-0.4-0.2-0.6C11.4,5.3,10.8,4.6,9.9,3.9z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

-17
View File
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 18 18" style="enable-background:new 0 0 18 18;" xml:space="preserve">
<style type="text/css">
.st0{fill:#858585;}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#858585;}
</style>
<path class="st0" d="M11.9,7.9c-1.2,0-1.5-0.8-1.8-2.4C10,4.4,9.8,2.5,8,2.5H4.6c-1.8,0-2,1.9-2.1,2.9C2.2,7.1,1.9,7.9,0.7,7.9v1.9
h11.2h5.4V7.9H11.9z"/>
<path class="st0" d="M16,5.7c-0.1-1-0.3-2.9-2.1-2.9c0,0-3.8,0-3.9,0c-0.1,0-0.1,0.2-0.1,0.2c1.1,0.5,1.3,1.9,1.4,2.7
c0.1,1.1,0.3,1.5,0.8,1.5c0.1,0,4.1,0,4.1,0s0.1,0,0.1-0.1C16.2,6.6,16.1,6.2,16,5.7z"/>
<path class="st1" d="M8,12.1H3.7v-1.2c0-0.3-0.2-0.4-0.5-0.2l-2.2,1.9c-0.3,0.2-0.3,0.6,0,0.9l2.2,1.9c0.3,0.2,0.5,0.2,0.5-0.2v-1.2
H8V12.1z"/>
<path class="st1" d="M17.1,12.6l-2.2-1.9c-0.3-0.2-0.5-0.2-0.5,0.2v1.2H10v1.9h4.3v1.2c0,0.3,0.2,0.4,0.5,0.2l2.2-1.9
C17.4,13.2,17.4,12.8,17.1,12.6z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 12 12" style="enable-background:new 0 0 12 12;" xml:space="preserve">
<style type="text/css">
.st0{fill:#858585;}
</style>
<circle class="st0" cx="6" cy="6" r="2"/>
<path class="st0" d="M11.5,5.4C11.4,5.2,8.6,2.3,6,2.3s-5.4,3-5.5,3.1C0.3,5.6,0.2,5.8,0.2,6v0c0,0.2,0.1,0.4,0.2,0.6
C0.6,6.8,3.4,9.8,6,9.8s5.4-3,5.5-3.1c0.2-0.2,0.2-0.4,0.2-0.6v0C11.7,5.8,11.7,5.6,11.5,5.4z M10.8,6C10.6,6.2,8.1,8.9,6,8.9
S1.3,6.2,1.1,6l0,0C1.3,5.8,3.8,3.1,6,3.1S10.6,5.8,10.8,6L10.8,6z"/>
</svg>

Before

Width:  |  Height:  |  Size: 755 B

-30
View File
@@ -1,30 +0,0 @@
const redirectUrl = new URL(window.location).searchParams.get("url");
document.getElementById("redirect-url").textContent = redirectUrl;
const redirectSite = new URL(redirectUrl).hostname;
document.getElementById("redirect-site").textContent = redirectSite;
document.getElementById("redirect-form").addEventListener("submit", (e) => {
e.preventDefault();
const neverAsk = document.getElementById("never-ask").checked;
// Sending neverAsk message to background to store for next time we see this process
if (neverAsk) {
browser.runtime.sendMessage({
neverAsk: true,
pageUrl: redirectUrl
}).then(() => {
redirect();
}).catch(() => {
// Can't really do much here user will have to click it again
});
}
browser.runtime.sendMessage({
method: "sendTelemetryPayload",
event: "click-to-reload-page-in-container",
});
redirect();
});
function redirect() {
const redirectUrl = document.getElementById("redirect-url").textContent;
document.location.replace(redirectUrl);
}
-634
View File
@@ -1,634 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
const CONTAINER_HIDE_SRC = "/img/container-hide.svg";
const CONTAINER_UNHIDE_SRC = "/img/container-unhide.svg";
const DEFAULT_COLOR = "blue";
const DEFAULT_ICON = "circle";
// List of panels
const P_ONBOARDING_1 = "onboarding1";
const P_ONBOARDING_2 = "onboarding2";
const P_ONBOARDING_3 = "onboarding3";
const P_CONTAINERS_LIST = "containersList";
const P_CONTAINERS_EDIT = "containersEdit";
const P_CONTAINER_INFO = "containerInfo";
const P_CONTAINER_EDIT = "containerEdit";
const P_CONTAINER_DELETE = "containerDelete";
/**
* Escapes any occurances of &, ", <, > or / with XML entities.
*
* @param {string} str
* The string to escape.
* @return {string} The escaped string.
*/
function escapeXML(str) {
const replacements = {"&": "&amp;", "\"": "&quot;", "'": "&apos;", "<": "&lt;", ">": "&gt;", "/": "&#x2F;"};
return String(str).replace(/[&"'<>/]/g, m => replacements[m]);
}
/**
* A tagged template function which escapes any XML metacharacters in
* interpolated values.
*
* @param {Array<string>} strings
* An array of literal strings extracted from the templates.
* @param {Array} values
* An array of interpolated values extracted from the template.
* @returns {string}
* The result of the escaped values interpolated with the literal
* strings.
*/
function escaped(strings, ...values) {
const result = [];
for (const [i, string] of strings.entries()) {
result.push(string);
if (i < values.length)
result.push(escapeXML(values[i]));
}
return result.join("");
}
// This object controls all the panels, identities and many other things.
const Logic = {
_identities: [],
_currentIdentity: null,
_currentPanel: null,
_previousPanel: null,
_panels: {},
init() {
// Retrieve the list of identities.
this.refreshIdentities()
// Routing to the correct panel.
.then(() => {
// If localStorage is disabled, we don't show the onboarding.
if (!localStorage || localStorage.getItem("onboarded3")) {
this.showPanel(P_CONTAINERS_LIST);
} else if (localStorage.getItem("onboarded2")) {
this.showPanel(P_ONBOARDING_3);
} else if (localStorage.getItem("onboarded1")) {
this.showPanel(P_ONBOARDING_2);
} else {
this.showPanel(P_ONBOARDING_1);
}
})
.catch(() => {
throw new Error("Failed to retrieve the identities. We cannot continue.");
});
},
refreshIdentities() {
return browser.runtime.sendMessage({
method: "queryIdentities"
})
.then(identities => {
this._identities = identities;
});
},
showPanel(panel, currentIdentity = null) {
// Invalid panel... ?!?
if (!(panel in this._panels)) {
throw new Error("Something really bad happened. Unknown panel: " + panel);
}
this._previousPanel = this._currentPanel;
this._currentPanel = panel;
this._currentIdentity = currentIdentity;
// Initialize the panel before showing it.
this._panels[panel].prepare().then(() => {
for (let panelElement of document.querySelectorAll(".panel")) { // eslint-disable-line prefer-const
panelElement.classList.add("hide");
}
document.querySelector(this._panels[panel].panelSelector).classList.remove("hide");
})
.catch(() => {
throw new Error("Failed to show panel " + panel);
});
},
showPreviousPanel() {
if (!this._previousPanel) {
throw new Error("Current panel not set!");
}
this.showPanel(this._previousPanel, this._currentIdentity);
},
registerPanel(panelName, panelObject) {
this._panels[panelName] = panelObject;
panelObject.initialize();
},
identities() {
return this._identities;
},
currentIdentity() {
if (!this._currentIdentity) {
throw new Error("CurrentIdentity must be set before calling Logic.currentIdentity.");
}
return this._currentIdentity;
},
generateIdentityName() {
const defaultName = "Container #";
const ids = [];
// This loop populates the 'ids' array with all the already-used ids.
this._identities.forEach(identity => {
if (identity.name.startsWith(defaultName)) {
const id = parseInt(identity.name.substr(defaultName.length), 10);
if (id) {
ids.push(id);
}
}
});
// Here we find the first valid id.
for (let id = 1;; ++id) {
if (ids.indexOf(id) === -1) {
return defaultName + (id < 10 ? "0" : "") + id;
}
}
},
};
// P_ONBOARDING_1: First page for Onboarding.
// ----------------------------------------------------------------------------
Logic.registerPanel(P_ONBOARDING_1, {
panelSelector: ".onboarding-panel-1",
// This method is called when the object is registered.
initialize() {
// Let's move to the next panel.
document.querySelector("#onboarding-start-button").addEventListener("click", () => {
localStorage.setItem("onboarded1", true);
Logic.showPanel(P_ONBOARDING_2);
});
},
// This method is called when the panel is shown.
prepare() {
return Promise.resolve(null);
},
});
// P_ONBOARDING_2: Second page for Onboarding.
// ----------------------------------------------------------------------------
Logic.registerPanel(P_ONBOARDING_2, {
panelSelector: ".onboarding-panel-2",
// This method is called when the object is registered.
initialize() {
// Let's move to the containers list panel.
document.querySelector("#onboarding-next-button").addEventListener("click", () => {
localStorage.setItem("onboarded2", true);
Logic.showPanel(P_ONBOARDING_3);
});
},
// This method is called when the panel is shown.
prepare() {
return Promise.resolve(null);
},
});
// P_ONBOARDING_3: Third page for Onboarding.
// ----------------------------------------------------------------------------
Logic.registerPanel(P_ONBOARDING_3, {
panelSelector: ".onboarding-panel-3",
// This method is called when the object is registered.
initialize() {
// Let's move to the containers list panel.
document.querySelector("#onboarding-done-button").addEventListener("click", () => {
localStorage.setItem("onboarded3", true);
Logic.showPanel(P_CONTAINERS_LIST);
});
},
// This method is called when the panel is shown.
prepare() {
return Promise.resolve(null);
},
});
// P_CONTAINERS_LIST: The list of containers. The main page.
// ----------------------------------------------------------------------------
Logic.registerPanel(P_CONTAINERS_LIST, {
panelSelector: "#container-panel",
// This method is called when the object is registered.
initialize() {
document.querySelector("#container-add-link").addEventListener("click", () => {
Logic.showPanel(P_CONTAINER_EDIT, { name: Logic.generateIdentityName() });
});
document.querySelector("#edit-containers-link").addEventListener("click", () => {
browser.runtime.sendMessage({
method: "sendTelemetryPayload",
event: "edit-containers"
});
Logic.showPanel(P_CONTAINERS_EDIT);
});
document.querySelector("#sort-containers-link").addEventListener("click", () => {
browser.runtime.sendMessage({
method: "sortTabs"
}).then(() => {
window.close();
}).catch(() => {
window.close();
});
});
},
// This method is called when the panel is shown.
prepare() {
const fragment = document.createDocumentFragment();
Logic.identities().forEach(identity => {
const hasTabs = (identity.hasHiddenTabs || identity.hasOpenTabs);
const tr = document.createElement("tr");
const context = document.createElement("td");
const manage = document.createElement("td");
tr.classList.add("container-panel-row");
context.classList.add("userContext-wrapper", "open-newtab", "clickable");
manage.classList.add("show-tabs", "pop-button");
context.innerHTML = escaped`
<div class="userContext-icon-wrapper open-newtab">
<div class="userContext-icon"
data-identity-icon="${identity.image}"
data-identity-color="${identity.color}">
</div>
</div>
<div class="container-name"></div>`;
context.querySelector(".container-name").textContent = identity.name;
manage.innerHTML = "<img src='/img/container-arrow.svg' class='show-tabs pop-button-image-small' />";
fragment.appendChild(tr);
tr.appendChild(context);
if (hasTabs) {
tr.appendChild(manage);
}
tr.addEventListener("click", e => {
if (e.target.matches(".open-newtab") || e.target.parentNode.matches(".open-newtab")) {
browser.runtime.sendMessage({
method: "openTab",
userContextId: identity.userContextId,
source: "pop-up"
}).then(() => {
window.close();
}).catch(() => {
window.close();
});
} else if (hasTabs) {
Logic.showPanel(P_CONTAINER_INFO, identity);
}
});
});
const list = document.querySelector(".identities-list");
list.innerHTML = "";
list.appendChild(fragment);
return Promise.resolve();
},
});
// P_CONTAINER_INFO: More info about a container.
// ----------------------------------------------------------------------------
Logic.registerPanel(P_CONTAINER_INFO, {
panelSelector: "#container-info-panel",
// This method is called when the object is registered.
initialize() {
document.querySelector("#close-container-info-panel").addEventListener("click", () => {
Logic.showPreviousPanel();
});
document.querySelector("#container-info-hideorshow").addEventListener("click", () => {
const identity = Logic.currentIdentity();
browser.runtime.sendMessage({
method: identity.hasHiddenTabs ? "showTabs" : "hideTabs",
userContextId: identity.userContextId
}).then(() => {
window.close();
}).catch(() => {
window.close();
});
});
// Check if the user has incompatible add-ons installed
browser.runtime.sendMessage({
method: "checkIncompatibleAddons"
}).then(incompatible => {
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 {
moveTabsEl.addEventListener("click", () => {
return browser.runtime.sendMessage({
method: "moveTabsToWindow",
userContextId: Logic.currentIdentity().userContextId,
}).then(() => {
window.close();
});
});
}
}).catch(() => {
throw new Error("Could not check for incompatible add-ons.");
});
},
// This method is called when the panel is shown.
prepare() {
const identity = Logic.currentIdentity();
// Populating the panel: name and icon
document.getElementById("container-info-name").textContent = identity.name;
const icon = document.getElementById("container-info-icon");
icon.setAttribute("data-identity-icon", identity.image);
icon.setAttribute("data-identity-color", identity.color);
// Show or not the has-tabs section.
for (let trHasTabs of document.getElementsByClassName("container-info-has-tabs")) { // eslint-disable-line prefer-const
trHasTabs.style.display = !identity.hasHiddenTabs && !identity.hasOpenTabs ? "none" : "";
}
const hideShowIcon = document.getElementById("container-info-hideorshow-icon");
hideShowIcon.src = identity.hasHiddenTabs ? CONTAINER_UNHIDE_SRC : CONTAINER_HIDE_SRC;
const hideShowLabel = document.getElementById("container-info-hideorshow-label");
hideShowLabel.textContent = identity.hasHiddenTabs ? "Show this container" : "Hide this container";
// Let's remove all the previous tabs.
const table = document.getElementById("container-info-table");
while (table.firstChild) {
table.firstChild.remove();
}
// Let's retrieve the list of tabs.
return browser.runtime.sendMessage({
method: "getTabs",
userContextId: identity.userContextId,
}).then(this.buildInfoTable);
},
buildInfoTable(tabs) {
// For each one, let's create a new line.
const fragment = document.createDocumentFragment();
for (let tab of tabs) { // eslint-disable-line prefer-const
const tr = document.createElement("tr");
fragment.appendChild(tr);
tr.classList.add("container-info-tab-row");
tr.innerHTML = escaped`
<td><img class="icon" src="${tab.favicon}" /></td>
<td class="container-info-tab-title">${tab.title}</td>`;
// On click, we activate this tab. But only if this tab is active.
if (tab.active) {
tr.classList.add("clickable");
tr.addEventListener("click", () => {
browser.runtime.sendMessage({
method: "showTab",
tabId: tab.id,
}).then(() => {
window.close();
}).catch(() => {
window.close();
});
});
}
}
document.getElementById("container-info-table").appendChild(fragment);
},
});
// P_CONTAINERS_EDIT: Makes the list editable.
// ----------------------------------------------------------------------------
Logic.registerPanel(P_CONTAINERS_EDIT, {
panelSelector: "#edit-containers-panel",
// This method is called when the object is registered.
initialize() {
document.querySelector("#exit-edit-mode-link").addEventListener("click", () => {
Logic.showPanel(P_CONTAINERS_LIST);
});
},
// This method is called when the panel is shown.
prepare() {
const fragment = document.createDocumentFragment();
Logic.identities().forEach(identity => {
const tr = document.createElement("tr");
fragment.appendChild(tr);
tr.classList.add("container-panel-row");
tr.innerHTML = escaped`
<td class="userContext-wrapper">
<div class="userContext-icon-wrapper">
<div class="userContext-icon"
data-identity-icon="${identity.image}"
data-identity-color="${identity.color}">
</div>
</div>
<div class="container-name"></div>
</td>
<td class="edit-container pop-button edit-container-icon">
<img
src="/img/container-edit.svg"
class="pop-button-image" />
</td>
<td class="remove-container pop-button delete-container-icon" >
<img
class="pop-button-image"
src="/img/container-delete.svg"
/>
</td>`;
tr.querySelector(".container-name").textContent = identity.name;
tr.querySelector(".edit-container .pop-button-image").setAttribute("title", `Edit ${identity.name} container`);
tr.querySelector(".remove-container .pop-button-image").setAttribute("title", `Edit ${identity.name} container`);
tr.addEventListener("click", e => {
if (e.target.matches(".edit-container-icon") || e.target.parentNode.matches(".edit-container-icon")) {
Logic.showPanel(P_CONTAINER_EDIT, identity);
} else if (e.target.matches(".delete-container-icon") || e.target.parentNode.matches(".delete-container-icon")) {
Logic.showPanel(P_CONTAINER_DELETE, identity);
}
});
});
const list = document.querySelector("#edit-identities-list");
list.innerHTML = "";
list.appendChild(fragment);
return Promise.resolve(null);
},
});
// P_CONTAINER_EDIT: Editor for a container.
// ----------------------------------------------------------------------------
Logic.registerPanel(P_CONTAINER_EDIT, {
panelSelector: "#edit-container-panel",
// This method is called when the object is registered.
initialize() {
this.initializeRadioButtons();
document.querySelector("#edit-container-panel-back-arrow").addEventListener("click", () => {
Logic.showPreviousPanel();
});
document.querySelector("#edit-container-cancel-link").addEventListener("click", () => {
Logic.showPreviousPanel();
});
this._editForm = document.getElementById("edit-container-panel-form");
const editLink = document.querySelector("#edit-container-ok-link");
editLink.addEventListener("click", this._submitForm.bind(this));
editLink.addEventListener("submit", this._submitForm.bind(this));
this._editForm.addEventListener("submit", this._submitForm.bind(this));
},
_submitForm() {
const identity = Logic.currentIdentity();
const formValues = new FormData(this._editForm);
browser.runtime.sendMessage({
method: identity.userContextId ? "updateIdentity" : "createIdentity",
userContextId: identity.userContextId || 0,
name: document.getElementById("edit-container-panel-name-input").value || Logic.generateIdentityName(),
icon: formValues.get("container-icon") || DEFAULT_ICON,
color: formValues.get("container-color") || DEFAULT_COLOR,
}).then(() => {
return Logic.refreshIdentities();
}).then(() => {
Logic.showPreviousPanel();
}).catch(() => {
Logic.showPanel(P_CONTAINERS_LIST);
});
},
initializeRadioButtons() {
const colorRadioTemplate = (containerColor) => {
return escaped`<input type="radio" value="${containerColor}" name="container-color" id="edit-container-panel-choose-color-${containerColor}" />
<label for="edit-container-panel-choose-color-${containerColor}" class="usercontext-icon choose-color-icon" data-identity-icon="circle" data-identity-color="${containerColor}">`;
};
const colors = ["blue", "turquoise", "green", "yellow", "orange", "red", "pink", "purple" ];
const colorRadioFieldset = document.getElementById("edit-container-panel-choose-color");
colors.forEach((containerColor) => {
const templateInstance = document.createElement("span");
// eslint-disable-next-line no-unescaped/enforce
templateInstance.innerHTML = colorRadioTemplate(containerColor);
colorRadioFieldset.appendChild(templateInstance);
});
const iconRadioTemplate = (containerIcon) => {
return escaped`<input type="radio" value="${containerIcon}" name="container-icon" id="edit-container-panel-choose-icon-${containerIcon}" />
<label for="edit-container-panel-choose-icon-${containerIcon}" class="usercontext-icon choose-color-icon" data-identity-color="grey" data-identity-icon="${containerIcon}">`;
};
const icons = ["fingerprint", "briefcase", "dollar", "cart", "vacation", "gift", "food", "fruit", "pet", "tree", "chill", "circle"];
const iconRadioFieldset = document.getElementById("edit-container-panel-choose-icon");
icons.forEach((containerIcon) => {
const templateInstance = document.createElement("span");
// eslint-disable-next-line no-unescaped/enforce
templateInstance.innerHTML = iconRadioTemplate(containerIcon);
iconRadioFieldset.appendChild(templateInstance);
});
},
// This method is called when the panel is shown.
prepare() {
const identity = Logic.currentIdentity();
document.querySelector("#edit-container-panel-name-input").value = identity.name || "";
[...document.querySelectorAll("[name='container-color']")].forEach(colorInput => {
colorInput.checked = colorInput.value === identity.color;
});
[...document.querySelectorAll("[name='container-icon']")].forEach(iconInput => {
iconInput.checked = iconInput.value === identity.image;
});
return Promise.resolve(null);
},
});
// P_CONTAINER_DELETE: Delete a container.
// ----------------------------------------------------------------------------
Logic.registerPanel(P_CONTAINER_DELETE, {
panelSelector: "#delete-container-panel",
// This method is called when the object is registered.
initialize() {
document.querySelector("#delete-container-cancel-link").addEventListener("click", () => {
Logic.showPreviousPanel();
});
document.querySelector("#delete-container-ok-link").addEventListener("click", () => {
browser.runtime.sendMessage({
method: "removeIdentity",
userContextId: Logic.currentIdentity().userContextId,
}).then(() => {
return Logic.refreshIdentities();
}).then(() => {
Logic.showPreviousPanel();
}).catch(() => {
Logic.showPanel(P_CONTAINERS_LIST);
});
});
},
// This method is called when the panel is shown.
prepare() {
const identity = Logic.currentIdentity();
// Populating the panel: name and icon
document.getElementById("delete-container-name").textContent = identity.name;
const icon = document.getElementById("delete-container-icon");
icon.setAttribute("data-identity-icon", identity.image);
icon.setAttribute("data-identity-color", identity.color);
return Promise.resolve(null);
},
});
Logic.init();
-48
View File
@@ -1,48 +0,0 @@
{
"manifest_version": 2,
"name": "Containers Experiment",
"version": "2.2.0",
"description": "Containers works by isolating cookie jars using separate origin-attributes defined visually by colored Container Tabs. This add-on is a modified version of the containers feature for Firefox Test Pilot.",
"icons": {
"48": "img/container-site-d-48.png",
"96": "img/container-site-d-96.png"
},
"applications": {
"gecko": {
"strict_min_version": "51.0",
"update_url": "https://testpilot.firefox.com/files/@testpilot-containers/updates.json"
}
},
"homepage_url": "https://testpilot.firefox.com/",
"permissions": [
"<all_urls>",
"activeTab",
"cookies",
"contextMenus",
"history",
"idle",
"notifications",
"storage",
"tabs",
"webRequestBlocking",
"webRequest"
],
"browser_action": {
"browser_style": true,
"default_icon": {
"16": "img/container-site-d-24.png",
"32": "img/container-site-d-48.png"
},
"default_title": "Containers",
"default_popup": "popup.html"
},
"background": {
"scripts": ["background.js"]
}
}
-137
View File
@@ -1,137 +0,0 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Containers browserAction Popup</title>
<link rel="stylesheet" href="/css/popup.css">
</head>
<body>
<div class="hide panel onboarding onboarding-panel-1" id="onboarding-panel-1">
<img class="onboarding-img" alt="Container Tabs Overview" src="/img/onboarding-1.png" />
<h3 class="onboarding-title">A better way to manage all the things you do online</h3>
<p>
Use containers to organize tasks, manage accounts, and keep your focus where you want it.
</p>
<a href="#" id="onboarding-start-button" class="onboarding-button">Get Started</a>
</div>
<div class="panel onboarding onboarding-panel-2 hide" id="onboarding-panel-2">
<img class="onboarding-img" alt="How Containers Work" src="/img/onboarding-2.png" />
<h3 class="onboarding-title">Put containers to work for you.</h3>
<p>Features like color-coding and separate container tabs help you find things easily, focus your attention, and minimize distractions.</p>
<a href="#" id="onboarding-next-button" class="onboarding-button">Next</a>
</div>
<div class="panel onboarding onboarding-panel-3 hide" id="onboarding-panel-3">
<img class="onboarding-img" alt="How Containers Work" src="/img/onboarding-3.png" />
<h3 class="onboarding-title">A place for everything, and everything in its place.</h3>
<p>Start with the containers we've created, or create your own.</p>
<a href="#" id="onboarding-done-button" class="onboarding-button">Done</a>
</div>
<div class="panel container-panel hide" id="container-panel">
<div class="panel-header">
<h3 class="panel-header-text">Containers</h3>
<a class="pop-button" id="sort-containers-link"><img class="pop-button-image" alt="Sort Containers" title="Sort Containers" src="/img/container-sort.svg"></a>
</div>
<div class="scrollable panel-content">
<table>
<tbody class="identities-list"></tbody>
</table>
</div>
<div class="panel-footer edit-identities">
<div class="edit-containers-text panel-footer-secondary">
<a id="edit-containers-link">Edit Containers</a>
</div>
<a class="add-container-link pop-button" id="container-add-link">
<img class="pop-button-image-small icon" alt="Create new container icon" title="Create new container" src="/img/container-add.svg" />
</a>
</div>
</div>
<div class="hide panel container-info-panel" id="container-info-panel">
<div class="columns">
<div class="panel-back-arrow" id="close-container-info-panel">
<img alt="Panel Back Arrow" src="/img/container-arrow.svg" class="back-arrow-img" />
</div>
<div class="column-panel-content">
<div class="panel-header container-info-panel-header">
<span class="usercontext-icon" id="container-info-icon"></span>
<h3 id="container-info-name" class="panel-header-text container-name"></h3>
</div>
<div class="select-row clickable container-info-panel-hide container-info-has-tabs" id="container-info-hideorshow">
<img id="container-info-hideorshow-icon" alt="Hide Container icon" src="/img/container-hide.svg" class="icon container-info-panel-hideorshow-icon"/>
<span id="container-info-hideorshow-label">Hide this container</span>
</div>
<div class="select-row clickable container-info-panel-movetabs container-info-has-tabs" id="container-info-movetabs">Move tabs to a new window</div>
<div class="scrollable">
<table id="container-info-table" class="container-info-list">
</table>
</div>
</div>
</div>
</div>
<div class="panel edit-containers-panel hide" id="edit-containers-panel">
<div class="panel-header">
<h3 class="panel-header-text">Edit Containers</h3>
</div>
<div class="scrollable panel-content">
<table class="unstriped">
<tbody id="edit-identities-list"></tbody>
</table>
</div>
<div class="panel-footer edit-containers-panel-footer">
<a id="exit-edit-mode-link" class="exit-edit-mode-link edit-containers-exit-text">Exit Edit Mode</a>
</div>
</div>
<div class="panel edit-container-panel hide" id="edit-container-panel">
<div class="columns">
<div class="panel-back-arrow" id="edit-container-panel-back-arrow">
<img alt="Panel Back Arrow" src="/img/container-arrow.svg" class="back-arrow-img" />
</div>
<div class="column-panel-content">
<form id="edit-container-panel-form">
<fieldset>
<legend>Name</legend>
<input type="text" name="container-name" id="edit-container-panel-name-input" maxlength="25"/>
</fieldset>
<fieldset id="edit-container-panel-choose-color">
<legend>Choose a color</legend>
</fieldset>
<fieldset id="edit-container-panel-choose-icon">
<legend>Choose an icon</legend>
</fieldset>
</form>
<div class="panel-footer">
<a class="button secondary expanded footer-button cancel-button" id="edit-container-cancel-link">Cancel</a>
<a class="button primary expanded footer-button" id="edit-container-ok-link">OK</a>
</div>
</div>
</div>
</div>
<div class="hide panel delete-container-panel" id="delete-container-panel">
<div class="panel-header">
<span class="usercontext-icon" id="delete-container-icon"></span>
<h3 id="delete-container-name" class="panel-header-text container-name"></h3>
</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>
</div>
<div class="panel-footer">
<a class="button expanded secondary footer-button cancel-button" id="delete-container-cancel-link">Cancel</a>
<a class="button expanded primary footer-button" id="delete-container-ok-link">OK</a>
</div>
</div>
<script src="js/popup.js"></script>
</body>
</html>