зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1790370 - [bidi] Add basic support for network.responseStarted event r=webdriver-reviewers,Sasha,whimboo
Depends on D165431 Differential Revision: https://phabricator.services.mozilla.com/D165432
This commit is contained in:
Родитель
b3d6d9d969
Коммит
707344df29
|
@ -462,6 +462,8 @@ export class NetworkObserver {
|
|||
httpActivity.owner.addResponseStart(
|
||||
{
|
||||
httpVersion,
|
||||
protocol: this.#getProtocol(httpActivity),
|
||||
fromCache: this.#isFromCache(httpActivity),
|
||||
remoteAddress: "",
|
||||
remotePort: "",
|
||||
status,
|
||||
|
@ -1036,6 +1038,8 @@ export class NetworkObserver {
|
|||
const response = {};
|
||||
response.discardResponseBody = httpActivity.discardResponseBody;
|
||||
response.httpVersion = httpActivity.httpVersion;
|
||||
response.protocol = this.#getProtocol(httpActivity);
|
||||
response.fromCache = this.#isFromCache(httpActivity);
|
||||
response.remoteAddress = httpActivity.channel.remoteAddress;
|
||||
response.remotePort = httpActivity.channel.remotePort;
|
||||
response.status = httpActivity.responseStatus;
|
||||
|
@ -1083,6 +1087,66 @@ export class NetworkObserver {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the protocol for the provided httpActivity. Either the ALPN negotiated
|
||||
* protocol or as a fallback a protocol computed from the scheme and the
|
||||
* response status.
|
||||
*
|
||||
* TODO: The `protocol` is similar to another response property called
|
||||
* `httpVersion`. `httpVersion` is uppercase and purely computed from the
|
||||
* response status, whereas `protocol` uses nsIHttpChannel.protocolVersion by
|
||||
* default and otherwise falls back on `httpVersion`. Ideally we should merge
|
||||
* the two properties.
|
||||
*
|
||||
* @param {Object} httpActivity
|
||||
* The httpActivity object for which we need to get the protocol.
|
||||
*
|
||||
* @returns {string}
|
||||
* The protocol as a string.
|
||||
*/
|
||||
#getProtocol(httpActivity) {
|
||||
const { channel, httpVersion } = httpActivity;
|
||||
let protocol = "";
|
||||
try {
|
||||
const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
|
||||
// protocolVersion corresponds to ALPN negotiated protocol.
|
||||
protocol = httpChannel.protocolVersion;
|
||||
} catch (e) {
|
||||
// Ignore errors reading protocolVersion.
|
||||
}
|
||||
|
||||
if (["", "unknown"].includes(protocol)) {
|
||||
protocol = channel.URI.scheme;
|
||||
if (
|
||||
typeof httpVersion == "string" &&
|
||||
(protocol === "http" || protocol === "https")
|
||||
) {
|
||||
protocol = httpVersion.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
return protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the channel data for the provided http activity is loaded from the
|
||||
* cache or not.
|
||||
*
|
||||
* @param {Object} httpActivity
|
||||
* The httpActivity object for which we need to check the cache status.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* True if the channel data is loaded from the cache, false otherwise.
|
||||
*/
|
||||
#isFromCache(httpActivity) {
|
||||
const { channel } = httpActivity;
|
||||
if (channel instanceof Ci.nsICacheInfoChannel) {
|
||||
return channel.isFromCache();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#getBlockedTiming(timings) {
|
||||
if (timings.STATUS_RESOLVING && timings.STATUS_CONNECTING_TO) {
|
||||
return timings.STATUS_RESOLVING.first - timings.REQUEST_HEADER.first;
|
||||
|
|
|
@ -21,6 +21,7 @@ export class NetworkEventRecord {
|
|||
#requestData;
|
||||
#requestId;
|
||||
#responseData;
|
||||
#wrappedChannel;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -36,54 +37,77 @@ export class NetworkEventRecord {
|
|||
const browsingContext = BrowsingContext.get(networkEvent.browsingContextID);
|
||||
this.#contextId = lazy.TabManager.getIdForBrowsingContext(browsingContext);
|
||||
this.#channel = channel;
|
||||
this.#wrappedChannel = ChannelWrapper.get(channel);
|
||||
|
||||
this.#networkListener = networkListener;
|
||||
|
||||
// The wrappedChannel id remains identical across redirects, whereas
|
||||
// nsIChannel.channelId is different for each and every request.
|
||||
const wrappedChannel = ChannelWrapper.get(channel);
|
||||
this.#requestId = wrappedChannel.id.toString();
|
||||
this.#requestId = this.#wrappedChannel.id.toString();
|
||||
|
||||
// See the RequestData type definition for the full list of properties that
|
||||
// should be set on this object.
|
||||
this.#requestData = {
|
||||
bodySize: 0,
|
||||
bodySize: null,
|
||||
cookies: [],
|
||||
headers: [],
|
||||
headersSize: networkEvent.headersSize || null,
|
||||
method: networkEvent.method || null,
|
||||
headersSize: networkEvent.headersSize,
|
||||
method: networkEvent.method,
|
||||
request: this.#requestId,
|
||||
timings: {},
|
||||
url: networkEvent.url || null,
|
||||
url: networkEvent.url,
|
||||
};
|
||||
|
||||
// See the ResponseData type definition for the full list of properties that
|
||||
// should be set on this object.
|
||||
this.#responseData = {
|
||||
// encoded size (body)
|
||||
bodySize: null,
|
||||
content: {
|
||||
// decoded size
|
||||
size: null,
|
||||
},
|
||||
// encoded size (headers)
|
||||
headersSize: null,
|
||||
url: networkEvent.url,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This method has to be defined to match the event owner API of the
|
||||
* NetworkObserver. It will only be called once per network event record, so
|
||||
* despite the name we will simply store the headers and rawHeaders.
|
||||
*
|
||||
* Set network request headers.
|
||||
*
|
||||
* Required API for a NetworkObserver event owner.
|
||||
*
|
||||
* It will only be called once per network event record, so
|
||||
* despite the name we will simply store the headers and rawHeaders.
|
||||
*
|
||||
* @param {Array} headers
|
||||
* The request headers array.
|
||||
* @param {string=} rawHeaders
|
||||
* The raw headers source.
|
||||
*/
|
||||
addRequestHeaders(headers, rawHeaders) {
|
||||
this.#requestData.headers = headers;
|
||||
if (typeof headers == "object") {
|
||||
this.#requestData.headers = headers;
|
||||
}
|
||||
this.#requestData.rawHeaders = rawHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set network request cookies.
|
||||
*
|
||||
* This method has to be defined to match the event owner API of the
|
||||
* NetworkObserver. It will only be called once per network event record, so
|
||||
* Required API for a NetworkObserver event owner.
|
||||
*
|
||||
* It will only be called once per network event record, so
|
||||
* despite the name we will simply store the cookies.
|
||||
*
|
||||
* @param {Array} cookies
|
||||
* The request cookies array.
|
||||
*/
|
||||
addRequestCookies(cookies) {
|
||||
this.#requestData.cookies = cookies;
|
||||
if (typeof cookies == "object") {
|
||||
this.#requestData.cookies = cookies;
|
||||
}
|
||||
|
||||
// By design, the NetworkObserver will synchronously create a "network event"
|
||||
// then call addRequestHeaders and finally addRequestCookies.
|
||||
|
@ -94,16 +118,143 @@ export class NetworkEventRecord {
|
|||
this.#emitBeforeRequestSent();
|
||||
}
|
||||
|
||||
// Expected network event owner API from the NetworkObserver.
|
||||
addEventTimings() {}
|
||||
addRequestPostData() {}
|
||||
addResponseCache() {}
|
||||
addResponseContent() {}
|
||||
addResponseCookies() {}
|
||||
addResponseHeaders() {}
|
||||
addResponseStart() {}
|
||||
addSecurityInfo() {}
|
||||
addServerTimings() {}
|
||||
/**
|
||||
* Add network request POST data.
|
||||
*
|
||||
* Required API for a NetworkObserver event owner.
|
||||
*
|
||||
* @param {Object} postData
|
||||
* The request POST data.
|
||||
*/
|
||||
addRequestPostData(postData) {
|
||||
// Only the postData size is needed for RemoteAgent consumers.
|
||||
this.#requestData.bodySize = postData.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the initial network response information.
|
||||
*
|
||||
* Required API for a NetworkObserver event owner.
|
||||
*
|
||||
* @param {Object} response
|
||||
* The response information.
|
||||
* @param {string} rawHeaders
|
||||
* The raw headers source.
|
||||
*/
|
||||
addResponseStart(response, rawHeaders) {
|
||||
this.#responseData = {
|
||||
...this.#responseData,
|
||||
fromCache: response.fromCache,
|
||||
bodySize: response.bodySize,
|
||||
// Note: at this point we only have access to the headers size. Parsed
|
||||
// headers will be added in addResponseHeaders.
|
||||
headersSize: response.headersSize,
|
||||
bytesReceived: response.transferredSize,
|
||||
mimeType: response.mimeType,
|
||||
protocol: response.protocol,
|
||||
status: parseInt(response.status),
|
||||
statusText: response.statusText,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add connection security information.
|
||||
*
|
||||
* Required API for a NetworkObserver event owner.
|
||||
*
|
||||
* Not used for RemoteAgent.
|
||||
*
|
||||
* @param {Object} info
|
||||
* The object containing security information.
|
||||
* @param {boolean} isRacing
|
||||
* True if the corresponding channel raced the cache and network requests.
|
||||
*/
|
||||
addSecurityInfo(info, isRacing) {}
|
||||
|
||||
/**
|
||||
* Add network response headers.
|
||||
*
|
||||
* Required API for a NetworkObserver event owner.
|
||||
*
|
||||
* @param {Array} headers
|
||||
* The response headers array.
|
||||
*/
|
||||
addResponseHeaders(headers) {
|
||||
this.#responseData.headers = headers;
|
||||
// This should be triggered when all headers have been received, matching
|
||||
// the WebDriverBiDi response started trigger in `4.6. HTTP-network fetch`
|
||||
// from the fetch specification, based on the PR visible at
|
||||
// https://github.com/whatwg/fetch/pull/1540
|
||||
this.#emitResponseStarted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add network response cookies.
|
||||
*
|
||||
* Required API for a NetworkObserver event owner.
|
||||
*
|
||||
* Not used for RemoteAgent.
|
||||
*
|
||||
* @param {Array} cookies
|
||||
* The response cookies array.
|
||||
*/
|
||||
addResponseCookies(cookies) {}
|
||||
|
||||
/**
|
||||
* Add network event timings.
|
||||
*
|
||||
* Required API for a NetworkObserver event owner.
|
||||
*
|
||||
* Not used for RemoteAgent.
|
||||
*
|
||||
* @param {number} total
|
||||
* The total time for the request.
|
||||
* @param {Object} timings
|
||||
* The har-like timings.
|
||||
* @param {Object} offsets
|
||||
* The har-like timings, but as offset from the request start.
|
||||
* @param {Array} serverTimings
|
||||
* The server timings.
|
||||
*/
|
||||
addEventTimings(total, timings, offsets, serverTimings) {}
|
||||
|
||||
/**
|
||||
* Add response cache entry.
|
||||
*
|
||||
* Required API for a NetworkObserver event owner.
|
||||
*
|
||||
* Not used for RemoteAgent.
|
||||
*
|
||||
* @param {Object} options
|
||||
* An object which contains a single responseCache property.
|
||||
*/
|
||||
addResponseCache(options) {}
|
||||
|
||||
/**
|
||||
* Add response content.
|
||||
*
|
||||
* Required API for a NetworkObserver event owner.
|
||||
*
|
||||
* Not used for RemoteAgent.
|
||||
*
|
||||
* @param {Object} response
|
||||
* An object which represents the response content.
|
||||
* @param {Object} responseInfo
|
||||
* Additional meta data about the response.
|
||||
*/
|
||||
addResponseContent(response, responseInfo) {}
|
||||
|
||||
/**
|
||||
* Add server timings.
|
||||
*
|
||||
* Required API for a NetworkObserver event owner.
|
||||
*
|
||||
* Not used for RemoteAgent.
|
||||
*
|
||||
* @param {Array} serverTimings
|
||||
* The server timings.
|
||||
*/
|
||||
addServerTimings(serverTimings) {}
|
||||
|
||||
#emitBeforeRequestSent() {
|
||||
const timedChannel = this.#channel.QueryInterface(Ci.nsITimedChannel);
|
||||
|
@ -113,7 +264,19 @@ export class NetworkEventRecord {
|
|||
contextId: this.#contextId,
|
||||
redirectCount: timedChannel.redirectCount,
|
||||
requestData: this.#requestData,
|
||||
requestId: this.#requestId,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
#emitResponseStarted() {
|
||||
const timedChannel = this.#channel.QueryInterface(Ci.nsITimedChannel);
|
||||
this.#requestData.timings = this.#getTimingsFromTimedChannel(timedChannel);
|
||||
|
||||
this.#networkListener.emit("response-started", {
|
||||
contextId: this.#contextId,
|
||||
redirectCount: timedChannel.redirectCount,
|
||||
requestData: this.#requestData,
|
||||
responseData: this.#responseData,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
@ -129,8 +292,9 @@ export class NetworkEventRecord {
|
|||
* @param {number} requestTime
|
||||
* High definition timestamp for the request start time relative from the
|
||||
* time origin.
|
||||
* @returns {number} High definition timestamp for the request timing relative
|
||||
* to the start time of the request, or 0 if the provided timing was 0.
|
||||
* @returns {number}
|
||||
* High definition timestamp for the request timing relative to the start
|
||||
* time of the request, or 0 if the provided timing was 0.
|
||||
*/
|
||||
#convertTimestamp(timing, requestTime) {
|
||||
if (timing == 0) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys
|
|||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
NetworkListener:
|
||||
"chrome://remote/content/shared/listeners/NetworkListener.sys.mjs",
|
||||
TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
|
||||
|
@ -88,7 +89,8 @@ const InitiatorType = {
|
|||
|
||||
/**
|
||||
* @typedef {Object} RequestData
|
||||
* @property {number} bodySize
|
||||
* @property {number|null} bodySize
|
||||
* Defaults to null.
|
||||
* @property {Array<Cookie>} cookies
|
||||
* @property {Array<Header>} headers
|
||||
* @property {number} headersSize
|
||||
|
@ -109,6 +111,40 @@ const InitiatorType = {
|
|||
* @typedef {BaseParameters & BeforeRequestSentParametersProperties} BeforeRequestSentParameters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ResponseContent
|
||||
* @property {number|null} size
|
||||
* Defaults to null.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ResponseData
|
||||
* @property {string} url
|
||||
* @property {string} protocol
|
||||
* @property {number} status
|
||||
* @property {string} statusText
|
||||
* @property {boolean} fromCache
|
||||
* @property {Array<Header>} headers
|
||||
* @property {string} mimeType
|
||||
* @property {number} bytesReceived
|
||||
* @property {number|null} headersSize
|
||||
* Defaults to null.
|
||||
* @property {number|null} bodySize
|
||||
* Defaults to null.
|
||||
* @property {ResponseContent} content
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ResponseStartedParametersProperties
|
||||
* @property {ResponseData} response
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parameters for the ResponseStarted event
|
||||
*
|
||||
* @typedef {BaseParameters & ResponseStartedParametersProperties} ResponseStartedParameters
|
||||
*/
|
||||
|
||||
class NetworkModule extends Module {
|
||||
#beforeRequestSentMap;
|
||||
#networkListener;
|
||||
|
@ -128,10 +164,12 @@ class NetworkModule extends Module {
|
|||
|
||||
this.#networkListener = new lazy.NetworkListener();
|
||||
this.#networkListener.on("before-request-sent", this.#onBeforeRequestSent);
|
||||
this.#networkListener.on("response-started", this.#onResponseStarted);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.#networkListener.off("before-request-sent", this.#onBeforeRequestSent);
|
||||
this.#networkListener.off("response-started", this.#onResponseStarted);
|
||||
|
||||
this.#beforeRequestSentMap = null;
|
||||
this.#subscribedEvents = null;
|
||||
|
@ -178,6 +216,47 @@ class NetworkModule extends Module {
|
|||
);
|
||||
};
|
||||
|
||||
#onResponseStarted = (name, data) => {
|
||||
const {
|
||||
contextId,
|
||||
requestData,
|
||||
responseData,
|
||||
timestamp,
|
||||
redirectCount,
|
||||
} = data;
|
||||
|
||||
const isRedirect = redirectCount > 0;
|
||||
|
||||
const requestId = requestData.requestId;
|
||||
if (this.#beforeRequestSentMap.get(requestId) != redirectCount) {
|
||||
throw new lazy.error.UnknownError(
|
||||
`Redirect count of the request ${requestId} does not match the before request sent map`
|
||||
);
|
||||
}
|
||||
|
||||
const baseParameters = {
|
||||
context: contextId,
|
||||
isRedirect,
|
||||
redirectCount,
|
||||
// Bug 1805405: Handle the navigation id.
|
||||
navigation: null,
|
||||
request: requestData,
|
||||
timestamp,
|
||||
};
|
||||
|
||||
const responseStartedEvent = {
|
||||
...baseParameters,
|
||||
response: responseData,
|
||||
};
|
||||
|
||||
const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
|
||||
this.emitEvent(
|
||||
"network.responseStarted",
|
||||
responseStartedEvent,
|
||||
this.#getContextInfo(browsingContext)
|
||||
);
|
||||
};
|
||||
|
||||
#startListening(event) {
|
||||
if (this.#subscribedEvents.size == 0) {
|
||||
this.#networkListener.startListening();
|
||||
|
@ -234,7 +313,7 @@ class NetworkModule extends Module {
|
|||
}
|
||||
|
||||
static get supportedEvents() {
|
||||
return ["network.beforeRequestSent"];
|
||||
return ["network.beforeRequestSent", "network.responseStarted"];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче