summaryrefslogtreecommitdiffstats
path: root/remote/cdp/sessions/TabSession.sys.mjs
blob: 90d098fba458f626e24afe16fe64beb715a22814 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/* 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/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { Session } from "chrome://remote/content/cdp/sessions/Session.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.CDP)
);

/**
 * A session to communicate with a given tab
 */
export class TabSession extends Session {
  /**
   * @param {Connection} connection
   *        The connection used to communicate with the server.
   * @param {TabTarget} target
   *        The tab target to which this session communicates with.
   * @param {number=} id
   *        If this session isn't the default one used for the HTTP endpoint we
   *        connected to, the session requires an id to distinguish it from the default
   *        one. This id is used to filter our request, responses and events between
   *        all active sessions.
   *        For now, this is only passed by `Target.attachToTarget()`.
   *        Otherwise it will be undefined when you are connecting directly to
   *        a given Tab. i.e. connect directly to the WebSocket URL provided by
   *        /json/list HTTP endpoint.
   */
  constructor(connection, target, id) {
    super(connection, target, id);

    // Request id => { resolve, reject }
    this.requestPromises = new Map();

    this.registerFramescript(this.mm);

    this.target.browser.addEventListener("XULFrameLoaderCreated", this);
  }

  destructor() {
    super.destructor();

    this.requestPromises.clear();

    this.target.browser.removeEventListener("XULFrameLoaderCreated", this);

    // this.mm might be null if the browser of the TabTarget was already closed.
    // See Bug 1747301.
    this.mm?.sendAsyncMessage("remote:destroy", {
      browsingContextId: this.browsingContext.id,
    });

    this.mm?.removeMessageListener("remote:event", this);
    this.mm?.removeMessageListener("remote:result", this);
    this.mm?.removeMessageListener("remote:error", this);
  }

  execute(id, domain, command, params) {
    // Check if the domain and command is implemented in the parent
    // and execute it there. Otherwise forward the command to the content process
    // in order to try to execute it in the content process.
    if (this.domains.domainSupportsMethod(domain, command)) {
      return super.execute(id, domain, command, params);
    }
    return this.executeInChild(id, domain, command, params);
  }

  executeInChild(id, domain, command, params) {
    return new Promise((resolve, reject) => {
      // Save the promise's resolution and rejection handler in order to later
      // resolve this promise once we receive the reply back from the content process.
      this.requestPromises.set(id, { resolve, reject });

      this.mm.sendAsyncMessage("remote:request", {
        browsingContextId: this.browsingContext.id,
        request: { id, domain, command, params },
      });
    });
  }

  get mm() {
    return this.target.mm;
  }

  get browsingContext() {
    return this.target.browsingContext;
  }

  /**
   * Register the framescript and listeners for the given message manager.
   *
   * @param {MessageManager} messageManager
   *     The message manager to use.
   */
  registerFramescript(messageManager) {
    messageManager.loadFrameScript(
      "chrome://remote/content/cdp/sessions/frame-script.js",
      false
    );

    messageManager.addMessageListener("remote:event", this);
    messageManager.addMessageListener("remote:result", this);
    messageManager.addMessageListener("remote:error", this);
  }

  // Event handler
  handleEvent = function ({ target, type }) {
    switch (type) {
      case "XULFrameLoaderCreated":
        if (target === this.target.browser) {
          lazy.logger.trace("Remoteness change detected");
          this.registerFramescript(this.mm);
        }
        break;
    }
  };

  // nsIMessageListener

  receiveMessage({ name, data }) {
    const { id, result, event, error } = data;

    switch (name) {
      case "remote:result":
        const { resolve } = this.requestPromises.get(id);
        resolve(result);
        this.requestPromises.delete(id);
        break;

      case "remote:event":
        this.connection.sendEvent(event.eventName, event.params, this.id);
        break;

      case "remote:error":
        const { reject } = this.requestPromises.get(id);
        reject(error);
        this.requestPromises.delete(id);
        break;
    }
  }
}