1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 // A module for working with panels with iframes shared across windows.
8 this.EXPORTED_SYMBOLS = ["PanelFrame"];
10 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
12 Cu.import("resource://gre/modules/Services.jsm");
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
15 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm");
17 XPCOMUtils.defineLazyModuleGetter(this, "DynamicResizeWatcher", "resource:///modules/Social.jsm");
19 // The minimum sizes for the auto-resize panel code.
20 const PANEL_MIN_HEIGHT = 100;
21 const PANEL_MIN_WIDTH = 330;
23 let PanelFrameInternal = {
25 * Helper function to get and hold a single instance of a DynamicResizeWatcher.
27 get _dynamicResizer() {
28 delete this._dynamicResizer;
29 this._dynamicResizer = new DynamicResizeWatcher();
30 return this._dynamicResizer;
34 * Status panels are one-per button per-process, we swap the docshells between
35 * windows when necessary.
37 * @param {DOMWindow} aWindow The window in which to show the popup.
38 * @param {PanelUI} aPanelUI The panel UI object that represents the application menu.
39 * @param {DOMElement} aButton The button element that is pressed to show the popup.
40 * @param {String} aType The type of panel this is, e.g. "social" or "loop".
41 * @param {String} aOrigin Optional, the origin to use for the iframe.
42 * @param {String} aSrc The url to load into the iframe.
43 * @param {String} aSize The initial size of the panel (width and height are the same
46 _attachNotificatonPanel: function(aWindow, aParent, aButton, aType, aOrigin, aSrc, aSize) {
47 aParent.hidden = false;
48 let notificationFrameId = aOrigin ? aType + "-status-" + aOrigin : aType;
49 let doc = aWindow.document;
50 let frame = doc.getElementById(notificationFrameId);
52 // If the button was customized to a new location, destroy the
53 // iframe and start fresh.
54 if (frame && frame.parentNode != aParent) {
55 frame.parentNode.removeChild(frame);
60 let {width, height} = aSize ? aSize : {width: PANEL_MIN_WIDTH, height: PANEL_MIN_HEIGHT};
61 frame = doc.createElement("browser");
65 // All frames use social-panel-frame as the class.
66 "class": "social-panel-frame",
67 "id": notificationFrameId,
68 "tooltip": "aHTMLTooltip",
69 "context": "contentAreaContextMenu",
72 // work around bug 793057 - by making the panel roughly the final size
73 // we are more likely to have the anchor in the correct position.
74 "style": "width: " + width + "px; height: " + height + "px;",
75 "dynamicresizer": !aSize,
80 for (let [k, v] of Iterator(attrs)) {
81 frame.setAttribute(k, v);
83 aParent.appendChild(frame);
85 frame.setAttribute("origin", aOrigin);
86 frame.setAttribute("src", aSrc);
88 aButton.setAttribute("notificationFrameId", notificationFrameId);
93 * The exported PanelFrame object
97 * Shows a popup in a pop-up panel, or in a sliding panel view in the application menu.
98 * It will move the iframe to different DOM locations depending on where it needs to be
99 * shown, enabling one iframe to be used for the entire session.
101 * @param {DOMWindow} aWindow The window in which to show the popup.
102 * @param {PanelUI} aPanelUI The panel UI object that represents the application menu.
103 * @param {DOMElement} aToolbarButton The button element that is pressed to show the popup.
104 * @param {String} aType The type of panel this is, e.g. "social" or "loop".
105 * @param {String} aOrigin Optional, the origin to use for the iframe.
106 * @param {String} aSrc The url to load into the iframe.
107 * @param {String} aSize The initial size of the panel (width and height are the same
109 * @param {Function} aCallback Optional, callback to be called with the iframe when it is
112 showPopup: function(aWindow, aToolbarButton, aType, aOrigin, aSrc, aSize, aCallback) {
113 // if we're overflowed, our anchor needs to be the overflow button
114 let widgetGroup = CustomizableUI.getWidget(aToolbarButton.getAttribute("id"));
115 let widget = widgetGroup.forWindow(aWindow);
116 // if we're a slice in the hamburger, our anchor will be the menu button,
117 // this panel will replace the menu panel when the button is clicked on
118 let anchorBtn = widget.anchor;
120 let panel = aWindow.document.getElementById(aType + "-notification-panel");
121 PanelFrameInternal._attachNotificatonPanel(aWindow, panel, aToolbarButton, aType, aOrigin, aSrc, aSize);
123 let notificationFrameId = aToolbarButton.getAttribute("notificationFrameId");
124 let notificationFrame = aWindow.document.getElementById(notificationFrameId);
127 // Clear dimensions on all browsers so the panel size will
128 // only use the selected browser.
129 let frameIter = panel.firstElementChild;
131 frameIter.collapsed = (frameIter != notificationFrame);
132 frameIter = frameIter.nextElementSibling;
135 function dispatchPanelEvent(name) {
136 let evt = notificationFrame.contentDocument.createEvent("CustomEvent");
137 evt.initCustomEvent(name, true, true, {});
138 notificationFrame.contentDocument.documentElement.dispatchEvent(evt);
141 // we only use a dynamic resizer when we're located the toolbar.
143 if (notificationFrame.getAttribute("dynamicresizer") == "true") {
144 dynamicResizer = PanelFrameInternal._dynamicResizer;
146 panel.addEventListener("popuphidden", function onpopuphiding() {
147 panel.removeEventListener("popuphidden", onpopuphiding);
148 anchorBtn.removeAttribute("open");
150 dynamicResizer.stop();
151 notificationFrame.docShell.isActive = false;
152 dispatchPanelEvent(aType + "FrameHide");
155 panel.addEventListener("popupshown", function onpopupshown() {
156 panel.removeEventListener("popupshown", onpopupshown);
157 let initFrameShow = () => {
158 notificationFrame.docShell.isActive = true;
159 notificationFrame.docShell.isAppTab = true;
161 dynamicResizer.start(panel, notificationFrame);
162 dispatchPanelEvent(aType + "FrameShow");
164 // This attribute is needed on both the button and the
165 // containing toolbaritem since the buttons on OS X have
166 // moz-appearance:none, while their container gets
167 // moz-appearance:toolbarbutton due to the way that toolbar buttons
168 // get combined on OS X.
169 anchorBtn.setAttribute("open", "true");
170 if (notificationFrame.contentDocument &&
171 notificationFrame.contentDocument.readyState == "complete") {
174 // first time load, wait for load and dispatch after load
175 notificationFrame.addEventListener("load", function panelBrowserOnload(e) {
176 notificationFrame.removeEventListener("load", panelBrowserOnload, true);
182 // in overflow, the anchor is a normal toolbarbutton, in toolbar it is a badge button
183 let anchor = aWindow.document.getAnonymousElementByAttribute(anchorBtn, "class", "toolbarbutton-badge-container") ||
184 aWindow.document.getAnonymousElementByAttribute(anchorBtn, "class", "toolbarbutton-icon");
185 // Bug 849216 - open the popup asynchronously so we avoid the auto-rollup
186 // handling from preventing it being opened in some cases.
187 Services.tm.mainThread.dispatch(function() {
188 panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
189 }, Ci.nsIThread.DISPATCH_NORMAL);
192 aCallback(notificationFrame);