addon-sdk/source/lib/sdk/panel/utils.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 module.metadata = {
michael@0 8 "stability": "unstable"
michael@0 9 };
michael@0 10
michael@0 11 const { Cc, Ci } = require("chrome");
michael@0 12 const { setTimeout } = require("../timers");
michael@0 13 const { platform } = require("../system");
michael@0 14 const { getMostRecentBrowserWindow, getOwnerBrowserWindow,
michael@0 15 getHiddenWindow, getScreenPixelsPerCSSPixel } = require("../window/utils");
michael@0 16
michael@0 17 const { create: createFrame, swapFrameLoaders } = require("../frame/utils");
michael@0 18 const { window: addonWindow } = require("../addon/window");
michael@0 19 const { isNil } = require("../lang/type");
michael@0 20 const events = require("../system/events");
michael@0 21
michael@0 22
michael@0 23 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
michael@0 24
michael@0 25 function calculateRegion({ position, width, height, defaultWidth, defaultHeight }, rect) {
michael@0 26 position = position || {};
michael@0 27
michael@0 28 let x, y;
michael@0 29
michael@0 30 let hasTop = !isNil(position.top);
michael@0 31 let hasRight = !isNil(position.right);
michael@0 32 let hasBottom = !isNil(position.bottom);
michael@0 33 let hasLeft = !isNil(position.left);
michael@0 34 let hasWidth = !isNil(width);
michael@0 35 let hasHeight = !isNil(height);
michael@0 36
michael@0 37 // if width is not specified by constructor or show's options, then get
michael@0 38 // the default width
michael@0 39 if (!hasWidth)
michael@0 40 width = defaultWidth;
michael@0 41
michael@0 42 // if height is not specified by constructor or show's options, then get
michael@0 43 // the default height
michael@0 44 if (!hasHeight)
michael@0 45 height = defaultHeight;
michael@0 46
michael@0 47 // default position is centered
michael@0 48 x = (rect.right - width) / 2;
michael@0 49 y = (rect.top + rect.bottom - height) / 2;
michael@0 50
michael@0 51 if (hasTop) {
michael@0 52 y = rect.top + position.top;
michael@0 53
michael@0 54 if (hasBottom && !hasHeight)
michael@0 55 height = rect.bottom - position.bottom - y;
michael@0 56 }
michael@0 57 else if (hasBottom) {
michael@0 58 y = rect.bottom - position.bottom - height;
michael@0 59 }
michael@0 60
michael@0 61 if (hasLeft) {
michael@0 62 x = position.left;
michael@0 63
michael@0 64 if (hasRight && !hasWidth)
michael@0 65 width = rect.right - position.right - x;
michael@0 66 }
michael@0 67 else if (hasRight) {
michael@0 68 x = rect.right - width - position.right;
michael@0 69 }
michael@0 70
michael@0 71 return {x: x, y: y, width: width, height: height};
michael@0 72 }
michael@0 73
michael@0 74 function open(panel, options, anchor) {
michael@0 75 // Wait for the XBL binding to be constructed
michael@0 76 if (!panel.openPopup) setTimeout(open, 50, panel, options, anchor);
michael@0 77 else display(panel, options, anchor);
michael@0 78 }
michael@0 79 exports.open = open;
michael@0 80
michael@0 81 function isOpen(panel) {
michael@0 82 return panel.state === "open"
michael@0 83 }
michael@0 84 exports.isOpen = isOpen;
michael@0 85
michael@0 86 function isOpening(panel) {
michael@0 87 return panel.state === "showing"
michael@0 88 }
michael@0 89 exports.isOpening = isOpening
michael@0 90
michael@0 91 function close(panel) {
michael@0 92 // Sometimes "TypeError: panel.hidePopup is not a function" is thrown
michael@0 93 // when quitting the host application while a panel is visible. To suppress
michael@0 94 // these errors, check for "hidePopup" in panel before calling it.
michael@0 95 // It's not clear if there's an issue or it's expected behavior.
michael@0 96
michael@0 97 return panel.hidePopup && panel.hidePopup();
michael@0 98 }
michael@0 99 exports.close = close
michael@0 100
michael@0 101
michael@0 102 function resize(panel, width, height) {
michael@0 103 // Resize the iframe instead of using panel.sizeTo
michael@0 104 // because sizeTo doesn't work with arrow panels
michael@0 105 panel.firstChild.style.width = width + "px";
michael@0 106 panel.firstChild.style.height = height + "px";
michael@0 107 }
michael@0 108 exports.resize = resize
michael@0 109
michael@0 110 function display(panel, options, anchor) {
michael@0 111 let document = panel.ownerDocument;
michael@0 112
michael@0 113 let x, y;
michael@0 114 let { width, height, defaultWidth, defaultHeight } = options;
michael@0 115
michael@0 116 let popupPosition = null;
michael@0 117
michael@0 118 // Panel XBL has some SDK incompatible styling decisions. We shim panel
michael@0 119 // instances until proper fix for Bug 859504 is shipped.
michael@0 120 shimDefaultStyle(panel);
michael@0 121
michael@0 122 if (!anchor) {
michael@0 123 // The XUL Panel doesn't have an arrow, so the margin needs to be reset
michael@0 124 // in order to, be positioned properly
michael@0 125 panel.style.margin = "0";
michael@0 126
michael@0 127 let viewportRect = document.defaultView.gBrowser.getBoundingClientRect();
michael@0 128
michael@0 129 ({x, y, width, height}) = calculateRegion(options, viewportRect);
michael@0 130 }
michael@0 131 else {
michael@0 132 // The XUL Panel has an arrow, so the margin needs to be reset
michael@0 133 // to the default value.
michael@0 134 panel.style.margin = "";
michael@0 135 let { CustomizableUI, window } = anchor.ownerDocument.defaultView;
michael@0 136
michael@0 137 // In Australis, widgets may be positioned in an overflow panel or the
michael@0 138 // menu panel.
michael@0 139 // In such cases clicking this widget will hide the overflow/menu panel,
michael@0 140 // and the widget's panel will show instead.
michael@0 141 // If `CustomizableUI` is not available, it means the anchor is not in a
michael@0 142 // chrome browser window, and therefore there is no need for this check.
michael@0 143 if (CustomizableUI) {
michael@0 144 let node = anchor;
michael@0 145 ({anchor}) = CustomizableUI.getWidget(anchor.id).forWindow(window);
michael@0 146
michael@0 147 // if `node` is not the `anchor` itself, it means the widget is
michael@0 148 // positioned in a panel, therefore we have to hide it before show
michael@0 149 // the widget's panel in the same anchor
michael@0 150 if (node !== anchor)
michael@0 151 CustomizableUI.hidePanelForNode(anchor);
michael@0 152 }
michael@0 153
michael@0 154 width = width || defaultWidth;
michael@0 155 height = height || defaultHeight;
michael@0 156
michael@0 157 // Open the popup by the anchor.
michael@0 158 let rect = anchor.getBoundingClientRect();
michael@0 159
michael@0 160 let zoom = getScreenPixelsPerCSSPixel(window);
michael@0 161 let screenX = rect.left + window.mozInnerScreenX * zoom;
michael@0 162 let screenY = rect.top + window.mozInnerScreenY * zoom;
michael@0 163
michael@0 164 // Set up the vertical position of the popup relative to the anchor
michael@0 165 // (always display the arrow on anchor center)
michael@0 166 let horizontal, vertical;
michael@0 167 if (screenY > window.screen.availHeight / 2 + height)
michael@0 168 vertical = "top";
michael@0 169 else
michael@0 170 vertical = "bottom";
michael@0 171
michael@0 172 if (screenY > window.screen.availWidth / 2 + width)
michael@0 173 horizontal = "left";
michael@0 174 else
michael@0 175 horizontal = "right";
michael@0 176
michael@0 177 let verticalInverse = vertical == "top" ? "bottom" : "top";
michael@0 178 popupPosition = vertical + "center " + verticalInverse + horizontal;
michael@0 179
michael@0 180 // Allow panel to flip itself if the panel can't be displayed at the
michael@0 181 // specified position (useful if we compute a bad position or if the
michael@0 182 // user moves the window and panel remains visible)
michael@0 183 panel.setAttribute("flip", "both");
michael@0 184 }
michael@0 185
michael@0 186 // Resize the iframe instead of using panel.sizeTo
michael@0 187 // because sizeTo doesn't work with arrow panels
michael@0 188 panel.firstChild.style.width = width + "px";
michael@0 189 panel.firstChild.style.height = height + "px";
michael@0 190
michael@0 191 panel.openPopup(anchor, popupPosition, x, y);
michael@0 192 }
michael@0 193 exports.display = display;
michael@0 194
michael@0 195 // This utility function is just a workaround until Bug 859504 has shipped.
michael@0 196 function shimDefaultStyle(panel) {
michael@0 197 let document = panel.ownerDocument;
michael@0 198 // Please note that `panel` needs to be part of document in order to reach
michael@0 199 // it's anonymous nodes. One of the anonymous node has a big padding which
michael@0 200 // doesn't work well since panel frame needs to fill all of the panel.
michael@0 201 // XBL binding is a not the best option as it's applied asynchronously, and
michael@0 202 // makes injected frames behave in strange way. Also this feels a lot
michael@0 203 // cheaper to do.
michael@0 204 ["panel-inner-arrowcontent", "panel-arrowcontent"].forEach(function(value) {
michael@0 205 let node = document.getAnonymousElementByAttribute(panel, "class", value);
michael@0 206 if (node) node.style.padding = 0;
michael@0 207 });
michael@0 208 }
michael@0 209
michael@0 210 function show(panel, options, anchor) {
michael@0 211 // Prevent the panel from getting focus when showing up
michael@0 212 // if focus is set to false
michael@0 213 panel.setAttribute("noautofocus", !options.focus);
michael@0 214
michael@0 215 let window = anchor && getOwnerBrowserWindow(anchor);
michael@0 216 let { document } = window ? window : getMostRecentBrowserWindow();
michael@0 217 attach(panel, document);
michael@0 218
michael@0 219 open(panel, options, anchor);
michael@0 220 }
michael@0 221 exports.show = show
michael@0 222
michael@0 223 function setupPanelFrame(frame) {
michael@0 224 frame.setAttribute("flex", 1);
michael@0 225 frame.setAttribute("transparent", "transparent");
michael@0 226 frame.setAttribute("autocompleteenabled", true);
michael@0 227 if (platform === "darwin") {
michael@0 228 frame.style.borderRadius = "6px";
michael@0 229 frame.style.padding = "1px";
michael@0 230 }
michael@0 231 }
michael@0 232
michael@0 233 function make(document) {
michael@0 234 document = document || getMostRecentBrowserWindow().document;
michael@0 235 let panel = document.createElementNS(XUL_NS, "panel");
michael@0 236 panel.setAttribute("type", "arrow");
michael@0 237
michael@0 238 // Note that panel is a parent of `viewFrame` who's `docShell` will be
michael@0 239 // configured at creation time. If `panel` and there for `viewFrame` won't
michael@0 240 // have an owner document attempt to access `docShell` will throw. There
michael@0 241 // for we attach panel to a document.
michael@0 242 attach(panel, document);
michael@0 243
michael@0 244 let frameOptions = {
michael@0 245 allowJavascript: true,
michael@0 246 allowPlugins: true,
michael@0 247 allowAuth: true,
michael@0 248 allowWindowControl: false,
michael@0 249 // Need to override `nodeName` to use `iframe` as `browsers` save session
michael@0 250 // history and in consequence do not dispatch "inner-window-destroyed"
michael@0 251 // notifications.
michael@0 252 browser: false,
michael@0 253 // Note that use of this URL let's use swap frame loaders earlier
michael@0 254 // than if we used default "about:blank".
michael@0 255 uri: "data:text/plain;charset=utf-8,"
michael@0 256 };
michael@0 257
michael@0 258 let backgroundFrame = createFrame(addonWindow, frameOptions);
michael@0 259 setupPanelFrame(backgroundFrame);
michael@0 260
michael@0 261 let viewFrame = createFrame(panel, frameOptions);
michael@0 262 setupPanelFrame(viewFrame);
michael@0 263
michael@0 264 function onDisplayChange({type, target}) {
michael@0 265 // Events from child element like <select /> may propagate (dropdowns are
michael@0 266 // popups too), in which case frame loader shouldn't be swapped.
michael@0 267 // See Bug 886329
michael@0 268 if (target !== this) return;
michael@0 269
michael@0 270 try { swapFrameLoaders(backgroundFrame, viewFrame); }
michael@0 271 catch(error) { console.exception(error); }
michael@0 272 events.emit(type, { subject: panel });
michael@0 273 }
michael@0 274
michael@0 275 function onContentReady({target, type}) {
michael@0 276 if (target === getContentDocument(panel)) {
michael@0 277 style(panel);
michael@0 278 events.emit(type, { subject: panel });
michael@0 279 }
michael@0 280 }
michael@0 281
michael@0 282 function onContentLoad({target, type}) {
michael@0 283 if (target === getContentDocument(panel))
michael@0 284 events.emit(type, { subject: panel });
michael@0 285 }
michael@0 286
michael@0 287 function onContentChange({subject, type}) {
michael@0 288 let document = subject;
michael@0 289 if (document === getContentDocument(panel) && document.defaultView)
michael@0 290 events.emit(type, { subject: panel });
michael@0 291 }
michael@0 292
michael@0 293 function onPanelStateChange({type}) {
michael@0 294 events.emit(type, { subject: panel })
michael@0 295 }
michael@0 296
michael@0 297 panel.addEventListener("popupshowing", onDisplayChange, false);
michael@0 298 panel.addEventListener("popuphiding", onDisplayChange, false);
michael@0 299 panel.addEventListener("popupshown", onPanelStateChange, false);
michael@0 300 panel.addEventListener("popuphidden", onPanelStateChange, false);
michael@0 301
michael@0 302 // Panel content document can be either in panel `viewFrame` or in
michael@0 303 // a `backgroundFrame` depending on panel state. Listeners are set
michael@0 304 // on both to avoid setting and removing listeners on panel state changes.
michael@0 305
michael@0 306 panel.addEventListener("DOMContentLoaded", onContentReady, true);
michael@0 307 backgroundFrame.addEventListener("DOMContentLoaded", onContentReady, true);
michael@0 308
michael@0 309 panel.addEventListener("load", onContentLoad, true);
michael@0 310 backgroundFrame.addEventListener("load", onContentLoad, true);
michael@0 311
michael@0 312 events.on("document-element-inserted", onContentChange);
michael@0 313
michael@0 314
michael@0 315 panel.backgroundFrame = backgroundFrame;
michael@0 316
michael@0 317 // Store event listener on the panel instance so that it won't be GC-ed
michael@0 318 // while panel is alive.
michael@0 319 panel.onContentChange = onContentChange;
michael@0 320
michael@0 321 return panel;
michael@0 322 }
michael@0 323 exports.make = make;
michael@0 324
michael@0 325 function attach(panel, document) {
michael@0 326 document = document || getMostRecentBrowserWindow().document;
michael@0 327 let container = document.getElementById("mainPopupSet");
michael@0 328 if (container !== panel.parentNode) {
michael@0 329 detach(panel);
michael@0 330 document.getElementById("mainPopupSet").appendChild(panel);
michael@0 331 }
michael@0 332 }
michael@0 333 exports.attach = attach;
michael@0 334
michael@0 335 function detach(panel) {
michael@0 336 if (panel.parentNode) panel.parentNode.removeChild(panel);
michael@0 337 }
michael@0 338 exports.detach = detach;
michael@0 339
michael@0 340 function dispose(panel) {
michael@0 341 panel.backgroundFrame.parentNode.removeChild(panel.backgroundFrame);
michael@0 342 panel.backgroundFrame = null;
michael@0 343 events.off("document-element-inserted", panel.onContentChange);
michael@0 344 panel.onContentChange = null;
michael@0 345 detach(panel);
michael@0 346 }
michael@0 347 exports.dispose = dispose;
michael@0 348
michael@0 349 function style(panel) {
michael@0 350 /**
michael@0 351 Injects default OS specific panel styles into content document that is loaded
michael@0 352 into given panel. Optionally `document` of the browser window can be
michael@0 353 given to inherit styles from it, by default it will use either panel owner
michael@0 354 document or an active browser's document. It should not matter though unless
michael@0 355 Firefox decides to style windows differently base on profile or mode like
michael@0 356 chrome for example.
michael@0 357 **/
michael@0 358
michael@0 359 try {
michael@0 360 let document = panel.ownerDocument;
michael@0 361 let contentDocument = getContentDocument(panel);
michael@0 362 let window = document.defaultView;
michael@0 363 let node = document.getAnonymousElementByAttribute(panel, "class",
michael@0 364 "panel-arrowcontent") ||
michael@0 365 // Before bug 764755, anonymous content was different:
michael@0 366 // TODO: Remove this when targeting FF16+
michael@0 367 document.getAnonymousElementByAttribute(panel, "class",
michael@0 368 "panel-inner-arrowcontent");
michael@0 369
michael@0 370 let { color, fontFamily, fontSize, fontWeight } = window.getComputedStyle(node);
michael@0 371
michael@0 372 let style = contentDocument.createElement("style");
michael@0 373 style.id = "sdk-panel-style";
michael@0 374 style.textContent = "body { " +
michael@0 375 "color: " + color + ";" +
michael@0 376 "font-family: " + fontFamily + ";" +
michael@0 377 "font-weight: " + fontWeight + ";" +
michael@0 378 "font-size: " + fontSize + ";" +
michael@0 379 "}";
michael@0 380
michael@0 381 let container = contentDocument.head ? contentDocument.head :
michael@0 382 contentDocument.documentElement;
michael@0 383
michael@0 384 if (container.firstChild)
michael@0 385 container.insertBefore(style, container.firstChild);
michael@0 386 else
michael@0 387 container.appendChild(style);
michael@0 388 }
michael@0 389 catch (error) {
michael@0 390 console.error("Unable to apply panel style");
michael@0 391 console.exception(error);
michael@0 392 }
michael@0 393 }
michael@0 394 exports.style = style;
michael@0 395
michael@0 396 let getContentFrame = panel =>
michael@0 397 (isOpen(panel) || isOpening(panel)) ?
michael@0 398 panel.firstChild :
michael@0 399 panel.backgroundFrame
michael@0 400 exports.getContentFrame = getContentFrame;
michael@0 401
michael@0 402 function getContentDocument(panel) getContentFrame(panel).contentDocument
michael@0 403 exports.getContentDocument = getContentDocument;
michael@0 404
michael@0 405 function setURL(panel, url) getContentFrame(panel).setAttribute("src", url)
michael@0 406 exports.setURL = setURL;

mercurial