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/. */
4 "use strict";
6 // The panel module currently supports only Firefox.
7 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
8 module.metadata = {
9 "stability": "stable",
10 "engines": {
11 "Firefox": "*"
12 }
13 };
15 const { Ci } = require("chrome");
16 const { setTimeout } = require('./timers');
17 const { isPrivateBrowsingSupported } = require('./self');
18 const { isWindowPBSupported } = require('./private-browsing/utils');
19 const { Class } = require("./core/heritage");
20 const { merge } = require("./util/object");
21 const { WorkerHost } = require("./content/utils");
22 const { Worker } = require("./content/worker");
23 const { Disposable } = require("./core/disposable");
24 const { WeakReference } = require('./core/reference');
25 const { contract: loaderContract } = require("./content/loader");
26 const { contract } = require("./util/contract");
27 const { on, off, emit, setListeners } = require("./event/core");
28 const { EventTarget } = require("./event/target");
29 const domPanel = require("./panel/utils");
30 const { events } = require("./panel/events");
31 const systemEvents = require("./system/events");
32 const { filter, pipe, stripListeners } = require("./event/utils");
33 const { getNodeView, getActiveView } = require("./view/core");
34 const { isNil, isObject, isNumber } = require("./lang/type");
35 const { getAttachEventType } = require("./content/utils");
36 const { number, boolean, object } = require('./deprecated/api-utils');
37 const { Style } = require("./stylesheet/style");
38 const { attach, detach } = require("./content/mod");
40 let isRect = ({top, right, bottom, left}) => [top, right, bottom, left].
41 some(value => isNumber(value) && !isNaN(value));
43 let isSDKObj = obj => obj instanceof Class;
45 let rectContract = contract({
46 top: number,
47 right: number,
48 bottom: number,
49 left: number
50 });
52 let position = {
53 is: object,
54 map: v => (isNil(v) || isSDKObj(v) || !isObject(v)) ? v : rectContract(v),
55 ok: v => isNil(v) || isSDKObj(v) || (isObject(v) && isRect(v)),
56 msg: 'The option "position" must be a SDK object registered as anchor; ' +
57 'or an object with one or more of the following keys set to numeric ' +
58 'values: top, right, bottom, left.'
59 }
61 let displayContract = contract({
62 width: number,
63 height: number,
64 focus: boolean,
65 position: position
66 });
68 let panelContract = contract(merge({
69 // contentStyle* / contentScript* are sharing the same validation constraints,
70 // so they can be mostly reused, except for the messages.
71 contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
72 msg: 'The `contentStyle` option must be a string or an array of strings.'
73 }),
74 contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
75 msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
76 })
77 }, displayContract.rules, loaderContract.rules));
80 function isDisposed(panel) !views.has(panel);
82 let panels = new WeakMap();
83 let models = new WeakMap();
84 let views = new WeakMap();
85 let workers = new WeakMap();
86 let styles = new WeakMap();
88 const viewFor = (panel) => views.get(panel);
89 const modelFor = (panel) => models.get(panel);
90 const panelFor = (view) => panels.get(view);
91 const workerFor = (panel) => workers.get(panel);
92 const styleFor = (panel) => styles.get(panel);
94 // Utility function takes `panel` instance and makes sure it will be
95 // automatically hidden as soon as other panel is shown.
96 let setupAutoHide = new function() {
97 let refs = new WeakMap();
99 return function setupAutoHide(panel) {
100 // Create system event listener that reacts to any panel showing and
101 // hides given `panel` if it's not the one being shown.
102 function listener({subject}) {
103 // It could be that listener is not GC-ed in the same cycle as
104 // panel in such case we remove listener manually.
105 let view = viewFor(panel);
106 if (!view) systemEvents.off("popupshowing", listener);
107 else if (subject !== view) panel.hide();
108 }
110 // system event listener is intentionally weak this way we'll allow GC
111 // to claim panel if it's no longer referenced by an add-on code. This also
112 // helps minimizing cleanup required on unload.
113 systemEvents.on("popupshowing", listener);
114 // To make sure listener is not claimed by GC earlier than necessary we
115 // associate it with `panel` it's associated with. This way it won't be
116 // GC-ed earlier than `panel` itself.
117 refs.set(panel, listener);
118 }
119 }
121 const Panel = Class({
122 implements: [
123 // Generate accessors for the validated properties that update model on
124 // set and return values from model on get.
125 panelContract.properties(modelFor),
126 EventTarget,
127 Disposable,
128 WeakReference
129 ],
130 extends: WorkerHost(workerFor),
131 setup: function setup(options) {
132 let model = merge({
133 defaultWidth: 320,
134 defaultHeight: 240,
135 focus: true,
136 position: Object.freeze({}),
137 }, panelContract(options));
138 models.set(this, model);
140 if (model.contentStyle || model.contentStyleFile) {
141 styles.set(this, Style({
142 uri: model.contentStyleFile,
143 source: model.contentStyle
144 }));
145 }
147 // Setup view
148 let view = domPanel.make();
149 panels.set(view, this);
150 views.set(this, view);
152 // Load panel content.
153 domPanel.setURL(view, model.contentURL);
155 setupAutoHide(this);
157 // Setup listeners.
158 setListeners(this, options);
159 let worker = new Worker(stripListeners(options));
160 workers.set(this, worker);
162 // pipe events from worker to a panel.
163 pipe(worker, this);
164 },
165 dispose: function dispose() {
166 this.hide();
167 off(this);
169 workerFor(this).destroy();
170 detach(styleFor(this));
172 domPanel.dispose(viewFor(this));
174 // Release circular reference between view and panel instance. This
175 // way view will be GC-ed. And panel as well once all the other refs
176 // will be removed from it.
177 views.delete(this);
178 },
179 /* Public API: Panel.width */
180 get width() modelFor(this).width,
181 set width(value) this.resize(value, this.height),
182 /* Public API: Panel.height */
183 get height() modelFor(this).height,
184 set height(value) this.resize(this.width, value),
186 /* Public API: Panel.focus */
187 get focus() modelFor(this).focus,
189 /* Public API: Panel.position */
190 get position() modelFor(this).position,
192 get contentURL() modelFor(this).contentURL,
193 set contentURL(value) {
194 let model = modelFor(this);
195 model.contentURL = panelContract({ contentURL: value }).contentURL;
196 domPanel.setURL(viewFor(this), model.contentURL);
197 // Detach worker so that messages send will be queued until it's
198 // reatached once panel content is ready.
199 workerFor(this).detach();
200 },
202 /* Public API: Panel.isShowing */
203 get isShowing() !isDisposed(this) && domPanel.isOpen(viewFor(this)),
205 /* Public API: Panel.show */
206 show: function show(options={}, anchor) {
207 if (options instanceof Ci.nsIDOMElement) {
208 [anchor, options] = [options, null];
209 }
211 if (anchor instanceof Ci.nsIDOMElement) {
212 console.warn(
213 "Passing a DOM node to Panel.show() method is an unsupported " +
214 "feature that will be soon replaced. " +
215 "See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877"
216 );
217 }
219 let model = modelFor(this);
220 let view = viewFor(this);
221 let anchorView = getNodeView(anchor || options.position || model.position);
223 options = merge({
224 position: model.position,
225 width: model.width,
226 height: model.height,
227 defaultWidth: model.defaultWidth,
228 defaultHeight: model.defaultHeight,
229 focus: model.focus
230 }, displayContract(options));
232 if (!isDisposed(this))
233 domPanel.show(view, options, anchorView);
235 return this;
236 },
238 /* Public API: Panel.hide */
239 hide: function hide() {
240 // Quit immediately if panel is disposed or there is no state change.
241 domPanel.close(viewFor(this));
243 return this;
244 },
246 /* Public API: Panel.resize */
247 resize: function resize(width, height) {
248 let model = modelFor(this);
249 let view = viewFor(this);
250 let change = panelContract({
251 width: width || model.width || model.defaultWidth,
252 height: height || model.height || model.defaultHeight
253 });
255 model.width = change.width
256 model.height = change.height
258 domPanel.resize(view, model.width, model.height);
260 return this;
261 }
262 });
263 exports.Panel = Panel;
265 // Note must be defined only after value to `Panel` is assigned.
266 getActiveView.define(Panel, viewFor);
268 // Filter panel events to only panels that are create by this module.
269 let panelEvents = filter(events, ({target}) => panelFor(target));
271 // Panel events emitted after panel has being shown.
272 let shows = filter(panelEvents, ({type}) => type === "popupshown");
274 // Panel events emitted after panel became hidden.
275 let hides = filter(panelEvents, ({type}) => type === "popuphidden");
277 // Panel events emitted after content inside panel is ready. For different
278 // panels ready may mean different state based on `contentScriptWhen` attribute.
279 // Weather given event represents readyness is detected by `getAttachEventType`
280 // helper function.
281 let ready = filter(panelEvents, ({type, target}) =>
282 getAttachEventType(modelFor(panelFor(target))) === type);
284 // Styles should be always added as soon as possible, and doesn't makes them
285 // depends on `contentScriptWhen`
286 let start = filter(panelEvents, ({type}) => type === "document-element-inserted");
288 // Forward panel show / hide events to panel's own event listeners.
289 on(shows, "data", ({target}) => emit(panelFor(target), "show"));
291 on(hides, "data", ({target}) => emit(panelFor(target), "hide"));
293 on(ready, "data", ({target}) => {
294 let panel = panelFor(target);
295 let window = domPanel.getContentDocument(target).defaultView;
297 workerFor(panel).attach(window);
298 });
300 on(start, "data", ({target}) => {
301 let panel = panelFor(target);
302 let window = domPanel.getContentDocument(target).defaultView;
304 attach(styleFor(panel), window);
305 });