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/. */
6 ChromeUtils.defineESModuleGetters(lazy, {
8 "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs",
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).
16 export class NetworkResponse {
24 #headersTransmittedSize;
27 #totalTransmittedSize;
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
44 constructor(channel, params) {
45 this.#channel = channel;
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;
72 this.#isDataURL || this.#isCachedCSS
74 : this.#channel.responseStatusText;
77 get decodedBodySize() {
78 return this.#decodedBodySize;
81 get encodedBodySize() {
82 return this.#encodedBodySize;
86 return this.#getHeadersList();
89 get headersTransmittedSize() {
90 return this.#headersTransmittedSize;
94 return this.#fromCache;
97 get fromServiceWorker() {
98 return this.#fromServiceWorker;
102 return this.#getComputedMimeType();
106 return lazy.NetworkUtils.getProtocol(this.#channel);
109 get serializedURL() {
110 return this.#channel.URI.spec;
117 get statusMessage() {
118 return this.#statusMessage;
121 get totalTransmittedSize() {
122 return this.#totalTransmittedSize;
126 * Clear a response header from the responses's headers list.
128 * @param {string} name
131 clearResponseHeader(name) {
132 this.#channel.setResponseHeader(
134 "", // aValue="" as an empty value
135 false // aMerge=false to force clearing the header
140 * Set a response header
142 * @param {string} 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.
151 setResponseHeader(name, value, options) {
152 const { merge = false } = options;
153 this.#channel.setResponseHeader(name, value, merge);
156 setResponseStatus(options) {
157 let { status, statusText } = options;
158 if (status === null) {
159 status = this.#channel.responseStatus;
162 if (statusText === null) {
163 statusText = this.#channel.responseStatusText;
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;
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.
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.
188 setResponseSizes(sizes) {
189 const { decodedBodySize, encodedBodySize, totalTransmittedSize } = sizes;
190 this.#decodedBodySize = decodedBodySize;
191 this.#encodedBodySize = encodedBodySize;
192 this.#totalTransmittedSize = totalTransmittedSize;
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.
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,
211 statusMessage: this.statusMessage,
212 totalTransmittedSize: this.totalTransmittedSize,
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.
225 if (this.#isDataURL || this.#isCachedCSS) {
226 mimeType = this.#channel.contentType;
228 mimeType = this.#wrappedChannel.contentType;
230 const contentCharset = this.#channel.contentCharset;
231 if (contentCharset) {
232 mimeType += `;charset=${contentCharset}`;
235 // Ignore exceptions when reading contentType/contentCharset
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]);
249 this.#channel.visitResponseHeaders({
250 visitHeader(name, value) {
251 headers.push([name, value]);