addon-sdk/source/lib/sdk/content/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/. */
     5 const ContentWorker = Object.freeze({
     6   // TODO: Bug 727854 Use same implementation than common JS modules,
     7   // i.e. EventEmitter module
     9   /**
    10    * Create an EventEmitter instance.
    11    */
    12   createEventEmitter: function createEventEmitter(emit) {
    13     let listeners = Object.create(null);
    14     let eventEmitter = Object.freeze({
    15       emit: emit,
    16       on: function on(name, callback) {
    17         if (typeof callback !== "function")
    18           return this;
    19         if (!(name in listeners))
    20           listeners[name] = [];
    21         listeners[name].push(callback);
    22         return this;
    23       },
    24       once: function once(name, callback) {
    25         eventEmitter.on(name, function onceCallback() {
    26           eventEmitter.removeListener(name, onceCallback);
    27           callback.apply(callback, arguments);
    28         });
    29       },
    30       removeListener: function removeListener(name, callback) {
    31         if (!(name in listeners))
    32           return;
    33         let index = listeners[name].indexOf(callback);
    34         if (index == -1)
    35           return;
    36         listeners[name].splice(index, 1);
    37       }
    38     });
    39     function onEvent(name) {
    40       if (!(name in listeners))
    41         return [];
    42       let args = Array.slice(arguments, 1);
    43       let results = [];
    44       for each (let callback in listeners[name]) {
    45         results.push(callback.apply(null, args));
    46       }
    47       return results;
    48     }
    49     function hasListenerFor(name) {
    50       if (!(name in listeners))
    51         return false;
    52       return listeners[name].length > 0;
    53     }
    54     return {
    55       eventEmitter: eventEmitter,
    56       emit: onEvent,
    57       hasListenerFor: hasListenerFor
    58     };
    59   },
    61   /**
    62    * Create an EventEmitter instance to communicate with chrome module
    63    * by passing only strings between compartments.
    64    * This function expects `emitToChrome` function, that allows to send
    65    * events to the chrome module. It returns the EventEmitter as `pipe`
    66    * attribute, and, `onChromeEvent` a function that allows chrome module
    67    * to send event into the EventEmitter.
    68    *
    69    *                  pipe.emit --> emitToChrome
    70    *              onChromeEvent --> callback registered through pipe.on
    71    */
    72   createPipe: function createPipe(emitToChrome) {
    73     function onEvent(type, ...args) {
    74       // JSON.stringify is buggy with cross-sandbox values,
    75       // it may return "{}" on functions. Use a replacer to match them correctly.
    76       let replacer = (k, v) =>
    77         typeof(v) === "function"
    78           ? (type === "console" ? Function.toString.call(v) : void(0))
    79           : v;
    81       let str = JSON.stringify([type, ...args], replacer);
    82       emitToChrome(str);
    83     }
    85     let { eventEmitter, emit, hasListenerFor } =
    86       ContentWorker.createEventEmitter(onEvent);
    88     return {
    89       pipe: eventEmitter,
    90       onChromeEvent: function onChromeEvent(array) {
    91         // We either receive a stringified array, or a real array.
    92         // We still allow to pass an array of objects, in WorkerSandbox.emitSync
    93         // in order to allow sending DOM node reference between content script
    94         // and modules (only used for context-menu API)
    95         let args = typeof array == "string" ? JSON.parse(array) : array;
    96         return emit.apply(null, args);
    97       },
    98       hasListenerFor: hasListenerFor
    99     };
   100   },
   102   injectConsole: function injectConsole(exports, pipe) {
   103     exports.console = Object.freeze({
   104       log: pipe.emit.bind(null, "console", "log"),
   105       info: pipe.emit.bind(null, "console", "info"),
   106       warn: pipe.emit.bind(null, "console", "warn"),
   107       error: pipe.emit.bind(null, "console", "error"),
   108       debug: pipe.emit.bind(null, "console", "debug"),
   109       exception: pipe.emit.bind(null, "console", "exception"),
   110       trace: pipe.emit.bind(null, "console", "trace"),
   111       time: pipe.emit.bind(null, "console", "time"),
   112       timeEnd: pipe.emit.bind(null, "console", "timeEnd")
   113     });
   114   },
   116   injectTimers: function injectTimers(exports, chromeAPI, pipe, console) {
   117     // wrapped functions from `'timer'` module.
   118     // Wrapper adds `try catch` blocks to the callbacks in order to
   119     // emit `error` event on a symbiont if exception is thrown in
   120     // the Worker global scope.
   121     // @see http://www.w3.org/TR/workers/#workerutils
   123     // List of all living timeouts/intervals
   124     let _timers = Object.create(null);
   126     // Keep a reference to original timeout functions
   127     let {
   128       setTimeout: chromeSetTimeout,
   129       setInterval: chromeSetInterval,
   130       clearTimeout: chromeClearTimeout,
   131       clearInterval: chromeClearInterval
   132     } = chromeAPI.timers;
   134     function registerTimer(timer) {
   135       let registerMethod = null;
   136       if (timer.kind == "timeout")
   137         registerMethod = chromeSetTimeout;
   138       else if (timer.kind == "interval")
   139         registerMethod = chromeSetInterval;
   140       else
   141         throw new Error("Unknown timer kind: " + timer.kind);
   143       if (typeof timer.fun == 'string') {
   144         let code = timer.fun;
   145         timer.fun = () => chromeAPI.sandbox.evaluate(exports, code);
   146       } else if (typeof timer.fun != 'function') {
   147         throw new Error('Unsupported callback type' + typeof timer.fun);
   148       }
   150       let id = registerMethod(onFire, timer.delay);
   151       function onFire() {
   152         try {
   153           if (timer.kind == "timeout")
   154             delete _timers[id];
   155           timer.fun.apply(null, timer.args);
   156         } catch(e) {
   157           console.exception(e);
   158           let wrapper = {
   159             instanceOfError: instanceOf(e, Error),
   160             value: e,
   161           };
   162           if (wrapper.instanceOfError) {
   163             wrapper.value = {
   164               message: e.message,
   165               fileName: e.fileName,
   166               lineNumber: e.lineNumber,
   167               stack: e.stack,
   168               name: e.name,
   169             };
   170           }
   171           pipe.emit('error', wrapper);
   172         }
   173       }
   174       _timers[id] = timer;
   175       return id;
   176     }
   178     // copied from sdk/lang/type.js since modules are not available here
   179     function instanceOf(value, Type) {
   180       var isConstructorNameSame;
   181       var isConstructorSourceSame;
   183       // If `instanceof` returned `true` we know result right away.
   184       var isInstanceOf = value instanceof Type;
   186       // If `instanceof` returned `false` we do ducktype check since `Type` may be
   187       // from a different sandbox. If a constructor of the `value` or a constructor
   188       // of the value's prototype has same name and source we assume that it's an
   189       // instance of the Type.
   190       if (!isInstanceOf && value) {
   191         isConstructorNameSame = value.constructor.name === Type.name;
   192         isConstructorSourceSame = String(value.constructor) == String(Type);
   193         isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) ||
   194                         instanceOf(Object.getPrototypeOf(value), Type);
   195       }
   196       return isInstanceOf;
   197     }
   199     function unregisterTimer(id) {
   200       if (!(id in _timers))
   201         return;
   202       let { kind } = _timers[id];
   203       delete _timers[id];
   204       if (kind == "timeout")
   205         chromeClearTimeout(id);
   206       else if (kind == "interval")
   207         chromeClearInterval(id);
   208       else
   209         throw new Error("Unknown timer kind: " + kind);
   210     }
   212     function disableAllTimers() {
   213       Object.keys(_timers).forEach(unregisterTimer);
   214     }
   216     exports.setTimeout = function ContentScriptSetTimeout(callback, delay) {
   217       return registerTimer({
   218         kind: "timeout",
   219         fun: callback,
   220         delay: delay,
   221         args: Array.slice(arguments, 2)
   222       });
   223     };
   224     exports.clearTimeout = function ContentScriptClearTimeout(id) {
   225       unregisterTimer(id);
   226     };
   228     exports.setInterval = function ContentScriptSetInterval(callback, delay) {
   229       return registerTimer({
   230         kind: "interval",
   231         fun: callback,
   232         delay: delay,
   233         args: Array.slice(arguments, 2)
   234       });
   235     };
   236     exports.clearInterval = function ContentScriptClearInterval(id) {
   237       unregisterTimer(id);
   238     };
   240     // On page-hide, save a list of all existing timers before disabling them,
   241     // in order to be able to restore them on page-show.
   242     // These events are fired when the page goes in/out of bfcache.
   243     // https://developer.mozilla.org/En/Working_with_BFCache
   244     let frozenTimers = [];
   245     pipe.on("pageshow", function onPageShow() {
   246       frozenTimers.forEach(registerTimer);
   247     });
   248     pipe.on("pagehide", function onPageHide() {
   249       frozenTimers = [];
   250       for (let id in _timers)
   251         frozenTimers.push(_timers[id]);
   252       disableAllTimers();
   253       // Some other pagehide listeners may register some timers that won't be
   254       // frozen as this particular pagehide listener is called first.
   255       // So freeze these timers on next cycle.
   256       chromeSetTimeout(function () {
   257         for (let id in _timers)
   258           frozenTimers.push(_timers[id]);
   259         disableAllTimers();
   260       }, 0);
   261     });
   263     // Unregister all timers when the page is destroyed
   264     // (i.e. when it is removed from bfcache)
   265     pipe.on("detach", function clearTimeouts() {
   266       disableAllTimers();
   267       _timers = {};
   268       frozenTimers = [];
   269     });
   270   },
   272   injectMessageAPI: function injectMessageAPI(exports, pipe, console) {
   274     let { eventEmitter: port, emit : portEmit } =
   275       ContentWorker.createEventEmitter(pipe.emit.bind(null, "event"));
   276     pipe.on("event", portEmit);
   278     let self = {
   279       port: port,
   280       postMessage: pipe.emit.bind(null, "message"),
   281       on: pipe.on.bind(null),
   282       once: pipe.once.bind(null),
   283       removeListener: pipe.removeListener.bind(null),
   284     };
   285     Object.defineProperty(exports, "self", {
   286       value: self
   287     });
   289     exports.on = function deprecatedOn() {
   290       console.error("DEPRECATED: The global `on()` function in content " +
   291                     "scripts is deprecated in favor of the `self.on()` " +
   292                     "function, which works the same. Replace calls to `on()` " +
   293                     "with calls to `self.on()`" +
   294                     "For more info on `self.on`, see " +
   295                     "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
   296       return self.on.apply(null, arguments);
   297     };
   299     // Deprecated use of `onMessage` from globals
   300     let onMessage = null;
   301     Object.defineProperty(exports, "onMessage", {
   302       get: function () onMessage,
   303       set: function (v) {
   304         if (onMessage)
   305           self.removeListener("message", onMessage);
   306         console.error("DEPRECATED: The global `onMessage` function in content" +
   307                       "scripts is deprecated in favor of the `self.on()` " +
   308                       "function. Replace `onMessage = function (data){}` " +
   309                       "definitions with calls to `self.on('message', " +
   310                       "function (data){})`. " +
   311                       "For more info on `self.on`, see " +
   312                       "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
   313         onMessage = v;
   314         if (typeof onMessage == "function")
   315           self.on("message", onMessage);
   316       }
   317     });
   318   },
   320   injectOptions: function (exports, options) {
   321     Object.defineProperty( exports.self, "options", { value: JSON.parse( options ) });
   322   },
   324   inject: function (exports, chromeAPI, emitToChrome, options) {
   325     let { pipe, onChromeEvent, hasListenerFor } =
   326       ContentWorker.createPipe(emitToChrome);
   328     ContentWorker.injectConsole(exports, pipe);
   329     ContentWorker.injectTimers(exports, chromeAPI, pipe, exports.console);
   330     ContentWorker.injectMessageAPI(exports, pipe, exports.console);
   331     if ( options !== undefined ) {
   332       ContentWorker.injectOptions(exports, options);
   333     }
   335     Object.freeze( exports.self );
   337     return {
   338       emitToContent: onChromeEvent,
   339       hasListenerFor: hasListenerFor
   340     };
   341   }
   342 });

mercurial