Skip to content

Commit

Permalink
WIP - Auto send users to origins they have set to go to. Fixes mozill…
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanKingston committed Mar 30, 2017
1 parent 7de6ef5 commit b02be0b
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 4 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "testpilot-containers",
"title": "Containers Experiment",
"description": "Containers works by isolating cookie jars using separate origin-attributes defined visually by colored ‘Container Tabs’. This add-on is a modified version of the containers feature for Firefox Test Pilot.",
"version": "2.0.0",
"version": "2.1.0",
"author": "Andrea Marchesini, Luke Crouch and Jonathan Kingston",
"bugs": {
"url": "https://github.com/mozilla/testpilot-containers/issues"
Expand Down
223 changes: 223 additions & 0 deletions webextension/background.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,226 @@
const assignManager = {
CLOSEABLE_WINDOWS: new Set([
"about:startpage",
"about:newtab",
"about:home",
"about:blank"
]),
MENU_ASSIGN_ID: "open-in-this-container",
MENU_REMOVE_ID: "remove-open-in-this-container",
storageArea: {
area: browser.storage.local,

getSiteStoreKey(pageUrl) {
const url = new window.URL(pageUrl);
const storagePrefix = "siteContainerMap@@_";
return `${storagePrefix}${url.hostname}`;
},

get(pageUrl) {
const siteStoreKey = this.getSiteStoreKey(pageUrl);
return new Promise((resolve, reject) => {
this.area.get([siteStoreKey]).then((storageResponse) => {
if (storageResponse && siteStoreKey in storageResponse) {
resolve(storageResponse[siteStoreKey]);
}
resolve(null);
}).catch((e) => {
reject(e);
});
});
},

set(pageUrl, data) {
const siteStoreKey = this.getSiteStoreKey(pageUrl);
return this.area.set({
[siteStoreKey]: data
});
},

remove(pageUrl) {
const siteStoreKey = this.getSiteStoreKey(pageUrl);
return this.area.remove([siteStoreKey]);
},
},

init() {
browser.tabs.onActivated.addListener((info) => {
browser.tabs.get(info.tabId).then((tab) => {
this.calculateContextMenu(tab);
}).catch((e) => {
throw e;
});
});

browser.windows.onFocusChanged.addListener((windowId) => {
browser.tabs.query({active: true, windowId}).then((tabs) => {
if (tabs && tabs[0]) {
this.calculateContextMenu(tabs[0]);
}
}).catch((e) => {
throw e;
});
});

browser.runtime.onMessage.addListener((neverAskMessage) => {
const pageUrl = neverAskMessage.pageUrl;
if (neverAskMessage.neverAsk === true) {
// If we have existing data and for some reason it hasn't been deleted etc lets update it
this.storageArea.get(pageUrl).then((siteSettings) => {
if (siteSettings) {
siteSettings.neverAsk = true;
this.storageArea.set(pageUrl, siteSettings);
}
}).catch((e) => {
throw e;
});
}
});

browser.contextMenus.onClicked.addListener((info, tab) => {
const userContextId = this.getUserContextIdFromCookieStore(tab);
// Mapping ${URL(info.pageUrl).hostname} to ${userContextId}
if (userContextId) {
let actionName;
let storageAction;
if (info.menuItemId === this.MENU_ASSIGN_ID) {
actionName = "added";
storageAction = this.storageArea.set(info.pageUrl, {
userContextId,
neverAsk: false
});
} else {
actionName = "removed";
storageAction = this.storageArea.remove(info.pageUrl);
}
storageAction.then(() => {
const pageUrl = new window.URL(info.pageUrl);
browser.notifications.create({
type: "basic",
title: "Containers",
message: `Successfully ${actionName} site to always open in this container`,
iconUrl: browser.extension.getURL("/img/onboarding-1.png")
});
this.calculateContextMenu(tab);
}).catch((e) => {
throw e;
});
}
});

browser.webRequest.onBeforeRequest.addListener((options) => {
if (options.frameId !== 0 || options.tabId === -1) {
return {};
}
return Promise.all([
browser.tabs.get(options.tabId),
this.storageArea.get(options.url)
]).then(([tab, siteSettings]) => {
const userContextId = this.getUserContextIdFromCookieStore(tab);
if (!siteSettings
|| userContextId === siteSettings.userContextId
|| tab.incognito) {
return {};
}

this.reloadPageInContainer(options.url, siteSettings.userContextId, tab.index, siteSettings.neverAsk);
this.calculateContextMenu(tab);
// If the user just opened the tab, we can auto close it
if (this.CLOSEABLE_WINDOWS.has(tab.url)) {
browser.tabs.remove(tab.id);
}
return {
cancel: true,
};
}).catch((e) => {
throw e;
});
},{urls: ["<all_urls>"], types: ["main_frame"]}, ["blocking"]);

browser.webRequest.onCompleted.addListener((options) => {
if (options.frameId !== 0 || options.tabId === -1) {
return {};
}
browser.tabs.get(options.tabId).then((tab) => {
this.calculateContextMenu(tab);
}).catch((e) => {
throw e;
});
},{urls: ["<all_urls>"], types: ["main_frame"]});
},

getUserContextIdFromCookieStore(tab) {
if (!("cookieStoreId" in tab)) {
return false;
}
const cookieStore = tab.cookieStoreId;
const container = cookieStore.replace("firefox-container-", "");
if (container !== cookieStore) {
return container;
}
return false;
},

isTabPermittedAssign(tab) {
// Ensure we are not an important about url
// Ensure we are not in incognito mode
if (this.CLOSEABLE_WINDOWS.has(tab.url)
|| tab.incognito) {
return false
}
return true;
},

calculateContextMenu(tab) {
// 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
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1215376#c16
// We also can't change for always private mode
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1352102
const cookieStore = this.getUserContextIdFromCookieStore(tab);
browser.contextMenus.remove(this.MENU_ASSIGN_ID);
browser.contextMenus.remove(this.MENU_REMOVE_ID);
// Ensure we have a cookieStore to assign to
if (cookieStore
&& this.isTabPermittedAssign(tab)) {
this.storageArea.get(tab.url).then((siteSettings) => {
// ✓ This is to mitigate https://bugzilla.mozilla.org/show_bug.cgi?id=1351418
let prefix = " "; // Alignment of non breaking space, unknown why this requires so many spaces to align with the tick
let menuId = this.MENU_ASSIGN_ID;
if (siteSettings) {
prefix = "✓";
menuId = this.MENU_REMOVE_ID;
}
browser.contextMenus.create({
id: menuId,
title: `${prefix} Always Open in This Container`,
checked: true,
contexts: ["all"],
});
}).catch((e) => {
throw e;
});
}
},

reloadPageInContainer(url, userContextId, index, neverAsk = false) {
const loadPage = browser.extension.getURL("confirm-page.html");
// If the user has explicitly checked "Never Ask Again" on the warning page we will send them straight there
if (neverAsk) {
browser.tabs.create({url, cookieStoreId: `firefox-container-${userContextId}`, index});
} else {
const confirmUrl = `${loadPage}?url=${url}`;
browser.tabs.create({url: confirmUrl, cookieStoreId: `firefox-container-${userContextId}`, index}).then(() => {
// We don't want to sync this URL ever nor clutter the users history
browser.history.deleteUrl({url: confirmUrl});
}).catch((e) => {
throw e;
});
}
}
};

assignManager.init();

const themeManager = {
existingTheme: null,
Expand Down
33 changes: 33 additions & 0 deletions webextension/confirm-page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Containers confirm navigation</title>
<link xmlns="http://www.w3.org/1999/xhtml" rel="stylesheet" href="chrome://browser/skin/aboutNetError.css" type="text/css" media="all" />
<link rel="stylesheet" href="/css/confirm-page.css" />
</head>
<body>
<main>
<div class="title">
<h1 class="title-text">Hey, we heard you liked containers!</h1>
</div>
<form id="redirect-form">
<p>
A page told us to open:
</p>
<div id="redirect-url"></div>
<p>
You asked for <span id="redirect-site"></span> to always open in <span id="container-name">this</span> container.<br />
</p>
<br />
<br />
<input id="never-ask" type="checkbox" /><label for="never-ask">Never ask me this again for this site</label>
<br />
<div class="button-container">
<button id="confirm" class="button primary">Take me there</button>
</div>
</form>
</main>

<script src="js/confirm-page.js"></script>
</body>
</html>
33 changes: 33 additions & 0 deletions webextension/css/confirm-page.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* General Rules and Resets */
.title {
background-image: none;
}

main {
background: url(/img/onboarding-1.png) no-repeat;
background-position: -10px -15px;
background-size: 285px;
margin-inline-start: -285px;
padding-inline-start: 285px;
}

@media only screen and (max-width: 1300px) {
main {
background: none;
}

/* for a mid sized window we have enough for this but not our image */
.title {
background-image: url("chrome://global/skin/icons/info.svg");
}
}

html {
box-sizing: border-box;
font: message-box;
}

#redirect-url,
#redirect-origin {
font-weight: bold;
}
2 changes: 1 addition & 1 deletion webextension/css/popup.css
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ span ~ .panel-header-text {
.panel-footer {
align-items: center;
background: #efefef;
block-size: 55px;
block-size: 54px;
border-block-end: 1px solid #d8d8d8;
color: #000;
display: flex;
Expand Down
26 changes: 26 additions & 0 deletions webextension/js/confirm-page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const redirectUrl = new URL(window.location).searchParams.get("url");
document.getElementById("redirect-url").textContent = redirectUrl;
const redirectSite = new URL(redirectUrl).hostname;
document.getElementById("redirect-site").textContent = redirectSite;

document.getElementById("redirect-form").addEventListener("submit", (e) => {
e.preventDefault();
const neverAsk = document.getElementById("never-ask").checked;
// Sending neverAsk message to background to store for next time we see this process
if (neverAsk) {
browser.runtime.sendMessage({
neverAsk: true,
pageUrl: redirectUrl
}).then(() => {
redirect();
}).catch(() => {
// Can't really do much here user will have to click it again
});
}
redirect();
});

function redirect() {
const redirectUrl = document.getElementById("redirect-url").textContent;
document.location.replace(redirectUrl);
}
12 changes: 10 additions & 2 deletions webextension/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Containers Experiment",
"version": "2.0.0",
"version": "2.1.0",

"description": "Containers works by isolating cookie jars using separate origin-attributes defined visually by colored ‘Container Tabs’. This add-on is a modified version of the containers feature for Firefox Test Pilot.",
"icons": {
Expand All @@ -19,8 +19,16 @@
"homepage_url": "https://testpilot.firefox.com/",

"permissions": [
"<all_urls>",
"activeTab",
"cookies",
"tabs"
"contextMenus",
"history",
"notifications",
"storage",
"tabs",
"webRequestBlocking",
"webRequest"
],

"browser_action": {
Expand Down

0 comments on commit b02be0b

Please sign in to comment.