network.sys.mjs (5757B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { WindowGlobalBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/WindowGlobalBiDiModule.sys.mjs"; 6 7 const lazy = {}; 8 9 ChromeUtils.defineESModuleGetters(lazy, { 10 BeforeStopRequestListener: 11 "chrome://remote/content/shared/listeners/BeforeStopRequestListener.sys.mjs", 12 CachedResourceListener: 13 "chrome://remote/content/shared/listeners/CachedResourceListener.sys.mjs", 14 DataChannelListener: 15 "chrome://remote/content/shared/listeners/DataChannelListener.sys.mjs", 16 NetworkRequest: "chrome://remote/content/shared/NetworkRequest.sys.mjs", 17 NetworkResponse: "chrome://remote/content/shared/NetworkResponse.sys.mjs", 18 }); 19 20 class NetworkModule extends WindowGlobalBiDiModule { 21 #beforeStopRequestListener; 22 #cachedResourceListener; 23 #dataChannelListener; 24 #subscribedEvents; 25 26 constructor(messageHandler) { 27 super(messageHandler); 28 29 this.#beforeStopRequestListener = new lazy.BeforeStopRequestListener( 30 this.messageHandler.context 31 ); 32 this.#beforeStopRequestListener.on( 33 "beforeStopRequest", 34 this.#onBeforeStopRequest 35 ); 36 37 this.#cachedResourceListener = new lazy.CachedResourceListener( 38 this.messageHandler.context 39 ); 40 this.#cachedResourceListener.on( 41 "cached-resource-sent", 42 this.#onCachedResourceSent 43 ); 44 45 this.#dataChannelListener = new lazy.DataChannelListener( 46 this.messageHandler.context 47 ); 48 this.#dataChannelListener.on( 49 "data-channel-opened", 50 this.#onDataChannelOpened 51 ); 52 53 // Set of event names which have active subscriptions. 54 this.#subscribedEvents = new Set(); 55 } 56 57 destroy() { 58 this.#beforeStopRequestListener.destroy(); 59 this.#cachedResourceListener.destroy(); 60 this.#dataChannelListener.destroy(); 61 this.#subscribedEvents = null; 62 } 63 64 #emitWindowGlobalNetworkResource(channel, request, response) { 65 this.messageHandler.emitEvent("network._windowGlobalNetworkResource", { 66 channelId: channel.channelId, 67 context: this.messageHandler.context, 68 request: request.toJSON(), 69 response: response.toJSON(), 70 }); 71 } 72 73 #onBeforeStopRequest = (event, data) => { 74 this.messageHandler.emitEvent("network._beforeStopRequest", { 75 channelId: data.channel.channelId, 76 contextId: this.messageHandler.contextId, 77 decodedBodySize: data.decodedBodySize, 78 }); 79 }; 80 81 #onCachedResourceSent = (event, data) => { 82 const request = new lazy.NetworkRequest(data.channel, { 83 eventRecord: this, 84 navigationManager: null, 85 }); 86 const response = new lazy.NetworkResponse(data.channel, { 87 fromCache: true, 88 fromServiceWorker: false, 89 isCachedResource: true, 90 }); 91 92 this.#emitWindowGlobalNetworkResource(data.channel, request, response); 93 }; 94 95 #onDataChannelOpened = (event, data) => { 96 const request = new lazy.NetworkRequest(data.channel, { 97 eventRecord: this, 98 navigationManager: null, 99 }); 100 const response = new lazy.NetworkResponse(data.channel, { 101 fromCache: false, 102 fromServiceWorker: false, 103 isCachedResource: false, 104 }); 105 106 this.#emitWindowGlobalNetworkResource(data.channel, request, response); 107 }; 108 109 #startListening() { 110 if (this.#subscribedEvents.size == 0) { 111 this.#beforeStopRequestListener.startListening(); 112 this.#cachedResourceListener.startListening(); 113 this.#dataChannelListener.startListening(); 114 } 115 } 116 117 #stopListening() { 118 if (this.#subscribedEvents.size == 0) { 119 this.#beforeStopRequestListener.stopListening(); 120 this.#cachedResourceListener.stopListening(); 121 this.#dataChannelListener.stopListening(); 122 } 123 } 124 125 #subscribeEvent(event) { 126 switch (event) { 127 case "network.beforeRequestSent": 128 this.#startListening(); 129 this.#subscribedEvents.add("network.beforeRequestSent"); 130 break; 131 case "network.responseStarted": 132 this.#startListening(); 133 this.#subscribedEvents.add("network.responseStarted"); 134 break; 135 case "network.responseCompleted": 136 this.#startListening(); 137 this.#subscribedEvents.add("network.responseCompleted"); 138 break; 139 } 140 } 141 142 #unsubscribeEvent(event) { 143 switch (event) { 144 case "network.beforeRequestSent": 145 this.#subscribedEvents.delete("network.beforeRequestSent"); 146 break; 147 case "network.responseStarted": 148 this.#subscribedEvents.delete("network.responseStarted"); 149 break; 150 case "network.responseCompleted": 151 this.#subscribedEvents.delete("network.responseCompleted"); 152 break; 153 } 154 155 this.#stopListening(); 156 } 157 158 /** 159 * Internal commands 160 */ 161 162 _applySessionData(params) { 163 // TODO: Bug 1775231. Move this logic to a shared module or an abstract 164 // class. 165 const { category } = params; 166 if (category === "event") { 167 const filteredSessionData = params.sessionData.filter(item => 168 this.messageHandler.matchesContext(item.contextDescriptor) 169 ); 170 for (const event of this.#subscribedEvents.values()) { 171 const hasSessionItem = filteredSessionData.some( 172 item => item.value === event 173 ); 174 // If there are no session items for this context, we should unsubscribe from the event. 175 if (!hasSessionItem) { 176 this.#unsubscribeEvent(event); 177 } 178 } 179 180 // Subscribe to all events, which have an item in SessionData. 181 for (const { value } of filteredSessionData) { 182 this.#subscribeEvent(value); 183 } 184 } 185 } 186 } 187 188 export const network = NetworkModule;