196 lines
5.1 KiB
JavaScript
196 lines
5.1 KiB
JavaScript
/* 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";
|
|
|
|
const {
|
|
LongStringActor,
|
|
} = require("resource://devtools/server/actors/string.js");
|
|
|
|
const {
|
|
TYPES: { WEBSOCKET },
|
|
} = require("resource://devtools/server/actors/resources/index.js");
|
|
|
|
const webSocketEventService = Cc[
|
|
"@mozilla.org/websocketevent/service;1"
|
|
].getService(Ci.nsIWebSocketEventService);
|
|
|
|
class WebSocketWatcher {
|
|
constructor() {
|
|
this.windowIds = new Set();
|
|
// Maintains a map of all the connection channels per websocket
|
|
// The map item is keyed on the `webSocketSerialID` and stores
|
|
// the `httpChannelId` as value.
|
|
this.connections = new Map();
|
|
this.onWindowReady = this.onWindowReady.bind(this);
|
|
this.onWindowDestroy = this.onWindowDestroy.bind(this);
|
|
}
|
|
|
|
static createResource(wsMessageType, eventParams) {
|
|
return {
|
|
resourceType: WEBSOCKET,
|
|
wsMessageType,
|
|
...eventParams,
|
|
};
|
|
}
|
|
|
|
static prepareFramePayload(targetActor, frame) {
|
|
const payload = new LongStringActor(targetActor.conn, frame.payload);
|
|
targetActor.manage(payload);
|
|
return payload.form();
|
|
}
|
|
|
|
watch(targetActor, { onAvailable }) {
|
|
this.targetActor = targetActor;
|
|
this.onAvailable = onAvailable;
|
|
|
|
for (const window of this.targetActor.windows) {
|
|
const { innerWindowId } = window.windowGlobalChild;
|
|
this.startListening(innerWindowId);
|
|
}
|
|
|
|
// On navigate/reload we should re-start listening with the
|
|
// new `innerWindowID`
|
|
this.targetActor.on("window-ready", this.onWindowReady);
|
|
this.targetActor.on("window-destroyed", this.onWindowDestroy);
|
|
}
|
|
|
|
onWindowReady({ window }) {
|
|
if (!this.targetActor.followWindowGlobalLifeCycle) {
|
|
const { innerWindowId } = window.windowGlobalChild;
|
|
this.startListening(innerWindowId);
|
|
}
|
|
}
|
|
|
|
onWindowDestroy({ id }) {
|
|
this.stopListening(id);
|
|
}
|
|
|
|
startListening(innerWindowId) {
|
|
if (!this.windowIds.has(innerWindowId)) {
|
|
this.windowIds.add(innerWindowId);
|
|
webSocketEventService.addListener(innerWindowId, this);
|
|
}
|
|
}
|
|
|
|
stopListening(innerWindowId) {
|
|
if (this.windowIds.has(innerWindowId)) {
|
|
this.windowIds.delete(innerWindowId);
|
|
if (!webSocketEventService.hasListenerFor(innerWindowId)) {
|
|
// The listener might have already been cleaned up on `window-destroy`.
|
|
console.warn(
|
|
"Already stopped listening to websocket events for this window."
|
|
);
|
|
return;
|
|
}
|
|
webSocketEventService.removeListener(innerWindowId, this);
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
for (const id of this.windowIds) {
|
|
this.stopListening(id);
|
|
}
|
|
this.targetActor.off("window-ready", this.onWindowReady);
|
|
this.targetActor.off("window-destroyed", this.onWindowDestroy);
|
|
}
|
|
|
|
// methods for the nsIWebSocketEventService
|
|
webSocketCreated() {}
|
|
|
|
webSocketOpened(
|
|
webSocketSerialID,
|
|
effectiveURI,
|
|
protocols,
|
|
extensions,
|
|
httpChannelId
|
|
) {
|
|
this.connections.set(webSocketSerialID, httpChannelId);
|
|
const resource = WebSocketWatcher.createResource("webSocketOpened", {
|
|
httpChannelId,
|
|
effectiveURI,
|
|
protocols,
|
|
extensions,
|
|
});
|
|
|
|
this.onAvailable([resource]);
|
|
}
|
|
|
|
webSocketMessageAvailable() {}
|
|
|
|
webSocketClosed(webSocketSerialID, wasClean, code, reason) {
|
|
const httpChannelId = this.connections.get(webSocketSerialID);
|
|
this.connections.delete(webSocketSerialID);
|
|
|
|
const resource = WebSocketWatcher.createResource("webSocketClosed", {
|
|
httpChannelId,
|
|
wasClean,
|
|
code,
|
|
reason,
|
|
});
|
|
|
|
this.onAvailable([resource]);
|
|
}
|
|
|
|
frameReceived(webSocketSerialID, frame) {
|
|
const httpChannelId = this.connections.get(webSocketSerialID);
|
|
if (!httpChannelId) {
|
|
return;
|
|
}
|
|
|
|
const payload = WebSocketWatcher.prepareFramePayload(
|
|
this.targetActor,
|
|
frame
|
|
);
|
|
const resource = WebSocketWatcher.createResource("frameReceived", {
|
|
httpChannelId,
|
|
data: {
|
|
type: "received",
|
|
payload,
|
|
timeStamp: frame.timeStamp,
|
|
finBit: frame.finBit,
|
|
rsvBit1: frame.rsvBit1,
|
|
rsvBit2: frame.rsvBit2,
|
|
rsvBit3: frame.rsvBit3,
|
|
opCode: frame.opCode,
|
|
mask: frame.mask,
|
|
maskBit: frame.maskBit,
|
|
},
|
|
});
|
|
|
|
this.onAvailable([resource]);
|
|
}
|
|
|
|
frameSent(webSocketSerialID, frame) {
|
|
const httpChannelId = this.connections.get(webSocketSerialID);
|
|
|
|
if (!httpChannelId) {
|
|
return;
|
|
}
|
|
|
|
const payload = WebSocketWatcher.prepareFramePayload(
|
|
this.targetActor,
|
|
frame
|
|
);
|
|
const resource = WebSocketWatcher.createResource("frameSent", {
|
|
httpChannelId,
|
|
data: {
|
|
type: "sent",
|
|
payload,
|
|
timeStamp: frame.timeStamp,
|
|
finBit: frame.finBit,
|
|
rsvBit1: frame.rsvBit1,
|
|
rsvBit2: frame.rsvBit2,
|
|
rsvBit3: frame.rsvBit3,
|
|
opCode: frame.opCode,
|
|
mask: frame.mask,
|
|
maskBit: frame.maskBit,
|
|
},
|
|
});
|
|
|
|
this.onAvailable([resource]);
|
|
}
|
|
}
|
|
|
|
module.exports = WebSocketWatcher;
|