Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
4b57adc
Write skeleton for PreferenceGroup
Sep 5, 2019
9bd54f5
Rename variables to use camelCase
Sep 6, 2019
6a556b3
First visible version of PreferenceGroup
Sep 6, 2019
e660c33
Move the position of preference descriptions
Sep 6, 2019
efd6120
Make saving PreferenceGroups work
Sep 7, 2019
f02063e
Don't fail updating a Preference from the DB if its value cannot be r…
Sep 7, 2019
0931dc2
Apply CSS to know if preference groups are activated or not
Sep 7, 2019
a4a64d5
Add utils.formatString function to interpret variables in strings
Sep 9, 2019
f821186
greentest utils::should replace alphanumeric variables
Sep 9, 2019
5fef840
First functional default container implementation
Sep 9, 2019
045c540
Add `valueOnly` and `valuesOnly` to PreferenceStorage get methods
Sep 9, 2019
d0a04d4
Fix lint issue
Sep 9, 2019
e16f8b0
Check URL rules before requesting anything from the storage
Sep 9, 2019
5dfeae8
Catch errors and don't fail when handling urls
Sep 9, 2019
8a0831f
Add info log for URLs that open in default container
Sep 9, 2019
23a65e0
Move towards Preferences that know how to fill themselves
Sep 9, 2019
16c4793
Add preferences for containers
Sep 10, 2019
74e5152
Improve CSS for groups and preferences
Sep 10, 2019
758a9cf
Try to add further visual distinction between groups and prefs
Sep 10, 2019
bda2b81
Fix bug when saving preference groups
Sep 10, 2019
169ae67
Use the correct variable for ContainerPreference names
Sep 10, 2019
4875769
Save 'lifetime' preference for created default containers
Sep 10, 2019
dd51582
Handle 'untilLastTab' lifetime preference of containers
Sep 10, 2019
457d77e
Remove a TODO
Sep 10, 2019
a6e709c
Remove redundant constructor
Sep 10, 2019
3729349
Improve doc a little
Sep 10, 2019
1332a33
Introduce defaultValue for preferences
Sep 10, 2019
591a47a
Cleanup temporary containers at startup
Sep 10, 2019
16c9e89
Add notice about the class naming choice
Sep 10, 2019
e01807d
Start adding more comments and code documentation
Sep 10, 2019
758da96
Remove unnecessary code
Sep 10, 2019
b0bceca
Use correct param for defaultValue
Sep 10, 2019
e1d9376
Add more documentation to Preference
Sep 10, 2019
ff9e370
Move PreferenceGroup doc to the right place
Sep 10, 2019
3030c4b
Clean keys in HostStorage after a container is removed
Sep 10, 2019
09441bf
Return a container from getDefaultContainer instead of a cookieStoreId
Sep 10, 2019
becddf9
Don't try and handle tabs we ourselves are creating
Sep 10, 2019
47a7c99
Make ContextualIdentity tests pass again
Sep 10, 2019
a8d27e6
Add moz-extension to the domains that won't be handled
Sep 11, 2019
16b22dd
Put save button on left to always make it accessible
Sep 11, 2019
c332fdc
Use regex correctly
Sep 11, 2019
4c69802
Merge branch 'master' of https://github.com/kintesh/containerise into…
Sep 12, 2019
7a7c0a2
Fix createTab call after merge from master
Sep 12, 2019
3815faf
Fix indent after merge
Sep 12, 2019
c1e4ad2
Save settings onchange instead of requiring a save button
Sep 16, 2019
2af38c1
Handle using "No Container" as the default container
Sep 16, 2019
1e2681d
Check variable before using it
Sep 16, 2019
e8114d9
Remove unnecessary log
Sep 16, 2019
ab32470
Fix support for "No Container"
Sep 16, 2019
56f5192
Fix test that uses NO_CONTAINER
Sep 16, 2019
efe0e48
Clean preferences of containers once they are deleted
Sep 17, 2019
83faa53
Provide method to test permanent profiles
Sep 19, 2019
e760bfb
Clean HostStorage and PreferenceStorage on deletion of a container
Sep 23, 2019
f4d8a89
Get rid of leftover preferences on addon start
Sep 23, 2019
9264815
Improve the definition of a "leftover" temporary container
Oct 3, 2019
55704dc
npm audit fix for handlebars
Oct 3, 2019
9473224
Correct a typo
Oct 3, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ containerise.iml
# build
build
web-ext-artifacts

