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.

     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 });

mercurial