remote/Connection.jsm
author Victor Porof <vporof@mozilla.com>
Fri, 17 May 2019 15:50:29 +0000
changeset 474353 edafe8d51cbf5f1585db25785ef204936777392e
parent 465452 ad30588da467bdd3288596be8db8e1a809a7cc47
child 481406 07ce31daca134d974d5d1ee2c86f6fdc307d9aa4
permissions -rw-r--r--
Bug 1551218 - Part 1: Enable running top-level eslint for the devtools debugger, r=Standard8,jlast Differential Revision: https://phabricator.services.mozilla.com/D30926

/* 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 = ["Connection"];

const {Log} = ChromeUtils.import("chrome://remote/content/Log.jsm");
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyGetter(this, "log", Log.get);
XPCOMUtils.defineLazyServiceGetter(this, "UUIDGen", "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");

class Connection {
  /**
   * @param WebSocketDebuggerTransport transport
   * @param httpd.js's Connection httpdConnection
   */
  constructor(transport, httpdConnection) {
    this.id = UUIDGen.generateUUID().toString();
    this.transport = transport;
    this.httpdConnection = httpdConnection;

    this.transport.hooks = this;
    this.transport.ready();

    this.defaultSession = null;
    this.sessions = new Map();
  }

  /**
   * Register a new Session to forward the messages to.
   * Session without any `id` attribute will be considered to be the
   * default one, to which messages without `sessionId` attribute are
   * forwarded to. Only one such session can be registered.
   *
   * @param Session session
   */
  registerSession(session) {
    if (!session.id) {
      if (this.defaultSession) {
        throw new Error("Default session is already set on Connection," +
                        "can't register another one.");
      }
      this.defaultSession = session;
    }
    this.sessions.set(session.id, session);
  }

  send(message) {
    log.trace(`<-(connection ${this.id}) ${JSON.stringify(message)}`);
    this.transport.send(message);
  }

  error(id, e) {
    const error = {
      message: e.message,
      data: e.stack,
    };
    this.send({id, error});
  }

  deserialize(data) {
    const id = data.id;
    const method = data.method;
    const params = data.params || {};
    return {id, method, params};
  }

  // transport hooks

  onPacket(packet) {
    log.trace(`(connection ${this.id})-> ${JSON.stringify(packet)}`);

    let message = {id: null};
    try {
      message = this.deserialize(packet);
      const { sessionId } = packet;
      if (!sessionId) {
        if (!this.defaultSession) {
          throw new Error(`Connection is missing a default Session.`);
        }
        this.defaultSession.onMessage(message);
      } else {
        const session = this.sessions.get(sessionId);
        if (!session) {
          throw new Error(`Session '${sessionId}' doesn't exists.`);
        }
        session.onMessage(message);
      }
    } catch (e) {
      log.warn(e);
      this.error(message.id, e);
    }
  }

  close() {
    this.transport.close();
    this.sessions.clear();

    // In addition to the WebSocket transport, we also have to close the Connection
    // used internaly within httpd.js. Otherwise the server doesn't shut down correctly
    // and keep these Connection instances alive.
    this.httpdConnection.close();
  }

  onClosed(status) {}

  toString() {
    return `[object Connection ${this.id}]`;
  }
}