Compare commits

..

76 commits

Author SHA1 Message Date
Danny Colin
aca51cc11c
Merge pull request #2755 from apostrophest/eslint-ecmascript-2021
Increase eslint ecmaVersion to 2021
2025-05-20 15:09:33 -04:00
Danny Colin
b684ce7016
Merge pull request #2764 from apostrophest/version-upgrade-8.3.0
Version upgrade 8.3.0
2025-05-06 15:24:43 -04:00
Stephen Thompson
115d411218 Version upgrade 8.3.0
## IMPORTANT NOTE

Version 8.2.0 of this add-on configured Ctrl + Comma as the default keyboard shortcut for sorting the tab strip by container. This keyboard shortcut was removed from 8.3.0 in patch #2758. If you use Ctrl + Comma in order to quickly sort tabs by container, then you will need to explicitly reconfigure the keyboard shortcut. https://support.mozilla.org/en-US/kb/manage-extension-shortcuts-firefox

## Features
- #2753 Avoid sorting tabs in Firefox tab groups
- #2758 Removed suggested keyboard shortcut for tab sorting
- #2722 Removed Mozilla VPN logo banner

## Bugs
- #2572 Fixed add/remove site assignments logic bug
- #2754 Fixed "open/reopen in container" bug that would reopen outside of a Firefox tab group
- #2760 Removed console log spam related to context menu cleanup

## Developer
- #2671 Updated contributor documentation with tips and corrected links
- #2723 Updated GitHub Actions build image
2025-05-06 12:04:57 -04:00
Danny Colin
aec2aa5fb0
Merge pull request #2722 from mozilla/basti/remove_vpn_banner
Remove the Mozilla-VPN Banner Ad
2025-04-30 22:00:04 -04:00
Sebastian Streich
69ee83bbf6 Remove the Mozilla-VPN Banner Ad 2025-04-30 21:57:36 -04:00
Danny Colin
9434147b48
Merge pull request #2758 from apostrophest/remove-sort-tabs-suggested-key
Remove suggested key for `sort_tabs`
2025-04-30 18:13:08 -04:00
Danny Colin
d82341ce4a
Merge pull request #2760 from Rob--W/logspam-contextMenus.remove
Avoid logspam: "Cannot find menu item with id ..."
2025-04-30 18:09:22 -04:00
Rob Wu
60a6666222 Avoid logspam: "Cannot find menu item with id ..."
The extension frequently tries to remove context menus that do not
exists, which results in errors like:

> Error: Cannot find menu item with id firefox-container-1

because starting from Firefox 136, the contextMenus.remove method
rejects if the menu item does not exist. To avoid logspam, catch it.

References:

- https://bugzilla.mozilla.org/show_bug.cgi?id=1688743
- https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/136#changes_for_add-on_developers
2025-04-30 00:51:11 +02:00
Stephen Thompson
f1a24ed6fb Address code review comments for #2758
- Use `commands.reset` insead of `commands.update`
- Do not swallow errors
2025-04-29 16:36:19 -04:00
Danny Colin
0372abdc33
Merge pull request #2754 from apostrophest/issue-2747-reopen-in-container-tab-groups
Fix #2747: open/reopen container tabs in tab groups when appropriate
2025-04-28 11:04:18 -04:00
Danny Colin
366a50c8b6
Merge pull request #2753 from apostrophest/issue-2746-sort-ungrouped-tabs-only
Fix #2746: sort only ungrouped tabs
2025-04-28 11:03:46 -04:00
Stephen Thompson
e96b275e02 Remove suggested key for sort_tabs
A number of users have accidentally pressed Ctrl + Comma and sorted their tabs by container. For some of those users, they did not know what had happened.

#2492 added Ctrl + Comma as a default shortcut key for sorting tabs by container. This change was released with addon v8.2.0 in Sept 2024.

It is useful to make the sort-tabs-by-container operation accessible by keyboard shortcut, but I think Ctrl + Comma is too close to the main Ctrl + Period shortcut for this addon. I think it's reasonable to make the default sort_tabs keyboard shortcut more complex, but in this patch, I'm recommending that we:

1. do not set a shortcut by default for any future users. Users can still configure a shortcut via Firefox's Manage Extension Shortcuts UI.
2. for existing users upgrading from 8.2.0 who configured their own, non-default keyboard shortcut for sort_tabs, keep their shortcut in place. These users demonstrated a strong interest in using the sort_tabs feature and I do not want to interfere.
3. for existing users upgrading from 8.2.0 who have the default keyboard shortcut configured, clear the shortcut. Users who did not use the sort_tabs keyboard shortcut will no longer be at risk of accidentally using it. Users who did use the default sort_tabs keyboard shortcut will be harmed, but those users can use the Manage Extension Shortcuts UI to manually configure Ctrl + Comma (or another key combination).

I am not aware of any simple ways to ensure that existing users using Ctrl + Comma can continue to do so without interruption. Ideally, I think #2492 should not have set a suggested key -- the other keyboard shortcuts are "non-destructive" and easy to understand. However, since that already happened, I think the best harm reduction approach at this point is to inconvenience existing users of Ctrl + Comma.
2025-04-25 13:08:44 -04:00
Stephen Thompson
65243e2c06 eslint ecmaVersion to 2021
CONTRIBUTING.md specifies the minimum supported Firefox version as 91.1.0.

Based on the caniuse.com data for ECMAScript features listed at https://gist.github.com/Julien-Marcou/156b19aea4704e1d2f48adafc6e2acbf, I determined the following minimum Firefox versions that support each ECMAScript version:

- ECMAScript 2018 (current setting) = Firefox 78
- ECMAScript 2019 = Firefox 64
- ECMAScript 2020 = Firefox 80
- ECMAScript 2021 = Firefox 79
- ECMAScript 2022 = Firefox 92
- ECMAScript 2023 = Firefox 104
- ECMAScript 2024 = Firefox 119+ (not exactly sure)

This project is using v7 of eslint which depends on v7 of espree for JavaScript parsing. espree v7 has a maximum ECMAScript support version of ECMAScript 2021.

Based on these findings, increasing the configured `parser.ecmaVersion` from `2018` to `2021` will allow using all JavaScript syntax supported in Firefox 91.1.0+ without raising eslint errors.
2025-04-23 15:09:26 -04:00
Stephen Thompson
ab3e1ce4d8 Address code review from @Rob--W 2025-04-23 14:31:13 -04:00
Stephen Thompson
5194fcad0e Address code review from @Rob--W 2025-04-23 14:23:07 -04:00
Stephen Thompson
b6a1bff9e8 Fix #2747: open/reopen container tabs in tab groups when appropriate
Firefox 137 introduced tab groups. Tab group web extension support is rolling out in Firefox 138 and later. Creating a new tab after the last tab in a tab group can inadvertently create the new tab outside of the tab group.

When a user opens a new tab that should be in a container, this patch will make sure that the new tab resides in the same tab group as the original tab.
2025-04-21 23:45:11 -04:00
Stephen Thompson
5ae2047b2c Fix #2746: sort only ungrouped tabs
Firefox 137 introduced tab groups. Tab group web extension support is rolling out in Firefox 138 and later. Tab movements, like the movements done when sorting tabs by container, can inadvertently add or remove tabs from tab groups.

