Example #1
0
function exportIntoContentScope(win, obj, defineAs) {
  let clone = Cu.createObjectIn(win, {
    defineAs: defineAs
  });

  let props = Object.getOwnPropertyNames(obj);
  for (let i = 0; i < props.length; i++) {
    let propName = props[i];
    let propValue = obj[propName];
    if (typeof propValue == "function") {
      Cu.exportFunction(propValue, clone, {
        defineAs: propName
      });
    }
  }
}
Example #2
0
// Exports variables that will be accessed by the non-privileged scripts.
function exportData(win, request) {
  let data = Cu.createObjectIn(win, {
    defineAs: "JSONView"
  });

  data.debug = debug;

  data.json = new win.Text();

  data.readyState = "uninitialized";

  let Locale = {
    $STR: key => {
      try {
        return jsonViewStrings.GetStringFromName(key);
      } catch (err) {
        console.error(err);
        return undefined;
      }
    }
  };
  data.Locale = Cu.cloneInto(Locale, win, {cloneFunctions: true});

  let headers = {
    response: [],
    request: []
  };
  // The request doesn't have to be always nsIHttpChannel
  // (e.g. in case of data: URLs)
  if (request instanceof Ci.nsIHttpChannel) {
    request.visitResponseHeaders({
      visitHeader: function (name, value) {
        headers.response.push({name: name, value: value});
      }
    });
    request.visitRequestHeaders({
      visitHeader: function (name, value) {
        headers.request.push({name: name, value: value});
      }
    });
  }
  data.headers = Cu.cloneInto(headers, win);

  return data;
}
Example #3
0
const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].
                        createInstance(Ci.nsIPrincipal);


