123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- /* 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');
- const { off, emit, setListeners } = require('../event/core');
- const { EventTarget } = require('../event/target');
- const { URL } = require('../url');
- const { add, remove, has, clear, iterator } = require('../lang/weak-set');
- const { id: addonID } = require('../self');
- const { WindowTracker } = require('../deprecated/window-utils');
- const { isShowing } = require('./sidebar/utils');
- const { isBrowser, getMostRecentBrowserWindow, windows, isWindowPrivate } = require('../window/utils');
- const { ns } = require('../core/namespace');
- const { remove: removeFromArray } = require('../util/array');
- const { show, hide, toggle } = require('./sidebar/actions');
- const { Worker } = require('../content/worker');
- const { contract: sidebarContract } = require('./sidebar/contract');
- const { create, dispose, updateTitle, updateURL, isSidebarShowing, showSidebar, hideSidebar } = require('./sidebar/view');
- const { defer } = require('../core/promise');
- const { models, views, viewsFor, modelFor } = require('./sidebar/namespace');
- const { isLocalURL } = require('../url');
- const { ensure } = require('../system/unload');
- const { identify } = require('./id');
- const { uuid } = require('../util/uuid');
- const sidebarNS = ns();
- const WEB_PANEL_BROWSER_ID = 'web-panels-browser';
- let sidebars = {};
- 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);
- return;
- }
- showSidebar(window, self);
- }, false);
- 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 != 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, false);
- windowNS(window).onWebPanelSidebarReady = null;
- emit(self, 'ready', worker);
- }
- windowNS(window).onWebPanelSidebarReady = onWebPanelSidebarReady;
- panelBrowser.contentWindow.addEventListener('DOMContentLoaded', onWebPanelSidebarReady, false);
- 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);
- // 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, false);
- 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);
- add(sidebars, this);
- },
- get id() (modelFor(this) || {}).id,
- get title() (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() (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() {
- return showSidebar(null, this);
- },
- hide: function() {
- return hideSidebar(null, this);
- },
- dispose: function() {
- const internals = sidebarNS(this);
- off(this);
- remove(sidebars, this);
- // stop tracking windows
- internals.tracker.unload();
- internals.tracker = null;
- internals.windowNS = null;
- views.delete(this);
- models.delete(this);
- }
- });
- exports.Sidebar = Sidebar;
- function validateTitleAndURLCombo(sidebar, title, 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));
|