In order to keep users' tab groups intact, this patch only sorts tabs outside of tab groups. Due to the lack of the tabGroups web extensions API as of Firefox 138, this patch cannot move tab groups, so all ungrouped tabs move to the end of the tab strip. That means after sorting, all tab groups will move to the beginning of the tab strip.
2025-04-21 23:44:13 -04:00
Danny Colin
a60f5bb1be
Merge pull request #2723 from mozilla/basti/update_ci
Update CI actions/upload-artifact to v4
2025-02-25 11:17:38 -05:00
Sebastian Streich
c8c4e0f0c5
Update CI actions/upload-artifact to v4 2025-02-25 13:56:52 +01:00
Andrea Marchesini
037a804725
Merge pull request #2572 from Cimbali/main
Small fixes
2024-09-26 15:13:51 +02:00
Cimbali
6fcb828e1d Fix error in function name 2024-09-26 13:10:25 +01:00
Cimbali
aa9bb41305 Do not resolve promise twice 2024-09-26 13:10:25 +01:00
luke crouch
546ee7a098
Merge pull request #2671 from kelimuttu/community-docs
Community documentation update
2024-09-25 06:12:58 -05:00
kelimuttu
d8cff7ca41 tidy up links 2024-09-25 14:37:53 +07:00
kelimuttu
e6e7d5178e remove discourse and redirect to GH discussions board 2024-09-25 14:32:18 +07:00
kelimuttu
7767bb0c58 update the contributor guidelines 2024-09-25 14:30:48 +07:00
Rafee Rahman
580fb5234b
Merge pull request #2663 from mozilla/version-upgrade
Version upgrade
2024-09-10 11:04:30 -04:00
Rafee
8edcb1587d Version upgrade 2024-09-10 10:57:47 -04:00
Rafee Rahman
97891f61b0
Merge pull request #2659 from dannycolin/bz#1823729
Fix bz#1823729 - Listen only for status on tab updated
2024-08-29 09:22:53 -04:00
Danny Colin
51ead29d2b Fix bz#1823729 - Listen only for status on tab updated 2024-08-28 13:52:55 -04:00
luke crouch
a53eb64c03
Merge pull request #2654 from mozilla/feat-#303
#303: Reset cookies in site manager
2024-08-28 11:26:57 -05:00
Rafee
c644a60e46 feat #303: ask for browsing data permission dynamically 2024-08-27 17:06:07 -04:00
Rafee
2cd38299e2 feat #303: confirmation page for clearing container storage, added destructive colors 2024-08-27 15:34:06 -04:00
luke crouch
077c7e08c8
Merge pull request #2649 from mozilla/2263-always-open-in-container-bug
Remember choice for default containers in the "Always open in" confirm page
2024-08-27 13:47:53 -05:00
Rafee
6bde0a78d7 fix #2603: remember choice when choosing 'previous' (deny) container option 2024-08-23 12:57:50 -04:00
Rafee
3debe8a36f feat #303: change individual cookie removal to browsingData api 2024-08-20 15:04:25 -04:00
Rafee
606e08d2b7 feat #303: Container storage deletion and confirmation popup 2024-08-20 13:47:36 -04:00
Danny Colin
f9f5daf8f4
Merge pull request #2616 from dannycolin/bug1958
Fix #1958 - Propagate container list reordering in Firefox menus
2024-08-14 10:31:54 -04:00
Rafee
ffbb740445 feat #303: reset cookies in site manager 2024-08-01 18:13:58 -04:00
Rafee
cd343ab8c3 fix #2603: remember choice to always open in default containers 2024-07-23 11:42:01 -04:00
Danny Colin
1537e9f6f2
Merge pull request #2634 from mozilla/missingTranslation
Missing translation for a contextmenu item
2024-04-22 15:35:47 -04:00
Andrea Marchesini
f94c00b68a
Missing translation for a contextmenu item 2024-04-17 14:59:56 +02:00
Danny Colin
0acc48af48
Merge pull request #2628 from emilio/icon-macos-fix
Explicitly specify width and height in SVG icon.
2024-03-04 10:33:04 -05:00
Emilio Cobos Álvarez
cb96bf385b
Explicitly specify width and height in SVG icon.
To work around https://bugzil.la/1883166.
2024-03-02 02:39:13 +01:00
Danny Colin
6fd2b70032
Merge pull request #2622 from emilio/release-changes-back-to-main
Release changes back to main
2024-02-23 18:49:22 -05:00
maxxcrawford
f5aec9cb5a
Add latest strings 2024-02-23 20:41:34 +01:00
maxxcrawford
9469ed424e
Bump version number to 8.1.3 2024-02-23 20:41:10 +01:00
Danny Colin
7305b54635 Fix #1958 - Propagate container list reordering in Firefox menus
Since Fx 123, we have a new API (contextualIdentities.move) that let
us reorder the container list everywhere in Firefox. This patch
looks if the API is supported by the client and propagate the change
if it's the case.
2024-02-13 13:56:45 -05:00
Danny Colin
018b458ef6
Merge pull request #2599 from abhillman/abhillman/shebang-bash-with-env
Use `/usr/bin/env` to find bash
2023-11-28 19:51:22 -05:00
Danny Colin
6573123af5
Merge pull request #2587 from dannycolin/issue2553-shortcut-preventdefault
Revert monitoring modifier in popup shortcuts
2023-11-20 09:40:09 -05:00
Aryeh Hillman
4a9bc37a46 Use /usr/bin/env to find bash
This enables `npm test` to successfully call scripts in `bin/` with
the `bash` binary specified by `PROFILE`.

---

Additional information:

In addition to potentially preferring an alternative version of bash
than the one in `/bin/bash`, some distributions forego storing bash in
`bin/` at all, such as `NixOS`:

```bash
$ cat /etc/os-release | rg '^NAME=' -r ''
NixOS
$ which bash
/run/current-system/sw/bin/bash
```
2023-11-17 15:55:12 -08:00
Danny Colin
59e951e5d2
Merge pull request #2578 from dannycolin/bug1852393-icon-in-toolbar-by-default
Add MAC icon in toolbar on install
2023-10-16 14:32:34 -04:00
Danny Colin
4d4851d058
Merge pull request #2577 from dannycolin/fxa-rebrand
Rebrand Firefox Account
2023-10-16 14:25:57 -04:00
Danny Colin
dd574bbe99
Merge pull request #2558 from leodag/fix-pr-template
fix: correct pull request template placement
2023-10-14 18:38:00 -04:00
Leonardo Dagnino
6374a28932 fix: correct pull request template placement 2023-10-14 19:05:45 -03:00
Danny Colin
6a9afcf266 Revert monitoring modifier in popup shortcuts 2023-10-13 14:45:32 -04:00
Danny Colin
bea1cdcf87
Merge pull request #2584 from emilio/icons
Use color-scheme-aware icons.
2023-10-10 22:13:40 -04:00
Emilio Cobos Álvarez
3779f86088
Make icons react to color-scheme properly.
Note that due to https://bugzilla.mozilla.org/show_bug.cgi?id=1779457 /
https://github.com/w3c/csswg-drafts/issues/7213, prefers-color-scheme
works fine even if the user has an explicitly light theme or so.

