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.

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

mercurial