Merge pull request #535 from jonathanKingston/assignment-controls_reject-assignment

Assignment controls reject assignment
This commit is contained in:
luke crouch 2017-05-25 11:48:45 -05:00 committed by GitHub
commit 0f9dd77687
10 changed files with 476 additions and 100 deletions

1
.gitignore vendored
View file

@ -3,6 +3,7 @@ node_modules
README.html README.html
*.xpi *.xpi
*.swp *.swp
*.swo
.vimrc .vimrc
.env .env
addon.env addon.env

View file

@ -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/",

View file

@ -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

View file

@ -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,43 +192,71 @@ 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 is to mitigate https://bugzilla.mozilla.org/show_bug.cgi?id=1351418
&& this.isTabPermittedAssign(tab)) { let prefix = " "; // Alignment of non breaking space, unknown why this requires so many spaces to align with the tick
this.storageArea.get(tab.url).then((siteSettings) => { let menuId = this.MENU_ASSIGN_ID;
// ✓ This is to mitigate https://bugzilla.mozilla.org/show_bug.cgi?id=1351418 if (siteSettings) {
let prefix = " "; // Alignment of non breaking space, unknown why this requires so many spaces to align with the tick prefix = "✓";
let menuId = this.MENU_ASSIGN_ID; menuId = this.MENU_REMOVE_ID;
if (siteSettings) {
prefix = "✓";
menuId = this.MENU_REMOVE_ID;
}
browser.contextMenus.create({
id: menuId,
title: `${prefix} Always Open in This Container`,
checked: true,
contexts: ["all"],
});
}).catch((e) => {
throw e;
});
} }
browser.contextMenus.create({
id: menuId,
title: `${prefix} Always Open in This Container`,
checked: true,
contexts: ["all"],
});
}, },
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 += `&currentCookieStoreId=${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) {

View file

@ -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>

View file

@ -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;
}

View file

@ -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 */

View file

@ -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);
e.preventDefault(); [...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();
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);
}
}

View file

@ -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");

View file

@ -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>