Sat, 03 Jan 2015 20:18:00 +0100
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 }