From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- devtools/server/actors/resources/websockets.js | 196 +++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 devtools/server/actors/resources/websockets.js (limited to 'devtools/server/actors/resources/websockets.js') diff --git a/devtools/server/actors/resources/websockets.js b/devtools/server/actors/resources/websockets.js new file mode 100644 index 0000000000..5845357a9c --- /dev/null +++ b/devtools/server/actors/resources/websockets.js @@ -0,0 +1,196 @@ +/* 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(webSocketSerialID, uri, protocols) {} + + webSocketOpened( + webSocketSerialID, + effectiveURI, + protocols, + extensions, + httpChannelId + ) { + this.connections.set(webSocketSerialID, httpChannelId); + const resource = WebSocketWatcher.createResource("webSocketOpened", { + httpChannelId, + effectiveURI, + protocols, + extensions, + }); + + this.onAvailable([resource]); + } + + webSocketMessageAvailable(webSocketSerialID, data, messageType) {} + + 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; -- cgit v1.2.3