# Other
/profile
16 changes: 13 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"build": "npm audit && npm run lint && npm run test && webpack --config ./webpack.prod",
"postbuild": "cp -a static/ build/ && web-ext build -s build/",
"web-ext": "web-ext run -s build/ --start-url=about:debugging#addons",
"web-ext-perm": "mkdirp ./profile && web-ext run -s build/ --start-url=about:debugging#addons -p ./profile --keep-profile-changes",
"lint": "eslint --ignore-path .gitignore ./",
"test": "jest",
"test:watch": "jest --watchAll"
Expand All @@ -28,6 +29,7 @@
"jest": "^24.8.0",
"mini-css-extract-plugin": "^0.8.0",
"node-sass": "^4.12.0",
"raw-loader": "^3.1.0",
"rimraf": "^3.0.0",
"sass-loader": "^8.0.0",
"style-loader": "^1.0.0",
Expand Down
5 changes: 4 additions & 1 deletion src/ContextualIdentity/__tests__/ContextualIdentity.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ describe('ContextualIdentities', () => {
addListener: jest.fn(() => {}),
},
},
storage: {
local: {},
},
};

ContextualIdentities = require('../index').default;
Expand All @@ -55,7 +58,7 @@ describe('ContextualIdentities', () => {
iconUrl: 'resource://usercontext-content/circle.svg',
color: 'grey',
colorCode: '#999',
cookieStoreId: 'no-container',
cookieStoreId: 'firefox-default',
}]);
});
});
Expand Down
53 changes: 51 additions & 2 deletions src/ContextualIdentity/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,65 @@
import HostStorage from '../Storage/HostStorage';
import PreferenceStorage from '../Storage/PreferenceStorage';

export const NO_CONTAINER = {
name: 'No Container',
icon: 'circle',
iconUrl: 'resource://usercontext-content/circle.svg',
color: 'grey',
colorCode: '#999',
cookieStoreId: 'no-container',
cookieStoreId: 'firefox-default',
};
export const COLORS = [
'blue',
'green',
'orange',
'pink',
'purple',
'red',
'turquoise',
'yellow',
];

