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.

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

mercurial