commit
3f31f17781
4 changed files with 448 additions and 102 deletions
9
data/filters.svg
Normal file
9
data/filters.svg
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<filter id="fill">
|
||||||
|
<feComposite in="FillPaint" in2="SourceGraphic" operator="in"/>
|
||||||
|
</filter>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 400 B |
|
@ -1,13 +1,3 @@
|
||||||
/* containers experiment */
|
|
||||||
.tabbrowser-tab[usercontextid] {
|
|
||||||
background-image: linear-gradient(to right, var(--identity-tab-color) 0%, var(--identity-tab-color) 100%) !important;
|
|
||||||
background-size: var(--identity-stroke-background-size) !important;
|
|
||||||
background-repeat: no-repeat !important;
|
|
||||||
background-position: 0 29px !important;
|
|
||||||
--identity-stroke-background-size: auto 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* set defaults for ids as currently we don't set identity color */
|
|
||||||
[usercontextid="1"],
|
[usercontextid="1"],
|
||||||
[data-identity-color="blue"] {
|
[data-identity-color="blue"] {
|
||||||
--identity-tab-color: #37adff;
|
--identity-tab-color: #37adff;
|
||||||
|
@ -52,6 +42,73 @@
|
||||||
--identity-icon-color: #af51f5;
|
--identity-icon-color: #af51f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-identity-icon="fingerprint"] {
|
||||||
|
/*--identity-icon: url("chrome://browser/content/usercontext.svg#fingerprint"); */
|
||||||
|
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#fingerprint");
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-identity-icon="briefcase"] {
|
||||||
|
/* --identity-icon: url("chrome://browser/content/usercontext.svg#briefcase"); */
|
||||||
|
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#briefcase");
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-identity-icon="dollar"] {
|
||||||
|
/* --identity-icon: url("chrome://browser/content/usercontext.svg#dollar"); */
|
||||||
|
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#dollar");
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-identity-icon="cart"] {
|
||||||
|
/* --identity-icon: url("chrome://browser/content/usercontext.svg#cart"); */
|
||||||
|
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#cart");
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-identity-icon="circle"] {
|
||||||
|
/* --identity-icon: url("chrome://browser/content/usercontext.svg#circle"); */
|
||||||
|
--identity-icon: url("resource://testpilot-containers/data/usercontext.svg#circle");
|
||||||
|
}
|
||||||
|
|
||||||
|
#userContext-indicator {
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#userContext-label {
|
||||||
|
margin-inline-end: 3px;
|
||||||
|
color: var(--identity-tab-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#userContext-icons {
|
||||||
|
-moz-box-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabbrowser-tab[usercontextid] {
|
||||||
|
background-image: linear-gradient(to right, transparent 20%, var(--identity-tab-color) 30%, var(--identity-tab-color) 70%, transparent 80%);
|
||||||
|
background-size: auto 2px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userContext-icon,
|
||||||
|
.menuitem-iconic[data-usercontextid] > .menu-iconic-left > .menu-iconic-icon,
|
||||||
|
.subviewbutton[usercontextid] > .toolbarbutton-icon,
|
||||||
|
#userContext-indicator {
|
||||||
|
background-image: var(--identity-icon);
|
||||||
|
filter: url(/img/filters.svg#fill);
|
||||||
|
filter: url(resource://testpilot-containers/data/filters.svg#fill);
|
||||||
|
fill: var(--identity-icon-color);
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* containers experiment */
|
||||||
|
.tabbrowser-tab[usercontextid] {
|
||||||
|
background-image: linear-gradient(to right, var(--identity-tab-color) 0%, var(--identity-tab-color) 100%) !important;
|
||||||
|
background-size: var(--identity-stroke-background-size) !important;
|
||||||
|
background-repeat: no-repeat !important;
|
||||||
|
background-position: 0 29px !important;
|
||||||
|
--identity-stroke-background-size: auto 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.tabbrowser-tab[usercontextid] .tab-background-start:not([selected="true"]) {
|
.tabbrowser-tab[usercontextid] .tab-background-start:not([selected="true"]) {
|
||||||
background-image: linear-gradient(to left, var(--identity-tab-color) 0%, var(--identity-tab-color) 50%, transparent 50%, transparent 100%);
|
background-image: linear-gradient(to left, var(--identity-tab-color) 0%, var(--identity-tab-color) 50%, transparent 50%, transparent 100%);
|
||||||
background-position: 0 28px;
|
background-position: 0 28px;
|
||||||
|
@ -114,3 +171,48 @@
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
/* end containers experiment */
|
/* end containers experiment */
|
||||||
|
|
||||||
|
.tabs-newtab-button .toolbarbutton-icon[type="menu"] {
|
||||||
|
margin-inline-end: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-newtab-button .toolbarbutton-menu-dropmarker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-tab-overlay {
|
||||||
|
visibility: visible;
|
||||||
|
block-size: 200px;
|
||||||
|
inline-size: auto;
|
||||||
|
display: block;
|
||||||
|
background: transparent;
|
||||||
|
position: absolute;
|
||||||
|
-moz-appearance: none;
|
||||||
|
offset-block-start: 29px;
|
||||||
|
}
|
||||||
|
#new-tab-overlay[hidden=true] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-tab-overlay menuitem {
|
||||||
|
background: white;
|
||||||
|
margin-block-end: 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
-moz-appearance: none;
|
||||||
|
color: #4b4b4b;
|
||||||
|
padding: 6px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
box-shadow: 3px 7px 7px #0006;
|
||||||
|
--icon-size: 26px;
|
||||||
|
/* Limited width to 8chars roughly */
|
||||||
|
inline-size: calc(calc(8*0.68em) + var(--icon-size) + 3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-tab-overlay .menuitem-iconic[data-usercontextid] > .menu-iconic-left > .menu-iconic-icon {
|
||||||
|
block-height: var(--icon-size);
|
||||||
|
block-width: var(--icon-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuitem-iconic[data-usercontextid] > .menu-iconic-left {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
392
index.js
392
index.js
|
@ -4,9 +4,31 @@
|
||||||
|
|
||||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||||
|
|
||||||
|
const HIDE_MENU_TIMEOUT = 1000;
|
||||||
|
|
||||||
|
const IDENTITY_COLORS = [
|
||||||
|
{ name: "blue", color: "#00a7e0" },
|
||||||
|
{ name: "turquoise", color: "#01bdad" },
|
||||||
|
{ name: "green", color: "#7dc14c" },
|
||||||
|
{ name: "yellow", color: "#ffcb00" },
|
||||||
|
{ name: "orange", color: "#f89c24" },
|
||||||
|
{ name: "red", color: "#d92215" },
|
||||||
|
{ name: "pink", color: "#ee5195" },
|
||||||
|
{ name: "purple", color: "#7a2f7a" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const IDENTITY_ICONS = [
|
||||||
|
{ name: "fingerprint", image: "chrome://browser/skin/usercontext/personal.svg" },
|
||||||
|
{ name: "briefcase", image: "chrome://browser/skin/usercontext/work.svg" },
|
||||||
|
{ name: "dollar", image: "chrome://browser/skin/usercontext/banking.svg" },
|
||||||
|
{ name: "cart", image: "chrome://browser/skin/usercontext/shopping.svg" },
|
||||||
|
{ name: "cirlce", image: "" }, // this doesn't exist in m-b
|
||||||
|
];
|
||||||
|
|
||||||
const { attachTo } = require("sdk/content/mod");
|
const { attachTo } = require("sdk/content/mod");
|
||||||
const { ContextualIdentityService } = require("resource://gre/modules/ContextualIdentityService.jsm");
|
const { ContextualIdentityService } = require("resource://gre/modules/ContextualIdentityService.jsm");
|
||||||
const { getFavicon } = require("sdk/places/favicon");
|
const { getFavicon } = require("sdk/places/favicon");
|
||||||
|
const { modelFor } = require("sdk/model/core");
|
||||||
const self = require("sdk/self");
|
const self = require("sdk/self");
|
||||||
const { Style } = require("sdk/stylesheet/style");
|
const { Style } = require("sdk/stylesheet/style");
|
||||||
const tabs = require("sdk/tabs");
|
const tabs = require("sdk/tabs");
|
||||||
|
@ -16,10 +38,12 @@ const webExtension = require("sdk/webextension");
|
||||||
const windows = require("sdk/windows");
|
const windows = require("sdk/windows");
|
||||||
const windowUtils = require("sdk/window/utils");
|
const windowUtils = require("sdk/window/utils");
|
||||||
|
|
||||||
const IDENTITY_COLORS = ["blue", "turquoise", "green", "yellow", "orange", "red", "pink", "purple"];
|
// ----------------------------------------------------------------------------
|
||||||
|
// ContainerService
|
||||||
|
|
||||||
const ContainerService = {
|
const ContainerService = {
|
||||||
_identitiesState: {},
|
_identitiesState: {},
|
||||||
|
_windowMap: {},
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// Enabling preferences
|
// Enabling preferences
|
||||||
|
@ -76,6 +100,7 @@ const ContainerService = {
|
||||||
if (userContextId) {
|
if (userContextId) {
|
||||||
++this._identitiesState[userContextId].openTabs;
|
++this._identitiesState[userContextId].openTabs;
|
||||||
}
|
}
|
||||||
|
this._hideAllPanels();
|
||||||
});
|
});
|
||||||
|
|
||||||
tabs.on("close", tab => {
|
tabs.on("close", tab => {
|
||||||
|
@ -83,16 +108,24 @@ const ContainerService = {
|
||||||
if (userContextId && this._identitiesState[userContextId].openTabs) {
|
if (userContextId && this._identitiesState[userContextId].openTabs) {
|
||||||
--this._identitiesState[userContextId].openTabs;
|
--this._identitiesState[userContextId].openTabs;
|
||||||
}
|
}
|
||||||
|
this._hideAllPanels();
|
||||||
|
});
|
||||||
|
|
||||||
|
tabs.on("activate", tab => {
|
||||||
|
this._hideAllPanels();
|
||||||
|
this._restyleTab(tab).catch(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Modify CSS and other stuff for each window.
|
// Modify CSS and other stuff for each window.
|
||||||
|
|
||||||
for (let window of windows.browserWindows) { // eslint-disable-line prefer-const
|
this.configureWindows().catch(() => {});
|
||||||
this.configureWindow(viewFor(window));
|
|
||||||
}
|
|
||||||
|
|
||||||
windows.browserWindows.on("open", window => {
|
windows.browserWindows.on("open", window => {
|
||||||
this.configureWindow(viewFor(window));
|
this.configureWindow(viewFor(window)).catch(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
windows.browserWindows.on("close", window => {
|
||||||
|
this.closeWindow(viewFor(window));
|
||||||
});
|
});
|
||||||
|
|
||||||
// WebExtension startup
|
// WebExtension startup
|
||||||
|
@ -111,51 +144,61 @@ const ContainerService = {
|
||||||
// utility methods
|
// utility methods
|
||||||
|
|
||||||
_convert(identity) {
|
_convert(identity) {
|
||||||
// In FF 50-51, the icon is the full path, in 52 and following
|
// Let's convert the known colors to their color names.
|
||||||
// releases, we have IDs to be used with a svg file. In this function
|
|
||||||
// we map URLs to svg IDs.
|
|
||||||
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 (IDENTITY_COLORS.indexOf(identity.color) !== -1) {
|
|
||||||
color = identity.color;
|
|
||||||
} else {
|
|
||||||
color = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: ContextualIdentityService.getUserContextLabel(identity.userContextId),
|
name: ContextualIdentityService.getUserContextLabel(identity.userContextId),
|
||||||
image,
|
image: this._fromIconToName(identity.icon),
|
||||||
color,
|
color: this._fromColorToName(identity.color),
|
||||||
userContextId: identity.userContextId,
|
userContextId: identity.userContextId,
|
||||||
hasHiddenTabs: !!this._identitiesState[identity.userContextId].hiddenTabUrls.length,
|
hasHiddenTabs: !!this._identitiesState[identity.userContextId].hiddenTabUrls.length,
|
||||||
hasOpenTabs: !!this._identitiesState[identity.userContextId].openTabs
|
hasOpenTabs: !!this._identitiesState[identity.userContextId].openTabs
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Helper methods for converting colors to names and names to colors.
|
||||||
|
|
||||||
|
_fromNameToColor(name) {
|
||||||
|
return this._fromNameOrColor(name, "color");
|
||||||
|
},
|
||||||
|
|
||||||
|
_fromColorToName(color) {
|
||||||
|
return this._fromNameOrColor(color, "name");
|
||||||
|
},
|
||||||
|
|
||||||
|
_fromNameOrColor(what, attribute) {
|
||||||
|
for (let color of IDENTITY_COLORS) { // eslint-disable-line prefer-const
|
||||||
|
if (what === color.color || what === color.name) {
|
||||||
|
return color[attribute];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper methods for converting icons to names and names to icons.
|
||||||
|
|
||||||
|
_fromNameToIcon(name) {
|
||||||
|
return this._fromNameOrIcon(name, "image", "");
|
||||||
|
},
|
||||||
|
|
||||||
|
_fromIconToName(icon) {
|
||||||
|
return this._fromNameOrIcon(icon, "name", "circle");
|
||||||
|
},
|
||||||
|
|
||||||
|
_fromNameOrIcon(what, attribute, defaultValue) {
|
||||||
|
for (let icon of IDENTITY_ICONS) { // eslint-disable-line prefer-const
|
||||||
|
if (what === icon.image || what === icon.name) {
|
||||||
|
return icon[attribute];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tab Helpers
|
||||||
|
|
||||||
_getUserContextIdFromTab(tab) {
|
_getUserContextIdFromTab(tab) {
|
||||||
return parseInt(viewFor(tab).getAttribute("usercontextid") || 0, 10);
|
return parseInt(viewFor(tab).getAttribute("usercontextid") || 0, 10);
|
||||||
},
|
},
|
||||||
|
@ -418,15 +461,21 @@ const ContainerService = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: icon and color conversion based on FF version.
|
const color = this._fromNameToColor(args.color);
|
||||||
const identity = ContextualIdentityService.create(args.name, args.icon, args.color);
|
const icon = this._fromNameToIcon(args.icon);
|
||||||
|
|
||||||
|
const identity = ContextualIdentityService.create(args.name, icon, color);
|
||||||
|
|
||||||
this._identitiesState[identity.userContextId] = {
|
this._identitiesState[identity.userContextId] = {
|
||||||
hiddenTabUrls: [],
|
hiddenTabUrls: [],
|
||||||
openTabs: 0
|
openTabs: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
return Promise.resolve(this._convert(identity));
|
this._refreshNeeded().then(() => {
|
||||||
|
return this._convert(identity);
|
||||||
|
}).catch(() => {
|
||||||
|
return this._convert(identity);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateIdentity(args) {
|
updateIdentity(args) {
|
||||||
|
@ -441,12 +490,18 @@ const ContainerService = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: icon and color conversion based on FF version.
|
const color = this._fromNameToColor(identity.color);
|
||||||
// FIXME: color/name update propagation
|
const icon = this._fromNameToIcon(identity.icon);
|
||||||
return Promise.resolve(ContextualIdentityService.update(args.userContextId,
|
|
||||||
identity.name,
|
const updated = ContextualIdentityService.update(args.userContextId,
|
||||||
identity.icon,
|
identity.name,
|
||||||
identity.color));
|
icon, color);
|
||||||
|
|
||||||
|
this._refreshNeeded().then(() => {
|
||||||
|
return updated;
|
||||||
|
}).catch(() => {
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
removeIdentity(args) {
|
removeIdentity(args) {
|
||||||
|
@ -458,47 +513,226 @@ const ContainerService = {
|
||||||
tab.close();
|
tab.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.resolve(ContextualIdentityService.remove(args.userContextId));
|
const removed = ContextualIdentityService.remove(args.userContextId);
|
||||||
|
|
||||||
|
this._refreshNeeded().then(() => {
|
||||||
|
return removed;
|
||||||
|
}).catch(() => {
|
||||||
|
return removed;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Styling the window
|
// Styling the window
|
||||||
|
|
||||||
configureWindow(window) {
|
configureWindows() {
|
||||||
const tabsElement = window.document.getElementById("tabbrowser-tabs");
|
const promises = [];
|
||||||
const button = window.document.getAnonymousElementByAttribute(tabsElement, "anonid", "tabs-newtab-button");
|
for (let window of windows.browserWindows) { // eslint-disable-line prefer-const
|
||||||
|
promises.push(this.configureWindow(viewFor(window)));
|
||||||
|
}
|
||||||
|
return Promise.all(promises);
|
||||||
|
},
|
||||||
|
|
||||||
while (button.firstChild) {
|
configureWindow(window) {
|
||||||
button.removeChild(button.firstChild);
|
const id = windowUtils.getInnerId(window);
|
||||||
|
if (!(id in this._windowMap)) {
|
||||||
|
this._windowMap[id] = new ContainerWindow(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
button.setAttribute("type", "menu");
|
return this._windowMap[id].configure();
|
||||||
const popup = window.document.createElementNS(XUL_NS, "menupopup");
|
},
|
||||||
|
|
||||||
popup.setAttribute("anonid", "newtab-popup");
|
closeWindow(window) {
|
||||||
popup.className = "new-tab-popup";
|
const id = windowUtils.getInnerId(window);
|
||||||
popup.setAttribute("position", "after_end");
|
delete this._windowMap[id];
|
||||||
|
},
|
||||||
|
|
||||||
ContextualIdentityService.getIdentities().forEach(identity => {
|
_refreshNeeded() {
|
||||||
identity = this._convert(identity);
|
return this.configureWindows();
|
||||||
|
},
|
||||||
|
|
||||||
const menuItem = window.document.createElementNS(XUL_NS, "menuitem");
|
_hideAllPanels() {
|
||||||
menuItem.setAttribute("class", "menuitem-iconic");
|
for (let id in this._windowMap) { // eslint-disable-line prefer-const
|
||||||
menuItem.setAttribute("label", identity.name);
|
this._windowMap[id].hidePanel();
|
||||||
menuItem.setAttribute("image", self.data.url("usercontext.svg") + "#" + identity.image);
|
}
|
||||||
|
},
|
||||||
|
|
||||||
menuItem.addEventListener("command", (event) => {
|
_restyleTab(tab) {
|
||||||
this.openTab({userContextId: identity.userContextId});
|
if (!tab) {
|
||||||
event.stopPropagation();
|
return Promise.resolve(null);
|
||||||
});
|
}
|
||||||
|
|
||||||
popup.appendChild(menuItem);
|
const userContextId = ContainerService._getUserContextIdFromTab(tab);
|
||||||
|
return ContainerService.getIdentity({userContextId}).then(identity => {
|
||||||
|
if (!identity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hbox = viewFor(tab.window).document.getElementById("userContext-icons");
|
||||||
|
hbox.setAttribute("data-identity-color", identity.color);
|
||||||
|
|
||||||
|
const label = viewFor(tab.window).document.getElementById("userContext-label");
|
||||||
|
label.setAttribute("value", identity.name);
|
||||||
|
label.style.color = ContainerService._fromNameToColor(identity.color);
|
||||||
|
|
||||||
|
const indicator = viewFor(tab.window).document.getElementById("userContext-indicator");
|
||||||
|
indicator.setAttribute("data-identity-icon", identity.image);
|
||||||
|
indicator.style.listStyleImage = "";
|
||||||
});
|
});
|
||||||
|
},
|
||||||
button.appendChild(popup);
|
|
||||||
const style = Style({ uri: self.data.url("chrome.css") });
|
|
||||||
|
|
||||||
attachTo(style, viewFor(window));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// ContainerWindow
|
||||||
|
|
||||||
|
// This object is used to configure a single window.
|
||||||
|
function ContainerWindow(window) {
|
||||||
|
this._init(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContainerWindow.prototype = {
|
||||||
|
_window: null,
|
||||||
|
_panelElement: null,
|
||||||
|
_timeoutId: 0,
|
||||||
|
|
||||||
|
_init(window) {
|
||||||
|
this._window = window;
|
||||||
|
const style = Style({ uri: self.data.url("usercontext.css") });
|
||||||
|
attachTo(style, this._window);
|
||||||
|
},
|
||||||
|
|
||||||
|
configure() {
|
||||||
|
return Promise.all([
|
||||||
|
this._configurePlusButtonMenu(),
|
||||||
|
this._configureActiveTab(),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
_configurePlusButtonMenu() {
|
||||||
|
const tabsElement = this._window.document.getElementById("tabbrowser-tabs");
|
||||||
|
|
||||||
|
const button = this._window.document.getAnonymousElementByAttribute(tabsElement, "anonid", "tabs-newtab-button");
|
||||||
|
|
||||||
|
// Let's remove the tooltip because it can go over our panel.
|
||||||
|
button.setAttribute("tooltip", "");
|
||||||
|
|
||||||
|
// Let's remove all the previous panels.
|
||||||
|
if (this._panelElement) {
|
||||||
|
this._panelElement.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._panelElement = this._window.document.createElementNS(XUL_NS, "panel");
|
||||||
|
this._panelElement.setAttribute("id", "new-tab-overlay");
|
||||||
|
button.after(this._panelElement);
|
||||||
|
this._panelElement.hidden = true;
|
||||||
|
|
||||||
|
this._repositionPopup();
|
||||||
|
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
this._panelElement.hidden = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
button.addEventListener("mouseover", () => {
|
||||||
|
this._repositionPopup();
|
||||||
|
this._panelElement.hidden = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
button.addEventListener("mouseout", () => {
|
||||||
|
this._createTimeout();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._panelElement.addEventListener("mouseout", (e) => {
|
||||||
|
if (e.target !== this._panelElement) {
|
||||||
|
this._createTimeout();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._repositionPopup();
|
||||||
|
});
|
||||||
|
|
||||||
|
return ContainerService.queryIdentities().then(identities => {
|
||||||
|
identities.forEach(identity => {
|
||||||
|
const menuItemElement = this._window.document.createElementNS(XUL_NS, "menuitem");
|
||||||
|
this._panelElement.appendChild(menuItemElement);
|
||||||
|
menuItemElement.className = "menuitem-iconic";
|
||||||
|
menuItemElement.setAttribute("label", identity.name);
|
||||||
|
menuItemElement.setAttribute("data-usercontextid", identity.userContextId);
|
||||||
|
menuItemElement.setAttribute("data-identity-icon", identity.image);
|
||||||
|
menuItemElement.setAttribute("data-identity-color", identity.color);
|
||||||
|
|
||||||
|
menuItemElement.addEventListener("command", e => {
|
||||||
|
ContainerService.openTab({userContextId: identity.userContextId});
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Command isn't working probably because I'm in a panel
|
||||||
|
menuItemElement.addEventListener("click", e => {
|
||||||
|
ContainerService.openTab({userContextId: identity.userContextId});
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
menuItemElement.addEventListener("mouseover", () => {
|
||||||
|
this._cleanTimeout();
|
||||||
|
});
|
||||||
|
|
||||||
|
menuItemElement.addEventListener("mouseout", () => {
|
||||||
|
this._createTimeout();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._panelElement.appendChild(menuItemElement);
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
this.hidePanel();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_configureActiveTab() {
|
||||||
|
const tab = modelFor(this._window).tabs.activeTab;
|
||||||
|
return ContainerService._restyleTab(tab);
|
||||||
|
},
|
||||||
|
|
||||||
|
// This function puts the popup in the correct place.
|
||||||
|
_repositionPopup() {
|
||||||
|
const tabsElement = this._window.document.getElementById("tabbrowser-tabs");
|
||||||
|
const button = this._window.document.getAnonymousElementByAttribute(tabsElement, "anonid", "tabs-newtab-button");
|
||||||
|
|
||||||
|
const size = button.getBoxQuads()[0];
|
||||||
|
const innerWindow = tabsElement.getBoxQuads()[0];
|
||||||
|
const panelElementWidth = 200;
|
||||||
|
|
||||||
|
// 1/4th of the way past the left hand side of the new tab button
|
||||||
|
// This seems to line up nicely with the left of the +
|
||||||
|
const offset = ((size.p3.x - size.p4.x) / 4);
|
||||||
|
let left = size.p4.x + offset;
|
||||||
|
if (left + panelElementWidth > innerWindow.p2.x) {
|
||||||
|
left -= panelElementWidth - offset;
|
||||||
|
}
|
||||||
|
this._panelElement.style.left = left + "px";
|
||||||
|
this._panelElement.style.top = size.p4.y + "px";
|
||||||
|
},
|
||||||
|
|
||||||
|
// This timer is used to hide the panel auto-magically if it's not used in
|
||||||
|
// the following X seconds. This is need to avoid the leaking of the panel
|
||||||
|
// when the mouse goes out of of the 'plus' button.
|
||||||
|
_createTimeout() {
|
||||||
|
this._cleanTimeout();
|
||||||
|
this._timeoutId = this._window.setTimeout(() => {
|
||||||
|
this.hidePanel();
|
||||||
|
this._timeoutId = 0;
|
||||||
|
}, HIDE_MENU_TIMEOUT);
|
||||||
|
},
|
||||||
|
|
||||||
|
_cleanTimeout() {
|
||||||
|
if (this._timeoutId) {
|
||||||
|
this._window.clearTimeout(this._timeoutId);
|
||||||
|
this._timeoutId = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hidePanel() {
|
||||||
|
this._cleanTimeout();
|
||||||
|
this._panelElement.hidden = true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Let's start :)
|
||||||
ContainerService.init();
|
ContainerService.init();
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
const CONTAINER_HIDE_SRC = "/img/container-hide.svg";
|
const CONTAINER_HIDE_SRC = "/img/container-hide.svg";
|
||||||
const CONTAINER_UNHIDE_SRC = "/img/container-unhide.svg";
|
const CONTAINER_UNHIDE_SRC = "/img/container-unhide.svg";
|
||||||
|
|
||||||
// TODO: Let's set it to false before releasing!!!
|
|
||||||
const DEBUG = true;
|
|
||||||
|
|
||||||
// List of panels
|
// List of panels
|
||||||
const P_ONBOARDING_1 = "onboarding1";
|
const P_ONBOARDING_1 = "onboarding1";
|
||||||
const P_ONBOARDING_2 = "onboarding2";
|
const P_ONBOARDING_2 = "onboarding2";
|
||||||
|
@ -17,12 +14,6 @@ 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";
|
||||||
|
|
||||||
function log(...args) {
|
|
||||||
if (DEBUG) {
|
|
||||||
console.log.call(console, ...args); // eslint-disable-line no-console
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This object controls all the panels, identities and many other things.
|
// This object controls all the panels, identities and many other things.
|
||||||
const Logic = {
|
const Logic = {
|
||||||
_identities: [],
|
_identities: [],
|
||||||
|
@ -204,7 +195,6 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
|
|
||||||
Logic.identities().forEach(identity => {
|
Logic.identities().forEach(identity => {
|
||||||
log("identities.forEach");
|
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
fragment.appendChild(tr);
|
fragment.appendChild(tr);
|
||||||
tr.classList.add("container-panel-row", "clickable");
|
tr.classList.add("container-panel-row", "clickable");
|
||||||
|
@ -314,7 +304,6 @@ Logic.registerPanel(P_CONTAINER_INFO, {
|
||||||
method: "getTabs",
|
method: "getTabs",
|
||||||
userContextId: identity.userContextId,
|
userContextId: identity.userContextId,
|
||||||
}).then(tabs => {
|
}).then(tabs => {
|
||||||
log("browser.runtime.sendMessage getTabs, tabs: ", tabs);
|
|
||||||
// For each one, let's create a new line.
|
// For each one, let's create a new line.
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
for (let tab of tabs) { // eslint-disable-line prefer-const
|
for (let tab of tabs) { // eslint-disable-line prefer-const
|
||||||
|
@ -431,8 +420,8 @@ Logic.registerPanel(P_CONTAINER_EDIT, {
|
||||||
method: identity.userContextId ? "updateIdentity" : "createIdentity",
|
method: identity.userContextId ? "updateIdentity" : "createIdentity",
|
||||||
userContextId: identity.userContextId || 0,
|
userContextId: identity.userContextId || 0,
|
||||||
name: document.getElementById("edit-container-panel-name-input").value || Logic.generateIdentityName(),
|
name: document.getElementById("edit-container-panel-name-input").value || Logic.generateIdentityName(),
|
||||||
icon: identity.image || "fingerprint",
|
icon: this._randomIcon(),
|
||||||
color: identity.color || "green",
|
color: this._randomColor(),
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return Logic.refreshIdentities();
|
return Logic.refreshIdentities();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
@ -452,6 +441,18 @@ Logic.registerPanel(P_CONTAINER_EDIT, {
|
||||||
|
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// These 2 methods are for debugging only. We should remove them when we
|
||||||
|
// finish the UI.
|
||||||
|
_randomIcon() {
|
||||||
|
const images = ["fingerprint", "briefcase", "dollar", "cart", "cirlce"];
|
||||||
|
return images[Math.floor(Math.random() * images.length)];
|
||||||
|
},
|
||||||
|
|
||||||
|
_randomColor() {
|
||||||
|
const colors = ["blue", "turquoise", "green", "yellow", "orange", "red", "pink", "purple" ];
|
||||||
|
return colors[Math.floor(Math.random() * colors.length)];
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// P_CONTAINER_DELETE: Delete a container.
|
// P_CONTAINER_DELETE: Delete a container.
|
||||||
|
|
Loading…
Add table
Reference in a new issue