Sat, 03 Jan 2015 20:18:00 +0100
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;