Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / remote / shared / NetworkResponse.sys.mjs
blob5cabe7ff770ea3f47015ebbe93764b591fa2f859
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/. */
5 const lazy = {};
6 ChromeUtils.defineESModuleGetters(lazy, {
7   NetworkUtils:
8     "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs",
9 });
11 /**
12  * The NetworkResponse class is a wrapper around the internal channel which
13  * provides getters and methods closer to fetch's response concept
14  * (https://fetch.spec.whatwg.org/#concept-response).
15  */
16 export class NetworkResponse {
17   #channel;
18   #decodedBodySize;
19   #encodedBodySize;
20   #fromCache;
21   #fromServiceWorker;
22   #isCachedCSS;
23   #isDataURL;
24   #headersTransmittedSize;
25   #status;
26   #statusMessage;
27   #totalTransmittedSize;
28   #wrappedChannel;
30   /**
31    *
32    * @param {nsIChannel} channel
33    *     The channel for the response.
34    * @param {object} params
35    * @param {boolean} params.fromCache
36    *     Whether the response was read from the cache or not.
37    * @param {boolean} params.fromServiceWorker
38    *     Whether the response is coming from a service worker or not.
39    * @param {boolean} params.isCachedCSS
40    *     Whether the response is coming from a cached css file.
41    * @param {string=} params.rawHeaders
42    *     The response's raw (ie potentially compressed) headers
43    */
44   constructor(channel, params) {
45     this.#channel = channel;
46     const {
47       fromCache,
48       fromServiceWorker,
49       isCachedCSS,
50       rawHeaders = "",
51     } = params;
52     this.#fromCache = fromCache;
53     this.#fromServiceWorker = fromServiceWorker;
54     this.#isCachedCSS = isCachedCSS;
55     this.#isDataURL = this.#channel instanceof Ci.nsIDataChannel;
56     this.#wrappedChannel = ChannelWrapper.get(channel);
58     this.#decodedBodySize = 0;
59     this.#encodedBodySize = 0;
60     this.#headersTransmittedSize = rawHeaders.length;
61     this.#totalTransmittedSize = rawHeaders.length;
63     // See https://github.com/w3c/webdriver-bidi/issues/761
64     // For 304 responses, the response will be replaced by the cached response
65     // between responseStarted and responseCompleted, which will effectively
66     // change the status and statusMessage.
67     // Until the issue linked above has been discussed and closed, we will
68     // cache the status/statusMessage in order to ensure consistent values
69     // between responseStarted and responseCompleted.
70     this.#status = this.#isDataURL ? 200 : this.#channel.responseStatus;
71     this.#statusMessage =
72       this.#isDataURL || this.#isCachedCSS
73         ? "OK"
74         : this.#channel.responseStatusText;
75   }
77   get decodedBodySize() {
78     return this.#decodedBodySize;
79   }
81   get encodedBodySize() {
82     return this.#encodedBodySize;
83   }
85   get headers() {
86     return this.#getHeadersList();
87   }
89   get headersTransmittedSize() {
90     return this.#headersTransmittedSize;
91   }
93   get fromCache() {
94     return this.#fromCache;
95   }
97   get fromServiceWorker() {
98     return this.#fromServiceWorker;
99   }
101   get mimeType() {
102     return this.#getComputedMimeType();
103   }
105   get protocol() {
106     return lazy.NetworkUtils.getProtocol(this.#channel);
107   }
109   get serializedURL() {
110     return this.#channel.URI.spec;
111   }
113   get status() {
114     return this.#status;
115   }
117   get statusMessage() {
118     return this.#statusMessage;
119   }
121   get totalTransmittedSize() {
122     return this.#totalTransmittedSize;
123   }
125   /**
126    * Clear a response header from the responses's headers list.
127    *
128    * @param {string} name
129    *     The header's name.
130    */
131   clearResponseHeader(name) {
132     this.#channel.setResponseHeader(
133       name, // aName
134       "", // aValue="" as an empty value
135       false // aMerge=false to force clearing the header
136     );
137   }
139   /**
140    * Set a response header
141    *
142    * @param {string} name
143    *     The header's name.
144    * @param {string} value
145    *     The header's value.
146    * @param {object} options
147    * @param {boolean} options.merge
148    *     True if the value should be merged with the existing value, false if it
149    *     should override it. Defaults to false.
150    */
151   setResponseHeader(name, value, options) {
152     const { merge = false } = options;
153     this.#channel.setResponseHeader(name, value, merge);
154   }
156   setResponseStatus(options) {
157     let { status, statusText } = options;
158     if (status === null) {
159       status = this.#channel.responseStatus;
160     }
162     if (statusText === null) {
163       statusText = this.#channel.responseStatusText;
164     }
166     this.#channel.setResponseStatus(status, statusText);
168     // Update the cached status and statusMessage.
169     this.#status = this.#channel.responseStatus;
170     this.#statusMessage = this.#channel.responseStatusText;
171   }
173   /**
174    * Set the various response sizes for this response. Depending on how the
175    * completion was monitored (DevTools NetworkResponseListener or ChannelWrapper
176    * event), sizes need to be retrieved differently.
177    * There this is a simple setter and the actual logic to retrieve sizes is in
178    * NetworkEventRecord.
179    *
180    * @param {object} sizes
181    * @param {number} sizes.decodedBodySize
182    *     The decoded body size.
183    * @param {number} sizes.encodedBodySize
184    *     The encoded body size.
185    * @param {number} sizes.totalTransmittedSize
186    *     The total transmitted size.
187    */
188   setResponseSizes(sizes) {
189     const { decodedBodySize, encodedBodySize, totalTransmittedSize } = sizes;
190     this.#decodedBodySize = decodedBodySize;
191     this.#encodedBodySize = encodedBodySize;
192     this.#totalTransmittedSize = totalTransmittedSize;
193   }
195   /**
196    * Return a static version of the class instance.
197    * This method is used to prepare the data to be sent with the events for cached resources
198    * generated from the content process but need to be sent to the parent.
199    */
200   toJSON() {
201     return {
202       decodedBodySize: this.decodedBodySize,
203       headers: this.headers,
204       headersTransmittedSize: this.headersTransmittedSize,
205       encodedBodySize: this.encodedBodySize,
206       fromCache: this.fromCache,
207       mimeType: this.mimeType,
208       protocol: this.protocol,
209       serializedURL: this.serializedURL,
210       status: this.status,
211       statusMessage: this.statusMessage,
212       totalTransmittedSize: this.totalTransmittedSize,
213     };
214   }
216   #getComputedMimeType() {
217     // TODO: DevTools NetworkObserver is computing a similar value in
218     // addResponseContent, but uses an inconsistent implementation in
219     // addResponseStart. This approach can only be used as early as in
220     // addResponseHeaders. We should move this logic to the NetworkObserver and
221     // expose mimeType in addResponseStart. Bug 1809670.
222     let mimeType = "";
224     try {
225       if (this.#isDataURL || this.#isCachedCSS) {
226         mimeType = this.#channel.contentType;
227       } else {
228         mimeType = this.#wrappedChannel.contentType;
229       }
230       const contentCharset = this.#channel.contentCharset;
231       if (contentCharset) {
232         mimeType += `;charset=${contentCharset}`;
233       }
234     } catch (e) {
235       // Ignore exceptions when reading contentType/contentCharset
236     }
238     return mimeType;
239   }
241   #getHeadersList() {
242     const headers = [];
244     // According to the fetch spec for data URLs we can just hardcode
245     // "Content-Type" header.
246     if (this.#isDataURL) {
247       headers.push(["Content-Type", this.#channel.contentType]);
248     } else {
249       this.#channel.visitResponseHeaders({
250         visitHeader(name, value) {
251           headers.push([name, value]);
252         },
253       });
254     }
256     return headers;
257   }