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.

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

mercurial