From 55f71f5ac83f150da2aff6d29bff48d3df6064a4 Mon Sep 17 00:00:00 2001 From: baku Date: Fri, 6 Jan 2017 17:48:47 +0100 Subject: [PATCH 01/15] Implementation of 'openTab' method --- index.js | 25 +++++++++++++++++++++++++ webextension/js/popup.js | 9 +++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index e1cd577..1c46172 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,8 @@ /* global require */ const {ContextualIdentityService} = require('resource://gre/modules/ContextualIdentityService.jsm'); +const { Cc, Ci, Cu, Cr } = require('chrome'); + +Cu.import("resource://gre/modules/Services.jsm"); const tabs = require('sdk/tabs'); const webExtension = require('sdk/webextension'); @@ -167,6 +170,25 @@ const contextualIdentities = { remove: removeContainer }; +function openTab(args) { + let browserWin = Services.wm.getMostRecentWindow('navigator:browser'); + + // This should not really happen. + if (!browserWin || !browserWin.gBrowser) { + return Promise.resolve(false); + } + + let userContextId = 0; + if ('cookieStoreId' in args) { + userContextId = getContainerForCookieStoreId(args.cookieStoreId); + } + + let tab = browserWin.gBrowser.addTab(args.url || null, + { userContextId: userContextId }) + browserWin.gBrowser.selectedTab = tab; + return Promise.resolve(true); +} + function handleWebExtensionMessage(message, sender, sendReply) { switch (message.method) { case 'query': @@ -198,6 +220,9 @@ function handleWebExtensionMessage(message, sender, sendReply) { tabs.open('about:preferences#containers'); sendReply({content: 'opened'}); break; + case 'openTab': + sendReply(openTab(message)); + break; } } diff --git a/webextension/js/popup.js b/webextension/js/popup.js index fb3fbd2..e6f8387 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -31,9 +31,10 @@ function showContainerTabs(containerId) { cookieStoreId: containerId }).then(hiddenTabUrls=> { hiddenTabUrls.forEach(url=> { - browser.tabs.create({ - url: url, - cookieStoreId: containerId + browser.runtime.sendMessage({ + method: 'openTab', + cookieStoreId: containerId, + url: url }); }); }); @@ -121,7 +122,7 @@ browser.runtime.sendMessage({method: 'query'}).then(identities=> { } }); } else if (e.target.matches('.newtab-icon')) { - browser.tabs.create({cookieStoreId: containerId}); + browser.runtime.sendMessage({method: 'openTab', cookieStoreId: containerId}); window.close(); } }); From 0b03b67f65f1a46981c876e7768b20e0f4453301 Mon Sep 17 00:00:00 2001 From: baku Date: Fri, 6 Jan 2017 18:11:54 +0100 Subject: [PATCH 02/15] Method renamed (ex: query => queryIdentities) --- index.js | 16 ++++++++-------- webextension/js/popup.js | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 1c46172..18d44be 100644 --- a/index.js +++ b/index.js @@ -191,32 +191,32 @@ function openTab(args) { function handleWebExtensionMessage(message, sender, sendReply) { switch (message.method) { - case 'query': + case 'queryIdentities': sendReply(contextualIdentities.query(message.arguments)); break; - case 'hide': + case 'hideTab': identitiesState[message.cookieStoreId].hiddenTabUrls = message.tabUrlsToSave; break; - case 'show': + case 'showTab': sendReply(identitiesState[message.cookieStoreId].hiddenTabUrls); identitiesState[message.cookieStoreId].hiddenTabUrls = []; break; - case 'get': + case 'getTab': sendReply(contextualIdentities.get(message.arguments)); break; - case 'create': + case 'createTab': sendReply(contextualIdentities.create(message.arguments)); break; - case 'update': + case 'updateTab': sendReply(contextualIdentities.update(message.arguments)); break; - case 'remove': + case 'removeTab': sendReply(contextualIdentities.remove(message.arguments)); break; case 'getIdentitiesState': sendReply(identitiesState); break; - case 'open-containers-preferences': + case 'openContainersPreferences': tabs.open('about:preferences#containers'); sendReply({content: 'opened'}); break; diff --git a/webextension/js/popup.js b/webextension/js/popup.js index e6f8387..1ad7fde 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -13,7 +13,7 @@ function hideContainerTabs(containerId) { tabUrlsToSave.push(tab.url); }); browser.runtime.sendMessage({ - method: 'hide', + method: 'hideTab', cookieStoreId: containerId, tabUrlsToSave: tabUrlsToSave }).then(()=> { @@ -27,7 +27,7 @@ function showContainerTabs(containerId) { const hideorshowIcon = document.querySelector(`#${containerId}-hideorshow-icon`); browser.runtime.sendMessage({ - method: 'show', + method: 'showTab', cookieStoreId: containerId }).then(hiddenTabUrls=> { hiddenTabUrls.forEach(url=> { @@ -68,7 +68,7 @@ document.querySelector('#onboarding-done-button').addEventListener('click', ()=> document.querySelector('#container-panel').classList.remove('hide'); }); -browser.runtime.sendMessage({method: 'query'}).then(identities=> { +browser.runtime.sendMessage({method: 'queryIdentities'}).then(identities=> { const identitiesListElement = document.querySelector('.identities-list'); identities.forEach(identity=> { @@ -130,7 +130,7 @@ browser.runtime.sendMessage({method: 'query'}).then(identities=> { }); document.querySelector('#edit-containers-link').addEventListener('click', ()=> { - browser.runtime.sendMessage({method: 'open-containers-preferences'}).then(()=> { + browser.runtime.sendMessage({method: 'openContainersPreferences'}).then(()=> { window.close(); }); }); @@ -145,7 +145,7 @@ function moveTabs(sortedTabsArray) { } document.querySelector('#sort-containers-link').addEventListener('click', ()=> { - browser.runtime.sendMessage({method: 'query'}).then(identities=> { + browser.runtime.sendMessage({method: 'queryIdentities'}).then(identities=> { identities.unshift({cookieStoreId: 'firefox-default'}); browser.tabs.query({}).then(tabsArray=> { From 404ee3304e0bc1b8830ccaecc606503d0ad41466 Mon Sep 17 00:00:00 2001 From: baku Date: Fri, 6 Jan 2017 18:52:35 +0100 Subject: [PATCH 03/15] Query() moved to the parent process --- index.js | 46 ++++++++++++++++++++++++++++++++++++++++ webextension/js/popup.js | 12 ++++++++--- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 18d44be..2179b0f 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ Cu.import("resource://gre/modules/Services.jsm"); const tabs = require('sdk/tabs'); const webExtension = require('sdk/webextension'); +const { viewFor } = require("sdk/view/core"); /* Let's start enabling Containers */ var prefs = [ @@ -26,6 +27,10 @@ const identitiesState = { }; function getCookieStoreIdForContainer(containerId) { + if (containerId == 0) { + return 'firefox-default'; + } + return CONTAINER_STORE + containerId; } @@ -189,11 +194,48 @@ function openTab(args) { return Promise.resolve(true); } +function queryTabs(args) { + return new Promise((resolve, reject) => { + let tabList = []; + + for (let tab of tabs) { + let xulTab = viewFor(tab); + let userContextId = xulTab.getAttribute('usercontextid') || 0; + let cookieStoreId = getCookieStoreIdForContainer(userContextId); + + if ("cookieStoreId" in args && args.cookieStoreId != cookieStoreId) { + continue; + } + + tabList.push({ + id: tab.id, + url: tab.url, + cookieStoreId: cookieStoreId, + }); + } + + resolve(tabList); + }); +} + +function removeTabs(ids) { + for (let tab of tabs) { + if (ids.indexOf(tab.id) != -1) { + tab.close(); + } + } + + return Promise.resolve(null); +} + function handleWebExtensionMessage(message, sender, sendReply) { switch (message.method) { case 'queryIdentities': sendReply(contextualIdentities.query(message.arguments)); break; + case 'queryTabs': + sendReply(queryTabs(message)); + break; case 'hideTab': identitiesState[message.cookieStoreId].hiddenTabUrls = message.tabUrlsToSave; break; @@ -201,6 +243,10 @@ function handleWebExtensionMessage(message, sender, sendReply) { sendReply(identitiesState[message.cookieStoreId].hiddenTabUrls); identitiesState[message.cookieStoreId].hiddenTabUrls = []; break; + case 'removeTabs': + sendReply(removeTabs(message.tabIds)); + identitiesState[message.cookieStoreId].hiddenTabUrls = []; + break; case 'getTab': sendReply(contextualIdentities.get(message.arguments)); break; diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 1ad7fde..463f0fe 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -7,7 +7,10 @@ function hideContainerTabs(containerId) { const tabUrlsToSave = []; const hideorshowIcon = document.querySelector(`#${containerId}-hideorshow-icon`); - browser.tabs.query({cookieStoreId: containerId}).then(tabs=> { + browser.runtime.sendMessage({ + method: 'queryTabs', + cookieStoreId: containerId + }).then(tabs=> { tabs.forEach(tab=> { tabIdsToRemove.push(tab.id); tabUrlsToSave.push(tab.url); @@ -17,7 +20,10 @@ function hideContainerTabs(containerId) { cookieStoreId: containerId, tabUrlsToSave: tabUrlsToSave }).then(()=> { - browser.tabs.remove(tabIdsToRemove); + browser.runtime.sendMessage({ + method: 'removeTabs', + tabIds: tabIdsToRemove + }); hideorshowIcon.src = CONTAINER_UNHIDE_SRC; }); }); @@ -148,7 +154,7 @@ document.querySelector('#sort-containers-link').addEventListener('click', ()=> { browser.runtime.sendMessage({method: 'queryIdentities'}).then(identities=> { identities.unshift({cookieStoreId: 'firefox-default'}); - browser.tabs.query({}).then(tabsArray=> { + browser.runtime.sendMessage({method: 'queryTabs'}).then(tabsArray=> { const sortedTabsArray = []; identities.forEach(identity=> { From a6f91752242db443a964874efc3ee532b0121171 Mon Sep 17 00:00:00 2001 From: baku Date: Fri, 6 Jan 2017 19:35:18 +0100 Subject: [PATCH 04/15] Migrate from cookieStoreId to userContextId --- index.js | 82 +++++++++++----------------------------- webextension/js/popup.js | 36 +++++++++--------- 2 files changed, 41 insertions(+), 77 deletions(-) diff --git a/index.js b/index.js index 2179b0f..2e95af0 100644 --- a/index.js +++ b/index.js @@ -21,63 +21,32 @@ prefs.forEach((pref) => { prefService.set(pref[0], pref[1]); }); -const CONTAINER_STORE = 'firefox-container-'; - const identitiesState = { }; -function getCookieStoreIdForContainer(containerId) { - if (containerId == 0) { - return 'firefox-default'; - } - - return CONTAINER_STORE + containerId; -} - function convert(identity) { - const cookieStoreId = getCookieStoreIdForContainer(identity.userContextId); let hiddenTabUrls = []; - if (cookieStoreId in identitiesState) { - hiddenTabUrls = identitiesState[cookieStoreId].hiddenTabUrls; + if (identity.userContextId in identitiesState) { + hiddenTabUrls = identitiesState[identity.userContextId].hiddenTabUrls; } const result = { name: ContextualIdentityService.getUserContextLabel(identity.userContextId), icon: identity.icon, color: identity.color, - cookieStoreId: cookieStoreId, + userContextId: identity.userContextId, hiddenTabUrls: hiddenTabUrls }; return result; } -function isContainerCookieStoreId(storeId) { - return storeId !== null && storeId.startsWith(CONTAINER_STORE); -} - -function getContainerForCookieStoreId(storeId) { - if (!isContainerCookieStoreId(storeId)) { - return null; - } - - const containerId = storeId.substring(CONTAINER_STORE.length); - - if (ContextualIdentityService.getIdentityFromId(containerId)) { - return parseInt(containerId, 10); - } - - return null; -} - -function getContainer(cookieStoreId) { - const containerId = getContainerForCookieStoreId(cookieStoreId); - - if (!containerId) { +function getContainer(userContextId) { + if (!userContextId) { return Promise.resolve(null); } - const identity = ContextualIdentityService.getIdentityFromId(containerId); + const identity = ContextualIdentityService.getIdentityFromId(userContextId); return Promise.resolve(convert(identity)); } @@ -94,8 +63,8 @@ function queryContainers(details) { const convertedIdentity = convert(identity); identities.push(convertedIdentity); - if (!(convertedIdentity.cookieStoreId in identitiesState)) { - identitiesState[convertedIdentity.cookieStoreId] = {hiddenTabUrls: []}; + if (!(convertedIdentity.userContextId in identitiesState)) { + identitiesState[convertedIdentity.userContextId] = {hiddenTabUrls: []}; } }); @@ -110,14 +79,12 @@ function createContainer(details) { return Promise.resolve(convert(identity)); } -function updateContainer(cookieStoreId, details) { - const containerId = getContainerForCookieStoreId(cookieStoreId); - - if (!containerId) { +function updateContainer(userContextId, details) { + if (!userContextId) { return Promise.resolve(null); } - const identity = ContextualIdentityService.getIdentityFromId(containerId); + const identity = ContextualIdentityService.getIdentityFromId(userContextId); if (!identity) { return Promise.resolve(null); @@ -144,14 +111,12 @@ function updateContainer(cookieStoreId, details) { return Promise.resolve(convert(identity)); } -function removeContainer(cookieStoreId) { - const containerId = getContainerForCookieStoreId(cookieStoreId); - - if (!containerId) { +function removeContainer(userContextId) { + if (!userContextId) { return Promise.resolve(null); } - const identity = ContextualIdentityService.getIdentityFromId(containerId); + const identity = ContextualIdentityService.getIdentityFromId(userContextId); if (!identity) { return Promise.resolve(null); @@ -184,8 +149,8 @@ function openTab(args) { } let userContextId = 0; - if ('cookieStoreId' in args) { - userContextId = getContainerForCookieStoreId(args.cookieStoreId); + if ('userContextId' in args) { + userContextId = args.userContextId; } let tab = browserWin.gBrowser.addTab(args.url || null, @@ -200,17 +165,16 @@ function queryTabs(args) { for (let tab of tabs) { let xulTab = viewFor(tab); - let userContextId = xulTab.getAttribute('usercontextid') || 0; - let cookieStoreId = getCookieStoreIdForContainer(userContextId); + let userContextId = parseInt(xulTab.getAttribute('usercontextid') || 0, 10); - if ("cookieStoreId" in args && args.cookieStoreId != cookieStoreId) { + if ("userContextId" in args && args.userContextId != userContextId) { continue; } tabList.push({ id: tab.id, url: tab.url, - cookieStoreId: cookieStoreId, + userContextId: userContextId, }); } @@ -237,15 +201,15 @@ function handleWebExtensionMessage(message, sender, sendReply) { sendReply(queryTabs(message)); break; case 'hideTab': - identitiesState[message.cookieStoreId].hiddenTabUrls = message.tabUrlsToSave; + identitiesState[message.userContextId].hiddenTabUrls = message.tabUrlsToSave; break; case 'showTab': - sendReply(identitiesState[message.cookieStoreId].hiddenTabUrls); - identitiesState[message.cookieStoreId].hiddenTabUrls = []; + sendReply(identitiesState[message.userContextId].hiddenTabUrls); + identitiesState[message.userContextId].hiddenTabUrls = []; break; case 'removeTabs': sendReply(removeTabs(message.tabIds)); - identitiesState[message.cookieStoreId].hiddenTabUrls = []; + identitiesState[message.userContextId].hiddenTabUrls = []; break; case 'getTab': sendReply(contextualIdentities.get(message.arguments)); diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 463f0fe..8440d7d 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -2,14 +2,14 @@ const CONTAINER_HIDE_SRC = '/img/container-hide.svg'; const CONTAINER_UNHIDE_SRC = '/img/container-unhide.svg'; -function hideContainerTabs(containerId) { +function hideContainerTabs(userContextId) { const tabIdsToRemove = []; const tabUrlsToSave = []; - const hideorshowIcon = document.querySelector(`#${containerId}-hideorshow-icon`); + const hideorshowIcon = document.querySelector(`#uci-${userContextId}-hideorshow-icon`); browser.runtime.sendMessage({ method: 'queryTabs', - cookieStoreId: containerId + userContextId: userContextId }).then(tabs=> { tabs.forEach(tab=> { tabIdsToRemove.push(tab.id); @@ -17,7 +17,7 @@ function hideContainerTabs(containerId) { }); browser.runtime.sendMessage({ method: 'hideTab', - cookieStoreId: containerId, + userContextId: userContextId, tabUrlsToSave: tabUrlsToSave }).then(()=> { browser.runtime.sendMessage({ @@ -29,17 +29,17 @@ function hideContainerTabs(containerId) { }); } -function showContainerTabs(containerId) { - const hideorshowIcon = document.querySelector(`#${containerId}-hideorshow-icon`); +function showContainerTabs(userContextId) { + const hideorshowIcon = document.querySelector(`#uci-${userContextId}-hideorshow-icon`); browser.runtime.sendMessage({ method: 'showTab', - cookieStoreId: containerId + userContextId: userContextId }).then(hiddenTabUrls=> { hiddenTabUrls.forEach(url=> { browser.runtime.sendMessage({ method: 'openTab', - cookieStoreId: containerId, + userContextId: userContextId, url: url }); }); @@ -84,7 +84,7 @@ browser.runtime.sendMessage({method: 'queryIdentities'}).then(identities=> { hideOrShowIconSrc = CONTAINER_UNHIDE_SRC; } const identityRow = ` - +
{ @@ -117,18 +117,18 @@ browser.runtime.sendMessage({method: 'queryIdentities'}).then(identities=> { rows.forEach(row=> { row.addEventListener('click', e=> { - const containerId = e.target.parentElement.parentElement.dataset.identityCookieStoreId; + const userContextId = e.target.parentElement.parentElement.dataset.identityCookieStoreId; if (e.target.matches('.hideorshow-icon')) { browser.runtime.sendMessage({method: 'getIdentitiesState'}).then(identitiesState=> { - if (identitiesState[containerId].hiddenTabUrls.length) { - showContainerTabs(containerId); + if (identitiesState[userContextId].hiddenTabUrls.length) { + showContainerTabs(userContextId); } else { - hideContainerTabs(containerId); + hideContainerTabs(userContextId); } }); } else if (e.target.matches('.newtab-icon')) { - browser.runtime.sendMessage({method: 'openTab', cookieStoreId: containerId}); + browser.runtime.sendMessage({method: 'openTab', userContextId: userContextId}); window.close(); } }); @@ -152,14 +152,14 @@ function moveTabs(sortedTabsArray) { document.querySelector('#sort-containers-link').addEventListener('click', ()=> { browser.runtime.sendMessage({method: 'queryIdentities'}).then(identities=> { - identities.unshift({cookieStoreId: 'firefox-default'}); + identities.unshift({userContextId: 0}); browser.runtime.sendMessage({method: 'queryTabs'}).then(tabsArray=> { const sortedTabsArray = []; identities.forEach(identity=> { tabsArray.forEach(tab=> { - if (tab.cookieStoreId === identity.cookieStoreId) { + if (tab.userContextId === identity.userContextId) { sortedTabsArray.push(tab.id); } }); From 2c2e8ed6025e81ad280e1459ef42fde055adf09a Mon Sep 17 00:00:00 2001 From: baku Date: Fri, 6 Jan 2017 20:05:33 +0100 Subject: [PATCH 05/15] Cleaning up existing methods --- index.js | 76 ++-------------------------------------- webextension/js/popup.js | 4 +-- 2 files changed, 5 insertions(+), 75 deletions(-) diff --git a/index.js b/index.js index 2e95af0..6f71099 100644 --- a/index.js +++ b/index.js @@ -41,16 +41,6 @@ function convert(identity) { return result; } -function getContainer(userContextId) { - if (!userContextId) { - return Promise.resolve(null); - } - - const identity = ContextualIdentityService.getIdentityFromId(userContextId); - - return Promise.resolve(convert(identity)); -} - function queryContainers(details) { const identities = []; @@ -71,46 +61,6 @@ function queryContainers(details) { return Promise.resolve(identities); } -function createContainer(details) { - const identity = ContextualIdentityService.create(details.name, - details.icon, - details.color); - - return Promise.resolve(convert(identity)); -} - -function updateContainer(userContextId, details) { - if (!userContextId) { - return Promise.resolve(null); - } - - const identity = ContextualIdentityService.getIdentityFromId(userContextId); - - if (!identity) { - return Promise.resolve(null); - } - - if (details.name !== null) { - identity.name = details.name; - } - - if (details.color !== null) { - identity.color = details.color; - } - - if (details.icon !== null) { - identity.icon = details.icon; - } - - if (!ContextualIdentityService.update(identity.userContextId, - identity.name, identity.icon, - identity.color)) { - return Promise.resolve(null); - } - - return Promise.resolve(convert(identity)); -} - function removeContainer(userContextId) { if (!userContextId) { return Promise.resolve(null); @@ -132,14 +82,6 @@ function removeContainer(userContextId) { return Promise.resolve(convertedIdentity); } -const contextualIdentities = { - get: getContainer, - query: queryContainers, - create: createContainer, - update: updateContainer, - remove: removeContainer -}; - function openTab(args) { let browserWin = Services.wm.getMostRecentWindow('navigator:browser'); @@ -195,15 +137,15 @@ function removeTabs(ids) { function handleWebExtensionMessage(message, sender, sendReply) { switch (message.method) { case 'queryIdentities': - sendReply(contextualIdentities.query(message.arguments)); + sendReply(queryContainers(message.arguments)); break; case 'queryTabs': sendReply(queryTabs(message)); break; - case 'hideTab': + case 'hideTabs': identitiesState[message.userContextId].hiddenTabUrls = message.tabUrlsToSave; break; - case 'showTab': + case 'showTabs': sendReply(identitiesState[message.userContextId].hiddenTabUrls); identitiesState[message.userContextId].hiddenTabUrls = []; break; @@ -211,18 +153,6 @@ function handleWebExtensionMessage(message, sender, sendReply) { sendReply(removeTabs(message.tabIds)); identitiesState[message.userContextId].hiddenTabUrls = []; break; - case 'getTab': - sendReply(contextualIdentities.get(message.arguments)); - break; - case 'createTab': - sendReply(contextualIdentities.create(message.arguments)); - break; - case 'updateTab': - sendReply(contextualIdentities.update(message.arguments)); - break; - case 'removeTab': - sendReply(contextualIdentities.remove(message.arguments)); - break; case 'getIdentitiesState': sendReply(identitiesState); break; diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 8440d7d..0e74484 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -16,7 +16,7 @@ function hideContainerTabs(userContextId) { tabUrlsToSave.push(tab.url); }); browser.runtime.sendMessage({ - method: 'hideTab', + method: 'hideTabs', userContextId: userContextId, tabUrlsToSave: tabUrlsToSave }).then(()=> { @@ -33,7 +33,7 @@ function showContainerTabs(userContextId) { const hideorshowIcon = document.querySelector(`#uci-${userContextId}-hideorshow-icon`); browser.runtime.sendMessage({ - method: 'showTab', + method: 'showTabs', userContextId: userContextId }).then(hiddenTabUrls=> { hiddenTabUrls.forEach(url=> { From 108673b492a4018f22f2f26b9b67be3423bfaf25 Mon Sep 17 00:00:00 2001 From: baku Date: Fri, 6 Jan 2017 20:12:46 +0100 Subject: [PATCH 06/15] Fixing open about:preferences#containers --- index.js | 4 ---- webextension/js/popup.js | 5 ++++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 6f71099..be2bb05 100644 --- a/index.js +++ b/index.js @@ -156,10 +156,6 @@ function handleWebExtensionMessage(message, sender, sendReply) { case 'getIdentitiesState': sendReply(identitiesState); break; - case 'openContainersPreferences': - tabs.open('about:preferences#containers'); - sendReply({content: 'opened'}); - break; case 'openTab': sendReply(openTab(message)); break; diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 0e74484..b987c53 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -136,7 +136,10 @@ browser.runtime.sendMessage({method: 'queryIdentities'}).then(identities=> { }); document.querySelector('#edit-containers-link').addEventListener('click', ()=> { - browser.runtime.sendMessage({method: 'openContainersPreferences'}).then(()=> { + browser.runtime.sendMessage({ + method: 'openTab', + url: "about:preferences#containers" + }).then(()=> { window.close(); }); }); From d7d7eeaacb9a46a771e728b6434860ed8fe9717d Mon Sep 17 00:00:00 2001 From: baku Date: Sat, 7 Jan 2017 00:03:34 +0100 Subject: [PATCH 07/15] Refactoring of index.js --- index.js | 273 +++++++++++++++++++-------------------- webextension/js/popup.js | 18 ++- 2 files changed, 147 insertions(+), 144 deletions(-) diff --git a/index.js b/index.js index be2bb05..d401779 100644 --- a/index.js +++ b/index.js @@ -8,162 +8,157 @@ const tabs = require('sdk/tabs'); const webExtension = require('sdk/webextension'); const { viewFor } = require("sdk/view/core"); -/* Let's start enabling Containers */ -var prefs = [ - [ "privacy.userContext.enabled", true ], - [ "privacy.userContext.ui.enabled", true ], - [ "privacy.usercontext.about_newtab_segregation.enabled", true ], - [ "privacy.usercontext.longPressBehavior", 1 ] -]; +let ContainerService = +{ + _identitiesState: {}, -const prefService = require("sdk/preferences/service"); -prefs.forEach((pref) => { - prefService.set(pref[0], pref[1]); -}); + init() { + // Enabling preferences -const identitiesState = { -}; + let prefs = [ + [ "privacy.userContext.enabled", true ], + [ "privacy.userContext.ui.enabled", true ], + [ "privacy.usercontext.about_newtab_segregation.enabled", true ], + [ "privacy.usercontext.longPressBehavior", 1 ] + ]; -function convert(identity) { - let hiddenTabUrls = []; + const prefService = require("sdk/preferences/service"); + prefs.forEach((pref) => { + prefService.set(pref[0], pref[1]); + }); - if (identity.userContextId in identitiesState) { - hiddenTabUrls = identitiesState[identity.userContextId].hiddenTabUrls; - } - const result = { - name: ContextualIdentityService.getUserContextLabel(identity.userContextId), - icon: identity.icon, - color: identity.color, - userContextId: identity.userContextId, - hiddenTabUrls: hiddenTabUrls - }; + // Message routing - return result; -} + // only these methods are allowed. We have a 1:1 mapping between messages + // and methods. These methods must return a promise. + let methods = [ + 'queryTabs', + 'hideTabs', + 'showTabs', + 'removeTabs', + 'openTab', + 'queryIdentities', + 'getIdentitiesState', + ]; -function queryContainers(details) { - const identities = []; + webExtension.startup().then(api => { + api.browser.runtime.onMessage.addListener((message, sender, sendReply) => { + if ("method" in message && methods.indexOf(message.method) != -1) { + sendReply(this[message.method](message)); + } + }); + }); + }, - ContextualIdentityService.getIdentities().forEach(identity=> { - if (details && details.name && - ContextualIdentityService.getUserContextLabel(identity.userContextId) !== details.name) { - return; + // utility methods + + _convert(identity) { + let hiddenTabUrls = []; + + if (identity.userContextId in this._identitiesState) { + hiddenTabUrls = this._identitiesState[identity.userContextId].hiddenTabUrls; } - const convertedIdentity = convert(identity); + return { + name: ContextualIdentityService.getUserContextLabel(identity.userContextId), + icon: identity.icon, + color: identity.color, + userContextId: identity.userContextId, + hiddenTabUrls: hiddenTabUrls + }; + }, - identities.push(convertedIdentity); - if (!(convertedIdentity.userContextId in identitiesState)) { - identitiesState[convertedIdentity.userContextId] = {hiddenTabUrls: []}; - } - }); + // Tabs management - return Promise.resolve(identities); -} + queryTabs(args) { + return new Promise((resolve, reject) => { + let tabList = []; -function removeContainer(userContextId) { - if (!userContextId) { - return Promise.resolve(null); - } + for (let tab of tabs) { + let xulTab = viewFor(tab); + let userContextId = parseInt(xulTab.getAttribute('usercontextid') || 0, 10); - const identity = ContextualIdentityService.getIdentityFromId(userContextId); + if ("userContextId" in args && args.userContextId != userContextId) { + continue; + } - if (!identity) { - return Promise.resolve(null); - } - - // We have to create the identity object before removing it. - const convertedIdentity = convert(identity); - - if (!ContextualIdentityService.remove(identity.userContextId)) { - return Promise.resolve(null); - } - - return Promise.resolve(convertedIdentity); -} - -function openTab(args) { - let browserWin = Services.wm.getMostRecentWindow('navigator:browser'); - - // This should not really happen. - if (!browserWin || !browserWin.gBrowser) { - return Promise.resolve(false); - } - - let userContextId = 0; - if ('userContextId' in args) { - userContextId = args.userContextId; - } - - let tab = browserWin.gBrowser.addTab(args.url || null, - { userContextId: userContextId }) - browserWin.gBrowser.selectedTab = tab; - return Promise.resolve(true); -} - -function queryTabs(args) { - return new Promise((resolve, reject) => { - let tabList = []; - - for (let tab of tabs) { - let xulTab = viewFor(tab); - let userContextId = parseInt(xulTab.getAttribute('usercontextid') || 0, 10); - - if ("userContextId" in args && args.userContextId != userContextId) { - continue; + tabList.push({ + id: tab.id, + url: tab.url, + userContextId: userContextId, + }); } - tabList.push({ - id: tab.id, - url: tab.url, - userContextId: userContextId, + resolve(tabList); + }); + }, + + hideTabs(args) { + this._identitiesState[args.userContextId].hiddenTabUrls = args.tabUrlsToSave; + return Promise.resolve(null); + }, + + showTabs(args) { + return new Promise((resolve, reject) => { + let hiddenTabUrls = this._identitiesState[args.userContextId].hiddenTabUrls; + this._identitiesState[args.userContextId].hiddenTabUrls = []; + resolve(hiddenTabUrls); + }); + }, + + removeTabs(args) { + return new Promise((resolve, reject) => { + for (let tab of tabs) { + if (args.tabIds.indexOf(tab.id) != -1) { + tab.close(); + } + } + resolve(null); + }); + }, + + openTab(args) { + return new Promise((resolve, reject) => { + let browserWin = Services.wm.getMostRecentWindow('navigator:browser'); + + // This should not really happen. + if (!browserWin || !browserWin.gBrowser) { + return Promise.resolve(false); + } + + let userContextId = 0; + if ('userContextId' in args) { + userContextId = args.userContextId; + } + + let tab = browserWin.gBrowser.addTab(args.url || null, + { userContextId: userContextId }) + browserWin.gBrowser.selectedTab = tab; + resolve(true); + }); + }, + + // Identities management + + queryIdentities(args) { + return new Promise((resolve, reject) => { + let identities = []; + + ContextualIdentityService.getIdentities().forEach(identity => { + let convertedIdentity = this._convert(identity); + identities.push(convertedIdentity); + if (!(convertedIdentity.userContextId in this._identitiesState)) { + this._identitiesState[convertedIdentity.userContextId] = {hiddenTabUrls: []}; + } }); - } - resolve(tabList); - }); -} + resolve(identities); + }); + }, -function removeTabs(ids) { - for (let tab of tabs) { - if (ids.indexOf(tab.id) != -1) { - tab.close(); - } - } + getIdentitiesState(args) { + return Promise.resolve(this._identitiesState); + }, +}; - return Promise.resolve(null); -} - -function handleWebExtensionMessage(message, sender, sendReply) { - switch (message.method) { - case 'queryIdentities': - sendReply(queryContainers(message.arguments)); - break; - case 'queryTabs': - sendReply(queryTabs(message)); - break; - case 'hideTabs': - identitiesState[message.userContextId].hiddenTabUrls = message.tabUrlsToSave; - break; - case 'showTabs': - sendReply(identitiesState[message.userContextId].hiddenTabUrls); - identitiesState[message.userContextId].hiddenTabUrls = []; - break; - case 'removeTabs': - sendReply(removeTabs(message.tabIds)); - identitiesState[message.userContextId].hiddenTabUrls = []; - break; - case 'getIdentitiesState': - sendReply(identitiesState); - break; - case 'openTab': - sendReply(openTab(message)); - break; - } -} - -webExtension.startup().then(api=> { - const {browser} = api; - - browser.runtime.onMessage.addListener(handleWebExtensionMessage); -}); +ContainerService.init(); diff --git a/webextension/js/popup.js b/webextension/js/popup.js index b987c53..2215759 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -20,10 +20,11 @@ function hideContainerTabs(userContextId) { userContextId: userContextId, tabUrlsToSave: tabUrlsToSave }).then(()=> { - browser.runtime.sendMessage({ + return browser.runtime.sendMessage({ method: 'removeTabs', tabIds: tabIdsToRemove }); + }).then(() => { hideorshowIcon.src = CONTAINER_UNHIDE_SRC; }); }); @@ -43,8 +44,9 @@ function showContainerTabs(userContextId) { url: url }); }); + }).then(() => { + hideorshowIcon.src = CONTAINER_HIDE_SRC; }); - hideorshowIcon.src = CONTAINER_HIDE_SRC; } if (localStorage.getItem('onboarded2')) { @@ -128,8 +130,12 @@ browser.runtime.sendMessage({method: 'queryIdentities'}).then(identities=> { } }); } else if (e.target.matches('.newtab-icon')) { - browser.runtime.sendMessage({method: 'openTab', userContextId: userContextId}); - window.close(); + browser.runtime.sendMessage({ + method: 'openTab', + userContextId: userContextId}) + .then(() => { + window.close(); + }); } }); }); @@ -154,7 +160,9 @@ function moveTabs(sortedTabsArray) { } document.querySelector('#sort-containers-link').addEventListener('click', ()=> { - browser.runtime.sendMessage({method: 'queryIdentities'}).then(identities=> { + browser.runtime.sendMessage({ + method: 'queryIdentities' + }).then(identities=> { identities.unshift({userContextId: 0}); browser.runtime.sendMessage({method: 'queryTabs'}).then(tabsArray=> { From fba6b1cbd6d93d98ea29505918f3bb42266a2b6d Mon Sep 17 00:00:00 2001 From: baku Date: Sat, 7 Jan 2017 08:40:59 +0100 Subject: [PATCH 08/15] New sorting algorithm in the parent process --- index.js | 41 +++++++++++++++++++++++++++++++++++++--- webextension/js/popup.js | 29 +--------------------------- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/index.js b/index.js index d401779..44d5da9 100644 --- a/index.js +++ b/index.js @@ -2,11 +2,11 @@ const {ContextualIdentityService} = require('resource://gre/modules/ContextualIdentityService.jsm'); const { Cc, Ci, Cu, Cr } = require('chrome'); -Cu.import("resource://gre/modules/Services.jsm"); - const tabs = require('sdk/tabs'); const webExtension = require('sdk/webextension'); const { viewFor } = require("sdk/view/core"); +var windowUtils = require('sdk/window/utils'); +var tabsUtils = require('sdk/tabs/utils'); let ContainerService = { @@ -36,6 +36,7 @@ let ContainerService = 'hideTabs', 'showTabs', 'removeTabs', + 'sortTabs', 'openTab', 'queryIdentities', 'getIdentitiesState', @@ -117,9 +118,43 @@ let ContainerService = }); }, + sortTabs(args) { + return new Promise((resolve, reject) => { + let windows = windowUtils.windows('navigator:browser', {includePrivate:false}); + for (let window of windows) { + let tabs = tabsUtils.getTabs(window); + + // Let's collect UCIs for each tab of this window. + let map = new Map; + for (let tab of tabs) { + let xulTab = viewFor(tab); + let userContextId = parseInt(xulTab.getAttribute('usercontextid') || 0, 10); + if (!map.has(userContextId)) { + map.set(userContextId, []); + } + map.get(userContextId).push(xulTab); + } + + // Let's sort the map. + let sortMap = new Map([...map.entries()].sort((a, b) => a[0] > b[0])); + + let pos = 0; + + // Let's move tabs. + for (let [userContextId, tabs] of sortMap) { + for (let tab of tabs) { + window.gBrowser.moveTabTo(tab, pos++); + } + } + } + + resolve(null); + }); + }, + openTab(args) { return new Promise((resolve, reject) => { - let browserWin = Services.wm.getMostRecentWindow('navigator:browser'); + let browserWin = windowUtils.getMostRecentBrowserWindow(); // This should not really happen. if (!browserWin || !browserWin.gBrowser) { diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 2215759..820b4b6 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -150,33 +150,6 @@ document.querySelector('#edit-containers-link').addEventListener('click', ()=> { }); }); -function moveTabs(sortedTabsArray) { - let positionIndex = 0; - - sortedTabsArray.forEach(tabID=> { - browser.tabs.move(tabID, {index: positionIndex}); - positionIndex++; - }); -} - document.querySelector('#sort-containers-link').addEventListener('click', ()=> { - browser.runtime.sendMessage({ - method: 'queryIdentities' - }).then(identities=> { - identities.unshift({userContextId: 0}); - - browser.runtime.sendMessage({method: 'queryTabs'}).then(tabsArray=> { - const sortedTabsArray = []; - - identities.forEach(identity=> { - tabsArray.forEach(tab=> { - if (tab.userContextId === identity.userContextId) { - sortedTabsArray.push(tab.id); - } - }); - }); - - moveTabs(sortedTabsArray); - }); - }); + browser.runtime.sendMessage({ method: 'sortTabs' }); }); From da65760baed79d96832642cc5737821fc329dfa7 Mon Sep 17 00:00:00 2001 From: baku Date: Sat, 7 Jan 2017 09:09:37 +0100 Subject: [PATCH 09/15] Close popup after sorting --- webextension/js/popup.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 820b4b6..fb0c510 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -151,5 +151,9 @@ document.querySelector('#edit-containers-link').addEventListener('click', ()=> { }); document.querySelector('#sort-containers-link').addEventListener('click', ()=> { - browser.runtime.sendMessage({ method: 'sortTabs' }); + browser.runtime.sendMessage({ + method: 'sortTabs' + }).then(()=> { + window.close(); + }); }); From 4b92b775b4f3275dbd03468a7c4367def63eef14 Mon Sep 17 00:00:00 2001 From: baku Date: Sat, 7 Jan 2017 09:24:17 +0100 Subject: [PATCH 10/15] Sorting must work with pinned tabs as well --- index.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 44d5da9..59de8c7 100644 --- a/index.js +++ b/index.js @@ -124,22 +124,27 @@ let ContainerService = for (let window of windows) { let tabs = tabsUtils.getTabs(window); - // Let's collect UCIs for each tab of this window. + let pos = 0; + + // Let's collect UCIs/tabs for this window. let map = new Map; for (let tab of tabs) { - let xulTab = viewFor(tab); - let userContextId = parseInt(xulTab.getAttribute('usercontextid') || 0, 10); + if (tabsUtils.isPinned(tab)) { + // pinned tabs must be consider as taken positions. + ++pos; + continue; + } + + let userContextId = parseInt(tab.getAttribute('usercontextid') || 0, 10); if (!map.has(userContextId)) { map.set(userContextId, []); } - map.get(userContextId).push(xulTab); + map.get(userContextId).push(tab); } // Let's sort the map. let sortMap = new Map([...map.entries()].sort((a, b) => a[0] > b[0])); - let pos = 0; - // Let's move tabs. for (let [userContextId, tabs] of sortMap) { for (let tab of tabs) { From f87eece6cc0b3cb6867f6b533a7897c9bc21be47 Mon Sep 17 00:00:00 2001 From: baku Date: Mon, 9 Jan 2017 09:36:58 +0100 Subject: [PATCH 11/15] Issue #11 - Showing all the hidden tabs before opening a new one --- webextension/js/popup.js | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/webextension/js/popup.js b/webextension/js/popup.js index fb0c510..1bc8af6 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -31,21 +31,23 @@ function hideContainerTabs(userContextId) { } function showContainerTabs(userContextId) { - const hideorshowIcon = document.querySelector(`#uci-${userContextId}-hideorshow-icon`); + return new Promise((resolve, reject) => { + const hideorshowIcon = document.querySelector(`#uci-${userContextId}-hideorshow-icon`); - browser.runtime.sendMessage({ - method: 'showTabs', - userContextId: userContextId - }).then(hiddenTabUrls=> { - hiddenTabUrls.forEach(url=> { - browser.runtime.sendMessage({ - method: 'openTab', - userContextId: userContextId, - url: url + browser.runtime.sendMessage({ + method: 'showTabs', + userContextId: userContextId + }).then(hiddenTabUrls=> { + hiddenTabUrls.forEach(url=> { + browser.runtime.sendMessage({ + method: 'openTab', + userContextId: userContextId, + url: url + }); }); - }); - }).then(() => { - hideorshowIcon.src = CONTAINER_HIDE_SRC; + }).then(() => { + hideorshowIcon.src = CONTAINER_HIDE_SRC; + }).then(resolve); }); } @@ -130,11 +132,13 @@ browser.runtime.sendMessage({method: 'queryIdentities'}).then(identities=> { } }); } else if (e.target.matches('.newtab-icon')) { - browser.runtime.sendMessage({ - method: 'openTab', - userContextId: userContextId}) - .then(() => { - window.close(); + showContainerTabs(userContextId).then(() => { + browser.runtime.sendMessage({ + method: 'openTab', + userContextId: userContextId}) + .then(() => { + window.close(); + }); }); } }); From 76ad3fcb0f4714695be71edf7d9636bdd3b0d397 Mon Sep 17 00:00:00 2001 From: baku Date: Mon, 9 Jan 2017 10:44:18 +0100 Subject: [PATCH 12/15] Refactoring of hide/show algorithm. --- index.js | 68 ++++++++++++++-------------------------- webextension/js/popup.js | 57 ++++++--------------------------- 2 files changed, 34 insertions(+), 91 deletions(-) diff --git a/index.js b/index.js index 59de8c7..18b7fd2 100644 --- a/index.js +++ b/index.js @@ -32,16 +32,21 @@ let ContainerService = // only these methods are allowed. We have a 1:1 mapping between messages // and methods. These methods must return a promise. let methods = [ - 'queryTabs', 'hideTabs', 'showTabs', - 'removeTabs', 'sortTabs', 'openTab', 'queryIdentities', - 'getIdentitiesState', + 'getIdentity', ]; + // Map of identities. + ContextualIdentityService.getIdentities().forEach(identity => { + this._identitiesState[identity.userContextId] = {hiddenTabUrls: []}; + }); + + // WebExtension startup + webExtension.startup().then(api => { api.browser.runtime.onMessage.addListener((message, sender, sendReply) => { if ("method" in message && methods.indexOf(message.method) != -1) { @@ -54,27 +59,19 @@ let ContainerService = // utility methods _convert(identity) { - let hiddenTabUrls = []; - - if (identity.userContextId in this._identitiesState) { - hiddenTabUrls = this._identitiesState[identity.userContextId].hiddenTabUrls; - } - return { name: ContextualIdentityService.getUserContextLabel(identity.userContextId), icon: identity.icon, color: identity.color, userContextId: identity.userContextId, - hiddenTabUrls: hiddenTabUrls + hasHiddenTabs: !!this._identitiesState[identity.userContextId].hiddenTabUrls.length, }; }, // Tabs management - queryTabs(args) { + hideTabs(args) { return new Promise((resolve, reject) => { - let tabList = []; - for (let tab of tabs) { let xulTab = viewFor(tab); let userContextId = parseInt(xulTab.getAttribute('usercontextid') || 0, 10); @@ -83,39 +80,24 @@ let ContainerService = continue; } - tabList.push({ - id: tab.id, - url: tab.url, - userContextId: userContextId, - }); + this._identitiesState[args.userContextId].hiddenTabUrls.push(tab.url); + tab.close(); } - resolve(tabList); + resolve(null); }); }, - hideTabs(args) { - this._identitiesState[args.userContextId].hiddenTabUrls = args.tabUrlsToSave; - return Promise.resolve(null); - }, - showTabs(args) { - return new Promise((resolve, reject) => { - let hiddenTabUrls = this._identitiesState[args.userContextId].hiddenTabUrls; - this._identitiesState[args.userContextId].hiddenTabUrls = []; - resolve(hiddenTabUrls); - }); - }, + let promises = []; - removeTabs(args) { - return new Promise((resolve, reject) => { - for (let tab of tabs) { - if (args.tabIds.indexOf(tab.id) != -1) { - tab.close(); - } - } - resolve(null); - }); + for (let url of this._identitiesState[args.userContextId].hiddenTabUrls) { + promises.push(this.openTab({ userContextId: args.userContextId, url })); + } + + this._identitiesState[args.userContextId].hiddenTabUrls = []; + + return Promise.all(promises); }, sortTabs(args) { @@ -187,17 +169,15 @@ let ContainerService = ContextualIdentityService.getIdentities().forEach(identity => { let convertedIdentity = this._convert(identity); identities.push(convertedIdentity); - if (!(convertedIdentity.userContextId in this._identitiesState)) { - this._identitiesState[convertedIdentity.userContextId] = {hiddenTabUrls: []}; - } }); resolve(identities); }); }, - getIdentitiesState(args) { - return Promise.resolve(this._identitiesState); + getIdentity(args) { + let identity = ContextualIdentityService.getIdentityFromId(args.userContextId); + return Promise.resolve(identity ? this._convert(identity) : null); }, }; diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 1bc8af6..6f03947 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -2,51 +2,15 @@ const CONTAINER_HIDE_SRC = '/img/container-hide.svg'; const CONTAINER_UNHIDE_SRC = '/img/container-unhide.svg'; -function hideContainerTabs(userContextId) { - const tabIdsToRemove = []; - const tabUrlsToSave = []; - const hideorshowIcon = document.querySelector(`#uci-${userContextId}-hideorshow-icon`); - - browser.runtime.sendMessage({ - method: 'queryTabs', - userContextId: userContextId - }).then(tabs=> { - tabs.forEach(tab=> { - tabIdsToRemove.push(tab.id); - tabUrlsToSave.push(tab.url); - }); - browser.runtime.sendMessage({ - method: 'hideTabs', - userContextId: userContextId, - tabUrlsToSave: tabUrlsToSave - }).then(()=> { - return browser.runtime.sendMessage({ - method: 'removeTabs', - tabIds: tabIdsToRemove - }); - }).then(() => { - hideorshowIcon.src = CONTAINER_UNHIDE_SRC; - }); - }); -} - -function showContainerTabs(userContextId) { +function showOrHideContainerTabs(userContextId, hasHiddenTabs) { return new Promise((resolve, reject) => { const hideorshowIcon = document.querySelector(`#uci-${userContextId}-hideorshow-icon`); browser.runtime.sendMessage({ - method: 'showTabs', + method: hasHiddenTabs ? 'showTabs' : 'hideTabs', userContextId: userContextId - }).then(hiddenTabUrls=> { - hiddenTabUrls.forEach(url=> { - browser.runtime.sendMessage({ - method: 'openTab', - userContextId: userContextId, - url: url - }); - }); }).then(() => { - hideorshowIcon.src = CONTAINER_HIDE_SRC; + hideorshowIcon.src = hasHiddenTabs ? CONTAINER_HIDE_SRC : CONTAINER_UNHIDE_SRC; }).then(resolve); }); } @@ -84,7 +48,7 @@ browser.runtime.sendMessage({method: 'queryIdentities'}).then(identities=> { identities.forEach(identity=> { let hideOrShowIconSrc = CONTAINER_HIDE_SRC; - if (identity.hiddenTabUrls.length) { + if (identity.hasHiddenTabs) { hideOrShowIconSrc = CONTAINER_UNHIDE_SRC; } const identityRow = ` @@ -124,15 +88,14 @@ browser.runtime.sendMessage({method: 'queryIdentities'}).then(identities=> { const userContextId = e.target.parentElement.parentElement.dataset.identityCookieStoreId; if (e.target.matches('.hideorshow-icon')) { - browser.runtime.sendMessage({method: 'getIdentitiesState'}).then(identitiesState=> { - if (identitiesState[userContextId].hiddenTabUrls.length) { - showContainerTabs(userContextId); - } else { - hideContainerTabs(userContextId); - } + browser.runtime.sendMessage({ + method: 'getIdentity', + userContextId + }).then(identity=> { + showOrHideContainerTabs(userContextId, identity.hasHiddenTabs); }); } else if (e.target.matches('.newtab-icon')) { - showContainerTabs(userContextId).then(() => { + showOrHideContainerTabs(userContextId, true).then(() => { browser.runtime.sendMessage({ method: 'openTab', userContextId: userContextId}) From 71725d829be6d43c06e9b7444c5db85b70a9398a Mon Sep 17 00:00:00 2001 From: baku Date: Mon, 9 Jan 2017 11:05:13 +0100 Subject: [PATCH 13/15] Hide/Show icon should be shown only if we have opened tabs --- index.js | 31 ++++++++++++++++++++++++++++++- webextension/js/popup.js | 17 +++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 18b7fd2..5db22ac 100644 --- a/index.js +++ b/index.js @@ -42,7 +42,35 @@ let ContainerService = // Map of identities. ContextualIdentityService.getIdentities().forEach(identity => { - this._identitiesState[identity.userContextId] = {hiddenTabUrls: []}; + this._identitiesState[identity.userContextId] = { + hiddenTabUrls: [], + openTabs: 0, + }; + }); + + // It can happen that this jsm is loaded after the opening a container tab. + for (let tab of tabs) { + let xulTab = viewFor(tab); + let userContextId = parseInt(xulTab.getAttribute('usercontextid') || 0, 10); + if (userContextId) { + ++this._identitiesState[userContextId].openTabs; + } + } + + tabs.on("open", tab => { + let xulTab = viewFor(tab); + let userContextId = parseInt(xulTab.getAttribute('usercontextid') || 0, 10); + if (userContextId) { + ++this._identitiesState[userContextId].openTabs; + } + }); + + tabs.on("close", tab => { + let xulTab = viewFor(tab); + let userContextId = parseInt(xulTab.getAttribute('usercontextid') || 0, 10); + if (userContextId && this._identitiesState[userContextId].openTabs) { + --this._identitiesState[userContextId].openTabs; + } }); // WebExtension startup @@ -65,6 +93,7 @@ let ContainerService = color: identity.color, userContextId: identity.userContextId, hasHiddenTabs: !!this._identitiesState[identity.userContextId].hiddenTabUrls.length, + hasOpenTabs: !!this._identitiesState[identity.userContextId].openTabs, }; }, diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 6f03947..4cd5746 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -10,6 +10,17 @@ function showOrHideContainerTabs(userContextId, hasHiddenTabs) { method: hasHiddenTabs ? 'showTabs' : 'hideTabs', userContextId: userContextId }).then(() => { + return browser.runtime.sendMessage({ + method: 'getIdentity', + userContextId: userContextId + }); + }).then((identity) => { + if (!identity.hasHiddenTabs && !identity.hasOpenTabs) { + hideorshowIcon.style.display = "none"; + } else { + hideorshowIcon.style.display = ""; + } + hideorshowIcon.src = hasHiddenTabs ? CONTAINER_HIDE_SRC : CONTAINER_UNHIDE_SRC; }).then(resolve); }); @@ -79,6 +90,12 @@ browser.runtime.sendMessage({method: 'queryIdentities'}).then(identities=> { `; identitiesListElement.innerHTML += identityRow; + + // No tabs, no icon. + if (!identity.hasHiddenTabs && !identity.hasOpenTabs) { + const hideorshowIcon = document.querySelector(`#uci-${identity.userContextId}-hideorshow-icon`); + hideorshowIcon.style.display = "none"; + } }); const rows = identitiesListElement.querySelectorAll('tr'); From aea0e27df07f5652f2e766e6fb9b4ee8ec445aca Mon Sep 17 00:00:00 2001 From: baku Date: Mon, 9 Jan 2017 11:57:32 +0100 Subject: [PATCH 14/15] Icons visible in FF50. --- webextension/css/popup.css | 10 ++++----- webextension/js/popup.js | 46 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/webextension/css/popup.css b/webextension/css/popup.css index 6edbabf..d4d0088 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -98,21 +98,21 @@ table.unstriped tbody tr { } [data-identity-icon="fingerprint"] { - --identity-icon: url("chrome://browser/content/usercontext.svg#fingerprint"); + --identity-icon: url("/img/usercontext.svg#fingerprint"); } [data-identity-icon="briefcase"] { - --identity-icon: url("chrome://browser/content/usercontext.svg#briefcase"); + --identity-icon: url("/img/usercontext.svg#briefcase"); } [data-identity-icon="dollar"] { - --identity-icon: url("chrome://browser/content/usercontext.svg#dollar"); + --identity-icon: url("/img/usercontext.svg#dollar"); } [data-identity-icon="cart"] { - --identity-icon: url("chrome://browser/content/usercontext.svg#cart"); + --identity-icon: url("/img/usercontext.svg#cart"); } [data-identity-icon="circle"] { - --identity-icon: url("chrome://browser/content/usercontext.svg#circle"); + --identity-icon: url("/img/usercontext.svg#circle"); } diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 4cd5746..f960115 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -26,6 +26,45 @@ function showOrHideContainerTabs(userContextId, hasHiddenTabs) { }); } +// In FF 50-51, the icon is the full path, in 52 and following releases, we +// have IDs to be used with a svg file. In this function we map URLs to svg IDs. +function getIconAndColorForIdentity(identity) { + let image, color; + + if (identity.icon == "fingerprint" || + identity.icon == "chrome://browser/skin/usercontext/personal.svg") { + image = "fingerprint"; + } else if (identity.icon == "briefcase" || + identity.icon == "chrome://browser/skin/usercontext/work.svg") { + image = "briefcase"; + } else if (identity.icon == "dollar" || + identity.icon == "chrome://browser/skin/usercontext/banking.svg") { + image = "dollar"; + } else if (identity.icon == "cart" || + identity.icon == "chrome://browser/skin/usercontext/shopping.svg") { + image = "cart"; + } else { + image = "circle"; + } + + if (identity.color == "#00a7e0") { + color = "blue"; + } else if (identity.color == "#f89c24") { + color = "orange"; + } else if (identity.color == "#7dc14c") { + color = "green"; + } else if (identity.color == "#ee5195") { + color = "pink"; + } else if (["blue", "turquoise", "green", "yellow", "orange", "red", + "pink", "purple"].indexOf(identity.color) != -1) { + color = identity.color; + } else { + color = ""; + } + + return { image, color }; +} + if (localStorage.getItem('onboarded2')) { for (const element of document.querySelectorAll('.onboarding')) { element.classList.add('hide'); @@ -62,12 +101,15 @@ browser.runtime.sendMessage({method: 'queryIdentities'}).then(identities=> { if (identity.hasHiddenTabs) { hideOrShowIconSrc = CONTAINER_UNHIDE_SRC; } + + let {image, color} = getIconAndColorForIdentity(identity); + const identityRow = `
+ data-identity-icon="${image}" + data-identity-color="${color}">
${identity.name} From 64498da783f0e323c1a4ae37e69bec53e421a3d9 Mon Sep 17 00:00:00 2001 From: baku Date: Mon, 9 Jan 2017 12:04:27 +0100 Subject: [PATCH 15/15] README updated for FF51 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5309d62..f0194d6 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ details](https://docs.google.com/document/d/1WQdHTVXROk7dYkSFluc6_hS44tqZjIrG9I- ## Requirements * node 7+ (for jpm) -* Firefox 52+ (For now; aiming at Firefox 51+) +* Firefox 51+ ## Run it @@ -53,7 +53,7 @@ The only way to run the experiment is using an [unbranded version build](https:/ 1. `git clone git@github.com:mozilla/testpilot-containers.git` 2. `cd testpilot-containers` 3. `npm install` -4. `./node_modules/.bin/jpm run -p /Path/To/Firefox/Profiles/{junk}.addon_dev -b FirefoxDeveloperEdition` (where FirefoxDeveloperEdition might be: ~//obj-x86_64-pc-linux-gnu/dist/bin/firefox) +4. `./node_modules/.bin/jpm run -p /Path/To/Firefox/Profiles/{junk}.addon_dev -b FirefoxBeta` (where FirefoxBeta might be: ~//obj-x86_64-pc-linux-gnu/dist/bin/firefox or ~//firefox) Check out the [Browser Toolbox](https://developer.mozilla.org/en-US/docs/Tools/Browser_Toolbox) for more information about debugging add-on code.