Merge pull request #535 from jonathanKingston/assignment-controls_reject-assignment
Assignment controls reject assignment
This commit is contained in:
commit
0f9dd77687
10 changed files with 476 additions and 100 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,6 +3,7 @@ node_modules
|
||||||
README.html
|
README.html
|
||||||
*.xpi
|
*.xpi
|
||||||
*.swp
|
*.swp
|
||||||
|
*.swo
|
||||||
.vimrc
|
.vimrc
|
||||||
.env
|
.env
|
||||||
addon.env
|
addon.env
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"declaration-block-no-duplicate-properties": true,
|
"declaration-block-no-duplicate-properties": true,
|
||||||
"order/declaration-block-properties-alphabetical-order": true,
|
"order/declaration-block-properties-alphabetical-order": true,
|
||||||
"property-blacklist": [
|
"property-blacklist": [
|
||||||
"/height/",
|
"/(min[-]|max[-])height/",
|
||||||
"/width/",
|
"/width/",
|
||||||
"/top/",
|
"/top/",
|
||||||
"/bottom/",
|
"/bottom/",
|
||||||
|
|
|
@ -220,7 +220,7 @@ of a `testpilottest` telemetry ping for each scenario.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* The user clicks "Take me there" to reload a site into a container after the user picked "Always Open in this Container".
|
* The user clicks "Open in *assigned* container" to reload a site into a container after the user picked "Always Open in this Container".
|
||||||
|
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
|
@ -229,6 +229,15 @@ of a `testpilottest` telemetry ping for each scenario.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* The user clicks "Open in *Current* container" to reload a site into a container after the user picked "Always Open in this Container".
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"uuid": <uuid>,
|
||||||
|
"event": "click-to-reload-page-in-same-container"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
* Firefox automatically reloads a site into a container after the user picked "Always Open in this Container".
|
* Firefox automatically reloads a site into a container after the user picked "Always Open in this Container".
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
|
|
@ -6,6 +6,7 @@ const assignManager = {
|
||||||
MENU_REMOVE_ID: "remove-open-in-this-container",
|
MENU_REMOVE_ID: "remove-open-in-this-container",
|
||||||
storageArea: {
|
storageArea: {
|
||||||
area: browser.storage.local,
|
area: browser.storage.local,
|
||||||
|
exemptedTabs: {},
|
||||||
|
|
||||||
getSiteStoreKey(pageUrl) {
|
getSiteStoreKey(pageUrl) {
|
||||||
const url = new window.URL(pageUrl);
|
const url = new window.URL(pageUrl);
|
||||||
|
@ -13,6 +14,22 @@ const assignManager = {
|
||||||
return `${storagePrefix}${url.hostname}`;
|
return `${storagePrefix}${url.hostname}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setExempted(pageUrl, tabId) {
|
||||||
|
if (!(tabId in this.exemptedTabs)) {
|
||||||
|
this.exemptedTabs[tabId] = [];
|
||||||
|
}
|
||||||
|
const siteStoreKey = this.getSiteStoreKey(pageUrl);
|
||||||
|
this.exemptedTabs[tabId].push(siteStoreKey);
|
||||||
|
},
|
||||||
|
|
||||||
|
isExempted(pageUrl, tabId) {
|
||||||
|
if (!(tabId in this.exemptedTabs)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const siteStoreKey = this.getSiteStoreKey(pageUrl);
|
||||||
|
return this.exemptedTabs[tabId].includes(siteStoreKey);
|
||||||
|
},
|
||||||
|
|
||||||
get(pageUrl) {
|
get(pageUrl) {
|
||||||
const siteStoreKey = this.getSiteStoreKey(pageUrl);
|
const siteStoreKey = this.getSiteStoreKey(pageUrl);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -70,39 +87,16 @@ const assignManager = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// We return here so the confirm page can load the tab when exempted
|
||||||
|
async _exemptTab(m) {
|
||||||
|
const pageUrl = m.pageUrl;
|
||||||
|
this.storageArea.setExempted(pageUrl, m.tabId);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
browser.contextMenus.onClicked.addListener((info, tab) => {
|
browser.contextMenus.onClicked.addListener((info, tab) => {
|
||||||
const userContextId = this.getUserContextIdFromCookieStore(tab);
|
this._onClickedHandler(info, 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")
|
|
||||||
});
|
|
||||||
backgroundLogic.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
|
// Before a request is handled by the browser we decide if we should route through a different container
|
||||||
|
@ -117,11 +111,12 @@ const assignManager = {
|
||||||
const userContextId = this.getUserContextIdFromCookieStore(tab);
|
const userContextId = this.getUserContextIdFromCookieStore(tab);
|
||||||
if (!siteSettings
|
if (!siteSettings
|
||||||
|| userContextId === siteSettings.userContextId
|
|| userContextId === siteSettings.userContextId
|
||||||
|| tab.incognito) {
|
|| tab.incognito
|
||||||
|
|| this.storageArea.isExempted(options.url, tab.id)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.reloadPageInContainer(options.url, siteSettings.userContextId, tab.index + 1, siteSettings.neverAsk);
|
this.reloadPageInContainer(options.url, userContextId, siteSettings.userContextId, tab.index + 1, siteSettings.neverAsk);
|
||||||
this.calculateContextMenu(tab);
|
this.calculateContextMenu(tab);
|
||||||
|
|
||||||
/* Removal of existing tabs:
|
/* Removal of existing tabs:
|
||||||
|
@ -149,6 +144,26 @@ const assignManager = {
|
||||||
},{urls: ["<all_urls>"], types: ["main_frame"]}, ["blocking"]);
|
},{urls: ["<all_urls>"], types: ["main_frame"]}, ["blocking"]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async _onClickedHandler(info, tab) {
|
||||||
|
const userContextId = this.getUserContextIdFromCookieStore(tab);
|
||||||
|
// Mapping ${URL(info.pageUrl).hostname} to ${userContextId}
|
||||||
|
if (userContextId) {
|
||||||
|
// let actionName;
|
||||||
|
let remove;
|
||||||
|
if (info.menuItemId === this.MENU_ASSIGN_ID) {
|
||||||
|
//actionName = "added";
|
||||||
|
// storageAction = this._setAssignment(info.pageUrl, userContextId, setOrRemove);
|
||||||
|
remove = false;
|
||||||
|
} else {
|
||||||
|
// actionName = "removed";
|
||||||
|
//storageAction = this.storageArea.remove(info.pageUrl);
|
||||||
|
remove = true;
|
||||||
|
}
|
||||||
|
await this._setOrRemoveAssignment(info.pageUrl, userContextId, remove);
|
||||||
|
this.calculateContextMenu(tab);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
deleteContainer(userContextId) {
|
deleteContainer(userContextId) {
|
||||||
this.storageArea.deleteContainer(userContextId);
|
this.storageArea.deleteContainer(userContextId);
|
||||||
|
@ -177,19 +192,50 @@ const assignManager = {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
calculateContextMenu(tab) {
|
async _setOrRemoveAssignment(pageUrl, userContextId, remove) {
|
||||||
|
let actionName;
|
||||||
|
if (!remove) {
|
||||||
|
await this.storageArea.set(pageUrl, {
|
||||||
|
userContextId,
|
||||||
|
neverAsk: false,
|
||||||
|
exempted: []
|
||||||
|
});
|
||||||
|
actionName = "added";
|
||||||
|
} else {
|
||||||
|
await this.storageArea.remove(pageUrl);
|
||||||
|
actionName = "removed";
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
});
|
||||||
|
backgroundLogic.sendTelemetryPayload({
|
||||||
|
event: `${actionName}-container-assignment`,
|
||||||
|
userContextId: userContextId,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async _getAssignment(tab) {
|
||||||
|
const cookieStore = this.getUserContextIdFromCookieStore(tab);
|
||||||
|
// Ensure we have a cookieStore to assign to
|
||||||
|
if (cookieStore
|
||||||
|
&& this.isTabPermittedAssign(tab)) {
|
||||||
|
return await this.storageArea.get(tab.url);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async calculateContextMenu(tab) {
|
||||||
// There is a focus issue in this menu where if you change window with a context menu click
|
// 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
|
// you get the wrong menu display because of async
|
||||||
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1215376#c16
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1215376#c16
|
||||||
// We also can't change for always private mode
|
// We also can't change for always private mode
|
||||||
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1352102
|
// 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_ASSIGN_ID);
|
||||||
browser.contextMenus.remove(this.MENU_REMOVE_ID);
|
browser.contextMenus.remove(this.MENU_REMOVE_ID);
|
||||||
// Ensure we have a cookieStore to assign to
|
const siteSettings = await this._getAssignment(tab);
|
||||||
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
|
// ✓ 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 prefix = " "; // Alignment of non breaking space, unknown why this requires so many spaces to align with the tick
|
||||||
let menuId = this.MENU_ASSIGN_ID;
|
let menuId = this.MENU_ASSIGN_ID;
|
||||||
|
@ -203,17 +249,14 @@ const assignManager = {
|
||||||
checked: true,
|
checked: true,
|
||||||
contexts: ["all"],
|
contexts: ["all"],
|
||||||
});
|
});
|
||||||
}).catch((e) => {
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
reloadPageInContainer(url, userContextId, index, neverAsk = false) {
|
reloadPageInContainer(url, currentUserContextId, userContextId, index, neverAsk = false) {
|
||||||
|
const cookieStoreId = backgroundLogic.cookieStoreId(userContextId);
|
||||||
const loadPage = browser.extension.getURL("confirm-page.html");
|
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 the user has explicitly checked "Never Ask Again" on the warning page we will send them straight there
|
||||||
if (neverAsk) {
|
if (neverAsk) {
|
||||||
browser.tabs.create({url, cookieStoreId: backgroundLogic.cookieStoreId(userContextId), index});
|
browser.tabs.create({url, cookieStoreId, index});
|
||||||
backgroundLogic.sendTelemetryPayload({
|
backgroundLogic.sendTelemetryPayload({
|
||||||
event: "auto-reload-page-in-container",
|
event: "auto-reload-page-in-container",
|
||||||
userContextId: userContextId,
|
userContextId: userContextId,
|
||||||
|
@ -223,8 +266,17 @@ const assignManager = {
|
||||||
event: "prompt-to-reload-page-in-container",
|
event: "prompt-to-reload-page-in-container",
|
||||||
userContextId: userContextId,
|
userContextId: userContextId,
|
||||||
});
|
});
|
||||||
const confirmUrl = `${loadPage}?url=${url}`;
|
let confirmUrl = `${loadPage}?url=${encodeURIComponent(url)}&cookieStoreId=${cookieStoreId}`;
|
||||||
browser.tabs.create({url: confirmUrl, cookieStoreId: backgroundLogic.cookieStoreId(userContextId), index}).then(() => {
|
let currentCookieStoreId;
|
||||||
|
if (currentUserContextId) {
|
||||||
|
currentCookieStoreId = backgroundLogic.cookieStoreId(currentUserContextId);
|
||||||
|
confirmUrl += `¤tCookieStoreId=${currentCookieStoreId}`;
|
||||||
|
}
|
||||||
|
browser.tabs.create({
|
||||||
|
url: confirmUrl,
|
||||||
|
cookieStoreId: currentCookieStoreId,
|
||||||
|
index
|
||||||
|
}).then(() => {
|
||||||
// We don't want to sync this URL ever nor clutter the users history
|
// We don't want to sync this URL ever nor clutter the users history
|
||||||
browser.history.deleteUrl({url: confirmUrl});
|
browser.history.deleteUrl({url: confirmUrl});
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
|
@ -354,7 +406,7 @@ const messageHandler = {
|
||||||
LAST_CREATED_TAB_TIMER: 2000,
|
LAST_CREATED_TAB_TIMER: 2000,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// Handles messages from webextension/js/popup.js
|
// Handles messages from webextension code
|
||||||
browser.runtime.onMessage.addListener((m) => {
|
browser.runtime.onMessage.addListener((m) => {
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
|
@ -372,11 +424,25 @@ const messageHandler = {
|
||||||
case "neverAsk":
|
case "neverAsk":
|
||||||
assignManager._neverAsk(m);
|
assignManager._neverAsk(m);
|
||||||
break;
|
break;
|
||||||
|
case "getAssignment":
|
||||||
|
response = browser.tabs.get(m.tabId).then((tab) => {
|
||||||
|
return assignManager._getAssignment(tab);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "setOrRemoveAssignment":
|
||||||
|
response = browser.tabs.get(m.tabId).then((tab) => {
|
||||||
|
const userContextId = assignManager.getUserContextIdFromCookieStore(tab);
|
||||||
|
return assignManager._setOrRemoveAssignment(tab.url, userContextId, m.value);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "exemptContainerAssignment":
|
||||||
|
response = assignManager._exemptTab(m);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handles messages from index.js
|
// Handles messages from sdk code
|
||||||
const port = browser.runtime.connect();
|
const port = browser.runtime.connect();
|
||||||
port.onMessage.addListener(m => {
|
port.onMessage.addListener(m => {
|
||||||
switch (m.type) {
|
switch (m.type) {
|
||||||
|
|
|
@ -8,22 +8,21 @@
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h1 class="title-text">Should we open this in your container?</h1>
|
<h1 class="title-text">Open this site in your assigned container?</h1>
|
||||||
</div>
|
</div>
|
||||||
<form id="redirect-form">
|
<form id="redirect-form">
|
||||||
<p>
|
<p>
|
||||||
Looks like you requested:
|
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>
|
</p>
|
||||||
<div id="redirect-url"></div>
|
<div id="redirect-url"></div>
|
||||||
<p>
|
<p>Would you still like to open in this current container?</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 />
|
||||||
<br />
|
<br />
|
||||||
<input id="never-ask" type="checkbox" /><label for="never-ask">Remember my decision for this site</label>
|
<input id="never-ask" type="checkbox" /><label for="never-ask">Remember my decision for this site</label>
|
||||||
<br />
|
<br />
|
||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
<button id="confirm" class="button primary" autofocus>Take me there</button>
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -4,11 +4,21 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
background: url(/img/onboarding-1.png) no-repeat;
|
background: url(/img/onboarding-4.png) no-repeat;
|
||||||
background-position: -10px -15px;
|
background-position: -10px -15px;
|
||||||
background-size: 285px;
|
background-size: 300px;
|
||||||
margin-inline-start: -285px;
|
margin-inline-start: -350px;
|
||||||
padding-inline-start: 285px;
|
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) {
|
@media only screen and (max-width: 1300px) {
|
||||||
|
@ -36,6 +46,28 @@ html {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#redirect-url {
|
||||||
|
background: #efefef;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#redirect-url img {
|
||||||
|
block-size: 16px;
|
||||||
|
inline-size: 16px;
|
||||||
|
margin-inline-end: 6px;
|
||||||
|
offset-block-start: 3px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
dfn {
|
dfn {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-container > button {
|
||||||
|
min-inline-size: 240px;
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,11 @@ html {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--font-size-heading: 16px;
|
||||||
|
--primary-action-color: #248aeb;
|
||||||
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
*::before,
|
*::before,
|
||||||
*::after {
|
*::after {
|
||||||
|
@ -35,6 +40,10 @@ table {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.offpage {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Color and icon helpers */
|
/* Color and icon helpers */
|
||||||
[data-identity-color="blue"] {
|
[data-identity-color="blue"] {
|
||||||
--identity-tab-color: #37adff;
|
--identity-tab-color: #37adff;
|
||||||
|
@ -140,6 +149,18 @@ table {
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Text links with actions */
|
||||||
|
|
||||||
|
.action-link:link {
|
||||||
|
color: var(--primary-action-color);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-link:active,
|
||||||
|
.action-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
/* Panels keep everything togethert */
|
/* Panels keep everything togethert */
|
||||||
.panel {
|
.panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -223,7 +244,7 @@ table {
|
||||||
|
|
||||||
.onboarding-title {
|
.onboarding-title {
|
||||||
color: #43484e;
|
color: #43484e;
|
||||||
font-size: 16px;
|
font-size: var(--font-size-heading);
|
||||||
margin-block-end: 0;
|
margin-block-end: 0;
|
||||||
margin-block-start: 0;
|
margin-block-start: 0;
|
||||||
margin-inline-end: 0;
|
margin-inline-end: 0;
|
||||||
|
@ -312,7 +333,7 @@ manage things like container crud */
|
||||||
.panel-header-text {
|
.panel-header-text {
|
||||||
color: #4a4a4a;
|
color: #4a4a4a;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: 16px;
|
font-size: var(--font-size-heading);
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin-block-end: 0;
|
margin-block-end: 0;
|
||||||
margin-block-start: 0;
|
margin-block-start: 0;
|
||||||
|
@ -324,6 +345,24 @@ manage things like container crud */
|
||||||
padding-inline-start: 16px;
|
padding-inline-start: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#container-panel .panel-header {
|
||||||
|
background-color: #efefef;
|
||||||
|
block-size: 26px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container-panel .panel-header-text {
|
||||||
|
color: #727272;
|
||||||
|
font-size: 14px;
|
||||||
|
padding-block-end: 0;
|
||||||
|
padding-block-start: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container-panel .sort-containers-link {
|
||||||
|
margin-inline-end: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
span ~ .panel-header-text {
|
span ~ .panel-header-text {
|
||||||
padding-block-end: 0;
|
padding-block-end: 0;
|
||||||
padding-block-start: 0;
|
padding-block-start: 0;
|
||||||
|
@ -331,6 +370,63 @@ span ~ .panel-header-text {
|
||||||
padding-inline-start: 0;
|
padding-inline-start: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#current-tab {
|
||||||
|
max-inline-size: 100%;
|
||||||
|
min-block-size: 94px;
|
||||||
|
padding-block-end: 16px;
|
||||||
|
padding-block-start: 16px;
|
||||||
|
padding-inline-end: 16px;
|
||||||
|
padding-inline-start: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#current-tab > h3 {
|
||||||
|
color: #4a4a4a;
|
||||||
|
font-size: var(--font-size-heading);
|
||||||
|
font-weight: normal;
|
||||||
|
margin-block-end: 0;
|
||||||
|
margin-block-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#current-page {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#current-page > img {
|
||||||
|
block-size: 16px;
|
||||||
|
inline-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#current-tab > label {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
margin-inline-start: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#current-tab > label > input {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#current-tab > label > img {
|
||||||
|
block-size: 12px;
|
||||||
|
display: inline-block;
|
||||||
|
inline-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#current-container {
|
||||||
|
display: contents;
|
||||||
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
#current-container > .usercontext-icon {
|
||||||
|
background-size: 16px;
|
||||||
|
block-size: 16px;
|
||||||
|
display: block;
|
||||||
|
flex: 0 0 20px;
|
||||||
|
inline-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Rows used when iterating over panels */
|
/* Rows used when iterating over panels */
|
||||||
.container-panel-row {
|
.container-panel-row {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -501,7 +597,7 @@ span ~ .panel-header-text {
|
||||||
|
|
||||||
.edit-containers-exit-text {
|
.edit-containers-exit-text {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #248aeb;
|
background: var(--primary-action-color);
|
||||||
block-size: 100%;
|
block-size: 100%;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -528,7 +624,7 @@ span ~ .panel-header-text {
|
||||||
|
|
||||||
.delete-container-confirm-title {
|
.delete-container-confirm-title {
|
||||||
color: #000;
|
color: #000;
|
||||||
font-size: 16px;
|
font-size: var(--font-size-heading);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Form info */
|
/* Form info */
|
||||||
|
|
|
@ -1,10 +1,46 @@
|
||||||
const redirectUrl = new URL(window.location).searchParams.get("url");
|
async function load() {
|
||||||
document.getElementById("redirect-url").textContent = redirectUrl;
|
const searchParams = new URL(window.location).searchParams;
|
||||||
const redirectSite = new URL(redirectUrl).hostname;
|
const redirectUrl = decodeURIComponent(searchParams.get("url"));
|
||||||
document.getElementById("redirect-site").textContent = redirectSite;
|
const cookieStoreId = searchParams.get("cookieStoreId");
|
||||||
|
const currentCookieStoreId = searchParams.get("currentCookieStoreId");
|
||||||
|
const redirectUrlElement = document.getElementById("redirect-url");
|
||||||
|
redirectUrlElement.textContent = redirectUrl;
|
||||||
|
createFavicon(redirectUrl, redirectUrlElement);
|
||||||
|
|
||||||
document.getElementById("redirect-form").addEventListener("submit", (e) => {
|
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("redirect-form").addEventListener("submit", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
const buttonTarget = e.explicitOriginalTarget;
|
||||||
|
switch (buttonTarget.id) {
|
||||||
|
case "confirm":
|
||||||
|
confirmSubmit(redirectUrl, cookieStoreId);
|
||||||
|
break;
|
||||||
|
case "deny":
|
||||||
|
denySubmit(redirectUrl);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFavicon(pageUrl, redirectUrlElement) {
|
||||||
|
const origin = new URL(pageUrl).origin;
|
||||||
|
const imageElement = document.createElement("img");
|
||||||
|
imageElement.src = `${origin}/favicon.ico`;
|
||||||
|
|
||||||
|
redirectUrlElement.prepend(imageElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmSubmit(redirectUrl, cookieStoreId) {
|
||||||
const neverAsk = document.getElementById("never-ask").checked;
|
const neverAsk = document.getElementById("never-ask").checked;
|
||||||
// Sending neverAsk message to background to store for next time we see this process
|
// Sending neverAsk message to background to store for next time we see this process
|
||||||
if (neverAsk) {
|
if (neverAsk) {
|
||||||
|
@ -12,20 +48,38 @@ document.getElementById("redirect-form").addEventListener("submit", (e) => {
|
||||||
method: "neverAsk",
|
method: "neverAsk",
|
||||||
neverAsk: true,
|
neverAsk: true,
|
||||||
pageUrl: redirectUrl
|
pageUrl: redirectUrl
|
||||||
}).then(() => {
|
|
||||||
redirect();
|
|
||||||
}).catch(() => {
|
|
||||||
// Can't really do much here user will have to click it again
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
browser.runtime.sendMessage({
|
browser.runtime.sendMessage({
|
||||||
method: "sendTelemetryPayload",
|
method: "sendTelemetryPayload",
|
||||||
event: "click-to-reload-page-in-container",
|
event: "click-to-reload-page-in-container",
|
||||||
});
|
});
|
||||||
redirect();
|
openInContainer(redirectUrl, cookieStoreId);
|
||||||
});
|
}
|
||||||
|
|
||||||
function redirect() {
|
async function denySubmit(redirectUrl) {
|
||||||
const redirectUrl = document.getElementById("redirect-url").textContent;
|
const tab = await browser.tabs.query({active: true});
|
||||||
|
await browser.runtime.sendMessage({
|
||||||
|
method: "exemptContainerAssignment",
|
||||||
|
tabId: tab[0].id,
|
||||||
|
pageUrl: redirectUrl
|
||||||
|
});
|
||||||
document.location.replace(redirectUrl);
|
document.location.replace(redirectUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
|
|
||||||
|
async function openInContainer(redirectUrl, cookieStoreId) {
|
||||||
|
const tabs = await browser.tabs.query({active: true});
|
||||||
|
browser.runtime.sendMessage({
|
||||||
|
method: "sendTelemetryPayload",
|
||||||
|
event: "click-to-reload-page-in-same-container",
|
||||||
|
});
|
||||||
|
await browser.tabs.create({
|
||||||
|
cookieStoreId,
|
||||||
|
url: redirectUrl
|
||||||
|
});
|
||||||
|
if (tabs.length > 0) {
|
||||||
|
browser.tabs.remove(tabs[0].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ const P_CONTAINERS_EDIT = "containersEdit";
|
||||||
const P_CONTAINER_INFO = "containerInfo";
|
const P_CONTAINER_INFO = "containerInfo";
|
||||||
const P_CONTAINER_EDIT = "containerEdit";
|
const P_CONTAINER_EDIT = "containerEdit";
|
||||||
const P_CONTAINER_DELETE = "containerDelete";
|
const P_CONTAINER_DELETE = "containerDelete";
|
||||||
|
const DEFAULT_FAVICON = "moz-icon://goat?size=16";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Escapes any occurances of &, ", <, > or / with XML entities.
|
* Escapes any occurances of &, ", <, > or / with XML entities.
|
||||||
|
@ -107,6 +108,16 @@ const Logic = {
|
||||||
browser.storage.local.set({browserActionBadgesClicked: storage.browserActionBadgesClicked});
|
browser.storage.local.set({browserActionBadgesClicked: storage.browserActionBadgesClicked});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async identity(cookieStoreId) {
|
||||||
|
const identity = await browser.contextualIdentities.get(cookieStoreId);
|
||||||
|
return identity || {
|
||||||
|
name: "Default",
|
||||||
|
cookieStoreId,
|
||||||
|
icon: "circle",
|
||||||
|
color: "black"
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
addEnterHandler(element, handler) {
|
addEnterHandler(element, handler) {
|
||||||
element.addEventListener("click", handler);
|
element.addEventListener("click", handler);
|
||||||
element.addEventListener("keydown", (e) => {
|
element.addEventListener("keydown", (e) => {
|
||||||
|
@ -121,6 +132,14 @@ const Logic = {
|
||||||
return (userContextId !== cookieStoreId) ? Number(userContextId) : false;
|
return (userContextId !== cookieStoreId) ? Number(userContextId) : false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async currentTab() {
|
||||||
|
const activeTabs = await browser.tabs.query({active: true});
|
||||||
|
if (activeTabs.length > 0) {
|
||||||
|
return activeTabs[0];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
refreshIdentities() {
|
refreshIdentities() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
browser.contextualIdentities.query({}),
|
browser.contextualIdentities.query({}),
|
||||||
|
@ -139,7 +158,7 @@ const Logic = {
|
||||||
}).catch((e) => {throw e;});
|
}).catch((e) => {throw e;});
|
||||||
},
|
},
|
||||||
|
|
||||||
showPanel(panel, currentIdentity = null) {
|
async showPanel(panel, currentIdentity = null) {
|
||||||
// Invalid panel... ?!?
|
// Invalid panel... ?!?
|
||||||
if (!(panel in this._panels)) {
|
if (!(panel in this._panels)) {
|
||||||
throw new Error("Something really bad happened. Unknown panel: " + panel);
|
throw new Error("Something really bad happened. Unknown panel: " + panel);
|
||||||
|
@ -151,15 +170,18 @@ const Logic = {
|
||||||
this._currentIdentity = currentIdentity;
|
this._currentIdentity = currentIdentity;
|
||||||
|
|
||||||
// Initialize the panel before showing it.
|
// Initialize the panel before showing it.
|
||||||
this._panels[panel].prepare().then(() => {
|
await this._panels[panel].prepare();
|
||||||
for (let panelElement of document.querySelectorAll(".panel")) { // eslint-disable-line prefer-const
|
Object.keys(this._panels).forEach((panelKey) => {
|
||||||
|
const panelItem = this._panels[panelKey];
|
||||||
|
const panelElement = document.querySelector(panelItem.panelSelector);
|
||||||
|
if (!panelElement.classList.contains("hide")) {
|
||||||
panelElement.classList.add("hide");
|
panelElement.classList.add("hide");
|
||||||
|
if ("unregister" in panelItem) {
|
||||||
|
panelItem.unregister();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
document.querySelector(this._panels[panel].panelSelector).classList.remove("hide");
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
throw new Error("Failed to show panel " + panel);
|
|
||||||
});
|
});
|
||||||
|
document.querySelector(this._panels[panel].panelSelector).classList.remove("hide");
|
||||||
},
|
},
|
||||||
|
|
||||||
showPreviousPanel() {
|
showPreviousPanel() {
|
||||||
|
@ -205,6 +227,21 @@ const Logic = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getAssignment(tab) {
|
||||||
|
return browser.runtime.sendMessage({
|
||||||
|
method: "getAssignment",
|
||||||
|
tabId: tab.id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setOrRemoveAssignment(tab, value) {
|
||||||
|
return browser.runtime.sendMessage({
|
||||||
|
method: "setOrRemoveAssignment",
|
||||||
|
tabId: tab.id,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
generateIdentityName() {
|
generateIdentityName() {
|
||||||
const defaultName = "Container #";
|
const defaultName = "Container #";
|
||||||
const ids = [];
|
const ids = [];
|
||||||
|
@ -364,12 +401,86 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// When the popup is open sometimes the tab will still be updating it's state
|
||||||
|
this.tabUpdateHandler = (tabId, changeInfo) => {
|
||||||
|
const propertiesToUpdate = ["title", "favIconUrl"];
|
||||||
|
const hasChanged = Object.keys(changeInfo).find((changeInfoKey) => {
|
||||||
|
if (propertiesToUpdate.includes(changeInfoKey)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (hasChanged) {
|
||||||
|
this.prepareCurrentTabHeader();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
browser.tabs.onUpdated.addListener(this.tabUpdateHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
unregister() {
|
||||||
|
browser.tabs.onUpdated.removeListener(this.tabUpdateHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
setupAssignmentCheckbox(siteSettings) {
|
||||||
|
const assignmentCheckboxElement = document.getElementById("container-page-assigned");
|
||||||
|
// Cater for null and false
|
||||||
|
assignmentCheckboxElement.checked = !!siteSettings;
|
||||||
|
let disabled = false;
|
||||||
|
if (siteSettings === false) {
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
assignmentCheckboxElement.disabled = disabled;
|
||||||
|
},
|
||||||
|
|
||||||
|
async prepareCurrentTabHeader() {
|
||||||
|
const currentTab = await Logic.currentTab();
|
||||||
|
const currentTabElement = document.getElementById("current-tab");
|
||||||
|
const assignmentCheckboxElement = document.getElementById("container-page-assigned");
|
||||||
|
assignmentCheckboxElement.addEventListener("change", () => {
|
||||||
|
Logic.setOrRemoveAssignment(currentTab, !assignmentCheckboxElement.checked);
|
||||||
|
});
|
||||||
|
currentTabElement.hidden = !currentTab;
|
||||||
|
this.setupAssignmentCheckbox(false);
|
||||||
|
if (currentTab) {
|
||||||
|
const identity = await Logic.identity(currentTab.cookieStoreId);
|
||||||
|
const siteSettings = await Logic.getAssignment(currentTab);
|
||||||
|
this.setupAssignmentCheckbox(siteSettings);
|
||||||
|
const currentPage = document.getElementById("current-page");
|
||||||
|
const favIconUrl = currentTab.favIconUrl || "";
|
||||||
|
currentPage.innerHTML = escaped`
|
||||||
|
<img class="offpage" src="${favIconUrl}" /> ${currentTab.title}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const imageElement = currentPage.querySelector("img");
|
||||||
|
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);
|
||||||
|
|
||||||
|
const currentContainer = document.getElementById("current-container");
|
||||||
|
currentContainer.innerHTML = escaped`
|
||||||
|
<div
|
||||||
|
class="usercontext-icon"
|
||||||
|
data-identity-icon="${identity.icon}"
|
||||||
|
data-identity-color="${identity.color}">
|
||||||
|
</div>
|
||||||
|
${identity.name}
|
||||||
|
`;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// This method is called when the panel is shown.
|
// This method is called when the panel is shown.
|
||||||
prepare() {
|
async prepare() {
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
|
|
||||||
|
this.prepareCurrentTabHeader();
|
||||||
|
|
||||||
Logic.identities().forEach(identity => {
|
Logic.identities().forEach(identity => {
|
||||||
const hasTabs = (identity.hasHiddenTabs || identity.hasOpenTabs);
|
const hasTabs = (identity.hasHiddenTabs || identity.hasOpenTabs);
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
|
|
|
@ -38,9 +38,17 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel container-panel hide" id="container-panel">
|
<div class="panel container-panel hide" id="container-panel">
|
||||||
|
<div id="current-tab">
|
||||||
|
<h3>Current Tab</h3>
|
||||||
|
<div id="current-page"></div>
|
||||||
|
<label for="container-page-assigned">
|
||||||
|
<input type="checkbox" id="container-page-assigned" />
|
||||||
|
Always open in <span id="current-container"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<h3 class="panel-header-text">Containers</h3>
|
<h3 class="panel-header-text">Containers</h3>
|
||||||
<a href="#" class="pop-button" id="sort-containers-link"><img class="pop-button-image" alt="Sort Containers" title="Sort Containers" src="/img/container-sort.svg"></a>
|
<a href="#" class="action-link" id="sort-containers-link">Sort Tabs</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="scrollable panel-content" tabindex="-1">
|
<div class="scrollable panel-content" tabindex="-1">
|
||||||
<table>
|
<table>
|
||||||
|
|
Loading…
Add table
Reference in a new issue