remote/Session.jsm
author Andreas Tolfsen <ato@sny.no>
Thu, 21 Feb 2019 13:01:23 +0000
changeset 521100 59da827edf02
parent 521078 d7e26a1b6ef7
child 521102 b35c2a9a00d2
permissions -rw-r--r--
bug 1523104: remote: drop protocol schema validation; r=ochameau

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var EXPORTED_SYMBOLS = ["Session"];

const {Domain} = ChromeUtils.import("chrome://remote/content/Domain.jsm");
const {formatError} = ChromeUtils.import("chrome://remote/content/Error.jsm");

class Session {
  constructor(connection, target) {
    this.connection = connection;
    this.target = target;

    this.browsingContext = target.browser.browsingContext;
    this.messageManager = target.browser.messageManager;
    this.messageManager.loadFrameScript("chrome://remote/content/frame-script.js", false);
    this.messageManager.addMessageListener("remote-protocol:event", this);
    this.messageManager.addMessageListener("remote-protocol:result", this);
    this.messageManager.addMessageListener("remote-protocol:error", this);

    this.connection.onmessage = this.dispatch.bind(this);
  }

  destructor() {
    this.connection.onmessage = null;

    this.messageManager.sendAsyncMessage("remote-protocol:destroy", {
      browsingContextId: this.browsingContext.id,
    });
    this.messageManager.removeMessageListener("remote-protocol:event", this);
    this.messageManager.removeMessageListener("remote-protocol:result", this);
    this.messageManager.removeMessageListener("remote-protocol:error", this);
  }

  async dispatch({id, method, params}) {
    try {
      if (typeof id == "undefined") {
        throw new TypeError("Message missing 'id' field");
      }
      if (typeof method == "undefined") {
        throw new TypeError("Message missing 'method' field");
      }

      const [domainName, methodName] = split(method, ".", 1);
      const domain = Domain[domainName];
      if (!domain) {
        throw new TypeError("No such domain: " + domainName);
      }

      this.messageManager.sendAsyncMessage("remote-protocol:request", {
        browsingContextId: this.browsingContext.id,
        request: {id, domainName, methodName, params},
      });
    } catch (e) {
      const error = formatError(e, {stack: true});
      this.connection.send({id, error});
    }
  }

  receiveMessage({ name, data }) {
    switch (name) {
      case "remote-protocol:result":
        this.connection.send({
          id: data.id,
          result: data.result,
        });
        break;
      case "remote-protocol:event":
        this.connection.send(data.event);
        break;
      case "remote-protocol:error":
        const error = formatError(data.error, {stack: true});
        this.connection.send({id: data.id, error});
        break;
    }
  }
}

/**
 * Split s by sep, returning list of substrings.
 * If max is given, at most max splits are done.
 * If max is 0, there is no limit on the number of splits.
 */
function split(s, sep, max = 0) {
  if (typeof s != "string" ||
      typeof sep != "string" ||
      typeof max != "number") {
    throw new TypeError();
  }
  if (!Number.isInteger(max) || max < 0) {
    throw new RangeError();
  }

  const rv = [];
  let i = 0;

  while (rv.length < max) {
    const si = s.indexOf(sep, i);
    if (!si) {
      break;
    }

    rv.push(s.substring(i, si));
    i = si + sep.length;
  }

  rv.push(s.substring(i));
  return rv;
}