const FakeCu = function() {
  const sandbox = Cu.Sandbox(systemPrincipal, {wantXrays: false});
  sandbox.toString = function() {
    return "[object BackstagePass]";
  }
  this.sandbox = sandbox;
}
FakeCu.prototype = {
  ["import"](url, scope) {
    const {sandbox} = this;
    sandbox.__URI__ = url;
    const target = Cu.createObjectIn(sandbox);
    target.toString = sandbox.toString;
    Cu.evalInSandbox(`(function(){` + readURISync(url) + `\n})`,
                     sandbox, "1.8", url).call(target);
    // Borrowed from mozJSComponentLoader.cpp to match errors closer.
    // https://github.com/mozilla/gecko-dev/blob/f6ca65e8672433b2ce1a0e7c31f72717930b5e27/js/xpconnect/loader/mozJSComponentLoader.cpp#L1205-L1208
    if (!Array.isArray(target.EXPORTED_SYMBOLS)) {
      throw Error("EXPORTED_SYMBOLS is not an array.");
    }

    for (let key of target.EXPORTED_SYMBOLS) {
      scope[key] = target[key];
    }

    return target;
  }
Example #4
0
  initialize: function WorkerSandbox(worker, window) {
    let model = {};
    sandboxes.set(this, model);
    model.worker = worker;
    // We receive a wrapped window, that may be an xraywrapper if it's content
    let proto = window;

    // TODO necessary?
    // Ensure that `emit` has always the right `this`
    this.emit = this.emit.bind(this);
    this.emitSync = this.emitSync.bind(this);

    // Use expanded principal for content-script if the content is a
    // regular web content for better isolation.
    // (This behavior can be turned off for now with the unsafe-content-script
    // flag to give addon developers time for making the necessary changes)
    // But prevent it when the Worker isn't used for a content script but for
    // injecting `addon` object into a Panel scope, for example.
    // That's because:
    // 1/ It is useless to use multiple domains as the worker is only used
    // to communicate with the addon,
    // 2/ By using it it would prevent the document to have access to any JS
    // value of the worker. As JS values coming from multiple domain principals
    // can't be accessed by 'mono-principals' (principal with only one domain).
    // Even if this principal is for a domain that is specified in the multiple
    // domain principal.
    let principals = window;
    let wantGlobalProperties = [];
    let isSystemPrincipal = secMan.isSystemPrincipal(
      window.document.nodePrincipal);
    if (!isSystemPrincipal && !requiresAddonGlobal(worker)) {
      if (EXPANDED_PRINCIPALS.length > 0) {
        // We have to replace XHR constructor of the content document
        // with a custom cross origin one, automagically added by platform code:
        delete proto.XMLHttpRequest;
        wantGlobalProperties.push('XMLHttpRequest');
      }
      if (!waiveSecurityMembrane)
        principals = EXPANDED_PRINCIPALS.concat(window);
    }

    // Create the sandbox and bind it to window in order for content scripts to
    // have access to all standard globals (window, document, ...)
    let content = sandbox(principals, {
      sandboxPrototype: proto,
      wantXrays: !requiresAddonGlobal(worker),
      wantGlobalProperties: wantGlobalProperties,
      wantExportHelpers: true,
      sameZoneAs: window,
      metadata: {
        SDKContentScript: true,
        'inner-window-id': getInnerId(window)
      }
    });
    model.sandbox = content;

    // We have to ensure that window.top and window.parent are the exact same
    // object than window object, i.e. the sandbox global object. But not
    // always, in case of iframes, top and parent are another window object.
    let top = window.top === window ? content : content.top;
    let parent = window.parent === window ? content : content.parent;
    merge(content, {
      // We need 'this === window === top' to be true in toplevel scope:
      get window() {
        return content;
      },
      get top() {
        return top;
      },
      get parent() {
        return 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!
    //
    // Note that because waivers aren't propagated between origins, we
    // need the unsafeWindow getter to live in the sandbox.
    var unsafeWindowGetter =
      new content.Function('return window.wrappedJSObject || window;');
    Object.defineProperty(content, 'unsafeWindow', {get: unsafeWindowGetter});

    // Load trusted code that will inject content script API.
    let ContentWorker = load(content, 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
    let onEvent = Cu.exportFunction(onContentEvent.bind(null, this), ContentWorker);
    let chromeAPI = createChromeAPI(ContentWorker);
    let result = Cu.waiveXrays(ContentWorker).inject(content, chromeAPI, onEvent, options);

    // Merge `emitToContent` into our private model of the
    // WorkerSandbox so we can communicate with content script
    model.emitToContent = result;

    let console = new PlainTextConsole(null, getInnerId(window));

    // Handle messages send by this script:
    setListeners(this, console);

    // Inject `addon` global into target document if document is trusted,
    // `addon` in document is equivalent to `self` in content script.
    if (requiresAddonGlobal(worker)) {
      Object.defineProperty(getUnsafeWindow(window), 'addon', {
          value: content.self,
          configurable: true
        }
      );
    }

    // Inject our `console` into target document if worker doesn't have a tab
    // (e.g Panel, PageWorker).
    // `worker.tab` can't be used because bug 804935.
    if (!isWindowInTab(window)) {
      let win = getUnsafeWindow(window);

      // export our chrome console to content window, as described here:
      // https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn
      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'),
        exception: genPropDesc('exception'),
        assert: genPropDesc('assert'),
        count: genPropDesc('count'),
        table: genPropDesc('table'),
        clear: genPropDesc('clear'),
        dirxml: genPropDesc('dirxml'),
        markTimeline: genPropDesc('markTimeline'),
        timeline: genPropDesc('timeline'),
        timelineEnd: genPropDesc('timelineEnd'),
        timeStamp: genPropDesc('timeStamp'),
      };

      Object.defineProperties(con, properties);
      Cu.makeObjectPropsNormal(con);

      win.console = con;
    };

    emit(events, "content-script-before-inserted", {
      window: window,
      worker: worker
    });

    // The order of `contentScriptFile` and `contentScript` evaluation is
    // intentional, so programs can load libraries like jQuery from script URLs
    // and use them in scripts.
    let contentScriptFile = ('contentScriptFile' in worker)
          ? worker.contentScriptFile
          : null,
        contentScript = ('contentScript' in worker)
          ? worker.contentScript
          : null;

    if (contentScriptFile)
      importScripts.apply(null, [this].concat(contentScriptFile));

    if (contentScript) {
      evaluateIn(
        this,
        Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript
      );
    }
  },
Example #5
0
  attach: function (window, innerId) {
    this._innerId = innerId,
    this._window = window;
    this._proto = Cu.createObjectIn(this._window);

    var id = this._scriptId;
    var uri = this._scriptCode;

    this._sandbox = sandbox(window, {
      sandboxName: uri,
      sandboxPrototype: this._proto,
      sameZoneAs: window,
      wantXrays: true,
      wantComponents: false,
      wantExportHelpers: false,
      metadata: {
        URI: uri,
        addonID: id,
        SDKDirectorScript: true,
        "inner-window-id": innerId
      }
    });

    // create a CommonJS module object which match the interface from addon-sdk
    // (addon-sdk/sources/lib/toolkit/loader.js#L678-L686)
    var module = Cu.cloneInto(Object.create(null, {
      id: { enumerable: true, value: id },
      uri: { enumerable: true, value: uri },
      exports: { enumerable: true, value: Cu.createObjectIn(this._sandbox) }
    }), this._sandbox);

    // create a console API object
    let directorScriptConsole = new PlainTextConsole(null, this._innerId);

    // inject CommonJS module globals into the sandbox prototype
    Object.defineProperties(this._proto, {
      module: { enumerable: true, value: module },
      exports: { enumerable: true, value: module.exports },
      console: {
        enumerable: true,
        value: Cu.cloneInto(directorScriptConsole, this._sandbox, { cloneFunctions: true })
      }
    });

    Object.defineProperties(this._sandbox, {
      require: {
        enumerable: true,
        value: Cu.cloneInto(function () {
          throw Error("NOT IMPLEMENTED");
        }, this._sandbox, { cloneFunctions: true })
      }
    });

    // TODO: if the debugger target is local, the debugger client could pass
    // to the director actor the resource url instead of the entire javascript source code.

    // evaluate the director script source in the sandbox
    evaluate(this._sandbox, this._scriptCode, "javascript:" + this._scriptCode);

    // prepare the messageport connected to the debugger client
    let { port1, port2 } = new this._window.MessageChannel();

    // prepare the unload callbacks queue
    var sandboxOnUnloadQueue = this._sandboxOnUnloadQueue = [];

    // create the attach options
    var attachOptions = this._attachOptions = Cu.createObjectIn(this._sandbox);
    Object.defineProperties(attachOptions, {
      port: { enumerable: true, value: port1 },
      window: { enumerable: true, value: window },
      scriptOptions: { enumerable: true, value: Cu.cloneInto(this._scriptOptions, this._sandbox) },
      onUnload: {
        enumerable: true,
        value: Cu.cloneInto(function (cb) {
          // collect unload callbacks
          if (typeof cb == "function") {
            sandboxOnUnloadQueue.push(cb);
          }
        }, this._sandbox, { cloneFunctions: true })
      }
    });

    // select the attach method
    var exports = this._proto.module.exports;
    if (this._scriptOptions && "attachMethod" in this._scriptOptions) {
      this._sandboxOnAttach = exports[this._scriptOptions.attachMethod];
    } else {
      this._sandboxOnAttach = exports;
    }

    if (typeof this._sandboxOnAttach !== "function") {
      throw Error("the configured attachMethod '" +
                  (this._scriptOptions.attachMethod || "module.exports") +
                  "' is not exported by the directorScript");
    }

    // call the attach method
    this._sandboxOnAttach.call(this._sandbox, attachOptions);

    return port2;
  },