aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.5/packages/addon-kit/lib/panel.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/addon-sdk-1.5/packages/addon-kit/lib/panel.js')
-rw-r--r--tools/addon-sdk-1.5/packages/addon-kit/lib/panel.js369
1 files changed, 369 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.5/packages/addon-kit/lib/panel.js b/tools/addon-sdk-1.5/packages/addon-kit/lib/panel.js
new file mode 100644
index 0000000..1365c02
--- /dev/null
+++ b/tools/addon-sdk-1.5/packages/addon-kit/lib/panel.js
@@ -0,0 +1,369 @@
+/* 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";
+
+if (!require("api-utils/xul-app").is("Firefox")) {
+ throw new Error([
+ "The panel module currently supports only Firefox. In the future ",
+ "we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps ",
+ "for more information."
+ ].join(""));
+}
+
+const { Cc, Ci } = require("chrome");
+
+const { validateOptions: valid } = require("api-utils/api-utils");
+const { Symbiont } = require("api-utils/content");
+const { EventEmitter } = require('api-utils/events');
+const timer = require("api-utils/timer");
+const runtime = require("api-utils/runtime");
+
+const windowMediator = Cc['@mozilla.org/appshell/window-mediator;1'].
+ getService(Ci.nsIWindowMediator);
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ ON_SHOW = 'popupshown',
+ ON_HIDE = 'popuphidden',
+ validNumber = { is: ['number', 'undefined', 'null'] };
+
+/**
+ * Emits show and hide events.
+ */
+const Panel = Symbiont.resolve({
+ constructor: '_init',
+ _onInit: '_onSymbiontInit',
+ destroy: '_symbiontDestructor',
+ _documentUnload: '_workerDocumentUnload'
+}).compose({
+ _frame: Symbiont.required,
+ _init: Symbiont.required,
+ _onSymbiontInit: Symbiont.required,
+ _symbiontDestructor: Symbiont.required,
+ _emit: Symbiont.required,
+ _asyncEmit: Symbiont.required,
+ on: Symbiont.required,
+ removeListener: Symbiont.required,
+
+ _inited: false,
+
+ /**
+ * If set to `true` frame loaders between xul panel frame and
+ * hidden frame are swapped. If set to `false` frame loaders are
+ * set back to normal. Setting the value that was already set will
+ * have no effect.
+ */
+ set _frameLoadersSwapped(value) {
+ if (this.__frameLoadersSwapped == value) return;
+ this._frame.QueryInterface(Ci.nsIFrameLoaderOwner)
+ .swapFrameLoaders(this._viewFrame);
+ this.__frameLoadersSwapped = value;
+ },
+ __frameLoadersSwapped: false,
+
+ constructor: function Panel(options) {
+ this._onShow = this._onShow.bind(this);
+ this._onHide = this._onHide.bind(this);
+ this.on('inited', this._onSymbiontInit.bind(this));
+
+ options = options || {};
+ if ('onShow' in options)
+ this.on('show', options.onShow);
+ if ('onHide' in options)
+ this.on('hide', options.onHide);
+ if ('width' in options)
+ this.width = options.width;
+ if ('height' in options)
+ this.height = options.height;
+ if ('contentURL' in options)
+ this.contentURL = options.contentURL;
+
+ this._init(options);
+ },
+ _destructor: function _destructor() {
+ this.hide();
+ this._removeAllListeners('show');
+ // defer cleanup to be performed after panel gets hidden
+ this._xulPanel = null;
+ this._symbiontDestructor(this);
+ this._removeAllListeners();
+ },
+ destroy: function destroy() {
+ this._destructor();
+ },
+ /* Public API: Panel.width */
+ get width() this._width,
+ set width(value)
+ this._width = valid({ $: value }, { $: validNumber }).$ || this._width,
+ _width: 320,
+ /* Public API: Panel.height */
+ get height() this._height,
+ set height(value)
+ this._height = valid({ $: value }, { $: validNumber }).$ || this._height,
+ _height: 240,
+
+ /* Public API: Panel.isShowing */
+ get isShowing() !!this._xulPanel && this._xulPanel.state == "open",
+
+ /* Public API: Panel.show */
+ show: function show(anchor) {
+ anchor = anchor || null;
+ let document = getWindow(anchor).document;
+ let xulPanel = this._xulPanel;
+ if (!xulPanel) {
+ xulPanel = this._xulPanel = document.createElementNS(XUL_NS, 'panel');
+ xulPanel.setAttribute("type", "arrow");
+
+ // One anonymous node has a big padding that doesn't work well with
+ // Jetpack, as we would like to display an iframe that completely fills
+ // the panel.
+ // -> Use a XBL wrapper with inner stylesheet to remove this padding.
+ let css = ".panel-inner-arrowcontent, .panel-arrowcontent {padding: 0;}";
+ let originalXBL = "chrome://global/content/bindings/popup.xml#arrowpanel";
+ let binding =
+ '<bindings xmlns="http://www.mozilla.org/xbl">' +
+ '<binding id="id" extends="' + originalXBL + '">' +
+ '<resources>' +
+ '<stylesheet src="data:text/css,' +
+ document.defaultView.encodeURIComponent(css) + '"/>' +
+ '</resources>' +
+ '</binding>' +
+ '</bindings>';
+ xulPanel.style.MozBinding = 'url("data:text/xml,' +
+ document.defaultView.encodeURIComponent(binding) + '")';
+
+ let frame = document.createElementNS(XUL_NS, 'iframe');
+ frame.setAttribute('type', 'content');
+ frame.setAttribute('flex', '1');
+ frame.setAttribute('transparent', 'transparent');
+ if (runtime.OS === "Darwin") {
+ frame.style.borderRadius = "6px";
+ frame.style.padding = "1px";
+ }
+
+ // Load an empty document in order to have an immediatly loaded iframe,
+ // so swapFrameLoaders is going to work without having to wait for load.
+ frame.setAttribute("src","data:,");
+
+ xulPanel.appendChild(frame);
+ document.getElementById("mainPopupSet").appendChild(xulPanel);
+ }
+ let { width, height } = this, x, y, position;
+
+ if (!anchor) {
+ // Open the popup in the middle of the window.
+ x = document.documentElement.clientWidth / 2 - width / 2;
+ y = document.documentElement.clientHeight / 2 - height / 2;
+ position = null;
+ }
+ else {
+ // Open the popup by the anchor.
+ let rect = anchor.getBoundingClientRect();
+
+ let window = anchor.ownerDocument.defaultView;
+
+ let zoom = window.mozScreenPixelsPerCSSPixel;
+ let screenX = rect.left + window.mozInnerScreenX * zoom;
+ let screenY = rect.top + window.mozInnerScreenY * zoom;
+
+ // Set up the vertical position of the popup relative to the anchor
+ // (always display the arrow on anchor center)
+ let horizontal, vertical;
+ if (screenY > window.screen.availHeight / 2 + height)
+ vertical = "top";
+ else
+ vertical = "bottom";
+
+ if (screenY > window.screen.availWidth / 2 + width)
+ horizontal = "left";
+ else
+ horizontal = "right";
+
+ let verticalInverse = vertical == "top" ? "bottom" : "top";
+ position = vertical + "center " + verticalInverse + horizontal;
+
+ // Allow panel to flip itself if the panel can't be displayed at the
+ // specified position (useful if we compute a bad position or if the
+ // user moves the window and panel remains visible)
+ xulPanel.setAttribute("flip","both");
+ }
+
+ // Resize the iframe instead of using panel.sizeTo
+ // because sizeTo doesn't work with arrow panels
+ xulPanel.firstChild.style.width = width + "px";
+ xulPanel.firstChild.style.height = height + "px";
+
+ // Wait for the XBL binding to be constructed
+ function waitForBinding() {
+ if (!xulPanel.openPopup) {
+ timer.setTimeout(waitForBinding, 50);
+ return;
+ }
+ xulPanel.openPopup(anchor, position, x, y);
+ }
+ waitForBinding();
+
+ return this._public;
+ },
+ /* Public API: Panel.hide */
+ hide: function hide() {
+ // The popuphiding handler takes care of swapping back the frame loaders
+ // and removing the XUL panel from the application window, we just have to
+ // trigger it by hiding the popup.
+ // XXX Sometimes I get "TypeError: xulPanel.hidePopup is not a function"
+ // when quitting the host application while a panel is visible. To suppress
+ // them, this now checks for "hidePopup" in xulPanel before calling it.
+ // It's not clear if there's an actual issue or the error is just normal.
+ let xulPanel = this._xulPanel;
+ if (xulPanel && "hidePopup" in xulPanel)
+ xulPanel.hidePopup();
+ return this._public;
+ },
+
+ /* Public API: Panel.resize */
+ resize: function resize(width, height) {
+ this.width = width;
+ this.height = height;
+ // Resize the iframe instead of using panel.sizeTo
+ // because sizeTo doesn't work with arrow panels
+ let xulPanel = this._xulPanel;
+ if (xulPanel) {
+ xulPanel.firstChild.style.width = width + "px";
+ xulPanel.firstChild.style.height = height + "px";
+ }
+ },
+
+ // While the panel is visible, this is the XUL <panel> we use to display it.
+ // Otherwise, it's null.
+ get _xulPanel() this.__xulPanel,
+ set _xulPanel(value) {
+ let xulPanel = this.__xulPanel;
+ if (value === xulPanel) return;
+ if (xulPanel) {
+ xulPanel.removeEventListener(ON_HIDE, this._onHide, false);
+ xulPanel.removeEventListener(ON_SHOW, this._onShow, false);
+ xulPanel.parentNode.removeChild(xulPanel);
+ }
+ if (value) {
+ value.addEventListener(ON_HIDE, this._onHide, false);
+ value.addEventListener(ON_SHOW, this._onShow, false);
+ }
+ this.__xulPanel = value;
+ },
+ __xulPanel: null,
+ get _viewFrame() this.__xulPanel.children[0],
+ /**
+ * When the XUL panel becomes hidden, we swap frame loaders back to move
+ * the content of the panel to the hidden frame & remove panel element.
+ */
+ _onHide: function _onHide() {
+ try {
+ this._frameLoadersSwapped = false;
+ this._xulPanel = null;
+ this._emit('hide');
+ } catch(e) {
+ this._emit('error', e);
+ }
+ },
+ /**
+ * When the XUL panel becomes shown, we swap frame loaders between panel
+ * frame and hidden frame to preserve state of the content dom.
+ */
+ _onShow: function _onShow() {
+ try {
+ if (!this._inited) { // defer if not initialized yet
+ this.on('inited', this._onShow.bind(this));
+ } else {
+ this._frameLoadersSwapped = true;
+
+ // Retrieve computed text color style in order to apply to the iframe
+ // document. As MacOS background is dark gray, we need to use skin's
+ // text color.
+ let win = this._xulPanel.ownerDocument.defaultView;
+ let node = win.document.getAnonymousElementByAttribute(this._xulPanel,
+ "class", "panel-inner-arrowcontent");
+ let textColor = win.getComputedStyle(node).getPropertyValue("color");
+ let doc = this._xulPanel.firstChild.contentDocument;
+ let style = doc.createElement("style");
+ style.textContent = "body { color: " + textColor + "; }";
+ let container = doc.head ? doc.head : doc.documentElement;
+
+ if (container.firstChild)
+ container.insertBefore(style, container.firstChild);
+ else
+ container.appendChild(style);
+
+ this._emit('show');
+ }
+ } catch(e) {
+ this._emit('error', e);
+ }
+ },
+ /**
+ * Notification that panel was fully initialized.
+ */
+ _onInit: function _onInit() {
+ this._inited = true;
+
+ // Avoid panel document from resizing the browser window
+ // New platform capability added through bug 635673
+ if ("allowWindowControl" in this._frame.docShell)
+ this._frame.docShell.allowWindowControl = false;
+
+ // perform all deferred tasks like initSymbiont, show, hide ...
+ // TODO: We're publicly exposing a private event here; this
+ // 'inited' event should really be made private, somehow.
+ this._emit('inited');
+ },
+
+ // Catch document unload event in order to rebind load event listener with
+ // Symbiont._initFrame if Worker._documentUnload destroyed the worker
+ _documentUnload: function(subject, topic, data) {
+ if (this._workerDocumentUnload(subject, topic, data)) {
+ this._initFrame(this._frame);
+ return true;
+ }
+ return false;
+ }
+});
+exports.Panel = function(options) Panel(options)
+exports.Panel.prototype = Panel.prototype;
+
+function getWindow(anchor) {
+ let window;
+
+ if (anchor) {
+ let anchorWindow = anchor.ownerDocument.defaultView.top;
+ let anchorDocument = anchorWindow.document;
+
+ let enumerator = windowMediator.getEnumerator("navigator:browser");
+ while (enumerator.hasMoreElements()) {
+ let enumWindow = enumerator.getNext();
+
+ // Check if the anchor is in this browser window.
+ if (enumWindow == anchorWindow) {
+ window = anchorWindow;
+ break;
+ }
+
+ // Check if the anchor is in a browser tab in this browser window.
+ let browser = enumWindow.gBrowser.getBrowserForDocument(anchorDocument);
+ if (browser) {
+ window = enumWindow;
+ break;
+ }
+
+ // Look in other subdocuments (sidebar, etc.)?
+ }
+ }
+
+ // If we didn't find the anchor's window (or we have no anchor),
+ // return the most recent browser window.
+ if (!window)
+ window = windowMediator.getMostRecentWindow("navigator:browser");
+
+ return window;
+}
+