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.
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 | }); |