This fixes #2583 entirely.
2023-10-09 00:57:23 +02:00
Emilio Cobos Álvarez
bb24647ff3
Make usercontext.svg use color-scheme-aware colors.
Fixes the individual icons in #2583.
2023-10-09 00:45:37 +02:00
Danny Colin
60466258b8 Rebrand Firefox Account
* Change Firefox Account to account
* Add brand=mozilla to accounts.firefox.com URLs
2023-10-02 13:13:17 -04:00
Danny Colin
dd4020069c Add MAC icon in toolbar on install 2023-09-23 19:31:01 -04:00
luke crouch
60b40a2d9f
Merge pull request #2562 from mozilla/fix-issue-template
Fix issue template
2023-08-11 09:57:42 -05:00
Danny Colin
1142c73812
Fix issue template
There's an escape character that breaks our issue template and prevents it to be parsed by Github. This patch removes it.
2023-08-10 14:05:34 -04:00
Danny Colin
f20688c453
Merge pull request #2543 from dannycolin/i18n-remove-hardcoded-strings
Fix missing i18n strings in the UI
2023-06-27 21:33:52 -04:00
Danny Colin
f85d75188a Fix missing i18n strings in the UI
This patch fix all the missing i18n strings in the UI. It also
standardize the main title to "Firefox Multi-Account Containers"
everywhere we use it in the addon.
2023-06-19 19:41:10 -04:00
Danny Colin
dae7d92595
Merge pull request #2501 from dannycolin/popup-shortcuts
Rework shortcut listener logic for the addon popup
2023-06-17 16:00:37 -04:00
Danny Colin
ef10307898 Rework shortcut listener logic for the addon popup
- Add Slash shortcut to focus search input
- Use event.code instead of event.keyCode
- Cancel [0-9] shortcuts when modified is pressed
- Cancel [0-9] shortcuts when search input is focused
2023-06-17 15:58:04 -04:00
Danny Colin
b29ba2094e
Merge pull request #2492 from dannycolin/bug-2457-sort-shortcut
Fix 2457 - add shortcut for sorting tabs by container
2023-06-08 11:55:27 -04:00
Andrea Marchesini
f7e9deebda
Merge pull request #2505 from mozilla/dependabot/npm_and_yarn/cacheable-request-10.2.7
Bump cacheable-request from 10.2.3 to 10.2.7
2023-03-11 05:05:23 -05:00
Danny Colin
473495ec0c
Merge pull request #2509 from drien/main
Add `file:` protocol to non-permissible protocol list
2023-02-23 18:03:55 -05:00
Adrien Delessert
38cdf0a98c
Merge branch 'mozilla:main' into main 2023-02-23 17:55:44 -05:00
Maxx Crawford
69bfac12de
Merge pull request #2511 from mozilla/revert-2506-npx-test-fix-fix
Fix Build and Test Github CI Scripts
2023-02-22 11:26:39 -06:00
Maxx Crawford
2dfd1ee4bb
Update build script to use npx instead of npm bin bash scripting 2023-02-22 11:23:45 -06:00
Adrien Delessert
2894de1127
Add file: protocol to non-permissible list 2023-02-16 12:39:44 -05:00
dependabot[bot]
caa52cebf4
Bump cacheable-request from 10.2.3 to 10.2.7
Bumps [cacheable-request](https://github.com/jaredwray/cacheable-request) from 10.2.3 to 10.2.7.
- [Release notes](https://github.com/jaredwray/cacheable-request/releases)
- [Commits](https://github.com/jaredwray/cacheable-request/commits)

---
updated-dependencies:
- dependency-name: cacheable-request
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-11 00:28:54 +00:00
Danny Colin
c948a9501d Fix 2457 - add shortcut for sorting tabs by container 2023-01-23 18:49:39 -05:00
32 changed files with 565 additions and 219 deletions

View file

@ -1,6 +1,6 @@
module.exports = {
"parserOptions": {
"ecmaVersion": 2018
"ecmaVersion": 2021
},
"env": {
"browser": true,

View file

@ -9,7 +9,7 @@ body:
options:
- label: "I updated to the latest version of Multi-Account Container and tested if I can reproduce the issue"
required: true
- label: "I searched for existing reports to see if it hasn\'t already been reported"
- label: "I searched for existing reports to see if it hasn't already been reported"
required: true
- type: textarea
id: step_to_reproduce

View file

@ -26,7 +26,7 @@ jobs:
./bin/build-addon.sh nightly.xpi
- name: Uploading
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{matrix.config.name}} Build
path: src/web-ext-artifacts

View file

@ -32,7 +32,23 @@ repository like any other. Before editing files in this folder, you need to:
You can then [open a pull request][pr] on [the l10n repository][l10n].
## Tips for contributing
1. Choose [an issue][issues] that you would like to work on.
2. Fork the repository and follow the instructions for setting it up locally.
3. Run the add-on locally and try reproducing the issue.
4. Debug add-ons by clicking the “Settings” icon in about:addons, and then clicking “Debug Add-ons”
5. Click “Inspect” on the MAC add-on to open developer tools for the popup extension (see [this documentation][extension-doc] for more information)
6. Once you have a fix ready, commit your changes with the following commit message template: “Fix #<insert issue id #>: <short description>
7. Push your changes and open a pull request for review.
If you run into an issue, you can always ask the other community members in the [discussions board][discussions].
<!-- Please keep the list in alphabetical order -->
[discussions]: https://github.com/mozilla/multi-account-containers/discussions
[extension-doc]: https://extensionworkshop.com/documentation/develop/debugging/
[fork]: https://docs.github.com/en/get-started/quickstart/fork-a-repo
[issues]: https://github.com/mozilla/multi-account-containers/issues
[l10n]: https://github.com/mozilla-l10n/multi-account-containers-l10n/
[pr]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests
[web-ext]: https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Getting_started_with_web-ext
[web-ext]: https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Getting_started_with_web-ext

View file

@ -13,7 +13,7 @@ Everyone is welcome to contribute to Multi-Account Containers. To learn how
to contribute a patch to Multi-Account Container, please
[read our contributing guide][contributing].
You can also chat with us on [our Matrix room][matrix] or [our forum][forum].
You can also chat with us on [our Matrix room][matrix] or ask in [our discussions board][discussions].
This repository is governed by Mozilla's code of conduct and etiquette
guidelines. For more details, [please read the Mozilla Community Participation Guidelines][cpg].
@ -29,4 +29,5 @@ file, You can obtain one at https://mozilla.org/MPL/2.0/.
[cpg]: https://www.mozilla.org/about/governance/policies/participation/
[enduser]: https://support.mozilla.org/en-US/kb/containers
[forum]: https://discourse.mozilla.org/c/containers/223
[discussions]: https://github.com/mozilla/multi-account-containers/discussions
[matrix]: https://matrix.to/#/#containers:mozilla.org

View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/env bash
# 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
@ -32,4 +32,4 @@ rm -rf $TMPDIR/src/_locales/.github || die
print G "done."
print Y "Running the test..."
$(npm bin)/addons-linter $TMPDIR/src || die
npx addons-linter $TMPDIR/src || die

View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/env bash
# 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
@ -23,4 +23,4 @@ if [[ $# -gt 0 ]]; then
EXTRA_PARAMS="--filename $1"
fi
$(npm bin)/web-ext build --overwrite-dest $EXTRA_PARAMS || die
npx web-ext build --overwrite-dest $EXTRA_PARAMS || die

View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/env bash
# 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

18
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "testpilot-containers",
"version": "8.1.1",
"version": "8.1.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
@ -2226,14 +2226,14 @@
}
},
"node_modules/cacheable-request": {
"version": "10.2.3",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.3.tgz",
"integrity": "sha512-6BehRBOs7iurNjAYN9iPazTwFDaMQavJO8W1MEm3s2pH8q/tkPTtLDRUZaweWK87WFGf2Y5wLAlaCJlR5kOz3w==",
"version": "10.2.7",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.7.tgz",
"integrity": "sha512-I4SA6mKgDxcxVbSt/UmIkb9Ny8qSkg6ReBHtAAXnZHk7KOSx5g3DTiAOaYzcHCs6oOdHn+bip9T48E6tMvK9hw==",
"dev": true,
"dependencies": {
"@types/http-cache-semantics": "^4.0.1",
"get-stream": "^6.0.1",
"http-cache-semantics": "^4.1.0",
"http-cache-semantics": "^4.1.1",
"keyv": "^4.5.2",
"mimic-response": "^4.0.0",
"normalize-url": "^8.0.0",
@ -15922,14 +15922,14 @@
"dev": true
},
"cacheable-request": {
"version": "10.2.3",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.3.tgz",
"integrity": "sha512-6BehRBOs7iurNjAYN9iPazTwFDaMQavJO8W1MEm3s2pH8q/tkPTtLDRUZaweWK87WFGf2Y5wLAlaCJlR5kOz3w==",
"version": "10.2.7",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.7.tgz",
"integrity": "sha512-I4SA6mKgDxcxVbSt/UmIkb9Ny8qSkg6ReBHtAAXnZHk7KOSx5g3DTiAOaYzcHCs6oOdHn+bip9T48E6tMvK9hw==",
"dev": true,
"requires": {
"@types/http-cache-semantics": "^4.0.1",
"get-stream": "^6.0.1",
"http-cache-semantics": "^4.1.0",
"http-cache-semantics": "^4.1.1",
"keyv": "^4.5.2",
"mimic-response": "^4.0.0",
"normalize-url": "^8.0.0",

View file

@ -2,14 +2,14 @@
"name": "testpilot-containers",
"title": "Multi-Account Containers",
"description": "Containers helps you keep all the parts of your online life contained in different tabs. Custom labels and color-coded tabs help keep different activities — like online shopping, travel planning, or checking work email — separate.",
"version": "8.1.1",
"version": "8.3.0",
"author": "Andrea Marchesini, Luke Crouch, Lesley Norton, Kendall Werts, Maxx Crawford, Jonathan Kingston",
"bugs": {
"url": "https://github.com/mozilla/multi-account-containers/issues"
},
"dependencies": {},
"devDependencies": {
"addons-linter": "^3.23.0",
"addons-linter": "^5.28.0",
"ajv": "^6.6.3",
"chai": "^4.2.0",
"eslint": "^7.32.0",

@ -1 +1 @@
Subproject commit fa5fb497bd4e5c9b7d69407ee593b853cf839009
Subproject commit bdaa01291b7367a5e815470fd263ea36c862fe32

View file

@ -110,7 +110,6 @@
--usercontext-bg-hover-color: #f0f0fa;
--usercontext-bg-focus-color: #e0e0e6;
--usercontext-bg-active-color: #cfcfd8;
--moz-vpn-tout-shadow: 0 0 7px 0 #9498a25e;
}
[data-theme="dark"] {
@ -180,7 +179,6 @@
--usercontext-bg-active-color: #5b5b66;
--usercontext-bg-focus-color: #2b2a33;
--usercontext-bg-hover-color: #52525e;
--moz-vpn-tout-shadow: 0 0 7px 0 #7478825e;
}
/* General Rules and Resets */
@ -228,6 +226,7 @@ body {
/* Hack for menu icons to use a light color without affecting container icons */
[data-theme="light"] img.delete-assignment,
[data-theme="dark"] img.reset-assignment,
[data-theme="dark"] .trash-button,
[data-theme="dark"] img.menu-icon,
[data-theme="dark"] .menu-icon > img,
@ -236,6 +235,7 @@ body {
filter: invert(1);
}
[data-theme="dark"] img.clear-storage-icon,
[data-theme="dark"] img.delete-assignment,
[data-theme="dark"] #edit-sites-assigned .menu-icon,
[data-theme="dark"] #container-info-table .menu-icon {
@ -285,9 +285,33 @@ table {
display: none !important;
}
.popup-notification-card {
opacity: 0;
pointer-events: none;
transition: opacity 2s;
border-radius: 4px;
font-size: 12px;
position: absolute;
inset-block-start: 0;
inset-inline-start: 0;
padding-block: 8px;
padding-inline: 8px;
margin-block: 8px;
margin-inline: 8px;
inline-size: calc(100vw - 25px);
background-color: var(--button-bg-active-color-secondary);
z-index: 3;
}
.is-shown {
pointer-events: auto;
opacity: 1;
transition: opacity 0s;
}
/* effect borrowed from tabs in firefox, ensure that the element flexes to the full width */
.truncate-text {
inline-size: calc(100vw - 80px);
inline-size: calc(100vw - 100px);
overflow: hidden;
position: relative;
white-space: nowrap;
@ -609,37 +633,8 @@ input:checked:hover:focus + .slider {
background-color: var(--button-bg-active-color-primary);
}
/* Mozilla VPN tout */
#moz-vpn-tout {
opacity: 0;
background-color: var(--panel-bg-color);
visibility: visible;
max-block-size: 500px;
position: absolute;
inset-block-end: var(--footerHeight);
inset-inline-start: 0;
inset-inline-end: 0;
box-shadow: var(--moz-vpn-tout-shadow);
animation: appear 0.2s ease-out 0.5s forwards;
transition: opacity 0.1s ease-in-out, max-height 0.3s ease-in-out;
}
@keyframes appear {
0% {
opacity: 0;
transform: translateY(10%);
}
100% {
opacity: 1;
transform: translateY(0%);
}
}
/* Mozilla VPN Controller UI in Container Management Panel */
.moz-vpn-content,
.moz-vpn-controller-content {
display: flex;
position: relative;
@ -1027,7 +1022,6 @@ input.proxies {
/* Mozilla VPN Server list */
.moz-vpn-logo,
.moz-vpn-logotype {
color: var(--text-color-primary);
background-image: var(--logoMozillaVpn);
@ -1505,8 +1499,9 @@ input[type=text] {
min-block-size: 500px;
}
.delete-container-panel {
min-block-size: 300px;
.delete-container-panel,
.clear-container-storage-panel {
min-block-size: 500px;
}
.panel.onboarding,
@ -1794,12 +1789,14 @@ manage things like container crud */
margin-inline-end: 0;
}
.delete-container-confirm {
.delete-container-confirm,
.clear-container-storage-confirm {
padding-inline-end: 20px;
padding-inline-start: 20px;
}
.delete-container-confirm-title {
.delete-container-confirm-title,
.clear-container-storage-confirm-title {
color: var(--text-color-primary);
font-size: var(--font-size-heading);
}
@ -2173,6 +2170,11 @@ hr {
text-align: center;
}
.confirmation-destructive-ok-btn {
background-color: var(--button-destructive-bg-color);
color: var(--button-destructive-text-color);
}
.delete-btn {
background-color: var(--button-destructive-bg-color);
block-size: 32px;
@ -2303,7 +2305,8 @@ input {
font-weight: bolder;
}
.delete-warning {
.delete-warning,
.clear-container-storage-warning {
padding-block-end: 8px;
padding-block-start: 8px;
padding-inline-end: 0;
@ -2314,7 +2317,8 @@ input {
* rules grouped together at the beginning of the file
*/
/* stylelint-disable no-descending-specificity */
.trash-button {
.trash-button,
.reset-button {
display: inline-block;
block-size: 20px;
inline-size: 20px;
@ -2323,11 +2327,21 @@ input {
text-align: center;
}
tr > td > .trash-button {
.reset-button {
margin-inline-end: 12px;
}
.tooltip-wrapper:hover .site-settings-tooltip {
display: block;
}
tr > td > .trash-button,
tr > td > .reset-button {
display: none;
}
tr:hover > td > .trash-button {
tr:hover > td > .trash-button,
tr:hover > td > .reset-button {
display: block;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,9 +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/. --><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>

Before

Width:  |  Height:  |  Size: 801 B

View file

@ -1,7 +1,13 @@
<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>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<style>
:root { color-scheme: light dark; }
rect, path { fill: rgb(24, 25, 26); }
@media (prefers-color-scheme: dark) {
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>
</svg>

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 727 B

View file

@ -4,6 +4,7 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;">
<style>
:root { color-scheme: light dark; }
path, circle, g {
fill: menutext;
}

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -61,8 +61,9 @@ window.assignManager = {
this.area.get([siteStoreKey]).then((storageResponse) => {
if (storageResponse && siteStoreKey in storageResponse) {
resolve(storageResponse[siteStoreKey]);
} else {
resolve(null);
}
resolve(null);
}).catch((e) => {
reject(e);
});
@ -165,11 +166,17 @@ window.assignManager = {
_neverAsk(m) {
const pageUrl = m.pageUrl;
if (m.neverAsk === true) {
if (m.defaultContainer === true) {
this.storageArea.remove(pageUrl);
return;
}
// 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;
siteSettings.userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(m.cookieStoreId);
this.storageArea.set(pageUrl, siteSettings);
}
}).catch((e) => {
@ -319,7 +326,8 @@ window.assignManager = {
options.url,
tab.index + 1,
tab.active,
openTabId
openTabId,
tab.groupId
);
} else {
this.reloadPageInContainer(
@ -329,7 +337,8 @@ window.assignManager = {
tab.index + 1,
tab.active,
siteSettings.neverAsk,
openTabId
openTabId,
tab.groupId
);
}
this.calculateContextMenu(tab);
@ -474,9 +483,7 @@ window.assignManager = {
},
contextualIdentityRemoved(changeInfo) {
browser.contextMenus.remove(
changeInfo.contextualIdentity.cookieStoreId
);
this.removeMenuItem(changeInfo.contextualIdentity.cookieStoreId);
},
async _onClickedHandler(info, tab) {
@ -571,6 +578,16 @@ window.assignManager = {
return true;
},
async _resetCookiesForSite(hostname, cookieStoreId) {
const hostNameTruncated = hostname.replace(/^www\./, ""); // Remove "www." from the hostname
await browser.browsingData.removeCookies({
cookieStoreId: cookieStoreId,
hostnames: [hostNameTruncated] // This does not remove cookies from associated domains. To remove all cookies, we have a container storage removal option.
});
return true;
},
async _setOrRemoveAssignment(tabId, pageUrl, userContextId, remove) {
let actionName;
// https://github.com/mozilla/testpilot-containers/issues/626
@ -622,7 +639,7 @@ window.assignManager = {
},
async _maybeRemoveSiteIsolation(userContextId) {
const assignments = await this.storageArea.getByContainer(userContextId);
const assignments = await this.storageArea.getAssignedSites(userContextId);
const hasAssignments = assignments && Object.keys(assignments).length > 0;
if (hasAssignments) {
return;
@ -653,11 +670,11 @@ window.assignManager = {
// 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);
this.removeMenuItem(this.MENU_ASSIGN_ID);
this.removeMenuItem(this.MENU_REMOVE_ID);
this.removeMenuItem(this.MENU_SEPARATOR_ID);
this.removeMenuItem(this.MENU_HIDE_ID);
this.removeMenuItem(this.MENU_MOVE_ID);
},
async calculateContextMenu(tab) {
@ -678,7 +695,7 @@ window.assignManager = {
}
browser.contextMenus.create({
id: menuId,
title: "Always Open in This Container",
title: browser.i18n.getMessage("alwaysOpenSiteInContainer"),
checked,
type: "checkbox",
contexts: ["all"],
@ -692,13 +709,13 @@ window.assignManager = {
browser.contextMenus.create({
id: this.MENU_HIDE_ID,
title: "Hide This Container",
title: browser.i18n.getMessage("hideThisContainer"),
contexts: ["all"],
});
browser.contextMenus.create({
id: this.MENU_MOVE_ID,
title: "Move Tabs to a New Window",
title: browser.i18n.getMessage("moveTabsToANewWindow"),
contexts: ["all"],
});
},
@ -710,7 +727,15 @@ window.assignManager = {
});
},
reloadPageInDefaultContainer(url, index, active, openerTabId) {
/**
* @param {string} url
* @param {number} index
* @param {boolean} active
* @param {number} [openerTabId]
* @param {number} [groupId]
* @returns {void}
*/
reloadPageInDefaultContainer(url, index, active, openerTabId, groupId) {
// To create a new tab in the default container, it is easiest just to omit the
// cookieStoreId entirely.
//
@ -729,16 +754,58 @@ window.assignManager = {
// 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});
this.createTabWrapper(url, cookieStoreId, index, active, openerTabId, groupId);
},
reloadPageInContainer(url, currentUserContextId, userContextId, index, active, neverAsk = false, openerTabId = null) {
/**
* Wraps around `browser.tabs.create` and `browser.tabs.group` to create a
* tab and ensure that it ends up in the requested tab group, if applicable.
*
* @param {string} url
* @param {string} cookieStoreId
* @param {number} index
* @param {boolean} active
* @param {number} openerTabId
* @param {number} [groupId] Tab group ID
* @returns {Promise<Tab>}
*/
async createTabWrapper(url, cookieStoreId, index, active, openerTabId, groupId) {
const newTab = await browser.tabs.create({
url,
cookieStoreId,
index,
active,
openerTabId,
});
if (groupId >= 0) {
// If the original tab was in a tab group, make sure that the reopened tab
// stays in the same tab group.
await browser.tabs.group({ groupId, tabIds: newTab.id });
}
return newTab;
},
/**
* @param {string} url
* @param {string} currentUserContextId
* @param {string} userContextId
* @param {number} index
* @param {boolean} active
* @param {boolean} [neverAsk=false]
* @param {number} [openerTabId=null]
* @param {number} [groupId]
* @returns {Promise<Tab>}
*/
reloadPageInContainer(url, currentUserContextId, userContextId, index, active, neverAsk = false, openerTabId = null, groupId = undefined) {
const cookieStoreId = backgroundLogic.cookieStoreId(userContextId);
const loadPage = browser.runtime.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});
return this.createTabWrapper(url, cookieStoreId, index, active, openerTabId, groupId);
} else {
let confirmUrl = `${loadPage}?url=${this.encodeURLProperty(url)}&cookieStoreId=${cookieStoreId}`;
let currentCookieStoreId;
@ -746,13 +813,14 @@ window.assignManager = {
currentCookieStoreId = backgroundLogic.cookieStoreId(currentUserContextId);
confirmUrl += `&currentCookieStoreId=${currentCookieStoreId}`;
}
return browser.tabs.create({
url: confirmUrl,
cookieStoreId: currentCookieStoreId,
openerTabId,
return this.createTabWrapper(
confirmUrl,
currentCookieStoreId,
index,
active
}).then(() => {
active,
openerTabId,
groupId
).then(() => {
// We don't want to sync this URL ever nor clutter the users history
browser.history.deleteUrl({url: confirmUrl});
}).catch((e) => {
@ -764,7 +832,7 @@ window.assignManager = {
async initBookmarksMenu() {
browser.contextMenus.create({
id: this.OPEN_IN_CONTAINER,
title: "Open Bookmark in Container Tab",
title: browser.i18n.getMessage("openBookmarkInContainerTab"),
contexts: ["bookmark"],
});
@ -780,12 +848,19 @@ window.assignManager = {
},
async removeBookmarksMenu() {
browser.contextMenus.remove(this.OPEN_IN_CONTAINER);
this.removeMenuItem(this.OPEN_IN_CONTAINER);
const identities = await browser.contextualIdentities.query({});
for (const identity of identities) {
browser.contextMenus.remove(identity.cookieStoreId);
this.removeMenuItem(identity.cookieStoreId);
}
},
removeMenuItem(menuItemId) {
// Callers do not check whether the menu exists before attempting to remove
// it. contextMenus.remove rejects when the menu does not exist, so we need
// to catch and swallow the error to avoid logspam.
browser.contextMenus.remove(menuItemId).catch(() => {});
}
};
assignManager.init();

View file

@ -14,7 +14,13 @@ const backgroundLogic = {
NUMBER_OF_KEYBOARD_SHORTCUTS: 10,
unhideQueue: [],
init() {
browser.commands.onCommand.addListener(function (command) {
if (command === "sort_tabs") {
backgroundLogic.sortTabs();
return;
}
for (let i=0; i < backgroundLogic.NUMBER_OF_KEYBOARD_SHORTCUTS; i++) {
const key = "open_container_" + i;
const cookieStoreId = identityState.keyboardShortcut[key];
@ -27,6 +33,49 @@ const backgroundLogic = {
browser.permissions.onAdded.addListener(permissions => this.resetPermissions(permissions));
browser.permissions.onRemoved.addListener(permissions => this.resetPermissions(permissions));
// Update Translation in Manifest
browser.runtime.onInstalled.addListener((details) => {
this.updateTranslationInManifest();
this._undoDefault820SortTabsKeyboardShortcut(details);
});
browser.runtime.onStartup.addListener(this.updateTranslationInManifest);
},
/**
* One-time migration after updating from v8.2.0:
* Unset the default keyboard shortcut (Ctrl+Comma) for the `sort_tabs`
* command if it was set in v8.2.0 of this addon. If the user remapped
* a different shortcut manually, retain their shortcut. Users who used
* the default keyboard shortcut will need to manually set a shortcut.
* See https://support.mozilla.org/en-US/kb/manage-extension-shortcuts-firefox
*
* @param {{reason: runtime.OnInstalledReason, previousVersion?: string}} details
*/
async _undoDefault820SortTabsKeyboardShortcut(details) {
if (details.reason === "update" && details.previousVersion === "8.2.0") {
const commands = await browser.commands.getAll();
const sortTabsCommand = commands.find(command => command.name === "sort_tabs");
if (sortTabsCommand) {
const previouslySuggestedKeys = [
"Ctrl+Comma", // "default"
"MacCtrl+Comma", // "mac"
];
if (previouslySuggestedKeys.includes(sortTabsCommand.shortcut)) {
browser.commands.reset("sort_tabs");
}
}
}
},
updateTranslationInManifest() {
for (let index = 0; index < 10; index++) {
const ajustedIndex = index + 1; // We want to start from 1 instead of 0 in the UI.
browser.commands.update({
name: `open_container_${index}`,
description: browser.i18n.getMessage("containerShortcut", `${ajustedIndex}`)
});
}
},
resetPermissions(permissions) {
@ -55,6 +104,19 @@ const backgroundLogic = {
return extensionInfo;
},
// Remove container data (cookies, localStorage and cache)
async deleteContainerDataOnly(userContextId) {
await browser.browsingData.removeCookies({
cookieStoreId: this.cookieStoreId(userContextId)
});
await browser.browsingData.removeLocalStorage({
cookieStoreId: this.cookieStoreId(userContextId)
});
return {done: true, userContextId};
},
getUserContextIdFromCookieStoreId(cookieStoreId) {
if (!cookieStoreId) {
return false;
@ -123,7 +185,8 @@ const backgroundLogic = {
// We can't open these we just have to throw them away
if (protocol === "about:"
|| protocol === "chrome:"
|| protocol === "moz-extension:") {
|| protocol === "moz-extension:"
|| protocol === "file:") {
return false;
}
return true;
@ -309,7 +372,13 @@ const backgroundLogic = {
let pos = 0;
// Let's collect UCIs/tabs for this window.
/** @type {Map<string, {order: string, tabs: Tab[]}>} */
const map = new Map;
const lastTab = tabs.at(-1);
/** @type {boolean} */
let lastTabIsInTabGroup = !!lastTab && lastTab.groupId >= 0;
for (const tab of tabs) {
if (pinnedTabs && !tab.pinned) {
// We don't have, or we already handled all the pinned tabs.
@ -322,6 +391,11 @@ const backgroundLogic = {
continue;
}
if (tab.groupId >= 0) {
// Skip over tabs in tab groups until it's possible to handle them better.
continue;
}
if (!map.has(tab.cookieStoreId)) {
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(tab.cookieStoreId);
map.set(tab.cookieStoreId, { order: userContextId, tabs: [] });
@ -343,15 +417,25 @@ const backgroundLogic = {
const sortMap = new Map([...map.entries()].sort((a, b) => a[1].order > b[1].order));
// Let's move tabs.
sortMap.forEach(obj => {
for (const tab of obj.tabs) {
for (const { tabs } of sortMap.values()) {
for (const tab of tabs) {
++pos;
browser.tabs.move(tab.id, {
windowId: windowObj.id,
index: pos
index: pinnedTabs ? pos : -1
});
// Pinned tabs are never grouped and always inserted in the front.
if (!pinnedTabs && lastTabIsInTabGroup && browser.tabs.ungroup) {
// If the last item in the tab strip is a grouped tab, moving a tab
// to its position will also add it to the tab group. Since this code
// is only sorting ungrouped tabs, this forcibly ungroups the first
// tab to be moved. All subsequent iterations will only be moving
// ungrouped tabs to the position of other ungrouped tabs.
lastTabIsInTabGroup = false;
browser.tabs.ungroup(tab.id);
}
}
});
}
},
async hideTabs(options) {

View file

@ -23,6 +23,9 @@ const messageHandler = {
case "deleteContainer":
response = backgroundLogic.deleteContainer(m.message.userContextId);
break;
case "deleteContainerDataOnly":
response = backgroundLogic.deleteContainerDataOnly(m.message.userContextId);
break;
case "createOrUpdateContainer":
response = backgroundLogic.createOrUpdateContainer(m.message);
break;
@ -45,6 +48,9 @@ const messageHandler = {
// m.url is the assignment to be removed/added
response = assignManager._setOrRemoveAssignment(m.tabId, m.url, m.userContextId, m.value);
break;
case "resetCookiesForSite":
response = assignManager._resetCookiesForSite(m.pageUrl, m.cookieStoreId);
break;
case "sortTabs":
backgroundLogic.sortTabs();
break;
@ -85,17 +91,21 @@ const messageHandler = {
m.newUserContextId,
m.tabIndex,
m.active,
true
true,
null,
m.groupId
);
break;
case "assignAndReloadInContainer":
tab = await assignManager.reloadPageInContainer(
m.url,
m.url,
m.currentUserContextId,
m.newUserContextId,
m.tabIndex,
m.newUserContextId,
m.tabIndex,
m.active,
true
true,
null,
m.groupId
);
// m.tabId is used for where to place the in content message
// m.url is the assignment to be removed/added
@ -220,7 +230,9 @@ const messageHandler = {
// 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);
browser.tabs.onUpdated.addListener(this.tabUpdateHandler, {
properties: ["status"]
});
}
}
}

View file

@ -7,14 +7,16 @@ async function load() {
redirectUrlElement.textContent = redirectUrl;
appendFavicon(redirectUrl, redirectUrlElement);
// Option for staying on the previous container
document.getElementById("deny").addEventListener("click", (e) => {
e.preventDefault();
denySubmit(redirectUrl);
denySubmit(redirectUrl, currentCookieStoreId);
});
// Option for going to the default container (no container)
document.getElementById("deny-no-container").addEventListener("click", (e) => {
e.preventDefault();
denySubmit(redirectUrl);
denySubmit(redirectUrl, currentCookieStoreId);
});
const container = await browser.contextualIdentities.get(cookieStoreId);
@ -27,6 +29,7 @@ async function load() {
el.textContent = browser.i18n.getMessage(elementData.messageId, containerName);
});
// Option for going to newly selected container
document.getElementById("confirm").addEventListener("click", (e) => {
e.preventDefault();
confirmSubmit(redirectUrl, cookieStoreId);
@ -59,24 +62,42 @@ function confirmSubmit(redirectUrl, cookieStoreId) {
browser.runtime.sendMessage({
method: "neverAsk",
neverAsk: true,
cookieStoreId: cookieStoreId,
pageUrl: redirectUrl
});
}
openInContainer(redirectUrl, cookieStoreId);
}
function getCurrentTab() {
return browser.tabs.query({
/**
* @returns {Promise<Tab>}
*/
async function getCurrentTab() {
const tabs = await browser.tabs.query({
active: true,
windowId: browser.windows.WINDOW_ID_CURRENT
});
return tabs[0];
}
async function denySubmit(redirectUrl) {
async function denySubmit(redirectUrl, currentCookieStoreId) {
const tab = await getCurrentTab();
const currentContainer = currentCookieStoreId ? await browser.contextualIdentities.get(currentCookieStoreId) : null;
const neverAsk = document.getElementById("never-ask").checked;
if (neverAsk) {
await browser.runtime.sendMessage({
method: "neverAsk",
neverAsk: true,
cookieStoreId: currentCookieStoreId,
pageUrl: redirectUrl,
defaultContainer: !currentContainer
});
}
await browser.runtime.sendMessage({
method: "exemptContainerAssignment",
tabId: tab[0].id,
tabId: tab.id,
pageUrl: redirectUrl
});
document.location.replace(redirectUrl);
@ -86,12 +107,15 @@ load();
async function openInContainer(redirectUrl, cookieStoreId) {
const tab = await getCurrentTab();
await browser.tabs.create({
index: tab[0].index + 1,
const reopenedTab = await browser.tabs.create({
index: tab.index + 1,
cookieStoreId,
url: redirectUrl
});
if (tab.length > 0) {
browser.tabs.remove(tab[0].id);
if (tab.groupId >= 0) {
// If the original tab was in a tab group, make sure that the reopened tab
// stays in the same tab group.
await browser.tabs.group({ groupId: tab.groupId, tabIds: reopenedTab.id });
}
await browser.tabs.remove(tab.id);
}

View file

@ -24,11 +24,12 @@ async function addMessage(message) {
divElement.innerText = message.text;
const imageElement = document.createElement("img");
const imagePath = browser.runtime.getURL("/img/container-site-d-24.png");
const imagePath = browser.runtime.getURL("/img/multiaccountcontainer-16.svg");
const response = await fetch(imagePath);
const blob = await response.blob();
const objectUrl = URL.createObjectURL(blob);
imageElement.src = objectUrl;
imageElement.width = imageElement.height = 24;
divElement.prepend(imageElement);
document.body.appendChild(divElement);

View file

@ -32,6 +32,7 @@ const P_CONTAINER_EDIT = "containerEdit";
const P_CONTAINER_DELETE = "containerDelete";
const P_CONTAINERS_ACHIEVEMENT = "containersAchievement";
const P_CONTAINER_ASSIGNMENTS = "containerAssignments";
const P_CLEAR_CONTAINER_STORAGE = "clearContainerStorage";
const P_MOZILLA_VPN_SERVER_LIST = "moz-vpn-server-list";
const P_ADVANCED_PROXY_SETTINGS = "advanced-proxy-settings-panel";
@ -122,6 +123,19 @@ const Logic = {
},
notify(i18nOpts) {
const notificationCards = document.querySelectorAll(".popup-notification-card");
const text = browser.i18n.getMessage(i18nOpts.messageId, i18nOpts.placeholders);
notificationCards.forEach(notificationCard => {
notificationCard.textContent = text;
notificationCard.classList.add("is-shown");
setTimeout(() => {
notificationCard.classList.remove("is-shown");
}, 2000);
});
},
async showAchievementOrContainersListPanel() {
// Do we need to show an achievement panel?
let showAchievements = false;
@ -211,6 +225,11 @@ const Logic = {
async saveContainerOrder(rows) {
const containerOrder = {};
rows.forEach((node, index) => {
if (typeof browser.contextualIdentities.move === "function") {
browser.contextualIdentities.move(
node.dataset.containerId, index);
}
return containerOrder[node.dataset.containerId] = index;
});
await browser.storage.local.set({
@ -396,7 +415,11 @@ const Logic = {
},
shortcutListener(e){
function openNewContainerTab(identity) {
function openTopContainers() {
const identities = Logic.identities();
const key = e.code.substring(5);
const identity = e.code === "Digit0" ? identities[9] : identities[key - 1];
try {
browser.tabs.create({
cookieStoreId: identity.cookieStoreId
@ -406,12 +429,34 @@ const Logic = {
window.close();
}
}
const identities = Logic.identities();
if ((e.keyCode >= 49 && e.keyCode <= 57) &&
Logic._currentPanel === "containersList") {
const identity = identities[e.keyCode - 49];
if (identity) {
openNewContainerTab(identity);
// We monitor if the search input is focused so we can disable opening
// containers by typing a digit between 0-9 while the popup is open.
const searchInput = document.getElementById("search-terms");
let isSearchInputFocused = false;
if (document.activeElement === searchInput) {
isSearchInputFocused = true;
}
if (Logic._currentPanel === "containersList" && !isSearchInputFocused) {
switch(e.code) {
case "Digit0":
case "Digit1":
case "Digit2":
case "Digit3":
case "Digit4":
case "Digit5":
case "Digit6":
case "Digit7":
case "Digit8":
case "Digit9":
openTopContainers();
break;
case "Slash":
document.getElementById("search-terms").focus();
e.preventDefault();
break;
}
}
},
@ -640,7 +685,7 @@ Logic.registerPanel(P_ONBOARDING_7, {
// Let's move to the containers list panel.
Utils.addEnterHandler(document.querySelector("#sign-in"), async () => {
browser.tabs.create({
url: "https://accounts.firefox.com/?service=sync&action=email&context=fx_desktop_v3&entrypoint=multi-account-containers&utm_source=addon&utm_medium=panel&utm_campaign=container-sync",
url: "https://accounts.firefox.com/?service=sync&action=email&context=fx_desktop_v3&entrypoint=multi-account-containers&utm_source=addon&utm_medium=panel&utm_campaign=container-sync&brand=mozilla",
});
await Logic.setOnboardingStage(7);
Logic.showPanel(P_ONBOARDING_8);
@ -722,7 +767,6 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
}
});
const mozillaVpnToutName = "moz-tout-main-panel";
const mozillaVpnPermissionsWarningDotName = "moz-permissions-warning-dot";
let { mozillaVpnHiddenToutsList } = await browser.storage.local.get("mozillaVpnHiddenToutsList");
@ -731,31 +775,6 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
mozillaVpnHiddenToutsList = [];
}
// Decide whether to show Mozilla VPN tout
const mozVpnTout = document.getElementById("moz-vpn-tout");
const mozillaVpnInstalled = await browser.runtime.sendMessage({ method: "MozillaVPN_getInstallationStatus" });
const mozillaVpnToutShouldBeHidden = mozillaVpnHiddenToutsList.find(tout => tout.name === mozillaVpnToutName);
if (mozillaVpnInstalled || mozillaVpnToutShouldBeHidden) {
mozVpnTout.remove();
}
// Add handlers if tout is visible
const mozVpnDismissTout = document.querySelector(".dismiss-moz-vpn-tout");
if (mozVpnDismissTout) {
Utils.addEnterHandler((mozVpnDismissTout), async() => {
mozVpnTout.remove();
mozillaVpnHiddenToutsList.push({
name: mozillaVpnToutName
});
await browser.storage.local.set({ mozillaVpnHiddenToutsList });
});
Utils.addEnterHandler(document.querySelector("#moz-vpn-learn-more"), () => {
MozillaVPN.handleMozillaCtaClick("mac-main-panel-btn");
window.close();
});
}
// Badge Options icon if both nativeMessaging and/or proxy permissions are disabled
const bothMozillaVpnPermissionsEnabled = await MozillaVPN.bothPermissionsEnabled();
const warningDotShouldBeHidden = mozillaVpnHiddenToutsList.find(tout => tout.name === mozillaVpnPermissionsWarningDotName);
@ -940,6 +959,7 @@ Logic.registerPanel(P_CONTAINER_INFO, {
Utils.alwaysOpenInContainer(identity);
window.close();
});
// 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" : "";
@ -963,6 +983,13 @@ Logic.registerPanel(P_CONTAINER_INFO, {
Utils.addEnterHandler(manageContainer, async () => {
Logic.showPanel(P_CONTAINER_EDIT, identity);
});
const clearContainerStorageButton = document.getElementById("clear-container-storage-info");
Utils.addEnterHandler(clearContainerStorageButton, async () => {
const granted = await browser.permissions.request({ permissions: ["browsingData"] });
if (granted) {
Logic.showPanel(P_CLEAR_CONTAINER_STORAGE, identity);
}
});
return this.buildOpenTabTable(tabs);
},
@ -1253,7 +1280,8 @@ Logic.registerPanel(REOPEN_IN_CONTAINER_PICKER, {
false,
newUserContextId,
currentTab.index + 1,
currentTab.active
currentTab.active,
currentTab.groupId
);
window.close();
};
@ -1283,7 +1311,8 @@ Logic.registerPanel(REOPEN_IN_CONTAINER_PICKER, {
false,
0,
currentTab.index + 1,
currentTab.active
currentTab.active,
currentTab.groupId
);
window.close();
});
@ -1424,11 +1453,14 @@ Logic.registerPanel(P_CONTAINER_ASSIGNMENTS, {
/* As we don't have the full or correct path the best we can assume is the path is HTTPS and then replace with a broken icon later if it doesn't load.
This is pending a better solution for favicons from web extensions */
const assumedUrl = `https://${site.hostname}/favicon.ico`;
const resetSiteCookiesInfo = browser.i18n.getMessage("clearSiteCookiesTooltipInfo");
const deleteSiteInfo = browser.i18n.getMessage("deleteSiteTooltipInfo");
trElement.innerHTML = Utils.escaped`
<td>
<div class="favicon"></div>
<span title="${site.hostname}" class="menu-text truncate-text">${site.hostname}</span>
<img class="trash-button delete-assignment" src="/img/container-delete.svg" />
<img title="${resetSiteCookiesInfo}" class="reset-button reset-assignment" src="/img/refresh-16.svg" />
<img title="${deleteSiteInfo}" class="trash-button delete-assignment" src="/img/container-delete.svg" />
</td>`;
trElement.getElementsByClassName("favicon")[0].appendChild(Utils.createFavIconElement(assumedUrl));
const deleteButton = trElement.querySelector(".trash-button");
@ -1440,6 +1472,20 @@ Logic.registerPanel(P_CONTAINER_ASSIGNMENTS, {
delete assignments[siteKey];
this.showAssignedContainers(assignments);
});
const resetButton = trElement.querySelector(".reset-button");
Utils.addEnterHandler(resetButton, async () => {
const cookieStoreId = Logic.currentCookieStoreId();
const granted = await browser.permissions.request({ permissions: ["browsingData"] });
if (!granted) {
return;
}
const result = await Utils.resetCookiesForSite(site.hostname, cookieStoreId);
if (result === true) {
Logic.notify({messageId: "cookiesClearedSuccess", placeholders: [site.hostname]});
} else {
Logic.notify({messageId: "cookiesCouldNotBeCleared", placeholders: [site.hostname]});
}
});
trElement.classList.add("menu-item", "hover-highlight", "keyboard-nav");
tableElement.appendChild(trElement);
});
@ -2215,6 +2261,47 @@ Logic.registerPanel(P_MOZILLA_VPN_SERVER_LIST, {
}
});
// P_CLEAR_CONTAINER_STORAGE: Page for confirming container storage removal.
// ----------------------------------------------------------------------------
Logic.registerPanel(P_CLEAR_CONTAINER_STORAGE, {
panelSelector: "#clear-container-storage-panel",
// This method is called when the object is registered.
initialize() {
Utils.addEnterHandler(document.querySelector("#clear-container-storage-cancel-link"), () => {
const identity = Logic.currentIdentity();
Logic.showPanel(P_CONTAINER_INFO, identity, false, false);
});
Utils.addEnterHandler(document.querySelector("#close-clear-container-storage-panel"), () => {
const identity = Logic.currentIdentity();
Logic.showPanel(P_CONTAINER_INFO, identity, false, false);
});
Utils.addEnterHandler(document.querySelector("#clear-container-storage-ok-link"), async () => {
const identity = Logic.currentIdentity();
const userContextId = Utils.userContextId(identity.cookieStoreId);
const result = await browser.runtime.sendMessage({
method: "deleteContainerDataOnly",
message: { userContextId }
});
if (result.done === true) {
Logic.notify({messageId: "storageWasClearedConfirmation", placeholders: [identity.name]});
}
Logic.showPanel(P_CONTAINER_INFO, identity, false, false);
});
},
// This method is called when the panel is shown.
prepare() {
const identity = Logic.currentIdentity();
// Populating the panel: name, icon, and warning message
document.getElementById("container-clear-storage-title").textContent = identity.name;
return Promise.resolve(null);
},
});
// P_CONTAINER_DELETE: Delete a container.
// ----------------------------------------------------------------------------

View file

@ -94,6 +94,9 @@ const Utils = {
return result.join("");
},
/**
* @returns {Promise<Tab|false>}
*/
async currentTab() {
const activeTabs = await browser.tabs.query({ active: true, windowId: browser.windows.WINDOW_ID_CURRENT });
if (activeTabs.length > 0) {
@ -138,14 +141,32 @@ const Utils = {
});
},
async reloadInContainer(url, currentUserContextId, newUserContextId, tabIndex, active) {
resetCookiesForSite(pageUrl, cookieStoreId) {
return browser.runtime.sendMessage({
method: "resetCookiesForSite",
pageUrl,
cookieStoreId,
});
},
/**
* @param {string} url
* @param {string} currentUserContextId
* @param {string} newUserContextId
* @param {number} tabIndex
* @param {boolean} active
* @param {number} [groupId]
* @returns {Promise<any>}
*/
async reloadInContainer(url, currentUserContextId, newUserContextId, tabIndex, active, groupId = undefined) {
return await browser.runtime.sendMessage({
method: "reloadInContainer",
url,
currentUserContextId,
newUserContextId,
tabIndex,
active
active,
groupId
});
},
@ -159,7 +180,8 @@ const Utils = {
currentUserContextId: false,
newUserContextId: assignedUserContextId,
tabIndex: currentTab.index +1,
active:currentTab.active
active: currentTab.active,
groupId: currentTab.groupId
});
}
await Utils.setOrRemoveAssignment(

View file

@ -1,12 +1,12 @@
{
"manifest_version": 2,
"name": "Firefox Multi-Account Containers",
"version": "8.1.1",
"version": "8.3.0",
"incognito": "not_allowed",
"description": "__MSG_extensionDescription__",
"icons": {
"48": "img/container-site-d-48.png",
"96": "img/container-site-d-96.png"
"48": "img/multiaccountcontainer-16.svg",
"96": "img/multiaccountcontainer-16.svg"
},
"homepage_url": "https://github.com/mozilla/multi-account-containers#readme",
"permissions": [
@ -26,6 +26,7 @@
],
"optional_permissions": [
"bookmarks",
"browsingData",
"nativeMessaging",
"proxy"
],
@ -41,77 +42,81 @@
"default": "Ctrl+Period",
"mac": "MacCtrl+Period"
},
"description": "Open containers panel"
"description": "__MSG_openContainerPanel__"
},
"sort_tabs": {
"description": "__MSG_sortTabsByContainer__"
},
"open_container_0": {
"suggested_key": {
"default": "Ctrl+Shift+1"
},
"description": "Container Shortcut 1"
"description": "__MSG_containerShortcut__"
},
"open_container_1": {
"suggested_key": {
"default": "Ctrl+Shift+2"
},
"description": "Container Shortcut 2"
"description": "__MSG_containerShortcut__"
},
"open_container_2": {
"suggested_key": {
"default": "Ctrl+Shift+3"
},
"description": "Container Shortcut 3"
"description": "__MSG_containerShortcut__"
},
"open_container_3": {
"suggested_key": {
"default": "Ctrl+Shift+4"
},
"description": "Container Shortcut 4"
"description": "__MSG_containerShortcut__"
},
"open_container_4": {
"suggested_key": {
"default": "Ctrl+Shift+5"
},
"description": "Container Shortcut 5"
"description": "__MSG_containerShortcut__"
},
"open_container_5": {
"suggested_key": {
"default": "Ctrl+Shift+6"
},
"description": "Container Shortcut 6"
"description": "__MSG_containerShortcut__"
},
"open_container_6": {
"suggested_key": {
"default": "Ctrl+Shift+7"
},
"description": "Container Shortcut 7"
"description": "__MSG_containerShortcut__"
},
"open_container_7": {
"suggested_key": {
"default": "Ctrl+Shift+8"
},
"description": "Container Shortcut 8"
"description": "__MSG_containerShortcut__"
},
"open_container_8": {
"suggested_key": {
"default": "Ctrl+Shift+9"
},
"description": "Container Shortcut 9"
"description": "__MSG_containerShortcut__"
},
"open_container_9": {
"suggested_key": {
"default": "Ctrl+Shift+0"
},
"description": "Container Shortcut 10"
"description": "__MSG_containerShortcut__"
}
},
"browser_action": {
"browser_style": true,
"default_icon": "img/multiaccountcontainer-16.svg",
"default_title": "Multi-Account Containers",
"default_title": "Firefox Multi-Account Containers",
"default_popup": "popup.html",
"default_area": "navbar",
"theme_icons": [
{
"light": "img/multiaccountcontainer-16-dark.svg",
"light": "img/multiaccountcontainer-16.svg",
"dark": "img/multiaccountcontainer-16.svg",
"size": 32
}
@ -120,7 +125,7 @@
"page_action": {
"browser_style": true,
"default_icon": "img/container-openin-16.svg",
"default_title": "Always open this in a Container",
"default_title": "__MSG_alwaysOpenSiteInContainer__",
"default_popup": "pageActionPopup.html",
"pinned": false,
"show_matches": ["*://*/*"]
@ -144,7 +149,7 @@
],
"default_locale": "en",
"web_accessible_resources": [
"/img/container-site-d-24.png"
"/img/multiaccountcontainer-16.svg"
],
"options_ui": {
"page": "options.html",

View file

@ -40,7 +40,7 @@
</div>
</div>
</div>
<h3 data-i18n-message-id="firefoxAccountsSync"></h3>
<h3 data-i18n-message-id="sync"></h3>
<div class="settings-group">
<label>
<input type="checkbox" id="syncCheck">

View file

@ -1,7 +1,7 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Multi-Account Containers</title>
<title>Firefox Multi-Account Containers</title>
<script type="text/javascript" src="./js/i18n.js"></script>
<link rel="stylesheet" type="text/css" href="css/popup.css">

View file

@ -1,7 +1,7 @@
<html data-theme="auto">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Multi-Account Containers</title>
<title>Firefox Multi-Account Containers</title>
<script type="text/javascript" src="./js/i18n.js"></script>
<link rel="stylesheet" href="./css/popup.css">
</head>
@ -44,7 +44,7 @@
<div class="panel onboarding onboarding-panel-6 hide" id="onboarding-panel-6">
<img class="onboarding-img" alt="" src="/img/Sync.svg" />
<h3 class="onboarding-title" data-i18n-message-id="onboarding-6-header"></h3>
<p data-i18n-message-id="onboarding-6-description"></p>
<p data-i18n-message-id="onboarding-6-description-2"></p>
<div class="half-button-wrapper">
<a href="#" id="no-sync" class="half-onboarding-button grey-button keyboard-nav" tabindex="0" data-i18n-message-id="notNow"></a>
<a href="#" id="start-sync-button" class="half-onboarding-button keyboard-nav" tabindex="0" data-i18n-message-id="startSyncing"></a>
@ -53,8 +53,8 @@
<div class="panel onboarding onboarding-panel-7 hide" id="onboarding-panel-7">
<img class="onboarding-img" alt="" src="/img/Account.svg" />
<h3 class="onboarding-title" data-i18n-message-id="onboarding-7-header"></h3>
<p data-i18n-message-id="onboarding-7-description"></p>
<h3 class="onboarding-title" data-i18n-message-id="onboarding-7-header-2"></h3>
<p data-i18n-message-id="onboarding-7-description-2"></p>
<div class="half-button-wrapper">
<a href="#" id="no-sign-in" class="half-onboarding-button grey-button keyboard-nav" tabindex="0" data-i18n-message-id="notNow"></a>
<a href="#" id="sign-in" class="half-onboarding-button keyboard-nav" tabindex="0" data-i18n-message-id="signIn"></a>
@ -107,7 +107,8 @@
</div>
<div class="panel menu-panel container-panel hide" id="container-panel">
<h3 class="title">Multi-Account Containers</h3>
<span class="popup-notification-card"></span>
<h3 class="title">Firefox Multi-Account Containers</h3>
<a href="#" class="info-icon" id="info-icon" tabindex="10">
<img data-i18n-attribute-message-id="info" data-i18n-attribute="alt" alt="" src="/img/info.svg" / >
</a>
@ -191,24 +192,14 @@
</tr>
</table>
</div>
<div id="moz-vpn-tout" class="moz-vpn-content expanded">
<div class="flx-row button-wrapper">
<h4 class="moz-vpn-logo">Mozilla VPN</h4>
<button class="controller dismiss-moz-vpn-tout" tab-index="0"></button>
</div>
<div class="collapsible-content flx-col controller-collapsible-content">
<div class="flx-row flx-space-between">
<span class="moz-vpn-subtitle" data-i18n-message-id="integrateContainers"></span>
</div>
<button id="moz-vpn-learn-more" class="moz-vpn-cta primary-cta" data-i18n-message-id="getMozillaVpn"></button>
</div>
</div>
<v-padding-hack-footer></v-padding-hack-footer> <!--prevents last container from getting covered up by the 'manage containers button' when list is long-->
<div class="bottom-btn keyboard-nav controller" id="manage-containers-link" tabindex="0" data-i18n-message-id="manageContainers"></div>
</div>
<div class="hide panel menu-panel container-info-panel" id="container-info-panel" tabindex="-1">
<span class="popup-notification-card"></span>
<h3 class="title" id="container-info-title" data-i18n-attribute-message-id="personal"></h3>
<button class="btn-return arrow-left controller keyboard-nav-back" id="close-container-info-panel" tabindex="0"></button>
<hr>
@ -245,6 +236,14 @@
</span>
</td>
</tr>
<tr class="menu-item hover-highlight keyboard-nav" id="clear-container-storage" tabindex="0">
<td>
<img class="menu-icon clear-storage-icon" alt="" src="img/container-delete.svg" />
<span class="menu-text" id="clear-container-storage-info" data-i18n-message-id="clearContainerStorage"></span>
<span class="menu-arrow">
</span>
</td>
</tr>
</table>
<hr>
<div class="sub-header-wrapper">
@ -267,8 +266,9 @@
<div class="panel menu-panel container-picker-panel hide" id="container-picker-panel">
<span class="popup-notification-card"></span>
<h3 class="title" id="picker-title">
Multi-Account Containers
Firefox Multi-Account Containers
</h3>
<button class="btn-return arrow-left controller keyboard-nav-back" id="close-container-picker-panel" tabindex="0"></button>
<hr>
@ -291,6 +291,7 @@
</div>
<div class="panel menu-panel edit-container-panel hide" id="edit-container-panel">
<span class="popup-notification-card"></span>
<h3 class="title" id="container-edit-title" data-i18n-message-id="default"></h3>
<button class="btn-return arrow-left controller" id="close-container-edit-panel"></button>
<hr>
@ -381,6 +382,7 @@
</div>
<div class="panel menu-panel edit-container-assignments hide" id="edit-container-assignments">
<span class="popup-notification-card"></span>
<h3 class="title" id="edit-assignments-title" data-i18n-message-id="default"></h3>
<button class="btn-return arrow-left controller" id="close-container-assignment-panel"></button>
<hr>
@ -410,7 +412,23 @@
</div>
<div class="panel-footer">
<a href="#" class="button expanded secondary footer-button cancel-button" data-i18n-message-id="cancel" id="delete-container-cancel-link"></a>
<a href="#" class="button expanded primary footer-button" data-i18n-message-id="ok" id="delete-container-ok-link"></a>
<a href="#" class="button expanded confirmation-destructive-ok-btn footer-button alert-text" data-i18n-message-id="ok" id="delete-container-ok-link"></a>
</div>
</div>
<div class="hide panel clear-container-storage-panel" id="clear-container-storage-panel">
<h3 class="title" id="container-clear-storage-title" data-i18n-message-id="default">
</h3>
<button class="btn-return arrow-left controller" id="close-clear-container-storage-panel"></button>
<hr>
<div class="panel-content clear-container-storage-confirm">
<h4 class="clear-container-storage-confirm-title" data-i18n-message-id="clearContainerStoragePanelTitle"></h4>
<p class="clear-container-storage-warning" data-i18n-message-id="clearContainerStorageConfirmation"></p>
</div>
<div class="panel-footer">
<a href="#" class="button expanded secondary footer-button cancel-button" data-i18n-message-id="cancel" id="clear-container-storage-cancel-link"></a>
<a href="#" class="button expanded confirmation-destructive-ok-btn footer-button alert-text" data-i18n-message-id="ok" id="clear-container-storage-ok-link"></a>
</div>
</div>

View file

@ -32,6 +32,10 @@ const buildDom = async ({background = {}, popup = {}}) => {
window.crypto = {
getRandomValues: arr => crypto.randomBytes(arr.length),
};
// By default, the mock contextMenus.remove() returns undefined;
// Let it return a Promise instead, so that .then() calls chained to
// it (in src/js/background/assignManager.js) do not fail.
window.browser.contextMenus.remove.resolves();
}
}
};

View file

@ -1,15 +0,0 @@
module.exports = {
sourceDir: "./src",
build: {
overwriteDest: true,
},
run: {
pref: [
"ui.popup.disable_autohide=true",
"extensions.manifestV3.enabled=false",
"xpinstall.signatures.required=false",
"ui.systemUsesDarkTheme=1"
],
browserConsole: true
},
};