Merge pull request #576 from jonathanKingston/shield-study

Shield study work
This commit is contained in:
luke crouch 2017-06-13 11:41:28 -05:00 committed by GitHub
commit fc789a49ac
11 changed files with 460 additions and 131 deletions

View file

@ -9,6 +9,7 @@ module.exports = {
"webextensions": true "webextensions": true
}, },
"globals": { "globals": {
"Utils": true,
"CustomizableUI": true, "CustomizableUI": true,
"CustomizableWidgets": true, "CustomizableWidgets": true,
"SessionStore": true, "SessionStore": true,

View file

@ -56,19 +56,25 @@ const assignManager = {
return this.area.remove([siteStoreKey]); return this.area.remove([siteStoreKey]);
}, },
deleteContainer(userContextId) { async deleteContainer(userContextId) {
const removeKeys = []; const sitesByContainer = await this.getByContainer(userContextId);
this.area.get().then((siteConfigs) => { this.area.remove(Object.keys(sitesByContainer));
Object.keys(siteConfigs).forEach((key) => { },
// For some reason this is stored as string... lets check them both as that
if (String(siteConfigs[key].userContextId) === String(userContextId)) { async getByContainer(userContextId) {
removeKeys.push(key); const sites = {};
} const siteConfigs = await this.area.get();
}); Object.keys(siteConfigs).forEach((key) => {
this.area.remove(removeKeys); // For some reason this is stored as string... lets check them both as that
}).catch((e) => { if (String(siteConfigs[key].userContextId) === String(userContextId)) {
throw e; const site = siteConfigs[key];
// In hindsight we should have stored this
// TODO file a follow up to clean the storage onLoad
site.hostname = key.replace(/^siteContainerMap@@_/, "");
sites[key] = site;
}
}); });
return sites;
} }
}, },
@ -104,6 +110,7 @@ const assignManager = {
if (options.frameId !== 0 || options.tabId === -1) { if (options.frameId !== 0 || options.tabId === -1) {
return {}; return {};
} }
this.removeContextMenu();
return Promise.all([ return Promise.all([
browser.tabs.get(options.tabId), browser.tabs.get(options.tabId),
this.storageArea.get(options.url) this.storageArea.get(options.url)
@ -151,15 +158,11 @@ const assignManager = {
// let actionName; // let actionName;
let remove; let remove;
if (info.menuItemId === this.MENU_ASSIGN_ID) { if (info.menuItemId === this.MENU_ASSIGN_ID) {
//actionName = "added";
// storageAction = this._setAssignment(info.pageUrl, userContextId, setOrRemove);
remove = false; remove = false;
} else { } else {
// actionName = "removed";
//storageAction = this.storageArea.remove(info.pageUrl);
remove = true; remove = true;
} }
await this._setOrRemoveAssignment(info.pageUrl, userContextId, remove); await this._setOrRemoveAssignment(tab.id, info.pageUrl, userContextId, remove);
this.calculateContextMenu(tab); this.calculateContextMenu(tab);
} }
}, },
@ -192,7 +195,7 @@ const assignManager = {
return true; return true;
}, },
async _setOrRemoveAssignment(pageUrl, userContextId, remove) { async _setOrRemoveAssignment(tabId, pageUrl, userContextId, remove) {
let actionName; let actionName;
if (!remove) { if (!remove) {
await this.storageArea.set(pageUrl, { await this.storageArea.set(pageUrl, {
@ -205,11 +208,8 @@ const assignManager = {
await this.storageArea.remove(pageUrl); await this.storageArea.remove(pageUrl);
actionName = "removed"; actionName = "removed";
} }
browser.notifications.create({ browser.tabs.sendMessage(tabId, {
type: "basic", text: `Successfully ${actionName} site to always open in this container`
title: "Containers",
message: `Successfully ${actionName} site to always open in this container`,
iconUrl: browser.extension.getURL("/img/onboarding-1.png")
}); });
backgroundLogic.sendTelemetryPayload({ backgroundLogic.sendTelemetryPayload({
event: `${actionName}-container-assignment`, event: `${actionName}-container-assignment`,
@ -227,7 +227,11 @@ const assignManager = {
return false; return false;
}, },
async calculateContextMenu(tab) { _getByContainer(userContextId) {
return this.storageArea.getByContainer(userContextId);
},
removeContextMenu() {
// 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
@ -235,6 +239,10 @@ const assignManager = {
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1352102 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1352102
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);
},
async calculateContextMenu(tab) {
this.removeContextMenu();
const siteSettings = await this._getAssignment(tab); const siteSettings = await this._getAssignment(tab);
// ✓ 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
@ -429,10 +437,13 @@ const messageHandler = {
return assignManager._getAssignment(tab); return assignManager._getAssignment(tab);
}); });
break; break;
case "getAssignmentObjectByContainer":
response = assignManager._getByContainer(m.message.userContextId);
break;
case "setOrRemoveAssignment": case "setOrRemoveAssignment":
response = browser.tabs.get(m.tabId).then((tab) => { response = browser.tabs.get(m.tabId).then((tab) => {
const userContextId = assignManager.getUserContextIdFromCookieStore(tab); const userContextId = assignManager.getUserContextIdFromCookieStore(tab);
return assignManager._setOrRemoveAssignment(tab.url, userContextId, m.value); return assignManager._setOrRemoveAssignment(tab.id, tab.url, userContextId, m.value);
}); });
break; break;
case "exemptContainerAssignment": case "exemptContainerAssignment":
@ -474,6 +485,7 @@ const messageHandler = {
}); });
browser.tabs.onActivated.addListener((info) => { browser.tabs.onActivated.addListener((info) => {
assignManager.removeContextMenu();
browser.tabs.get(info.tabId).then((tab) => { browser.tabs.get(info.tabId).then((tab) => {
tabPageCounter.initTabCounter(tab); tabPageCounter.initTabCounter(tab);
assignManager.calculateContextMenu(tab); assignManager.calculateContextMenu(tab);
@ -483,6 +495,7 @@ const messageHandler = {
}); });
browser.windows.onFocusChanged.addListener((windowId) => { browser.windows.onFocusChanged.addListener((windowId) => {
assignManager.removeContextMenu();
browser.tabs.query({active: true, windowId}).then((tabs) => { browser.tabs.query({active: true, windowId}).then((tabs) => {
if (tabs && tabs[0]) { if (tabs && tabs[0]) {
tabPageCounter.initTabCounter(tabs[0]); tabPageCounter.initTabCounter(tabs[0]);
@ -511,6 +524,7 @@ const messageHandler = {
if (details.frameId !== 0 || details.tabId === -1) { if (details.frameId !== 0 || details.tabId === -1) {
return {}; return {};
} }
assignManager.removeContextMenu();
browser.tabs.get(details.tabId).then((tab) => { browser.tabs.get(details.tabId).then((tab) => {
tabPageCounter.incrementTabCount(tab); tabPageCounter.incrementTabCount(tab);

View file

@ -27,6 +27,7 @@
</form> </form>
</main> </main>
<script src="js/utils.js"></script>
<script src="js/confirm-page.js"></script> <script src="js/confirm-page.js"></script>
</body> </body>
</html> </html>

View file

@ -0,0 +1,22 @@
.container-notification {
background: #33f70c;
color: #003f07;
display: block;
inline-size: 100vw;
offset-block-start: 0;
offset-inline-start: 0;
padding-block-end: 8px;
padding-block-start: 8px;
padding-inline-end: 8px;
padding-inline-start: 8px;
position: fixed;
transform: translateY(-100%);
transition: transform 0.3s cubic-bezier(0.07, 0.95, 0, 1) 0.3s;
z-index: 999999999999;
}
.container-notification img {
block-size: 16px;
inline-size: 16px;
margin-inline-end: 3px;
}

View file

@ -1,16 +1,55 @@
/* General Rules and Resets */ /* General Rules and Resets */
body { * {
inline-size: 300px; font-size: inherit;
max-inline-size: 300px; margin-block-end: 0;
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
padding-block-end: 0;
padding-block-start: 0;
padding-inline-end: 0;
padding-inline-start: 0;
} }
html { html {
box-sizing: border-box; box-sizing: border-box;
font-size: 12px;
}
body {
font-family: Roboto, Noto, "San Francisco", Ubuntu, "Segoe UI", "Fira Sans", message-box, Arial, sans-serif;
inline-size: 300px;
max-inline-size: 300px;
} }
:root { :root {
--font-size-heading: 16px;
--primary-action-color: #248aeb; --primary-action-color: #248aeb;
--title-text-color: #000;
--text-normal-color: #4a4a4a;
--text-heading-color: #000;
/* calculated from 12px */
--font-size-heading: 1.33rem; /* 16px */
--block-line-space-size: 0.5rem; /* 6px */
--inline-item-space-size: 0.5rem; /* 6px */
--block-line-separation-size: 0.33rem; /* 10px */
--inline-icon-space-size: 0.833rem; /* 10px */
/* Use for url and icon size */
--block-url-label-size: 2rem; /* 24px */
--inline-start-size: 1.66rem; /* 20px */
--inline-button-size: 5.833rem; /* 70px */
--icon-size: 1.166rem; /* 14px */
--small-text-size: 0.833rem; /* 10px */
--small-radius: 3px;
--icon-button-size: calc(calc(var(--block-line-separation-size) * 2) + 1.66rem); /* 20px */
}
@media (min-resolution: 1dppx) {
html {
font-size: 14px;
}
} }
*, *,
@ -19,6 +58,13 @@ html {
box-sizing: inherit; box-sizing: inherit;
} }
form {
margin-block-end: 0;
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
}
table { table {
border: 0; border: 0;
border-spacing: 0; border-spacing: 0;
@ -35,6 +81,7 @@ table {
} }
.scrollable { .scrollable {
border-block-start: 1px solid #f1f1f1;
inline-size: 100%; inline-size: 100%;
max-block-size: 400px; max-block-size: 400px;
overflow: auto; overflow: auto;
@ -146,6 +193,10 @@ table {
} }
/* Buttons */ /* Buttons */
.button {
color: black;
}
.button.primary { .button.primary {
background-color: #0996f8; background-color: #0996f8;
color: white; color: white;
@ -216,7 +267,7 @@ table {
.column-panel-content .button, .column-panel-content .button,
.panel-footer .button { .panel-footer .button {
align-items: center; align-items: center;
block-size: 54px; block-size: 100%;
display: flex; display: flex;
flex: 1; flex: 1;
justify-content: center; justify-content: center;
@ -265,7 +316,7 @@ table {
} }
.onboarding p { .onboarding p {
color: #4a4a4a; color: var(--text-normal-color);
font-size: 14px; font-size: 14px;
margin-block-end: 16px; margin-block-end: 16px;
max-inline-size: 84%; max-inline-size: 84%;
@ -294,9 +345,10 @@ table {
manage things like container crud */ manage things like container crud */
.pop-button { .pop-button {
align-items: center; align-items: center;
block-size: 48px; block-size: var(--icon-button-size);
cursor: pointer;
display: flex; display: flex;
flex: 0 0 48px; flex: 0 0 var(--icon-button-size);
justify-content: center; justify-content: center;
} }
@ -321,6 +373,10 @@ manage things like container crud */
.pop-button-image { .pop-button-image {
block-size: 20px; block-size: 20px;
flex: 0 0 20px; flex: 0 0 20px;
margin-block-end: auto;
margin-block-start: auto;
margin-inline-end: auto;
margin-inline-start: auto;
} }
.pop-button-image-small { .pop-button-image-small {
@ -332,18 +388,21 @@ manage things like container crud */
.panel-header { .panel-header {
align-items: center; align-items: center;
block-size: 48px; block-size: 48px;
border-block-end: 1px solid #ebebeb;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.panel-header .usercontext-icon {
inline-size: var(--icon-button-size);
}
.column-panel-content .panel-header { .column-panel-content .panel-header {
flex: 0 0 48px; flex: 0 0 48px;
inline-size: 100%; inline-size: 100%;
} }
.panel-header-text { .panel-header-text {
color: #4a4a4a; color: var(--text-normal-color);
flex: 1; flex: 1;
font-size: var(--font-size-heading); font-size: var(--font-size-heading);
font-weight: normal; font-weight: normal;
@ -371,8 +430,31 @@ manage things like container crud */
text-transform: uppercase; text-transform: uppercase;
} }
.container-panel-controls {
display: flex;
justify-content: flex-end;
margin-block-end: var(--block-line-space-size);
margin-block-start: var(--block-line-space-size);
margin-inline-end: var(--inline-item-space-size);
margin-inline-start: var(--inline-item-space-size);
}
#container-panel #sort-containers-link { #container-panel #sort-containers-link {
margin-inline-end: 16px; align-items: center;
block-size: var(--block-url-label-size);
border: 1px solid #d8d8d8;
border-radius: var(--small-radius);
color: var(--title-text-color);
display: flex;
font-size: var(--small-text-size);
inline-size: var(--inline-button-size);
justify-content: center;
text-decoration: none;
}
#container-panel #sort-containers-link:hover,
#container-panel #sort-containers-link:focus {
background: #f2f2f2;
} }
span ~ .panel-header-text { span ~ .panel-header-text {
@ -383,41 +465,75 @@ span ~ .panel-header-text {
} }
#current-tab { #current-tab {
align-items: center;
color: var(--text-normal-color);
display: grid;
font-size: var(--small-text-size);
grid-column-gap: var(--inline-item-space-size);
grid-row-gap: var(--block-line-space-size);
grid-template-columns: var(--icon-size) var(--icon-size) 1fr;
margin-block-end: var(--block-line-space-size);
margin-block-start: var(--block-line-separation-size);
margin-inline-end: var(--inline-start-size);
margin-inline-start: var(--inline-start-size);
max-inline-size: 100%; max-inline-size: 100%;
min-block-size: 91px; }
padding-block-end: 13px;
padding-block-start: 13px; #current-tab img {
padding-inline-end: 16px; max-block-size: var(--icon-size);
padding-inline-start: 16px;
} }
#current-tab > h3 { #current-tab > h3 {
color: #4a4a4a; color: var(--text-heading-color);
font-size: var(--font-size-heading);
font-weight: normal; font-weight: normal;
margin-block-end: 3px; grid-column: span 3;
margin-block-end: 0;
margin-block-start: 0; margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
} }
#current-page > img { #current-page {
block-size: 16px; display: contents;
inline-size: 16px; }
#current-tab .page-title {
font-size: var(--font-size-heading);
grid-column: 2 / 4;
} }
#current-tab > label { #current-tab > label {
align-items: center; display: contents;
display: flex; font-size: var(--small-text-size);
margin-inline-start: 17px;
white-space: nowrap;
} }
#current-tab > label > input { #current-tab > label > input {
display: inline; -moz-appearance: none;
block-size: var(--icon-size);
border: 1px solid #d8d8d8;
border-radius: var(--small-radius);
display: block;
grid-column-start: 2;
inline-size: var(--icon-size);
margin-block-end: 0;
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
}
#current-tab > label > input[disabled] {
background-color: #efefef;
}
#current-tab > label > input:checked {
background-image: url("chrome://global/skin/in-content/check.svg#check-native");
background-position: -1px -1px;
background-size: var(--icon-size);
} }
#current-container { #current-container {
color: var(--identity-tab-color);
flex: 1; flex: 1;
text-transform: lowercase;
} }
#current-tab > label > .usercontext-icon { #current-tab > label > .usercontext-icon {
@ -434,7 +550,6 @@ span ~ .panel-header-text {
.container-panel-row { .container-panel-row {
align-items: center; align-items: center;
background-color: #fefefe !important; background-color: #fefefe !important;
block-size: 48px;
border-block-end: 1px solid #f1f1f1; border-block-end: 1px solid #f1f1f1;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
@ -465,8 +580,9 @@ span ~ .panel-header-text {
} }
.userContext-icon-wrapper { .userContext-icon-wrapper {
block-size: 48px; block-size: var(--icon-button-size);
flex: 0 0 48px; flex: 0 0 var(--icon-button-size);
margin-inline-start: var(--inline-icon-space-size);
} }
/* .userContext-icon is used natively, Bug 1333811 was raised to fix */ /* .userContext-icon is used natively, Bug 1333811 was raised to fix */
@ -475,24 +591,28 @@ span ~ .panel-header-text {
background-position: center center; background-position: center center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 20px 20px; background-size: 20px 20px;
block-size: 48px; block-size: 100%;
fill: var(--identity-icon-color); fill: var(--identity-icon-color);
filter: url('/img/filters.svg#fill'); filter: url('/img/filters.svg#fill');
flex: 0 0 48px;
} }
.container-panel-row:hover .clickable .usercontext-icon, .container-panel-row:hover .clickable .usercontext-icon,
.container-panel-row:focus .clickable .usercontext-icon { .container-panel-row:focus .clickable .usercontext-icon {
background-image: url('/img/container-newtab.svg'); background-image: url('/img/container-newtab.svg');
fill: 'gray'; fill: #979797;
filter: url('/img/filters.svg#fill'); filter: url('/img/filters.svg#fill');
} }
.container-panel-row .clickable:hover .usercontext-icon,
.container-panel-row .clickable:focus .usercontext-icon {
fill: #0094fb;
}
/* Panel Footer */ /* Panel Footer */
.panel-footer { .panel-footer {
align-items: center; align-items: center;
background: #efefef; background: #efefef;
block-size: 54px; block-size: var(--icon-button-size);
border-block-end: 1px solid #d8d8d8; border-block-end: 1px solid #d8d8d8;
color: #000; color: #000;
display: flex; display: flex;
@ -501,14 +621,9 @@ span ~ .panel-header-text {
justify-content: space-between; justify-content: space-between;
} }
.panel-footer .pop-button {
block-size: 54px;
flex: 0 0 54px;
}
.edit-containers-text { .edit-containers-text {
align-items: center; align-items: center;
block-size: 54px; block-size: 100%;
border-inline-end: solid 1px #d8d8d8; border-inline-end: solid 1px #d8d8d8;
display: flex; display: flex;
flex: 1; flex: 1;
@ -517,7 +632,7 @@ span ~ .panel-header-text {
.edit-containers-text a { .edit-containers-text a {
align-items: center; align-items: center;
block-size: 54px; block-size: 100%;
color: #0a0a0a; color: #0a0a0a;
display: flex; display: flex;
flex: 1; flex: 1;
@ -525,8 +640,8 @@ span ~ .panel-header-text {
} }
/* Container info list */ /* Container info list */
#container-info-name { .container-info-tab-title {
margin-inline-end: 0.5rem; flex: 1;
} }
#container-info-hideorshow { #container-info-hideorshow {
@ -574,13 +689,16 @@ span ~ .panel-header-text {
} }
.container-info-list { .container-info-list {
border-block-start: 1px solid #ebebeb;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-block-start: 4px; margin-block-start: 4px;
padding-block-start: 4px; padding-block-start: 4px;
} }
.container-info-list tbody {
display: contents;
}
.clickable { .clickable {
cursor: pointer; cursor: pointer;
} }
@ -631,6 +749,32 @@ span ~ .panel-header-text {
padding-inline-start: 16px; padding-inline-start: 16px;
} }
#edit-sites-assigned {
flex: 1;
}
#edit-sites-assigned h3 {
font-size: 14px;
font-weight: normal;
padding-block-end: 5px;
padding-inline-end: 16px;
padding-inline-start: 16px;
}
#edit-sites-assigned table td {
display: flex;
padding-inline-end: 16px;
padding-inline-start: 16px;
}
#edit-sites-assigned .delete-assignment {
display: none;
}
#edit-sites-assigned tr:hover > td > .delete-assignment {
display: block;
}
.column-panel-content form span { .column-panel-content form span {
align-items: center; align-items: center;
block-size: 44px; block-size: 44px;
@ -679,6 +823,10 @@ span ~ .panel-header-text {
padding-inline-start: 0; padding-inline-start: 0;
} }
.edit-container-panel fieldset:last-of-type {
margin-block-end: 0;
}
.edit-container-panel input[type="text"] { .edit-container-panel input[type="text"] {
block-size: 36px; block-size: 36px;
border-radius: 3px; border-radius: 3px;

View file

@ -5,7 +5,7 @@ async function load() {
const currentCookieStoreId = searchParams.get("currentCookieStoreId"); const currentCookieStoreId = searchParams.get("currentCookieStoreId");
const redirectUrlElement = document.getElementById("redirect-url"); const redirectUrlElement = document.getElementById("redirect-url");
redirectUrlElement.textContent = redirectUrl; redirectUrlElement.textContent = redirectUrl;
createFavicon(redirectUrl, redirectUrlElement); appendFavicon(redirectUrl, redirectUrlElement);
const container = await browser.contextualIdentities.get(cookieStoreId); const container = await browser.contextualIdentities.get(cookieStoreId);
[...document.querySelectorAll(".container-name")].forEach((containerNameElement) => { [...document.querySelectorAll(".container-name")].forEach((containerNameElement) => {
@ -32,12 +32,11 @@ async function load() {
}); });
} }
function createFavicon(pageUrl, redirectUrlElement) { function appendFavicon(pageUrl, redirectUrlElement) {
const origin = new URL(pageUrl).origin; const origin = new URL(pageUrl).origin;
const imageElement = document.createElement("img"); const favIconElement = Utils.createFavIconElement(`${origin}/favicon.ico`);
imageElement.src = `${origin}/favicon.ico`;
redirectUrlElement.prepend(imageElement); redirectUrlElement.prepend(favIconElement);
} }
function confirmSubmit(redirectUrl, cookieStoreId) { function confirmSubmit(redirectUrl, cookieStoreId) {

View file

@ -0,0 +1,53 @@
async function delayAnimation(delay = 350) {
return new Promise((resolve) => {
setTimeout(resolve, delay);
});
}
async function doAnimation(element, property, value) {
return new Promise((resolve) => {
const handler = () => {
resolve();
element.removeEventListener("transitionend", handler);
};
element.addEventListener("transitionend", handler);
window.requestAnimationFrame(() => {
element.style[property] = value;
});
});
}
/*
async function awaitEvent(eventName) {
return new Promise((resolve) => {
const handler = () => {
resolve();
divElement.removeEventListener(eventName, handler);
};
divElement.addEventListener(eventName, handler);
});
}
*/
async function addMessage(message) {
const divElement = document.createElement("div");
divElement.classList.add("container-notification");
// For the eager eyed, this is an experiment. It is however likely that a website will know it is "contained" anyway
divElement.innerText = message.text;
const imageElement = document.createElement("img");
imageElement.src = browser.extension.getURL("/img/container-site-d-24.png");
divElement.prepend(imageElement);
document.body.appendChild(divElement);
await delayAnimation(100);
await doAnimation(divElement, "transform", "translateY(0)");
await delayAnimation(2000);
await doAnimation(divElement, "transform", "translateY(-100%)");
divElement.remove();
}
browser.runtime.onMessage.addListener((message) => {
addMessage(message);
});

View file

@ -18,7 +18,6 @@ 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.
@ -119,7 +118,9 @@ const Logic = {
}, },
addEnterHandler(element, handler) { addEnterHandler(element, handler) {
element.addEventListener("click", handler); element.addEventListener("click", (e) => {
handler(e);
});
element.addEventListener("keydown", (e) => { element.addEventListener("keydown", (e) => {
if (e.keyCode === 13) { if (e.keyCode === 13) {
handler(e); handler(e);
@ -208,6 +209,11 @@ const Logic = {
return this._currentIdentity; return this._currentIdentity;
}, },
currentUserContextId() {
const identity = Logic.currentIdentity();
return Logic.userContextId(identity.cookieStoreId);
},
sendTelemetryPayload(message = {}) { sendTelemetryPayload(message = {}) {
if (!message.event) { if (!message.event) {
throw new Error("Missing event name for telemetry"); throw new Error("Missing event name for telemetry");
@ -234,10 +240,19 @@ const Logic = {
}); });
}, },
setOrRemoveAssignment(tab, value) { getAssignmentObjectByContainer(userContextId) {
return browser.runtime.sendMessage({
method: "getAssignmentObjectByContainer",
message: {userContextId}
});
},
setOrRemoveAssignment(tabId, url, userContextId, value) {
return browser.runtime.sendMessage({ return browser.runtime.sendMessage({
method: "setOrRemoveAssignment", method: "setOrRemoveAssignment",
tabId: tab.id, tabId,
url,
userContextId,
value value
}); });
}, },
@ -437,7 +452,8 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
const currentTabElement = document.getElementById("current-tab"); const currentTabElement = document.getElementById("current-tab");
const assignmentCheckboxElement = document.getElementById("container-page-assigned"); const assignmentCheckboxElement = document.getElementById("container-page-assigned");
assignmentCheckboxElement.addEventListener("change", () => { assignmentCheckboxElement.addEventListener("change", () => {
Logic.setOrRemoveAssignment(currentTab, !assignmentCheckboxElement.checked); const userContextId = Logic.userContextId(currentTab.cookieStoreId);
Logic.setOrRemoveAssignment(currentTab.id, currentTab.url, userContextId, !assignmentCheckboxElement.checked);
}); });
currentTabElement.hidden = !currentTab; currentTabElement.hidden = !currentTab;
this.setupAssignmentCheckbox(false); this.setupAssignmentCheckbox(false);
@ -446,29 +462,14 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
const siteSettings = await Logic.getAssignment(currentTab); const siteSettings = await Logic.getAssignment(currentTab);
this.setupAssignmentCheckbox(siteSettings); this.setupAssignmentCheckbox(siteSettings);
const currentPage = document.getElementById("current-page"); const currentPage = document.getElementById("current-page");
const favIconUrl = currentTab.favIconUrl || ""; currentPage.innerHTML = escaped`<span class="page-title truncate-text">${currentTab.title}</span>`;
currentPage.innerHTML = escaped` const favIconElement = Utils.createFavIconElement(currentTab.favIconUrl || "");
<img class="offpage" src="${favIconUrl}" /> ${currentTab.title} currentPage.prepend(favIconElement);
`;
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"); const currentContainer = document.getElementById("current-container");
currentContainer.innerText = identity.name; currentContainer.innerText = identity.name;
const currentContainerIcon = document.getElementById("current-container-icon"); currentContainer.setAttribute("data-identity-color", identity.color);
currentContainerIcon.setAttribute("data-identity-icon", identity.icon);
currentContainerIcon.setAttribute("data-identity-color", identity.color);
} }
}, },
@ -530,15 +531,21 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
}); });
}); });
const list = document.querySelector(".identities-list"); const list = document.querySelector(".identities-list tbody");
list.innerHTML = ""; list.innerHTML = "";
list.appendChild(fragment); list.appendChild(fragment);
/* Not sure why extensions require a focus for the doorhanger, /* Not sure why extensions require a focus for the doorhanger,
however it allows us to have a tabindex before the first selected item however it allows us to have a tabindex before the first selected item
*/ */
document.addEventListener("focus", () => { const focusHandler = () => {
list.querySelector("tr").focus(); list.querySelector("tr").focus();
document.removeEventListener("focus", focusHandler);
};
document.addEventListener("focus", focusHandler);
/* If the user mousedown's first then remove the focus handler */
document.addEventListener("mousedown", () => {
document.removeEventListener("focus", focusHandler);
}); });
return Promise.resolve(); return Promise.resolve();
@ -561,7 +568,7 @@ Logic.registerPanel(P_CONTAINER_INFO, {
const identity = Logic.currentIdentity(); const identity = Logic.currentIdentity();
browser.runtime.sendMessage({ browser.runtime.sendMessage({
method: identity.hasHiddenTabs ? "showTabs" : "hideTabs", method: identity.hasHiddenTabs ? "showTabs" : "hideTabs",
userContextId: Logic.userContextId(identity.cookieStoreId) userContextId: Logic.currentUserContextId()
}).then(() => { }).then(() => {
window.close(); window.close();
}).catch(() => { }).catch(() => {
@ -633,7 +640,7 @@ Logic.registerPanel(P_CONTAINER_INFO, {
// Let's retrieve the list of tabs. // Let's retrieve the list of tabs.
return browser.runtime.sendMessage({ return browser.runtime.sendMessage({
method: "getTabs", method: "getTabs",
userContextId: Logic.userContextId(identity.cookieStoreId), userContextId: Logic.currentUserContextId(),
}).then(this.buildInfoTable); }).then(this.buildInfoTable);
}, },
@ -645,8 +652,9 @@ Logic.registerPanel(P_CONTAINER_INFO, {
fragment.appendChild(tr); fragment.appendChild(tr);
tr.classList.add("container-info-tab-row"); tr.classList.add("container-info-tab-row");
tr.innerHTML = escaped` tr.innerHTML = escaped`
<td><img class="icon" src="${tab.favicon}" /></td> <td></td>
<td class="container-info-tab-title truncate-text">${tab.title}</td>`; <td class="container-info-tab-title truncate-text">${tab.title}</td>`;
tr.querySelector("td").appendChild(Utils.createFavIconElement(tab.favicon));
// On click, we activate this tab. But only if this tab is active. // On click, we activate this tab. But only if this tab is active.
if (tab.active) { if (tab.active) {
@ -743,27 +751,19 @@ Logic.registerPanel(P_CONTAINER_EDIT, {
this.initializeRadioButtons(); this.initializeRadioButtons();
Logic.addEnterHandler(document.querySelector("#edit-container-panel-back-arrow"), () => { Logic.addEnterHandler(document.querySelector("#edit-container-panel-back-arrow"), () => {
Logic.showPreviousPanel(); this._submitForm();
}); });
Logic.addEnterHandler(document.querySelector("#edit-container-cancel-link"), () => {
Logic.showPreviousPanel();
});
this._editForm = document.getElementById("edit-container-panel-form"); this._editForm = document.getElementById("edit-container-panel-form");
const editLink = document.querySelector("#edit-container-ok-link");
Logic.addEnterHandler(editLink, this._submitForm.bind(this));
editLink.addEventListener("submit", this._submitForm.bind(this));
this._editForm.addEventListener("submit", this._submitForm.bind(this)); this._editForm.addEventListener("submit", this._submitForm.bind(this));
}, },
_submitForm() { _submitForm() {
const identity = Logic.currentIdentity();
const formValues = new FormData(this._editForm); const formValues = new FormData(this._editForm);
return browser.runtime.sendMessage({ return browser.runtime.sendMessage({
method: "createOrUpdateContainer", method: "createOrUpdateContainer",
message: { message: {
userContextId: Logic.userContextId(identity.cookieStoreId) || false, userContextId: Logic.currentUserContextId() || false,
params: { params: {
name: document.getElementById("edit-container-panel-name-input").value || Logic.generateIdentityName(), name: document.getElementById("edit-container-panel-name-input").value || Logic.generateIdentityName(),
icon: formValues.get("container-icon") || DEFAULT_ICON, icon: formValues.get("container-icon") || DEFAULT_ICON,
@ -779,6 +779,50 @@ Logic.registerPanel(P_CONTAINER_EDIT, {
}); });
}, },
showAssignedContainers(assignments) {
const assignmentPanel = document.getElementById("edit-sites-assigned");
const assignmentKeys = Object.keys(assignments);
assignmentPanel.hidden = !(assignmentKeys.length > 0);
if (assignments) {
const tableElement = assignmentPanel.querySelector("table > tbody");
/* Remove previous assignment list,
after removing one we rerender the list */
while (tableElement.firstChild) {
tableElement.firstChild.remove();
}
assignmentKeys.forEach((siteKey) => {
const site = assignments[siteKey];
const trElement = document.createElement("tr");
/* 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}`;
trElement.innerHTML = escaped`
<td><img class="icon" src="${assumedUrl}/favicon.ico"></td>
<td title="${site.hostname}" class="truncate-text">${site.hostname}
<img
class="pop-button-image delete-assignment"
src="/img/container-delete.svg"
/>
</td>`;
const deleteButton = trElement.querySelector(".delete-assignment");
Logic.addEnterHandler(deleteButton, () => {
const userContextId = Logic.currentUserContextId();
// Lets show the message to the current tab
// TODO remove then when firefox supports arrow fn async
Logic.currentTab().then((currentTab) => {
Logic.setOrRemoveAssignment(currentTab.id, assumedUrl, userContextId, true);
delete assignments[siteKey];
this.showAssignedContainers(assignments);
}).catch((e) => {
throw e;
});
});
trElement.classList.add("container-info-tab-row", "clickable");
tableElement.appendChild(trElement);
});
}
},
initializeRadioButtons() { initializeRadioButtons() {
const colorRadioTemplate = (containerColor) => { const colorRadioTemplate = (containerColor) => {
return escaped`<input type="radio" value="${containerColor}" name="container-color" id="edit-container-panel-choose-color-${containerColor}" /> return escaped`<input type="radio" value="${containerColor}" name="container-color" id="edit-container-panel-choose-color-${containerColor}" />
@ -808,8 +852,13 @@ Logic.registerPanel(P_CONTAINER_EDIT, {
}, },
// This method is called when the panel is shown. // This method is called when the panel is shown.
prepare() { async prepare() {
const identity = Logic.currentIdentity(); const identity = Logic.currentIdentity();
const userContextId = Logic.currentUserContextId();
const assignments = await Logic.getAssignmentObjectByContainer(userContextId);
this.showAssignedContainers(assignments);
document.querySelector("#edit-container-panel-name-input").value = identity.name || ""; document.querySelector("#edit-container-panel-name-input").value = identity.name || "";
[...document.querySelectorAll("[name='container-color']")].forEach(colorInput => { [...document.querySelectorAll("[name='container-color']")].forEach(colorInput => {
colorInput.checked = colorInput.value === identity.color; colorInput.checked = colorInput.value === identity.color;

23
webextension/js/utils.js Normal file
View file

@ -0,0 +1,23 @@
const DEFAULT_FAVICON = "moz-icon://goat?size=16";
// TODO use export here instead of globals
window.Utils = {
createFavIconElement(url) {
const imageElement = document.createElement("img");
imageElement.classList.add("icon", "offpage");
imageElement.src = url;
const loadListener = (e) => {
e.target.classList.remove("offpage");
e.target.removeEventListener("load", loadListener);
e.target.removeEventListener("error", errorListener);
};
const errorListener = (e) => {
e.target.src = DEFAULT_FAVICON;
};
imageElement.addEventListener("error", errorListener);
imageElement.addEventListener("load", loadListener);
return imageElement;
}
};

View file

@ -26,7 +26,6 @@
"contextualIdentities", "contextualIdentities",
"history", "history",
"idle", "idle",
"notifications",
"storage", "storage",
"tabs", "tabs",
"webRequestBlocking", "webRequestBlocking",
@ -36,7 +35,7 @@
"commands": { "commands": {
"_execute_browser_action": { "_execute_browser_action": {
"suggested_key": { "suggested_key": {
"default": "Ctrl+Y" "default": "Ctrl+Period"
}, },
"description": "Open containers panel" "description": "Open containers panel"
} }
@ -54,5 +53,17 @@
"background": { "background": {
"scripts": ["background.js"] "scripts": ["background.js"]
} },
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/content-script.js"],
"css": ["css/content.css"]
}
],
"web_accessible_resources": [
"/img/container-site-d-24.png"
]
} }

View file

@ -3,6 +3,7 @@
<meta http-equiv="content-type" content="text/html; charset=utf-8"> <meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Containers browserAction Popup</title> <title>Containers browserAction Popup</title>
<link rel="stylesheet" href="/css/popup.css"> <link rel="stylesheet" href="/css/popup.css">
</head> </head>
<body> <body>
@ -40,21 +41,21 @@
<div class="panel container-panel hide" id="container-panel"> <div class="panel container-panel hide" id="container-panel">
<div id="current-tab"> <div id="current-tab">
<h3>Current Tab</h3> <h3>Current Tab</h3>
<div id="current-page" class="truncate-text"></div> <div id="current-page"></div>
<label for="container-page-assigned"> <label for="container-page-assigned">
<input type="checkbox" id="container-page-assigned" /> <input type="checkbox" id="container-page-assigned" />
Always open in <span class="truncate-text">
<div id="current-container-icon" class="usercontext-icon"></div> Always open in
<span id="current-container" class="truncate-text"></span> <span id="current-container"></span>
</span>
</label> </label>
</div> </div>
<div class="panel-header"> <div class="container-panel-controls">
<h3 class="panel-header-text">Containers</h3>
<a href="#" class="action-link" id="sort-containers-link">Sort Tabs</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 class="identities-list">
<tbody class="identities-list"></tbody> <tbody></tbody>
</table> </table>
</div> </div>
<div class="panel-footer edit-identities"> <div class="panel-footer edit-identities">
@ -125,9 +126,16 @@
<legend>Choose an icon</legend> <legend>Choose an icon</legend>
</fieldset> </fieldset>
</form> </form>
<div class="panel-footer"> <div id="edit-sites-assigned" class="scrollable" hidden>
<a href="#" class="button secondary expanded footer-button cancel-button" id="edit-container-cancel-link">Cancel</a> <table id="container-assignement-table" class="container-info-list">
<a class="button primary expanded footer-button" id="edit-container-ok-link">OK</a> <thead>
<th colspan="3">
<h3>Sites assigned to this container</h3>
</th>
</thead>
<tbody>
</tbody>
</table>
</div> </div>
</div> </div>
</div> </div>
@ -148,7 +156,7 @@
</div> </div>
</div> </div>
<script src="js/utils.js"></script>
<script src="js/popup.js"></script> <script src="js/popup.js"></script>
</body> </body>
</html> </html>