addon-sdk/source/lib/sdk/content/worker.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 module.metadata = {
     7   "stability": "unstable"
     8 };
    10 const { Class } = require('../core/heritage');
    11 const { EventTarget } = require('../event/target');
    12 const { on, off, emit, setListeners } = require('../event/core');
    13 const {
    14   attach, detach, destroy
    15 } = require('./utils');
    16 const { method } = require('../lang/functional');
    17 const { Ci, Cu, Cc } = require('chrome');
    18 const unload = require('../system/unload');
    19 const events = require('../system/events');
    20 const { getInnerId } = require("../window/utils");
    21 const { WorkerSandbox } = require('./sandbox');
    22 const { getTabForWindow } = require('../tabs/helpers');
    24 // A weak map of workers to hold private attributes that
    25 // should not be exposed
    26 const workers = new WeakMap();
    28 let modelFor = (worker) => workers.get(worker);
    30 const ERR_DESTROYED =
    31   "Couldn't find the worker to receive this message. " +
    32   "The script may not be initialized yet, or may already have been unloaded.";
    34 const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
    35                    "until it is visible again.";
    37 /**
    38  * Message-passing facility for communication between code running
    39  * in the content and add-on process.
    40  * @see https://addons.mozilla.org/en-US/developers/docs/sdk/latest/modules/sdk/content/worker.html
    41  */
    42 const Worker = Class({
    43   implements: [EventTarget],
    44   initialize: function WorkerConstructor (options) {
    45     // Save model in weak map to not expose properties
    46     let model = createModel();
    47     workers.set(this, model);
    49     options = options || {};
    51     if ('contentScriptFile' in options)
    52       this.contentScriptFile = options.contentScriptFile;
    53     if ('contentScriptOptions' in options)
    54       this.contentScriptOptions = options.contentScriptOptions;
    55     if ('contentScript' in options)
    56       this.contentScript = options.contentScript;
    57     if ('injectInDocument' in options)
    58       this.injectInDocument = !!options.injectInDocument;
    60     setListeners(this, options);
    62     unload.ensure(this, "destroy");
    64     // Ensure that worker.port is initialized for contentWorker to be able
    65     // to send events during worker initialization.
    66     this.port = createPort(this);
    68     model.documentUnload = documentUnload.bind(this);
    69     model.pageShow = pageShow.bind(this);
    70     model.pageHide = pageHide.bind(this);
    72     if ('window' in options)
    73       attach(this, options.window);
    74   },
    76   /**
    77    * Sends a message to the worker's global scope. Method takes single
    78    * argument, which represents data to be sent to the worker. The data may
    79    * be any primitive type value or `JSON`. Call of this method asynchronously
    80    * emits `message` event with data value in the global scope of this
    81    * symbiont.
    82    *
    83    * `message` event listeners can be set either by calling
    84    * `self.on` with a first argument string `"message"` or by
    85    * implementing `onMessage` function in the global scope of this worker.
    86    * @param {Number|String|JSON} data
    87    */
    88   postMessage: function (...data) {
    89     let model = modelFor(this);
    90     let args = ['message'].concat(data);
    91     if (!model.inited) {
    92       model.earlyEvents.push(args);
    93       return;
    94     }
    95     processMessage.apply(null, [this].concat(args));
    96   },
    98   get url () {
    99     let model = modelFor(this);
   100     // model.window will be null after detach
   101     return model.window ? model.window.document.location.href : null;
   102   },
   104   get contentURL () {
   105     let model = modelFor(this);
   106     return model.window ? model.window.document.URL : null;
   107   },
   109   get tab () {
   110     let model = modelFor(this);
   111     // model.window will be null after detach
   112     if (model.window)
   113       return getTabForWindow(model.window);
   114     return null;
   115   },
   117   // Implemented to provide some of the previous features of exposing sandbox
   118   // so that Worker can be extended
   119   getSandbox: function () {
   120     return modelFor(this).contentWorker;
   121   },
   123   toString: function () { return '[object Worker]'; },
   124   attach: method(attach),
   125   detach: method(detach),
   126   destroy: method(destroy)
   127 });
   128 exports.Worker = Worker;
   130 attach.define(Worker, function (worker, window) {
   131   let model = modelFor(worker);
   132   model.window = window;
   133   // Track document unload to destroy this worker.
   134   // We can't watch for unload event on page's window object as it
   135   // prevents bfcache from working:
   136   // https://developer.mozilla.org/En/Working_with_BFCache
   137   model.windowID = getInnerId(model.window);
   138   events.on("inner-window-destroyed", model.documentUnload);
   140   // Listen to pagehide event in order to freeze the content script
   141   // while the document is frozen in bfcache:
   142   model.window.addEventListener("pageshow", model.pageShow, true);
   143   model.window.addEventListener("pagehide", model.pageHide, true);
   145   // will set model.contentWorker pointing to the private API:
   146   model.contentWorker = WorkerSandbox(worker, model.window);
   148   // Mainly enable worker.port.emit to send event to the content worker
   149   model.inited = true;
   150   model.frozen = false;
   152   // Fire off `attach` event
   153   emit(worker, 'attach', window);
   155   // Process all events and messages that were fired before the
   156   // worker was initialized.
   157   model.earlyEvents.forEach(args => processMessage.apply(null, [worker].concat(args)));
   158 });
   160 /**
   161  * Remove all internal references to the attached document
   162  * Tells _port to unload itself and removes all the references from itself.
   163  */
   164 detach.define(Worker, function (worker, reason) {
   165   let model = modelFor(worker);
   167   // maybe unloaded before content side is created
   168   if (model.contentWorker) {
   169     model.contentWorker.destroy(reason);
   170   }
   172   model.contentWorker = null;
   173   if (model.window) {
   174     model.window.removeEventListener("pageshow", model.pageShow, true);
   175     model.window.removeEventListener("pagehide", model.pageHide, true);
   176   }
   177   model.window = null;
   178   // This method may be called multiple times,
   179   // avoid dispatching `detach` event more than once
   180   if (model.windowID) {
   181     model.windowID = null;
   182     events.off("inner-window-destroyed", model.documentUnload);
   183     model.earlyEvents.length = 0;
   184     emit(worker, 'detach');
   185   }
   186   model.inited = false;
   187 });
   189 /**
   190  * Tells content worker to unload itself and
   191  * removes all the references from itself.
   192  */
   193 destroy.define(Worker, function (worker, reason) {
   194   detach(worker, reason);
   195   modelFor(worker).inited = true;
   196   // Specifying no type or listener removes all listeners
   197   // from target
   198   off(worker);
   199 });
   201 /**
   202  * Events fired by workers
   203  */
   204 function documentUnload ({ subject, data }) {
   205   let model = modelFor(this);
   206   let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
   207   if (innerWinID != model.windowID) return false;
   208   detach(this);
   209   return true;
   210 }
   212 function pageShow () {
   213   let model = modelFor(this);
   214   model.contentWorker.emitSync('pageshow');
   215   emit(this, 'pageshow');
   216   model.frozen = false;
   217 }
   219 function pageHide () {
   220   let model = modelFor(this);
   221   model.contentWorker.emitSync('pagehide');
   222   emit(this, 'pagehide');
   223   model.frozen = true;
   224 }
   226 /**
   227  * Fired from postMessage and emitEventToContent, or from the earlyMessage
   228  * queue when fired before the content is loaded. Sends arguments to
   229  * contentWorker if able
   230  */
   232 function processMessage (worker, ...args) {
   233   let model = modelFor(worker) || {};
   234   if (!model.contentWorker)
   235     throw new Error(ERR_DESTROYED);
   236   if (model.frozen)
   237     throw new Error(ERR_FROZEN);
   238   model.contentWorker.emit.apply(null, args);
   239 }
   241 function createModel () {
   242   return {
   243     // List of messages fired before worker is initialized
   244     earlyEvents: [],
   245     // Is worker connected to the content worker sandbox ?
   246     inited: false,
   247     // Is worker being frozen? i.e related document is frozen in bfcache.
   248     // Content script should not be reachable if frozen.
   249     frozen: true,
   250     /**
   251      * Reference to the content side of the worker.
   252      * @type {WorkerGlobalScope}
   253      */
   254     contentWorker: null,
   255     /**
   256      * Reference to the window that is accessible from
   257      * the content scripts.
   258      * @type {Object}
   259      */
   260     window: null
   261   };
   262 }
   264 function createPort (worker) {
   265   let port = EventTarget();
   266   port.emit = emitEventToContent.bind(null, worker);
   267   return port;
   268 }
   270 /**
   271  * Emit a custom event to the content script,
   272  * i.e. emit this event on `self.port`
   273  */
   274 function emitEventToContent (worker, ...eventArgs) {
   275   let model = modelFor(worker);
   276   let args = ['event'].concat(eventArgs);
   277   if (!model.inited) {
   278     model.earlyEvents.push(args);
   279     return;
   280   }
   281   processMessage.apply(null, [worker].concat(args));
   282 }

mercurial