diff options
Diffstat (limited to 'addon-sdk/source/lib/sdk/panel.js')
-rw-r--r-- | addon-sdk/source/lib/sdk/panel.js | 427 |
1 files changed, 0 insertions, 427 deletions
diff --git a/addon-sdk/source/lib/sdk/panel.js b/addon-sdk/source/lib/sdk/panel.js deleted file mode 100644 index 4b625799d..000000000 --- a/addon-sdk/source/lib/sdk/panel.js +++ /dev/null @@ -1,427 +0,0 @@ -/* 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"; - -// The panel module currently supports only Firefox and SeaMonkey. -// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps -module.metadata = { - "stability": "stable", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Cu, Ci } = require("chrome"); -const { setTimeout } = require('./timers'); -const { Class } = require("./core/heritage"); -const { merge } = require("./util/object"); -const { WorkerHost } = require("./content/utils"); -const { Worker } = require("./deprecated/sync-worker"); -const { Disposable } = require("./core/disposable"); -const { WeakReference } = require('./core/reference'); -const { contract: loaderContract } = require("./content/loader"); -const { contract } = require("./util/contract"); -const { on, off, emit, setListeners } = require("./event/core"); -const { EventTarget } = require("./event/target"); -const domPanel = require("./panel/utils"); -const { getDocShell } = require('./frame/utils'); -const { events } = require("./panel/events"); -const systemEvents = require("./system/events"); -const { filter, pipe, stripListeners } = require("./event/utils"); -const { getNodeView, getActiveView } = require("./view/core"); -const { isNil, isObject, isNumber } = require("./lang/type"); -const { getAttachEventType } = require("./content/utils"); -const { number, boolean, object } = require('./deprecated/api-utils'); -const { Style } = require("./stylesheet/style"); -const { attach, detach } = require("./content/mod"); - -var isRect = ({top, right, bottom, left}) => [top, right, bottom, left]. - some(value => isNumber(value) && !isNaN(value)); - -var isSDKObj = obj => obj instanceof Class; - -var rectContract = contract({ - top: number, - right: number, - bottom: number, - left: number -}); - -var position = { - is: object, - map: v => (isNil(v) || isSDKObj(v) || !isObject(v)) ? v : rectContract(v), - ok: v => isNil(v) || isSDKObj(v) || (isObject(v) && isRect(v)), - msg: 'The option "position" must be a SDK object registered as anchor; ' + - 'or an object with one or more of the following keys set to numeric ' + - 'values: top, right, bottom, left.' -} - -var displayContract = contract({ - width: number, - height: number, - focus: boolean, - position: position -}); - -var panelContract = contract(merge({ - // contentStyle* / contentScript* are sharing the same validation constraints, - // so they can be mostly reused, except for the messages. - contentStyle: merge(Object.create(loaderContract.rules.contentScript), { - msg: 'The `contentStyle` option must be a string or an array of strings.' - }), - contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), { - msg: 'The `contentStyleFile` option must be a local URL or an array of URLs' - }), - contextMenu: boolean, - allow: { - is: ['object', 'undefined', 'null'], - map: function (allow) { return { script: !allow || allow.script !== false }} - }, -}, displayContract.rules, loaderContract.rules)); - -function Allow(panel) { - return { - get script() { return getDocShell(viewFor(panel).backgroundFrame).allowJavascript; }, - set script(value) { return setScriptState(panel, value); }, - }; -} - -function setScriptState(panel, value) { - let view = viewFor(panel); - getDocShell(view.backgroundFrame).allowJavascript = value; - getDocShell(view.viewFrame).allowJavascript = value; - view.setAttribute("sdkscriptenabled", "" + value); -} - -function isDisposed(panel) { - return !views.has(panel); -} - -var panels = new WeakMap(); -var models = new WeakMap(); -var views = new WeakMap(); -var workers = new WeakMap(); -var styles = new WeakMap(); - -const viewFor = (panel) => views.get(panel); -const modelFor = (panel) => models.get(panel); -const panelFor = (view) => panels.get(view); -const workerFor = (panel) => workers.get(panel); -const styleFor = (panel) => styles.get(panel); - -function getPanelFromWeakRef(weakRef) { - if (!weakRef) { - return null; - } - let panel = weakRef.get(); - if (!panel) { - return null; - } - if (isDisposed(panel)) { - return null; - } - return panel; -} - -var SinglePanelManager = { - visiblePanel: null, - enqueuedPanel: null, - enqueuedPanelCallback: null, - // Calls |callback| with no arguments when the panel may be shown. - requestOpen: function(panelToOpen, callback) { - let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel); - if (currentPanel || SinglePanelManager.enqueuedPanel) { - SinglePanelManager.enqueuedPanel = Cu.getWeakReference(panelToOpen); - SinglePanelManager.enqueuedPanelCallback = callback; - if (currentPanel && currentPanel.isShowing) { - currentPanel.hide(); - } - } else { - SinglePanelManager.notifyPanelCanOpen(panelToOpen, callback); - } - }, - notifyPanelCanOpen: function(panel, callback) { - let view = viewFor(panel); - // Can't pass an arrow function as the event handler because we need to be - // able to call |removeEventListener| later. - view.addEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true); - view.addEventListener("popupshown", SinglePanelManager.onVisiblePanelShown, false); - SinglePanelManager.enqueuedPanel = null; - SinglePanelManager.enqueuedPanelCallback = null; - SinglePanelManager.visiblePanel = Cu.getWeakReference(panel); - callback(); - }, - onVisiblePanelShown: function(event) { - let panel = panelFor(event.target); - if (SinglePanelManager.enqueuedPanel) { - // Another panel started waiting for |panel| to close before |panel| was - // even done opening. - panel.hide(); - } - }, - onVisiblePanelHidden: function(event) { - let view = event.target; - let panel = panelFor(view); - let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel); - if (currentPanel && currentPanel != panel) { - return; - } - SinglePanelManager.visiblePanel = null; - view.removeEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true); - view.removeEventListener("popupshown", SinglePanelManager.onVisiblePanelShown, false); - let nextPanel = getPanelFromWeakRef(SinglePanelManager.enqueuedPanel); - let nextPanelCallback = SinglePanelManager.enqueuedPanelCallback; - if (nextPanel) { - SinglePanelManager.notifyPanelCanOpen(nextPanel, nextPanelCallback); - } - } -}; - -const Panel = Class({ - implements: [ - // Generate accessors for the validated properties that update model on - // set and return values from model on get. - panelContract.properties(modelFor), - EventTarget, - Disposable, - WeakReference - ], - extends: WorkerHost(workerFor), - setup: function setup(options) { - let model = merge({ - defaultWidth: 320, - defaultHeight: 240, - focus: true, - position: Object.freeze({}), - contextMenu: false - }, panelContract(options)); - model.ready = false; - models.set(this, model); - - if (model.contentStyle || model.contentStyleFile) { - styles.set(this, Style({ - uri: model.contentStyleFile, - source: model.contentStyle - })); - } - - // Setup view - let viewOptions = {allowJavascript: !model.allow || (model.allow.script !== false)}; - let view = domPanel.make(null, viewOptions); - panels.set(view, this); - views.set(this, view); - - // Load panel content. - domPanel.setURL(view, model.contentURL); - - // Allow context menu - domPanel.allowContextMenu(view, model.contextMenu); - - // Setup listeners. - setListeners(this, options); - let worker = new Worker(stripListeners(options)); - workers.set(this, worker); - - // pipe events from worker to a panel. - pipe(worker, this); - }, - dispose: function dispose() { - this.hide(); - off(this); - - workerFor(this).destroy(); - detach(styleFor(this)); - - domPanel.dispose(viewFor(this)); - - // Release circular reference between view and panel instance. This - // way view will be GC-ed. And panel as well once all the other refs - // will be removed from it. - views.delete(this); - }, - /* Public API: Panel.width */ - get width() { - return modelFor(this).width; - }, - set width(value) { - this.resize(value, this.height); - }, - /* Public API: Panel.height */ - get height() { - return modelFor(this).height; - }, - set height(value) { - this.resize(this.width, value); - }, - - /* Public API: Panel.focus */ - get focus() { - return modelFor(this).focus; - }, - - /* Public API: Panel.position */ - get position() { - return modelFor(this).position; - }, - - /* Public API: Panel.contextMenu */ - get contextMenu() { - return modelFor(this).contextMenu; - }, - set contextMenu(allow) { - let model = modelFor(this); - model.contextMenu = panelContract({ contextMenu: allow }).contextMenu; - domPanel.allowContextMenu(viewFor(this), model.contextMenu); - }, - - get contentURL() { - return modelFor(this).contentURL; - }, - set contentURL(value) { - let model = modelFor(this); - model.contentURL = panelContract({ contentURL: value }).contentURL; - domPanel.setURL(viewFor(this), model.contentURL); - // Detach worker so that messages send will be queued until it's - // reatached once panel content is ready. - workerFor(this).detach(); - }, - - get allow() { return Allow(this); }, - set allow(value) { - let allowJavascript = panelContract({ allow: value }).allow.script; - return setScriptState(this, value); - }, - - /* Public API: Panel.isShowing */ - get isShowing() { - return !isDisposed(this) && domPanel.isOpen(viewFor(this)); - }, - - /* Public API: Panel.show */ - show: function show(options={}, anchor) { - SinglePanelManager.requestOpen(this, () => { - if (options instanceof Ci.nsIDOMElement) { - [anchor, options] = [options, null]; - } - - if (anchor instanceof Ci.nsIDOMElement) { - console.warn( - "Passing a DOM node to Panel.show() method is an unsupported " + - "feature that will be soon replaced. " + - "See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877" - ); - } - - let model = modelFor(this); - let view = viewFor(this); - let anchorView = getNodeView(anchor || options.position || model.position); - - options = merge({ - position: model.position, - width: model.width, - height: model.height, - defaultWidth: model.defaultWidth, - defaultHeight: model.defaultHeight, - focus: model.focus, - contextMenu: model.contextMenu - }, displayContract(options)); - - if (!isDisposed(this)) { - domPanel.show(view, options, anchorView); - } - }); - return this; - }, - - /* Public API: Panel.hide */ - hide: function hide() { - // Quit immediately if panel is disposed or there is no state change. - domPanel.close(viewFor(this)); - - return this; - }, - - /* Public API: Panel.resize */ - resize: function resize(width, height) { - let model = modelFor(this); - let view = viewFor(this); - let change = panelContract({ - width: width || model.width || model.defaultWidth, - height: height || model.height || model.defaultHeight - }); - - model.width = change.width - model.height = change.height - - domPanel.resize(view, model.width, model.height); - - return this; - } -}); -exports.Panel = Panel; - -// Note must be defined only after value to `Panel` is assigned. -getActiveView.define(Panel, viewFor); - -// Filter panel events to only panels that are create by this module. -var panelEvents = filter(events, ({target}) => panelFor(target)); - -// Panel events emitted after panel has being shown. -var shows = filter(panelEvents, ({type}) => type === "popupshown"); - -// Panel events emitted after panel became hidden. -var hides = filter(panelEvents, ({type}) => type === "popuphidden"); - -// Panel events emitted after content inside panel is ready. For different -// panels ready may mean different state based on `contentScriptWhen` attribute. -// Weather given event represents readyness is detected by `getAttachEventType` -// helper function. -var ready = filter(panelEvents, ({type, target}) => - getAttachEventType(modelFor(panelFor(target))) === type); - -// Panel event emitted when the contents of the panel has been loaded. -var readyToShow = filter(panelEvents, ({type}) => type === "DOMContentLoaded"); - -// Styles should be always added as soon as possible, and doesn't makes them -// depends on `contentScriptWhen` -var start = filter(panelEvents, ({type}) => type === "document-element-inserted"); - -// Forward panel show / hide events to panel's own event listeners. -on(shows, "data", ({target}) => { - let panel = panelFor(target); - if (modelFor(panel).ready) - emit(panel, "show"); -}); - -on(hides, "data", ({target}) => { - let panel = panelFor(target); - if (modelFor(panel).ready) - emit(panel, "hide"); -}); - -on(ready, "data", ({target}) => { - let panel = panelFor(target); - let window = domPanel.getContentDocument(target).defaultView; - - workerFor(panel).attach(window); -}); - -on(readyToShow, "data", ({target}) => { - let panel = panelFor(target); - - if (!modelFor(panel).ready) { - modelFor(panel).ready = true; - - if (viewFor(panel).state == "open") - emit(panel, "show"); - } -}); - -on(start, "data", ({target}) => { - let panel = panelFor(target); - let window = domPanel.getContentDocument(target).defaultView; - - attach(styleFor(panel), window); -}); |