123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660 |
- "use strict";
- module.metadata = {
- "stability": "deprecated"
- };
- const { Trait } = require('./traits');
- const { EventEmitter, EventEmitterTrait } = require('./events');
- const { Ci, Cu, Cc } = require('chrome');
- const timer = require('../timers');
- const { URL } = require('../url');
- const unload = require('../system/unload');
- const observers = require('../system/events');
- const { Cortex } = require('./cortex');
- const { sandbox, evaluate, load } = require("../loader/sandbox");
- const { merge } = require('../util/object');
- const xulApp = require("../system/xul-app");
- const { getInnerId } = require("../window/utils")
- const USE_JS_PROXIES = !xulApp.versionInRange(xulApp.platformVersion,
- "17.0a2", "*");
- const { getTabForWindow } = require('../tabs/helpers');
- const { getTabForContentWindow } = require('../tabs/utils');
- let prefix = module.uri.split('deprecated/traits-worker.js')[0];
- const CONTENT_WORKER_URL = prefix + 'content/content-worker.js';
- const permissions = require('@loader/options').metadata['permissions'] || {};
- const EXPANDED_PRINCIPALS = permissions['cross-domain-content'] || [];
- const JS_VERSION = '1.8';
- const ERR_DESTROYED =
- "Couldn't find the worker to receive this message. " +
- "The script may not be initialized yet, or may already have been unloaded.";
- const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
- "until it is visible again.";
- const WorkerSandbox = EventEmitter.compose({
-
- emit: function emit() {
-
-
- let array = Array.slice(arguments);
-
-
- function replacer(k, v) {
- return typeof v === "function" ? undefined : v;
- }
-
- let self = this;
- timer.setTimeout(function () {
- self._emitToContent(JSON.stringify(array, replacer));
- }, 0);
- },
-
- emitSync: function emitSync() {
- let args = Array.slice(arguments);
- return this._emitToContent(args);
- },
-
- hasListenerFor: function hasListenerFor(name) {
- return this._hasListenerFor(name);
- },
-
- _onContentEvent: function onContentEvent(args) {
-
- let self = this;
- timer.setTimeout(function () {
-
- self._emit.apply(self, JSON.parse(args));
- }, 0);
- },
-
- constructor: function WorkerSandbox(worker) {
- this._addonWorker = worker;
-
- this.emit = this.emit.bind(this);
- this.emitSync = this.emitSync.bind(this);
-
- let window = worker._window;
- let proto = window;
-
-
-
-
-
-
-
-
-
-
-
-
- let principals = window;
- let wantGlobalProperties = []
- if (EXPANDED_PRINCIPALS.length > 0 && !worker._injectInDocument) {
- principals = EXPANDED_PRINCIPALS.concat(window);
-
-
- delete proto.XMLHttpRequest;
- wantGlobalProperties.push("XMLHttpRequest");
- }
-
-
- let apiSandbox = sandbox(principals, { wantXrays: true, sameZoneAs: window });
- apiSandbox.console = console;
-
-
- let content = this._sandbox = sandbox(principals, {
- sandboxPrototype: proto,
- wantXrays: true,
- wantGlobalProperties: wantGlobalProperties,
- sameZoneAs: window,
- metadata: { SDKContentScript: true }
- });
-
-
-
- let top = window.top === window ? content : content.top;
- let parent = window.parent === window ? content : content.parent;
- merge(content, {
-
- get window() content,
- get top() top,
- get parent() parent,
- // Use the Greasemonkey naming convention to provide access to the
- // unwrapped window object so the content script can access document
- // JavaScript values.
- // NOTE: this functionality is experimental and may change or go away
- // at any time!
- get unsafeWindow() window.wrappedJSObject
- });
- // Load trusted code that will inject content script API.
- // We need to expose JS objects defined in same principal in order to
- // avoid having any kind of wrapper.
- load(apiSandbox, CONTENT_WORKER_URL);
- // prepare a clean `self.options`
- let options = 'contentScriptOptions' in worker ?
- JSON.stringify( worker.contentScriptOptions ) :
- undefined;
- // Then call `inject` method and communicate with this script
- // by trading two methods that allow to send events to the other side:
- // - `onEvent` called by content script
- // - `result.emitToContent` called by addon script
- // Bug 758203: We have to explicitely define `__exposedProps__` in order
- // to allow access to these chrome object attributes from this sandbox with
- // content priviledges
- // https://developer.mozilla.org/en/XPConnect_wrappers#Other_security_wrappers
- let chromeAPI = {
- timers: {
- setTimeout: timer.setTimeout,
- setInterval: timer.setInterval,
- clearTimeout: timer.clearTimeout,
- clearInterval: timer.clearInterval,
- __exposedProps__: {
- setTimeout: 'r',
- setInterval: 'r',
- clearTimeout: 'r',
- clearInterval: 'r'
- }
- },
- sandbox: {
- evaluate: evaluate,
- __exposedProps__: {
- evaluate: 'r',
- }
- },
- __exposedProps__: {
- timers: 'r',
- sandbox: 'r',
- }
- };
- let onEvent = this._onContentEvent.bind(this);
-
- let result = apiSandbox.ContentWorker.inject(content, chromeAPI, onEvent, options);
- this._emitToContent = result.emitToContent;
- this._hasListenerFor = result.hasListenerFor;
-
- let self = this;
-
- this.on("console", function consoleListener(kind) {
- console[kind].apply(console, Array.slice(arguments, 1));
- });
-
- this.on("message", function postMessage(data) {
-
- if (self._addonWorker)
- self._addonWorker._emit('message', data);
- });
-
- this.on("event", function portEmit(name, args) {
-
- if (self._addonWorker)
- self._addonWorker._onContentScriptEvent.apply(self._addonWorker, arguments);
- });
-
- this.on("error", function onError({instanceOfError, value}) {
- if (self._addonWorker) {
- let error = value;
- if (instanceOfError) {
- error = new Error(value.message, value.fileName, value.lineNumber);
- error.stack = value.stack;
- error.name = value.name;
- }
- self._addonWorker._emit('error', error);
- }
- });
-
-
- if (worker._injectInDocument) {
- let win = window.wrappedJSObject ? window.wrappedJSObject : window;
- Object.defineProperty(win, "addon", {
- value: content.self
- }
- );
- }
-
-
-
- if (!getTabForContentWindow(window)) {
- let win = window.wrappedJSObject ? window.wrappedJSObject : window;
-
-
-
-
-
-
- let con = Cu.createObjectIn(win);
- let genPropDesc = function genPropDesc(fun) {
- return { enumerable: true, configurable: true, writable: true,
- value: console[fun] };
- }
- const properties = {
- log: genPropDesc('log'),
- info: genPropDesc('info'),
- warn: genPropDesc('warn'),
- error: genPropDesc('error'),
- debug: genPropDesc('debug'),
- trace: genPropDesc('trace'),
- dir: genPropDesc('dir'),
- group: genPropDesc('group'),
- groupCollapsed: genPropDesc('groupCollapsed'),
- groupEnd: genPropDesc('groupEnd'),
- time: genPropDesc('time'),
- timeEnd: genPropDesc('timeEnd'),
- profile: genPropDesc('profile'),
- profileEnd: genPropDesc('profileEnd'),
- __noSuchMethod__: { enumerable: true, configurable: true, writable: true,
- value: function() {} }
- };
- Object.defineProperties(con, properties);
- Cu.makeObjectPropsNormal(con);
- win.console = con;
- };
-
-
-
- let contentScriptFile = ('contentScriptFile' in worker) ? worker.contentScriptFile
- : null,
- contentScript = ('contentScript' in worker) ? worker.contentScript : null;
- if (contentScriptFile) {
- if (Array.isArray(contentScriptFile))
- this._importScripts.apply(this, contentScriptFile);
- else
- this._importScripts(contentScriptFile);
- }
- if (contentScript) {
- this._evaluate(
- Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript
- );
- }
- },
- destroy: function destroy() {
- this.emitSync("detach");
- this._sandbox = null;
- this._addonWorker = null;
- },
-
- _sandbox: null,
-
- _addonWorker: null,
-
- _evaluate: function(code, filename) {
- try {
- evaluate(this._sandbox, code, filename || 'javascript:' + code);
- }
- catch(e) {
- this._addonWorker._emit('error', e);
- }
- },
-
- _importScripts: function _importScripts(url) {
- let urls = Array.slice(arguments, 0);
- for each (let contentScriptFile in urls) {
- try {
- let uri = URL(contentScriptFile);
- if (uri.scheme === 'resource')
- load(this._sandbox, String(uri));
- else
- throw Error("Unsupported `contentScriptFile` url: " + String(uri));
- }
- catch(e) {
- this._addonWorker._emit('error', e);
- }
- }
- }
- });
- const Worker = EventEmitter.compose({
- on: Trait.required,
- _removeAllListeners: Trait.required,
-
- get _earlyEvents() {
- delete this._earlyEvents;
- this._earlyEvents = [];
- return this._earlyEvents;
- },
-
- postMessage: function (data) {
- let args = ['message'].concat(Array.slice(arguments));
- if (!this._inited) {
- this._earlyEvents.push(args);
- return;
- }
- processMessage.apply(this, args);
- },
-
- get port() {
-
-
-
- this._port = EventEmitterTrait.create({
- emit: this._emitEventToContent.bind(this)
- });
-
-
-
-
- delete this._public.port;
- this._public.port = Cortex(this._port);
-
- delete this.port;
- this.port = this._public.port;
- return this._port;
- },
-
- _port: null,
-
- _emitEventToContent: function () {
- let args = ['event'].concat(Array.slice(arguments));
- if (!this._inited) {
- this._earlyEvents.push(args);
- return;
- }
- processMessage.apply(this, args);
- },
-
- _inited: false,
-
-
- _frozen: true,
- constructor: function Worker(options) {
- options = options || {};
- if ('contentScriptFile' in options)
- this.contentScriptFile = options.contentScriptFile;
- if ('contentScriptOptions' in options)
- this.contentScriptOptions = options.contentScriptOptions;
- if ('contentScript' in options)
- this.contentScript = options.contentScript;
- this._setListeners(options);
- unload.ensure(this._public, "destroy");
-
-
- this.port;
- this._documentUnload = this._documentUnload.bind(this);
- this._pageShow = this._pageShow.bind(this);
- this._pageHide = this._pageHide.bind(this);
- if ("window" in options) this._attach(options.window);
- },
- _setListeners: function(options) {
- if ('onError' in options)
- this.on('error', options.onError);
- if ('onMessage' in options)
- this.on('message', options.onMessage);
- if ('onDetach' in options)
- this.on('detach', options.onDetach);
- },
- _attach: function(window) {
- this._window = window;
-
-
-
-
- this._windowID = getInnerId(this._window);
- observers.on("inner-window-destroyed", this._documentUnload);
-
-
- this._window.addEventListener("pageshow", this._pageShow, true);
- this._window.addEventListener("pagehide", this._pageHide, true);
-
- this._contentWorker = WorkerSandbox(this);
-
- this._inited = true;
- this._frozen = false;
-
-
- this._earlyEvents.forEach((function (args) {
- processMessage.apply(this, args);
- }).bind(this));
- },
- _documentUnload: function _documentUnload({ subject, data }) {
- let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
- if (innerWinID != this._windowID) return false;
- this._workerCleanup();
- return true;
- },
- _pageShow: function _pageShow() {
- this._contentWorker.emitSync("pageshow");
- this._emit("pageshow");
- this._frozen = false;
- },
- _pageHide: function _pageHide() {
- this._contentWorker.emitSync("pagehide");
- this._emit("pagehide");
- this._frozen = true;
- },
- get url() {
-
- return this._window ? this._window.document.location.href : null;
- },
- get tab() {
-
- if (this._window)
- return getTabForWindow(this._window);
- return null;
- },
-
- destroy: function destroy() {
- this._workerCleanup();
- this._inited = true;
- this._removeAllListeners();
- },
-
- _workerCleanup: function _workerCleanup() {
-
-
- if (this._contentWorker)
- this._contentWorker.destroy();
- this._contentWorker = null;
- if (this._window) {
- this._window.removeEventListener("pageshow", this._pageShow, true);
- this._window.removeEventListener("pagehide", this._pageHide, true);
- }
- this._window = null;
-
-
- if (this._windowID) {
- this._windowID = null;
- observers.off("inner-window-destroyed", this._documentUnload);
- this._earlyEvents.length = 0;
- this._emit("detach");
- }
- this._inited = false;
- },
-
- _onContentScriptEvent: function _onContentScriptEvent() {
- this._port._emit.apply(this._port, arguments);
- },
-
- _contentWorker: null,
-
- _window: null,
-
- _injectInDocument: false
- });
- function processMessage () {
- if (!this._contentWorker)
- throw new Error(ERR_DESTROYED);
- if (this._frozen)
- throw new Error(ERR_FROZEN);
- this._contentWorker.emit.apply(null, Array.slice(arguments));
- }
- exports.Worker = Worker;
|