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

mercurial