Files
tubestation/addon-sdk/source/lib/sdk/ui/sidebar.js
Kris Maglione 9f593b06aa Bug 1314861: Lazily load UI modules, as much as possible. r=rpl
MozReview-Commit-ID: JdHHw639Lwv
2017-04-07 16:28:42 -07:00

305 lines
10 KiB
JavaScript

/* 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/. */
'use strict';
module.metadata = {
'stability': 'experimental',
'engines': {
'Firefox': '*'
}
};
const { Class } = require('../core/heritage');
const { merge } = require('../util/object');
const { Disposable } = require('../core/disposable');
lazyRequire(this, '../event/core', "off", "emit", "setListeners");
const { EventTarget } = require('../event/target');
lazyRequire(this, '../url', "URL");
lazyRequire(this, '../self', { "id": "addonID" }, "data");
lazyRequire(this, '../deprecated/window-utils', 'WindowTracker');
lazyRequire(this, './sidebar/utils', "isShowing");
lazyRequire(this, '../window/utils', "isBrowser", "getMostRecentBrowserWindow", "windows", "isWindowPrivate");
const { ns } = require('../core/namespace');
lazyRequire(this, '../util/array', { "remove": "removeFromArray" });
lazyRequire(this, './sidebar/actions', "show", "hide", "toggle");
lazyRequire(this, '../deprecated/sync-worker', "Worker");
const { contract: sidebarContract } = require('./sidebar/contract');
lazyRequire(this, './sidebar/view', "create", "dispose", "updateTitle", "updateURL", "isSidebarShowing", "showSidebar", "hideSidebar");
lazyRequire(this, '../core/promise', "defer");
lazyRequire(this, './sidebar/namespace', "models", "views", "viewsFor", "modelFor");
lazyRequire(this, '../url', "isLocalURL");
const { ensure } = require('../system/unload');
lazyRequire(this, './id', "identify");
lazyRequire(this, '../util/uuid', "uuid");
lazyRequire(this, '../view/core', "viewFor");
const resolveURL = (url) => url ? data.url(url) : url;
const sidebarNS = ns();
const WEB_PANEL_BROWSER_ID = 'web-panels-browser';
const Sidebar = Class({
implements: [ Disposable ],
extends: EventTarget,
setup: function(options) {
// inital validation for the model information
let model = sidebarContract(options);
// save the model information
models.set(this, model);
// generate an id if one was not provided
model.id = model.id || addonID + '-' + uuid();
// further validation for the title and url
validateTitleAndURLCombo({}, this.title, this.url);
const self = this;
const internals = sidebarNS(self);
const windowNS = internals.windowNS = ns();
// see bug https://bugzilla.mozilla.org/show_bug.cgi?id=886148
ensure(this, 'destroy');
setListeners(this, options);
let bars = [];
internals.tracker = WindowTracker({
onTrack: function(window) {
if (!isBrowser(window))
return;
let sidebar = window.document.getElementById('sidebar');
let sidebarBox = window.document.getElementById('sidebar-box');
let bar = create(window, {
id: self.id,
title: self.title,
sidebarurl: self.url
});
bars.push(bar);
windowNS(window).bar = bar;
bar.addEventListener('command', function() {
if (isSidebarShowing(window, self)) {
hideSidebar(window, self).catch(() => {});
return;
}
showSidebar(window, self);
});
function onSidebarLoad() {
// check if the sidebar is ready
let isReady = sidebar.docShell && sidebar.contentDocument;
if (!isReady)
return;
// check if it is a web panel
let panelBrowser = sidebar.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
if (!panelBrowser) {
bar.removeAttribute('checked');
return;
}
let sbTitle = window.document.getElementById('sidebar-title');
function onWebPanelSidebarCreated() {
if (panelBrowser.contentWindow.location != resolveURL(model.url) ||
sbTitle.value != model.title) {
return;
}
let worker = windowNS(window).worker = Worker({
window: panelBrowser.contentWindow,
injectInDocument: true
});
function onWebPanelSidebarUnload() {
windowNS(window).onWebPanelSidebarUnload = null;
// uncheck the associated menuitem
bar.setAttribute('checked', 'false');
emit(self, 'hide', {});
emit(self, 'detach', worker);
windowNS(window).worker = null;
}
windowNS(window).onWebPanelSidebarUnload = onWebPanelSidebarUnload;
panelBrowser.contentWindow.addEventListener('unload', onWebPanelSidebarUnload, true);
// check the associated menuitem
bar.setAttribute('checked', 'true');
function onWebPanelSidebarReady() {
panelBrowser.contentWindow.removeEventListener('DOMContentLoaded', onWebPanelSidebarReady);
windowNS(window).onWebPanelSidebarReady = null;
emit(self, 'ready', worker);
}
windowNS(window).onWebPanelSidebarReady = onWebPanelSidebarReady;
panelBrowser.contentWindow.addEventListener('DOMContentLoaded', onWebPanelSidebarReady);
function onWebPanelSidebarLoad() {
panelBrowser.contentWindow.removeEventListener('load', onWebPanelSidebarLoad, true);
windowNS(window).onWebPanelSidebarLoad = null;
// TODO: decide if returning worker is acceptable..
//emit(self, 'show', { worker: worker });
emit(self, 'show', {});
}
windowNS(window).onWebPanelSidebarLoad = onWebPanelSidebarLoad;
panelBrowser.contentWindow.addEventListener('load', onWebPanelSidebarLoad, true);
emit(self, 'attach', worker);
}
windowNS(window).onWebPanelSidebarCreated = onWebPanelSidebarCreated;
panelBrowser.addEventListener('DOMWindowCreated', onWebPanelSidebarCreated, true);
}
windowNS(window).onSidebarLoad = onSidebarLoad;
sidebar.addEventListener('load', onSidebarLoad, true); // removed properly
},
onUntrack: function(window) {
if (!isBrowser(window))
return;
// hide the sidebar if it is showing
hideSidebar(window, self).catch(() => {});
// kill the menu item
let { bar } = windowNS(window);
if (bar) {
removeFromArray(viewsFor(self), bar);
dispose(bar);
}
// kill listeners
let sidebar = window.document.getElementById('sidebar');
if (windowNS(window).onSidebarLoad) {
sidebar && sidebar.removeEventListener('load', windowNS(window).onSidebarLoad, true)
windowNS(window).onSidebarLoad = null;
}
let panelBrowser = sidebar && sidebar.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
if (windowNS(window).onWebPanelSidebarCreated) {
panelBrowser && panelBrowser.removeEventListener('DOMWindowCreated', windowNS(window).onWebPanelSidebarCreated, true);
windowNS(window).onWebPanelSidebarCreated = null;
}
if (windowNS(window).onWebPanelSidebarReady) {
panelBrowser && panelBrowser.contentWindow.removeEventListener('DOMContentLoaded', windowNS(window).onWebPanelSidebarReady);
windowNS(window).onWebPanelSidebarReady = null;
}
if (windowNS(window).onWebPanelSidebarLoad) {
panelBrowser && panelBrowser.contentWindow.removeEventListener('load', windowNS(window).onWebPanelSidebarLoad, true);
windowNS(window).onWebPanelSidebarLoad = null;
}
if (windowNS(window).onWebPanelSidebarUnload) {
panelBrowser && panelBrowser.contentWindow.removeEventListener('unload', windowNS(window).onWebPanelSidebarUnload, true);
windowNS(window).onWebPanelSidebarUnload();
}
}
});
views.set(this, bars);
},
get id() {
return (modelFor(this) || {}).id;
},
get title() {
return (modelFor(this) || {}).title;
},
set title(v) {
// destroyed?
if (!modelFor(this))
return;
// validation
if (typeof v != 'string')
throw Error('title must be a string');
validateTitleAndURLCombo(this, v, this.url);
// do update
updateTitle(this, v);
return modelFor(this).title = v;
},
get url() {
return (modelFor(this) || {}).url;
},
set url(v) {
// destroyed?
if (!modelFor(this))
return;
// validation
if (!isLocalURL(v))
throw Error('the url must be a valid local url');
validateTitleAndURLCombo(this, this.title, v);
// do update
updateURL(this, v);
modelFor(this).url = v;
},
show: function(window) {
return showSidebar(viewFor(window), this);
},
hide: function(window) {
return hideSidebar(viewFor(window), this);
},
dispose: function() {
const internals = sidebarNS(this);
off(this);
// stop tracking windows
if (internals.tracker) {
internals.tracker.unload();
}
internals.tracker = null;
internals.windowNS = null;
views.delete(this);
models.delete(this);
}
});
exports.Sidebar = Sidebar;
function validateTitleAndURLCombo(sidebar, title, url) {
url = resolveURL(url);
if (sidebar.title == title && sidebar.url == url) {
return false;
}
for (let window of windows(null, { includePrivate: true })) {
let sidebar = window.document.querySelector('menuitem[sidebarurl="' + url + '"][label="' + title + '"]');
if (sidebar) {
throw Error('The provided title and url combination is invalid (already used).');
}
}
return false;
}
isShowing.define(Sidebar, isSidebarShowing.bind(null, null));
show.define(Sidebar, showSidebar.bind(null, null));
hide.define(Sidebar, hideSidebar.bind(null, null));
identify.define(Sidebar, function(sidebar) {
return sidebar.id;
});
function toggleSidebar(window, sidebar) {
// TODO: make sure this is not private
window = window || getMostRecentBrowserWindow();
if (isSidebarShowing(window, sidebar)) {
return hideSidebar(window, sidebar);
}
return showSidebar(window, sidebar);
}
toggle.define(Sidebar, toggleSidebar.bind(null, null));