diff --git a/doc/api/inspector.md b/doc/api/inspector.md index 6886cc6a7fe467..0d8b46f701e9f1 100644 --- a/doc/api/inspector.md +++ b/doc/api/inspector.md @@ -557,6 +557,22 @@ This feature is only available with the `--experimental-network-inspection` flag Broadcasts the `Network.loadingFinished` event to connected frontends. This event indicates that HTTP request has finished loading. +### `inspector.Network.loadingFailed([params])` + + + +> Stability: 1 - Experimental + +* `params` {Object} + +This feature is only available with the `--experimental-network-inspection` flag enabled. + +Broadcasts the `Network.loadingFailed` event to connected frontends. This event indicates that +HTTP request has failed to load. + ## Support of breakpoints The Chrome DevTools Protocol [`Debugger` domain][] allows an diff --git a/lib/inspector.js b/lib/inspector.js index b38bb1af974819..b623d96b68c3f7 100644 --- a/lib/inspector.js +++ b/lib/inspector.js @@ -201,6 +201,7 @@ const Network = { requestWillBeSent: (params) => broadcastToFrontend('Network.requestWillBeSent', params), responseReceived: (params) => broadcastToFrontend('Network.responseReceived', params), loadingFinished: (params) => broadcastToFrontend('Network.loadingFinished', params), + loadingFailed: (params) => broadcastToFrontend('Network.loadingFailed', params), }; module.exports = { diff --git a/lib/internal/inspector_network_tracking.js b/lib/internal/inspector_network_tracking.js index ab6dd789a3612e..df3d2e5bf8e539 100644 --- a/lib/internal/inspector_network_tracking.js +++ b/lib/internal/inspector_network_tracking.js @@ -49,6 +49,19 @@ function onClientRequestStart({ request }) { }); } +function onClientRequestError({ request, error }) { + if (typeof request._inspectorRequestId !== 'string') { + return; + } + const timestamp = DateNow() / 1000; + Network.loadingFailed({ + requestId: request._inspectorRequestId, + timestamp, + type: 'Other', + errorText: error.message, + }); +} + function onClientResponseFinish({ request, response }) { if (typeof request._inspectorRequestId !== 'string') { return; @@ -80,11 +93,13 @@ function enable() { Network = require('inspector').Network; } dc.subscribe('http.client.request.start', onClientRequestStart); + dc.subscribe('http.client.request.error', onClientRequestError); dc.subscribe('http.client.response.finish', onClientResponseFinish); } function disable() { dc.unsubscribe('http.client.request.start', onClientRequestStart); + dc.unsubscribe('http.client.request.error', onClientRequestError); dc.unsubscribe('http.client.response.finish', onClientResponseFinish); } diff --git a/src/inspector/network_agent.cc b/src/inspector/network_agent.cc index 6c1de24a2a1cab..f8b42b29158572 100644 --- a/src/inspector/network_agent.cc +++ b/src/inspector/network_agent.cc @@ -33,6 +33,7 @@ NetworkAgent::NetworkAgent(NetworkInspector* inspector) : inspector_(inspector) { event_notifier_map_["requestWillBeSent"] = &NetworkAgent::requestWillBeSent; event_notifier_map_["responseReceived"] = &NetworkAgent::responseReceived; + event_notifier_map_["loadingFailed"] = &NetworkAgent::loadingFailed; event_notifier_map_["loadingFinished"] = &NetworkAgent::loadingFinished; } @@ -117,6 +118,20 @@ void NetworkAgent::responseReceived( createResponse(url, status, statusText, std::move(headers))); } +void NetworkAgent::loadingFailed( + std::unique_ptr params) { + String request_id; + params->getString("requestId", &request_id); + double timestamp; + params->getDouble("timestamp", ×tamp); + String type; + params->getString("type", &type); + String error_text; + params->getString("errorText", &error_text); + + frontend_->loadingFailed(request_id, timestamp, type, error_text); +} + void NetworkAgent::loadingFinished( std::unique_ptr params) { String request_id; diff --git a/src/inspector/network_agent.h b/src/inspector/network_agent.h index ba113baf062b70..8d0e71c49b440d 100644 --- a/src/inspector/network_agent.h +++ b/src/inspector/network_agent.h @@ -29,6 +29,8 @@ class NetworkAgent : public Network::Backend { void responseReceived(std::unique_ptr params); + void loadingFailed(std::unique_ptr params); + void loadingFinished(std::unique_ptr params); private: diff --git a/src/inspector/node_protocol.pdl b/src/inspector/node_protocol.pdl index 9c442c008dcb09..d5b50dc81b40f1 100644 --- a/src/inspector/node_protocol.pdl +++ b/src/inspector/node_protocol.pdl @@ -180,6 +180,17 @@ experimental domain Network # Response data. Response response + event loadingFailed + parameters + # Request identifier. + RequestId requestId + # Timestamp. + MonotonicTime timestamp + # Resource type. + ResourceType type + # Error message. + string errorText + event loadingFinished parameters # Request identifier. diff --git a/test/parallel/test-inspector-emit-protocol-event.js b/test/parallel/test-inspector-emit-protocol-event.js index fe8e1bc0d15d57..eda95d34e57620 100644 --- a/test/parallel/test-inspector-emit-protocol-event.js +++ b/test/parallel/test-inspector-emit-protocol-event.js @@ -62,6 +62,15 @@ const EXPECTED_EVENTS = { timestamp: 1000, } }, + { + name: 'loadingFailed', + params: { + requestId: 'request-id-1', + timestamp: 1000, + type: 'Document', + errorText: 'Failed to load resource' + } + }, ] }; diff --git a/test/parallel/test-inspector-network-domain.js b/test/parallel/test-inspector-network-domain.js index 982de9e6314407..d2a56dca95a4ff 100644 --- a/test/parallel/test-inspector-network-domain.js +++ b/test/parallel/test-inspector-network-domain.js @@ -5,6 +5,7 @@ const common = require('../common'); common.skipIfInspectorDisabled(); const assert = require('node:assert'); +const { addresses } = require('../common/internet'); const fixtures = require('../common/fixtures'); const http = require('node:http'); const https = require('node:https'); @@ -144,11 +145,50 @@ const testHttpsGet = () => new Promise((resolve, reject) => { }, common.mustCall()); }); +const testHttpError = () => new Promise((resolve, reject) => { + session.on('Network.requestWillBeSent', common.mustCall()); + session.on('Network.loadingFailed', common.mustCall(({ params }) => { + assert.ok(params.requestId.startsWith('node-network-event-')); + assert.strictEqual(typeof params.timestamp, 'number'); + assert.strictEqual(params.type, 'Other'); + assert.strictEqual(typeof params.errorText, 'string'); + resolve(); + })); + session.on('Network.responseReceived', common.mustNotCall()); + session.on('Network.loadingFinished', common.mustNotCall()); + + http.get({ + host: addresses.INVALID_HOST, + }, common.mustNotCall()).on('error', common.mustCall()); +}); + + +const testHttpsError = () => new Promise((resolve, reject) => { + session.on('Network.requestWillBeSent', common.mustCall()); + session.on('Network.loadingFailed', common.mustCall(({ params }) => { + assert.ok(params.requestId.startsWith('node-network-event-')); + assert.strictEqual(typeof params.timestamp, 'number'); + assert.strictEqual(params.type, 'Other'); + assert.strictEqual(typeof params.errorText, 'string'); + resolve(); + })); + session.on('Network.responseReceived', common.mustNotCall()); + session.on('Network.loadingFinished', common.mustNotCall()); + + https.get({ + host: addresses.INVALID_HOST, + }, common.mustNotCall()).on('error', common.mustCall()); +}); + const testNetworkInspection = async () => { await testHttpGet(); session.removeAllListeners(); await testHttpsGet(); session.removeAllListeners(); + await testHttpError(); + session.removeAllListeners(); + await testHttpsError(); + session.removeAllListeners(); }; httpServer.listen(0, () => {