class ContextualIdentities {

constructor() {
this.contextualIdentities = browser.contextualIdentities;
this.addOnRemoveListener((changeInfo) => {
const cookieStoreId = changeInfo.contextualIdentity.cookieStoreId;
this.cleanPreferences(cookieStoreId);
this.cleanMaps(cookieStoreId);
});
}

create(name) {
return this.contextualIdentities.create({
name: name,
color: COLORS[Math.floor(Math.random() * COLORS.length)],
icon: 'circle',
});
}

/**
* Gets rid of a container and all corresponding rules
*/
async remove(cookieStoreId) {
if (cookieStoreId === NO_CONTAINER.cookieStoreId) {
return;
}
return this.contextualIdentities.remove(cookieStoreId);
}

async cleanMaps(cookieStoreId) {
const hostMaps = await HostStorage.getAll();
return HostStorage.remove(Object.keys(hostMaps)
.filter(host => hostMaps[host].cookieStoreId === cookieStoreId)
);
}
async cleanPreferences(cookieStoreId) {
const preferences = await PreferenceStorage.getAll();
return PreferenceStorage.remove(Object.keys(preferences)
.filter(prefName => prefName.startsWith(`containers.${cookieStoreId}`))
);
}

getAll(details = {}) {
Expand All @@ -19,7 +68,7 @@ class ContextualIdentities {

get(name) {
if (name === NO_CONTAINER.name) {
return Promise.resolve(NO_CONTAINER);
return Promise.resolve([NO_CONTAINER]);
}
return this.contextualIdentities.query({name});
}
Expand Down
12 changes: 12 additions & 0 deletions src/ExtendedURL/__tests__/ExtendURL.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
describe('utils', () => {

const ExtendedURL = require('../index').default;

it('should have domain', function () {
const eUrl = new ExtendedURL('https://gist.github.com');
expect(eUrl.domain).toEqual('github');
expect(eUrl.tld).toEqual('com');
});


});
12 changes: 12 additions & 0 deletions src/ExtendedURL/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default class ExtendedURL extends URL {
constructor(url) {
super(url);
const split = this.hostname.split('.');
this.tld = split[split.length - 1];
if (split.length > 1) {
this.domain = split[split.length - 2];
} else {
this.domain = this.tld;
}
}
}
17 changes: 17 additions & 0 deletions src/Storage/PreferenceStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ class PreferenceStorage extends PrefixStorage {
this.PREFIX = 'pref=';
}

async getAll(valuesOnly = false) {
let preferences = await super.getAll();
if (valuesOnly) {
for (let preferenceKey in preferences) {
preferences[preferenceKey] = preferences[preferenceKey].value;
}
}
return preferences;
}

async get(key, valueOnly = false) {
let preference = await super.get(key);
if (valueOnly && preference !== undefined) {
preference = preference.value;
}
return preference;
}
}

export default new PreferenceStorage();
66 changes: 66 additions & 0 deletions src/__tests__/utils.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
describe('utils', () => {

const utils = require('../utils');

describe('formatString', () => {
it('should return same string without variables', function () {
const string = 'Farouq Nadeeb';
expect(utils.formatString(string, {}))
.toEqual(string);
});

it('should replace alphanumeric variables', function () {
const name = 'Farouq';
const lastName = 'Nadeeb';
expect(utils.formatString('{name} {lastName}', {
name, lastName,
})).toEqual(`${name} ${lastName}`);
});

it('should replace kebab-case variables', function () {
const name = 'Farouq';
const lastName = 'Nadeeb';
expect(utils.formatString('{name} {last-name}', {
name, ['last-name']: lastName,
})).toEqual(`${name} ${lastName}`);
});

it('should throw on non-existent variables', function () {
const name = 'Farouq';
const lastName = 'Nadeeb';
expect(() => {
utils.formatString('{name} {lastName} - born {dob}', {
name, lastName,
});
}).toThrow('Cannot find variable \'dob\' in context');
});

});

describe('filterByKey', () => {
it('should create object with keys that don\'t start with a string', function () {
expect(utils.filterByKey({
removeThis: 'lol',
removeThat: 'rofl',
removeAnother: 'do eet!',
keepMe: 'kept',
keepThem: 'kept',
}, (key) => !key.startsWith('remove')))
.toEqual({
keepMe: 'kept',
keepThem: 'kept',
});
});

it('should fail without a filter function', function () {
expect(() => {
utils.filterByKey({
a: true,
b: true,
});
}).toThrow('undefined is not a function');
});

});

});
80 changes: 60 additions & 20 deletions src/containers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ import Storage from './Storage/HostStorage';
import ContextualIdentity, {NO_CONTAINER} from './ContextualIdentity';
import Tabs from './Tabs';
import PreferenceStorage from './Storage/PreferenceStorage';
import {filterByKey} from './utils';
import {buildDefaultContainer} from './defaultContainer';

const IGNORED_URLS_REGEX = /^(about|moz-extension):/;

/**
* Keep track of the tabs we're creating
* tabId: url
*/
const creatingTabs = {};

const createTab = (url, newTabIndex, currentTabId, openerTabId, cookieStoreId) => {
Tabs.get(currentTabId).then((currentTab) => {
Expand All @@ -17,6 +27,7 @@ const createTab = (url, newTabIndex, currentTabId, openerTabId, cookieStoreId) =
createOptions.openerTabId = openerTabId;
}
Tabs.create(createOptions).then((createdTab) => {
creatingTabs[createdTab.id] = url;
if (!cookieStoreId && openerTabId) {
Tabs.update(createdTab.id, {
openerTabId: openerTabId,
Expand All @@ -38,35 +49,65 @@ const createTab = (url, newTabIndex, currentTabId, openerTabId, cookieStoreId) =
};
};

function handle(url, tabId) {
return Promise.all([

async function handle(url, tabId) {
const creatingUrl = creatingTabs[tabId];
if (IGNORED_URLS_REGEX.test(url) || creatingUrl === url) {
return;
} else if (creatingUrl) {
delete creatingTabs[tabId];
}

let [hostMap, preferences, identities, currentTab] = await Promise.all([
Storage.get(url),
PreferenceStorage.getAll(true),
ContextualIdentity.getAll(),
Tabs.get(tabId),
]).then(([hostMap, identities, currentTab]) => {
]);

if (currentTab.incognito || !hostMap) {
return {};
}
if (currentTab.incognito || !hostMap) {
return {};
}

const hostIdentity = identities.find((identity) => identity.cookieStoreId === hostMap.cookieStoreId);
const tabIdentity = identities.find((identity) => identity.cookieStoreId === currentTab.cookieStoreId);
const hostIdentity = identities.find((identity) => identity.cookieStoreId === hostMap.cookieStoreId);
const tabIdentity = identities.find((identity) => identity.cookieStoreId === currentTab.cookieStoreId);

if (!hostIdentity) {
return {};
if (!hostIdentity) {
if (preferences.defaultContainer) {
const defaultContainer = await buildDefaultContainer(
filterByKey(preferences, prefKey => prefKey.startsWith('defaultContainer')),
url
);
const defaultCookieStoreId = defaultContainer.cookieStoreId;
const defaultIsNoContainer = defaultCookieStoreId === NO_CONTAINER.cookieStoreId;
const tabHasContainer = currentTab.cookieStoreId !== NO_CONTAINER.cookieStoreId;
const tabInDifferentContainer = currentTab.cookieStoreId !== defaultCookieStoreId;
const openInNoContainer = defaultIsNoContainer && tabHasContainer;
if ((tabInDifferentContainer && !openInNoContainer) || openInNoContainer) {
console.debug('Opening', url, 'in default container', defaultCookieStoreId, defaultContainer.name);
return createTab(
url,
currentTab.index + 1, currentTab.id,
currentTab.openerTabId,
defaultCookieStoreId);
}
}
return {};

const openerTabId = currentTab.openerTabId;
if (hostIdentity.cookieStoreId === NO_CONTAINER.cookieStoreId && tabIdentity) {
return createTab(url, currentTab.index + 1, currentTab.id, openerTabId);
}
}

if (hostIdentity.cookieStoreId !== currentTab.cookieStoreId && hostIdentity.cookieStoreId !== NO_CONTAINER.cookieStoreId) {
return createTab(url, currentTab.index + 1, currentTab.id, openerTabId, hostIdentity.cookieStoreId);
}
const openerTabId = currentTab.openerTabId;
if (hostIdentity.cookieStoreId === NO_CONTAINER.cookieStoreId && tabIdentity) {
return createTab(url, currentTab.index + 1, currentTab.id, openerTabId);
}

if (hostIdentity.cookieStoreId !== currentTab.cookieStoreId && hostIdentity.cookieStoreId !== NO_CONTAINER.cookieStoreId) {
return createTab(url, currentTab.index + 1, currentTab.id, openerTabId, hostIdentity.cookieStoreId);
}


return {};

return {};
});
}

export const webRequestListener = (requestDetails) => {
Expand All @@ -81,6 +122,5 @@ export const tabUpdatedListener = (tabId, changeInfo) => {
if (!changeInfo.url) {
return;
}
console.log(tabId, 'url changed', changeInfo.url);
return handle(changeInfo.url, tabId);
};
Loading