diff --git a/devtools/client/locales/en-US/netmonitor.properties b/devtools/client/locales/en-US/netmonitor.properties index d12d9db7abab..3d65e5ca736e 100644 --- a/devtools/client/locales/en-US/netmonitor.properties +++ b/devtools/client/locales/en-US/netmonitor.properties @@ -1071,6 +1071,10 @@ netmonitor.context.editAndResend=Edit and Resend # for the "Edit and Resend" menu item displayed in the context menu for a request netmonitor.context.editAndResend.accesskey=E +# LOCALIZATION NOTE (netmonitor.context.blockURL): This is the label displayed +# on the context menu that blocks any requests matching the selected request's URL. +netmonitor.context.blockURL=Block URL + # LOCALIZATION NOTE (netmonitor.context.newTab): This is the label # for the Open in New Tab menu item displayed in the context menu of the # network container diff --git a/devtools/client/netmonitor/src/actions/requests.js b/devtools/client/netmonitor/src/actions/requests.js index e82e09b7f96a..a189cf027e57 100644 --- a/devtools/client/netmonitor/src/actions/requests.js +++ b/devtools/client/netmonitor/src/actions/requests.js @@ -77,6 +77,22 @@ function sendCustomRequest(connector) { }; } +/** + * Tell the backend to block future requests that match the URL of the selected one. + */ +function blockSelectedRequestURL(connector) { + return (dispatch, getState) => { + const selected = getSelectedRequest(getState()); + + if (!selected) { + return; + } + + const { url } = selected; + connector.blockRequest({ url }); + }; +} + /** * Remove a request from the list. Supports removing only cloned requests with a * "isCustom" attribute. Other requests never need to be removed. @@ -104,6 +120,7 @@ function toggleRecording() { module.exports = { addRequest, + blockSelectedRequestURL, clearRequests, cloneSelectedRequest, removeSelectedCustomRequest, diff --git a/devtools/client/netmonitor/src/components/RequestListContent.js b/devtools/client/netmonitor/src/components/RequestListContent.js index 4a695ea38d87..e1ea0dabd6f0 100644 --- a/devtools/client/netmonitor/src/components/RequestListContent.js +++ b/devtools/client/netmonitor/src/components/RequestListContent.js @@ -50,6 +50,7 @@ const MAX_SCROLL_HEIGHT = 2147483647; class RequestListContent extends Component { static get propTypes() { return { + blockSelectedRequestURL: PropTypes.func.isRequired, connector: PropTypes.object.isRequired, columns: PropTypes.object.isRequired, networkDetailsOpen: PropTypes.bool.isRequired, @@ -265,12 +266,14 @@ class RequestListContent extends Component { if (!this.contextMenu) { const { + blockSelectedRequestURL, connector, cloneSelectedRequest, sendCustomRequest, openStatistics, } = this.props; this.contextMenu = new RequestListContextMenu({ + blockSelectedRequestURL, connector, cloneSelectedRequest, sendCustomRequest, @@ -359,6 +362,9 @@ module.exports = connect( requestFilterTypes: state.filters.requestFilterTypes, }), (dispatch, props) => ({ + blockSelectedRequestURL: () => { + dispatch(Actions.blockSelectedRequestURL(props.connector)); + }, cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()), sendCustomRequest: () => dispatch(Actions.sendCustomRequest(props.connector)), openStatistics: (open) => dispatch(Actions.openStatistics(props.connector, open)), diff --git a/devtools/client/netmonitor/src/connector/chrome-connector.js b/devtools/client/netmonitor/src/connector/chrome-connector.js index 6f406c702ba9..68fa3f038420 100644 --- a/devtools/client/netmonitor/src/connector/chrome-connector.js +++ b/devtools/client/netmonitor/src/connector/chrome-connector.js @@ -89,6 +89,15 @@ class ChromeConnector { // TODO : not support. currently didn't provide this feature in CDP API. } + /** + * Block future requests matching a filter. + * + * @param {object} filter request filter specifying what to block + */ + blockRequest(filter) { + // TODO: Implement for Chrome as well. + } + setPreferences() { // TODO : implement. } diff --git a/devtools/client/netmonitor/src/connector/firefox-connector.js b/devtools/client/netmonitor/src/connector/firefox-connector.js index 73a39b9a05b8..b62587ef4366 100644 --- a/devtools/client/netmonitor/src/connector/firefox-connector.js +++ b/devtools/client/netmonitor/src/connector/firefox-connector.js @@ -233,6 +233,15 @@ class FirefoxConnector { this.webConsoleClient.sendHTTPRequest(data).then(callback); } + /** + * Block future requests matching a filter. + * + * @param {object} filter request filter specifying what to block + */ + blockRequest(filter) { + return this.webConsoleClient.blockRequest(filter); + } + /** * Set network preferences to control network flow * diff --git a/devtools/client/netmonitor/src/connector/index.js b/devtools/client/netmonitor/src/connector/index.js index 2d75b7d16a8a..71c069d4ad41 100644 --- a/devtools/client/netmonitor/src/connector/index.js +++ b/devtools/client/netmonitor/src/connector/index.js @@ -96,6 +96,10 @@ class Connector { return this.connector.sendHTTPRequest(...arguments); } + blockRequest() { + return this.connector.blockRequest(...arguments); + } + setPreferences() { return this.connector.setPreferences(...arguments); } diff --git a/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js b/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js index 2cf2b3e23256..748decf65940 100644 --- a/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js +++ b/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js @@ -44,6 +44,7 @@ class RequestListContextMenu { url, } = selectedRequest; const { + blockSelectedRequestURL, connector, cloneSelectedRequest, sendCustomRequest, @@ -201,6 +202,13 @@ class RequestListContextMenu { click: cloneSelectedRequest, }); + menu.push({ + id: "request-list-context-block-url", + label: L10N.getStr("netmonitor.context.blockURL"), + visible: !!selectedRequest, + click: blockSelectedRequestURL, + }); + menu.push({ type: "separator", visible: copySubmenu.slice(15, 16).some((subMenu) => subMenu.visible), diff --git a/devtools/server/actors/network-monitor.js b/devtools/server/actors/network-monitor.js index c0c79699def2..1c7b815f4473 100644 --- a/devtools/server/actors/network-monitor.js +++ b/devtools/server/actors/network-monitor.js @@ -52,6 +52,7 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, { this.onStackTraceAvailable = this.onStackTraceAvailable.bind(this); this.onRequestContent = this.onRequestContent.bind(this); this.onSetPreference = this.onSetPreference.bind(this); + this.onBlockRequest = this.onBlockRequest.bind(this); this.onGetNetworkEventActor = this.onGetNetworkEventActor.bind(this); this.onDestroyMessage = this.onDestroyMessage.bind(this); @@ -71,6 +72,8 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, { this.onRequestContent); this.messageManager.addMessageListener("debug:netmonitor-preference", this.onSetPreference); + this.messageManager.addMessageListener("debug:block-request", + this.onBlockRequest); this.messageManager.addMessageListener("debug:get-network-event-actor:request", this.onGetNetworkEventActor); this.messageManager.addMessageListener("debug:destroy-network-monitor", @@ -84,6 +87,8 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, { this.onRequestContent); this.messageManager.removeMessageListener("debug:netmonitor-preference", this.onSetPreference); + this.messageManager.removeMessageListener("debug:block-request", + this.onBlockRequest); this.messageManager.removeMessageListener("debug:get-network-event-actor:request", this.onGetNetworkEventActor); this.messageManager.removeMessageListener("debug:destroy-network-monitor", @@ -172,6 +177,11 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, { } }, + onBlockRequest({ data }) { + const { filter } = data; + this.observer.blockRequest(filter); + }, + onGetNetworkEventActor({ data }) { const actor = this.getNetworkEventActor(data.channelId); this.messageManager.sendAsyncMessage("debug:get-network-event-actor:response", { diff --git a/devtools/server/actors/network-monitor/network-observer.js b/devtools/server/actors/network-monitor/network-observer.js index 8d5aa10033e3..09b6de98da10 100644 --- a/devtools/server/actors/network-monitor/network-observer.js +++ b/devtools/server/actors/network-monitor/network-observer.js @@ -6,7 +6,7 @@ "use strict"; -const {Cc, Ci} = require("chrome"); +const {Cc, Ci, Cr} = require("chrome"); const Services = require("Services"); const flags = require("devtools/shared/flags"); @@ -126,6 +126,8 @@ function NetworkObserver(filters, owner) { this.openRequests = new Map(); this.openResponses = new Map(); + this.blockedURLs = new Set(); + this._httpResponseExaminer = DevToolsUtils.makeInfallible(this._httpResponseExaminer).bind(this); this._httpModifyExaminer = @@ -644,6 +646,21 @@ NetworkObserver.prototype = { return httpActivity; }, + /** + * Block a request based on certain filtering options. + * + * Currently, an exact URL match is the only supported filter type. + */ + blockRequest(filter) { + if (!filter || !filter.url) { + // In the future, there may be other types of filters, such as domain. + // For now, ignore anything other than URL. + return; + } + + this.blockedURLs.add(filter.url); + }, + /** * Setup the network response listener for the given HTTP activity. The * NetworkResponseListener is responsible for storing the response body. @@ -656,6 +673,11 @@ NetworkObserver.prototype = { const channel = httpActivity.channel; channel.QueryInterface(Ci.nsITraceableChannel); + if (this.blockedURLs.has(httpActivity.url)) { + channel.cancel(Cr.NS_BINDING_ABORTED); + return; + } + if (!fromCache) { const throttler = this._getThrottler(); if (throttler) { diff --git a/devtools/server/actors/webconsole.js b/devtools/server/actors/webconsole.js index a82c0d62120c..7cafd0c480d6 100644 --- a/devtools/server/actors/webconsole.js +++ b/devtools/server/actors/webconsole.js @@ -1681,6 +1681,28 @@ WebConsoleActor.prototype = }); }, + /** + * Block a request based on certain filtering options. + * + * Currently, an exact URL match is the only supported filter type. + * In the future, there may be other types of filters, such as domain. + * For now, ignore anything other than URL. + * + * @param object filter + * An object containing a `url` key with a URL to block. + */ + async blockRequest({ filter }) { + if (this.netmonitors) { + for (const { messageManager } of this.netmonitors) { + messageManager.sendAsyncMessage("debug:block-request", { + filter, + }); + } + } + + return {}; + }, + /** * Handler for file activity. This method sends the file request information * to the remote Web Console client. @@ -1832,6 +1854,7 @@ WebConsoleActor.prototype.requestTypes = getPreferences: WebConsoleActor.prototype.getPreferences, setPreferences: WebConsoleActor.prototype.setPreferences, sendHTTPRequest: WebConsoleActor.prototype.sendHTTPRequest, + blockRequest: WebConsoleActor.prototype.blockRequest, }; exports.WebConsoleActor = WebConsoleActor; diff --git a/devtools/shared/specs/webconsole.js b/devtools/shared/specs/webconsole.js index 227bb415770c..a15c20973c11 100644 --- a/devtools/shared/specs/webconsole.js +++ b/devtools/shared/specs/webconsole.js @@ -234,6 +234,12 @@ const webconsoleSpecPrototype = { }, response: RetVal("json"), }, + + blockRequest: { + request: { + filter: Arg(0, "json"), + }, + }, }, };