browser(firefox): manage network activity per page (#1700)

a33663a362

Network events are now sent to corresponding page session. Previously they would be broadcast to all sessions.
This commit is contained in:
Yury Semikhatsky 2020-04-07 23:03:43 -07:00 committed by GitHub
parent 20ff327827
commit aff2ffacf8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 196 additions and 168 deletions

View file

@ -1 +1 @@
1074 1075

View file

@ -1269,10 +1269,10 @@ index 0000000000000000000000000000000000000000..b8e6649fb91be6cd72b000426fb4d582
+ +
diff --git a/juggler/NetworkObserver.js b/juggler/NetworkObserver.js diff --git a/juggler/NetworkObserver.js b/juggler/NetworkObserver.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb24580449dc12a66 index 0000000000000000000000000000000000000000..b8a98b058ea36f85fddfa21e992e2d0674c11e3d
--- /dev/null --- /dev/null
+++ b/juggler/NetworkObserver.js +++ b/juggler/NetworkObserver.js
@@ -0,0 +1,760 @@ @@ -0,0 +1,789 @@
+"use strict"; +"use strict";
+ +
+const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm'); +const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm');
@ -1305,6 +1305,105 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+const SINK_CONTRACT_ID = "@mozilla.org/network/monitor/channeleventsink;1"; +const SINK_CONTRACT_ID = "@mozilla.org/network/monitor/channeleventsink;1";
+const SINK_CATEGORY_NAME = "net-channel-event-sinks"; +const SINK_CATEGORY_NAME = "net-channel-event-sinks";
+ +
+const pageNetworkSymbol = Symbol('PageNetwork');
+
+class PageNetwork {
+ static _forPageTarget(networkObserver, target) {
+ let result = target[pageNetworkSymbol];
+ if (!result) {
+ result = new PageNetwork(networkObserver, target);
+ target[pageNetworkSymbol] = result;
+ }
+ return result;
+ }
+
+ constructor(networkObserver, target) {
+ EventEmitter.decorate(this);
+ this._networkObserver = networkObserver;
+ this._target = target;
+ this._sessionCount = 0;
+ this._extraHTTPHeaders = null;
+ this._responseStorage = null;
+ this._requestInterceptionEnabled = false;
+ this._requestIdToInterceptor = null;
+ }
+
+ addSession() {
+ if (this._sessionCount === 0) {
+ this._responseStorage = new ResponseStorage(this._networkObserver, MAX_RESPONSE_STORAGE_SIZE, MAX_RESPONSE_STORAGE_SIZE / 10);
+ }
+ ++this._sessionCount;
+ return () => this._stopTracking();
+ }
+
+ _stopTracking() {
+ --this._sessionCount;
+ if (this._sessionCount === 0) {
+ this._extraHTTPHeaders = null;
+ this._responseStorage = null;
+ this._requestInterceptionEnabled = false;
+ this._requestIdToInterceptor = null;
+ }
+ }
+
+ _isActive() {
+ return this._sessionCount > 0;
+ }
+
+ setExtraHTTPHeaders(headers) {
+ this._extraHTTPHeaders = headers;
+ }
+
+ enableRequestInterception() {
+ this._requestInterceptionEnabled = true;
+ }
+
+ disableRequestInterception() {
+ this._requestInterceptionEnabled = false;
+ const interceptors = this._requestIdToInterceptor;
+ if (!interceptors)
+ return;
+ this._requestIdToInterceptor = null;
+ for (const interceptor of interceptors.values())
+ interceptor._resume();
+ }
+
+ resumeInterceptedRequest(requestId, method, headers, postData) {
+ this._takeInterceptor(requestId)._resume(method, headers, postData);
+ }
+
+ fulfillInterceptedRequest(requestId, status, statusText, headers, base64body) {
+ this._takeInterceptor(requestId)._fulfill(status, statusText, headers, base64body);
+ }
+
+ abortInterceptedRequest(requestId, errorCode) {
+ this._takeInterceptor(requestId)._abort(errorCode);
+ }
+
+ getResponseBody(requestId) {
+ if (!this._responseStorage)
+ throw new Error('Responses are not tracked for the given browser');
+ return this._responseStorage.getBase64EncodedResponse(requestId);
+ }
+
+ _ensureInterceptors() {
+ if (!this._requestIdToInterceptor)
+ this._requestIdToInterceptor = new Map();
+ return this._requestIdToInterceptor;
+ }
+
+ _takeInterceptor(requestId) {
+ const interceptors = this._requestIdToInterceptor;
+ if (!interceptors)
+ throw new Error(`Request interception is not enabled`);
+ const interceptor = interceptors.get(requestId);
+ if (!interceptor)
+ throw new Error(`Cannot find request "${requestId}"`);
+ interceptors.delete(requestId);
+ return interceptor;
+ }
+}
+
+class NetworkObserver { +class NetworkObserver {
+ static instance() { + static instance() {
+ return NetworkObserver._instance || null; + return NetworkObserver._instance || null;
@ -1315,7 +1414,6 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ NetworkObserver._instance = this; + NetworkObserver._instance = this;
+ +
+ this._targetRegistry = targetRegistry; + this._targetRegistry = targetRegistry;
+ this._browserSessionCount = new Map();
+ this._activityDistributor = Cc["@mozilla.org/network/http-activity-distributor;1"].getService(Ci.nsIHttpActivityDistributor); + this._activityDistributor = Cc["@mozilla.org/network/http-activity-distributor;1"].getService(Ci.nsIHttpActivityDistributor);
+ this._activityDistributor.addObserver(this); + this._activityDistributor.addObserver(this);
+ +
@ -1342,11 +1440,6 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ registrar.registerFactory(SINK_CLASS_ID, SINK_CLASS_DESCRIPTION, SINK_CONTRACT_ID, this._channelSinkFactory); + registrar.registerFactory(SINK_CLASS_ID, SINK_CLASS_DESCRIPTION, SINK_CONTRACT_ID, this._channelSinkFactory);
+ Services.catMan.addCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID, SINK_CONTRACT_ID, false, true); + Services.catMan.addCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID, SINK_CONTRACT_ID, false, true);
+ +
+ this._browsersWithEnabledInterception = new Set();
+ this._browserInterceptors = new Map(); // Browser => (requestId => interceptor).
+ this._extraHTTPHeaders = new Map();
+ this._browserResponseStorages = new Map();
+
+ this._eventListeners = [ + this._eventListeners = [
+ helper.addObserver(this._onRequest.bind(this), 'http-on-modify-request'), + helper.addObserver(this._onRequest.bind(this), 'http-on-modify-request'),
+ helper.addObserver(this._onResponse.bind(this, false /* fromCache */), 'http-on-examine-response'), + helper.addObserver(this._onResponse.bind(this, false /* fromCache */), 'http-on-examine-response'),
@ -1355,57 +1448,6 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ ]; + ];
+ } + }
+ +
+ setExtraHTTPHeaders(browser, headers) {
+ if (!headers)
+ this._extraHTTPHeaders.delete(browser);
+ else
+ this._extraHTTPHeaders.set(browser, headers);
+ }
+
+ enableRequestInterception(browser) {
+ this._browsersWithEnabledInterception.add(browser);
+ }
+
+ disableRequestInterception(browser) {
+ this._browsersWithEnabledInterception.delete(browser);
+ const interceptors = this._browserInterceptors.get(browser);
+ if (!interceptors)
+ return;
+ this._browserInterceptors.delete(browser);
+ for (const interceptor of interceptors.values())
+ interceptor._resume();
+ }
+
+ _takeInterceptor(browser, requestId) {
+ const interceptors = this._browserInterceptors.get(browser);
+ if (!interceptors)
+ throw new Error(`Request interception is not enabled`);
+ const interceptor = interceptors.get(requestId);
+ if (!interceptor)
+ throw new Error(`Cannot find request "${requestId}"`);
+ interceptors.delete(requestId);
+ return interceptor;
+ }
+
+ resumeInterceptedRequest(browser, requestId, method, headers, postData) {
+ this._takeInterceptor(browser, requestId)._resume(method, headers, postData);
+ }
+
+ getResponseBody(browser, requestId) {
+ const responseStorage = this._browserResponseStorages.get(browser);
+ if (!responseStorage)
+ throw new Error('Responses are not tracked for the given browser');
+ return responseStorage.getBase64EncodedResponse(requestId);
+ }
+
+ fulfillInterceptedRequest(browser, requestId, status, statusText, headers, base64body) {
+ this._takeInterceptor(browser, requestId)._fulfill(status, statusText, headers, base64body);
+ }
+
+ abortInterceptedRequest(browser, requestId, errorCode) {
+ this._takeInterceptor(browser, requestId)._abort(errorCode);
+ }
+
+ _requestAuthenticated(httpChannel) { + _requestAuthenticated(httpChannel) {
+ this._pendingAuthentication.add(httpChannel.channelId + ''); + this._pendingAuthentication.add(httpChannel.channelId + '');
+ } + }
@ -1425,8 +1467,8 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ return; + return;
+ const oldHttpChannel = oldChannel.QueryInterface(Ci.nsIHttpChannel); + const oldHttpChannel = oldChannel.QueryInterface(Ci.nsIHttpChannel);
+ const newHttpChannel = newChannel.QueryInterface(Ci.nsIHttpChannel); + const newHttpChannel = newChannel.QueryInterface(Ci.nsIHttpChannel);
+ const browser = this._getBrowserForChannel(oldHttpChannel); + const pageNetwork = this._pageNetworkForChannel(oldHttpChannel);
+ if (!browser) + if (!pageNetwork)
+ return; + return;
+ const oldRequestId = this._requestId(oldHttpChannel); + const oldRequestId = this._requestId(oldHttpChannel);
+ const newRequestId = this._requestId(newHttpChannel); + const newRequestId = this._requestId(newHttpChannel);
@ -1462,8 +1504,8 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ if (!(channel instanceof Ci.nsIHttpChannel)) + if (!(channel instanceof Ci.nsIHttpChannel))
+ return; + return;
+ const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel); + const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
+ const browser = this._getBrowserForChannel(httpChannel); + const pageNetwork = this._pageNetworkForChannel(httpChannel);
+ if (!browser) + if (!pageNetwork)
+ return; + return;
+ if (activitySubtype !== Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE) + if (activitySubtype !== Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE)
+ return; + return;
@ -1471,14 +1513,24 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ return; + return;
+ if (this._requestIdBeforeAuthentication(httpChannel)) + if (this._requestIdBeforeAuthentication(httpChannel))
+ return; + return;
+ this._sendOnRequestFinished(httpChannel); + this._sendOnRequestFinished(pageNetwork, httpChannel);
+ } + }
+ +
+ _getBrowserForChannel(httpChannel) { + pageNetworkForTarget(target) {
+ return PageNetwork._forPageTarget(this, target);
+ }
+
+ _pageNetworkForChannel(httpChannel) {
+ let loadContext = helper.getLoadContext(httpChannel); + let loadContext = helper.getLoadContext(httpChannel);
+ if (!loadContext || !this._browserSessionCount.has(loadContext.topFrameElement)) + if (!loadContext)
+ return; + return;
+ return loadContext.topFrameElement; + const target = this._targetRegistry.targetForBrowser(loadContext.topFrameElement);
+ if (!target)
+ return;
+ const pageNetwork = PageNetwork._forPageTarget(this, target);
+ if (!pageNetwork._isActive())
+ return;
+ return pageNetwork;
+ } + }
+ +
+ _isResumedChannel(httpChannel) { + _isResumedChannel(httpChannel) {
@ -1489,12 +1541,12 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ if (!(channel instanceof Ci.nsIHttpChannel)) + if (!(channel instanceof Ci.nsIHttpChannel))
+ return; + return;
+ const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel); + const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
+ const browser = this._getBrowserForChannel(httpChannel); + const pageNetwork = this._pageNetworkForChannel(httpChannel);
+ if (!browser) + if (!pageNetwork)
+ return; + return;
+ if (this._isResumedChannel(httpChannel)) { + if (this._isResumedChannel(httpChannel)) {
+ // Ignore onRequest for resumed requests, but listen to their response. + // Ignore onRequest for resumed requests, but listen to their response.
+ new ResponseBodyListener(this, browser, httpChannel); + new ResponseBodyListener(this, pageNetwork, httpChannel);
+ return; + return;
+ } + }
+ // Convert pending auth bit into auth mapping. + // Convert pending auth bit into auth mapping.
@ -1507,54 +1559,54 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ if (bodyListener) + if (bodyListener)
+ bodyListener.dispose(); + bodyListener.dispose();
+ } + }
+ const browserContext = this._targetRegistry.browserContextForBrowser(browser); + const browserContext = pageNetwork._target.browserContext();
+ if (browserContext) + if (browserContext)
+ this._appendExtraHTTPHeaders(httpChannel, browserContext.options.extraHTTPHeaders); + this._appendExtraHTTPHeaders(httpChannel, browserContext.options.extraHTTPHeaders);
+ this._appendExtraHTTPHeaders(httpChannel, this._extraHTTPHeaders.get(browser)); + this._appendExtraHTTPHeaders(httpChannel, pageNetwork._extraHTTPHeaders);
+ const requestId = this._requestId(httpChannel); + const requestId = this._requestId(httpChannel);
+ const isRedirect = this._redirectMap.has(requestId); + const isRedirect = this._redirectMap.has(requestId);
+ const interceptionEnabled = this._isInterceptionEnabledForBrowser(browser); + const interceptionEnabled = this._isInterceptionEnabledForPage(pageNetwork);
+ if (!interceptionEnabled) { + if (!interceptionEnabled) {
+ new NotificationCallbacks(this, browser, httpChannel, false); + new NotificationCallbacks(this, pageNetwork, httpChannel, false);
+ this._sendOnRequest(httpChannel, false); + this._sendOnRequest(httpChannel, false);
+ new ResponseBodyListener(this, browser, httpChannel); + new ResponseBodyListener(this, pageNetwork, httpChannel);
+ } else if (isRedirect) { + } else if (isRedirect) {
+ // We pretend that redirect is interceptable in the protocol, although it's actually not + // We pretend that redirect is interceptable in the protocol, although it's actually not
+ // and therefore we do not instantiate the interceptor. + // and therefore we do not instantiate the interceptor.
+ // TODO: look into REDIRECT_MODE_MANUAL. + // TODO: look into REDIRECT_MODE_MANUAL.
+ const interceptors = this._ensureInterceptors(browser); + const interceptors = pageNetwork._ensureInterceptors();
+ interceptors.set(requestId, { + interceptors.set(requestId, {
+ _resume: () => {}, + _resume: () => {},
+ _abort: () => {}, + _abort: () => {},
+ _fulfill: () => {}, + _fulfill: () => {},
+ }); + });
+ new NotificationCallbacks(this, browser, httpChannel, false); + new NotificationCallbacks(this, pageNetwork, httpChannel, false);
+ this._sendOnRequest(httpChannel, true); + this._sendOnRequest(httpChannel, true);
+ new ResponseBodyListener(this, browser, httpChannel); + new ResponseBodyListener(this, pageNetwork, httpChannel);
+ } else { + } else {
+ const previousCallbacks = httpChannel.notificationCallbacks; + const previousCallbacks = httpChannel.notificationCallbacks;
+ if (previousCallbacks instanceof Ci.nsIInterfaceRequestor) { + if (previousCallbacks instanceof Ci.nsIInterfaceRequestor) {
+ const interceptor = previousCallbacks.getInterface(Ci.nsINetworkInterceptController); + const interceptor = previousCallbacks.getInterface(Ci.nsINetworkInterceptController);
+ // We assume that interceptor is a service worker if there is one. + // We assume that interceptor is a service worker if there is one.
+ if (interceptor && interceptor.shouldPrepareForIntercept(httpChannel.URI, httpChannel)) { + if (interceptor && interceptor.shouldPrepareForIntercept(httpChannel.URI, httpChannel)) {
+ new NotificationCallbacks(this, browser, httpChannel, false); + new NotificationCallbacks(this, pageNetwork, httpChannel, false);
+ this._sendOnRequest(httpChannel, false); + this._sendOnRequest(httpChannel, false);
+ new ResponseBodyListener(this, browser, httpChannel); + new ResponseBodyListener(this, pageNetwork, httpChannel);
+ } else { + } else {
+ // We'll issue onRequest once it's intercepted. + // We'll issue onRequest once it's intercepted.
+ new NotificationCallbacks(this, browser, httpChannel, true); + new NotificationCallbacks(this, pageNetwork, httpChannel, true);
+ } + }
+ } else { + } else {
+ // We'll issue onRequest once it's intercepted. + // We'll issue onRequest once it's intercepted.
+ new NotificationCallbacks(this, browser, httpChannel, true); + new NotificationCallbacks(this, pageNetwork, httpChannel, true);
+ } + }
+ } + }
+ } + }
+ +
+ _isInterceptionEnabledForBrowser(browser) { + _isInterceptionEnabledForPage(pageNetwork) {
+ if (this._browsersWithEnabledInterception.has(browser)) + if (pageNetwork._requestInterceptionEnabled)
+ return true; + return true;
+ const browserContext = this._targetRegistry.browserContextForBrowser(browser); + const browserContext = pageNetwork._target.browserContext();
+ if (browserContext && browserContext.options.requestInterceptionEnabled) + if (browserContext && browserContext.options.requestInterceptionEnabled)
+ return true; + return true;
+ if (browserContext && browserContext.options.onlineOverride === 'offline') + if (browserContext && browserContext.options.onlineOverride === 'offline')
@ -1562,15 +1614,6 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ return false; + return false;
+ } + }
+ +
+ _ensureInterceptors(browser) {
+ let interceptors = this._browserInterceptors.get(browser);
+ if (!interceptors) {
+ interceptors = new Map();
+ this._browserInterceptors.set(browser, interceptors);
+ }
+ return interceptors;
+ }
+
+ _appendExtraHTTPHeaders(httpChannel, headers) { + _appendExtraHTTPHeaders(httpChannel, headers) {
+ if (!headers) + if (!headers)
+ return; + return;
@ -1579,35 +1622,34 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ } + }
+ +
+ _onIntercepted(httpChannel, interceptor) { + _onIntercepted(httpChannel, interceptor) {
+ const browser = this._getBrowserForChannel(httpChannel); + const pageNetwork = this._pageNetworkForChannel(httpChannel);
+ if (!browser) { + if (!pageNetwork) {
+ interceptor._resume(); + interceptor._resume();
+ return; + return;
+ } + }
+ + const browserContext = pageNetwork._target.browserContext();
+ const browserContext = this._targetRegistry.browserContextForBrowser(browser);
+ if (browserContext && browserContext.options.onlineOverride === 'offline') { + if (browserContext && browserContext.options.onlineOverride === 'offline') {
+ interceptor._abort(Cr.NS_ERROR_OFFLINE); + interceptor._abort(Cr.NS_ERROR_OFFLINE);
+ return; + return;
+ } + }
+ +
+ const interceptionEnabled = this._isInterceptionEnabledForBrowser(browser); + const interceptionEnabled = this._isInterceptionEnabledForPage(pageNetwork);
+ this._sendOnRequest(httpChannel, !!interceptionEnabled); + this._sendOnRequest(httpChannel, !!interceptionEnabled);
+ if (interceptionEnabled) + if (interceptionEnabled)
+ this._ensureInterceptors(browser).set(this._requestId(httpChannel), interceptor); + pageNetwork._ensureInterceptors().set(this._requestId(httpChannel), interceptor);
+ else + else
+ interceptor._resume(); + interceptor._resume();
+ } + }
+ +
+ _sendOnRequest(httpChannel, isIntercepted) { + _sendOnRequest(httpChannel, isIntercepted) {
+ const browser = this._getBrowserForChannel(httpChannel); + const pageNetwork = this._pageNetworkForChannel(httpChannel);
+ if (!browser) + if (!pageNetwork)
+ return; + return;
+ const causeType = httpChannel.loadInfo ? httpChannel.loadInfo.externalContentPolicyType : Ci.nsIContentPolicy.TYPE_OTHER; + const causeType = httpChannel.loadInfo ? httpChannel.loadInfo.externalContentPolicyType : Ci.nsIContentPolicy.TYPE_OTHER;
+ const requestId = this._requestId(httpChannel); + const requestId = this._requestId(httpChannel);
+ const redirectedFrom = this._redirectMap.get(requestId); + const redirectedFrom = this._redirectMap.get(requestId);
+ this._redirectMap.delete(requestId); + this._redirectMap.delete(requestId);
+ this.emit('request', httpChannel, { + pageNetwork.emit(PageNetwork.Events.Request, httpChannel, {
+ url: httpChannel.URI.spec, + url: httpChannel.URI.spec,
+ isIntercepted, + isIntercepted,
+ requestId, + requestId,
@ -1620,15 +1662,15 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ }); + });
+ } + }
+ +
+ _sendOnRequestFinished(httpChannel) { + _sendOnRequestFinished(pageNetwork, httpChannel) {
+ this.emit('requestfinished', httpChannel, { + pageNetwork.emit(PageNetwork.Events.RequestFinished, httpChannel, {
+ requestId: this._requestId(httpChannel), + requestId: this._requestId(httpChannel),
+ }); + });
+ this._cleanupChannelState(httpChannel); + this._cleanupChannelState(httpChannel);
+ } + }
+ +
+ _sendOnRequestFailed(httpChannel, error) { + _sendOnRequestFailed(pageNetwork, httpChannel, error) {
+ this.emit('requestfailed', httpChannel, { + pageNetwork.emit(PageNetwork.Events.RequestFailed, httpChannel, {
+ requestId: this._requestId(httpChannel), + requestId: this._requestId(httpChannel),
+ errorCode: helper.getNetworkErrorStatusText(error), + errorCode: helper.getNetworkErrorStatusText(error),
+ }); + });
@ -1642,8 +1684,8 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ } + }
+ +
+ _onResponse(fromCache, httpChannel, topic) { + _onResponse(fromCache, httpChannel, topic) {
+ const browser = this._getBrowserForChannel(httpChannel); + const pageNetwork = this._pageNetworkForChannel(httpChannel);
+ if (!browser) + if (!pageNetwork)
+ return; + return;
+ httpChannel.QueryInterface(Ci.nsIHttpChannelInternal); + httpChannel.QueryInterface(Ci.nsIHttpChannelInternal);
+ const headers = []; + const headers = [];
@ -1659,7 +1701,7 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ } catch (e) { + } catch (e) {
+ // remoteAddress is not defined for cached requests. + // remoteAddress is not defined for cached requests.
+ } + }
+ this.emit('response', httpChannel, { + pageNetwork.emit(PageNetwork.Events.Response, httpChannel, {
+ requestId: this._requestId(httpChannel), + requestId: this._requestId(httpChannel),
+ securityDetails: getSecurityDetails(httpChannel), + securityDetails: getSecurityDetails(httpChannel),
+ fromCache, + fromCache,
@ -1671,32 +1713,11 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ }); + });
+ } + }
+ +
+ _onResponseFinished(browser, httpChannel, body) { + _onResponseFinished(pageNetwork, httpChannel, body) {
+ const responseStorage = this._browserResponseStorages.get(browser); + if (!pageNetwork._isActive())
+ if (!responseStorage)
+ return; + return;
+ responseStorage.addResponseBody(httpChannel, body); + pageNetwork._responseStorage.addResponseBody(httpChannel, body);
+ this._sendOnRequestFinished(httpChannel); + this._sendOnRequestFinished(pageNetwork, httpChannel);
+ }
+
+ startTrackingBrowserNetwork(browser) {
+ const value = this._browserSessionCount.get(browser) || 0;
+ this._browserSessionCount.set(browser, value + 1);
+ if (value === 0)
+ this._browserResponseStorages.set(browser, new ResponseStorage(this, MAX_RESPONSE_STORAGE_SIZE, MAX_RESPONSE_STORAGE_SIZE / 10));
+ return () => this.stopTrackingBrowserNetwork(browser);
+ }
+
+ stopTrackingBrowserNetwork(browser) {
+ const value = this._browserSessionCount.get(browser);
+ if (value) {
+ this._browserSessionCount.set(browser, value - 1);
+ } else {
+ this._browserSessionCount.delete(browser);
+ this._browserResponseStorages.delete(browser);
+ this._browsersWithEnabledInterception.delete(browser);
+ this._browserInterceptors.delete(browser);
+ }
+ } + }
+ +
+ dispose() { + dispose() {
@ -1833,9 +1854,9 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+} +}
+ +
+class ResponseBodyListener { +class ResponseBodyListener {
+ constructor(networkObserver, browser, httpChannel) { + constructor(networkObserver, pageNetwork, httpChannel) {
+ this._networkObserver = networkObserver; + this._networkObserver = networkObserver;
+ this._browser = browser; + this._pageNetwork = pageNetwork;
+ this._httpChannel = httpChannel; + this._httpChannel = httpChannel;
+ this._chunks = []; + this._chunks = [];
+ this.QueryInterface = ChromeUtils.generateQI([Ci.nsIStreamListener]); + this.QueryInterface = ChromeUtils.generateQI([Ci.nsIStreamListener]);
@ -1874,7 +1895,7 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ +
+ const body = this._chunks.join(''); + const body = this._chunks.join('');
+ delete this._chunks; + delete this._chunks;
+ this._networkObserver._onResponseFinished(this._browser, this._httpChannel, body); + this._networkObserver._onResponseFinished(this._pageNetwork, this._httpChannel, body);
+ this.dispose(); + this.dispose();
+ } + }
+ +
@ -1885,9 +1906,9 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+} +}
+ +
+class NotificationCallbacks { +class NotificationCallbacks {
+ constructor(networkObserver, browser, httpChannel, shouldIntercept) { + constructor(networkObserver, pageNetwork, httpChannel, shouldIntercept) {
+ this._networkObserver = networkObserver; + this._networkObserver = networkObserver;
+ this._browser = browser; + this._pageNetwork = pageNetwork;
+ this._shouldIntercept = shouldIntercept; + this._shouldIntercept = shouldIntercept;
+ this._httpChannel = httpChannel; + this._httpChannel = httpChannel;
+ this._previousCallbacks = httpChannel.notificationCallbacks; + this._previousCallbacks = httpChannel.notificationCallbacks;
@ -1957,7 +1978,7 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ promptAuth(aChannel, level, authInfo) { + promptAuth(aChannel, level, authInfo) {
+ if (authInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED) + if (authInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED)
+ return false; + return false;
+ const browserContext = this._networkObserver._targetRegistry.browserContextForBrowser(this._browser); + const browserContext = this._pageNetwork._target.browserContext();
+ const credentials = browserContext ? browserContext.options.httpCredentials : undefined; + const credentials = browserContext ? browserContext.options.httpCredentials : undefined;
+ if (!credentials) + if (!credentials)
+ return false; + return false;
@ -1996,7 +2017,7 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ synthesized.data = body; + synthesized.data = body;
+ this._intercepted.startSynthesizedResponse(synthesized, null, null, '', false); + this._intercepted.startSynthesizedResponse(synthesized, null, null, '', false);
+ this._intercepted.finishSynthesizedResponse(); + this._intercepted.finishSynthesizedResponse();
+ this._networkObserver.emit('response', this._httpChannel, { + this._pageNetwork.emit(PageNetwork.Events.Response, this._httpChannel, {
+ requestId: this._networkObserver._requestId(this._httpChannel), + requestId: this._networkObserver._requestId(this._httpChannel),
+ securityDetails: null, + securityDetails: null,
+ fromCache: false, + fromCache: false,
@ -2004,13 +2025,13 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ status, + status,
+ statusText, + statusText,
+ }); + });
+ this._networkObserver._onResponseFinished(this._browser, this._httpChannel, body); + this._networkObserver._onResponseFinished(this._pageNetwork, this._httpChannel, body);
+ } + }
+ +
+ _abort(errorCode) { + _abort(errorCode) {
+ const error = errorMap[errorCode] || Cr.NS_ERROR_FAILURE; + const error = errorMap[errorCode] || Cr.NS_ERROR_FAILURE;
+ this._intercepted.cancelInterception(error); + this._intercepted.cancelInterception(error);
+ this._networkObserver._sendOnRequestFailed(this._httpChannel, error); + this._networkObserver._sendOnRequestFailed(this._pageNetwork, this._httpChannel, error);
+ } + }
+} +}
+ +
@ -2031,8 +2052,16 @@ index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb2458044
+ 'failed': Cr.NS_ERROR_FAILURE, + 'failed': Cr.NS_ERROR_FAILURE,
+}; +};
+ +
+var EXPORTED_SYMBOLS = ['NetworkObserver']; +PageNetwork.Events = {
+ Request: Symbol('PageNetwork.Events.Request'),
+ Response: Symbol('PageNetwork.Events.Response'),
+ RequestFinished: Symbol('PageNetwork.Events.RequestFinished'),
+ RequestFailed: Symbol('PageNetwork.Events.RequestFailed'),
+};
+
+var EXPORTED_SYMBOLS = ['NetworkObserver', 'PageNetwork'];
+this.NetworkObserver = NetworkObserver; +this.NetworkObserver = NetworkObserver;
+this.PageNetwork = PageNetwork;
diff --git a/juggler/SimpleChannel.js b/juggler/SimpleChannel.js diff --git a/juggler/SimpleChannel.js b/juggler/SimpleChannel.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..ba34976ad05e7f5f1a99777f76ac08b171af40b7 index 0000000000000000000000000000000000000000..ba34976ad05e7f5f1a99777f76ac08b171af40b7
@ -2171,10 +2200,10 @@ index 0000000000000000000000000000000000000000..ba34976ad05e7f5f1a99777f76ac08b1
+this.SimpleChannel = SimpleChannel; +this.SimpleChannel = SimpleChannel;
diff --git a/juggler/TargetRegistry.js b/juggler/TargetRegistry.js diff --git a/juggler/TargetRegistry.js b/juggler/TargetRegistry.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..27cfe133cab5dc4b1218c0e5624b7eb2f8bc08e3 index 0000000000000000000000000000000000000000..5ae2f349b65eb437aa646d14b8d5afd76380b3ad
--- /dev/null --- /dev/null
+++ b/juggler/TargetRegistry.js +++ b/juggler/TargetRegistry.js
@@ -0,0 +1,661 @@ @@ -0,0 +1,660 @@
+const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm'); +const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm');
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); +const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
+const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js'); +const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js');
@ -2477,11 +2506,6 @@ index 0000000000000000000000000000000000000000..27cfe133cab5dc4b1218c0e5624b7eb2
+ return Array.from(this._browserToTarget.values()); + return Array.from(this._browserToTarget.values());
+ } + }
+ +
+ browserContextForBrowser(browser) {
+ const target = this._browserToTarget.get(browser);
+ return target ? target._browserContext : undefined;
+ }
+
+ targetForBrowser(browser) { + targetForBrowser(browser) {
+ return this._browserToTarget.get(browser); + return this._browserToTarget.get(browser);
+ } + }
@ -2533,6 +2557,10 @@ index 0000000000000000000000000000000000000000..27cfe133cab5dc4b1218c0e5624b7eb2
+ return this._linkedBrowser; + return this._linkedBrowser;
+ } + }
+ +
+ browserContext() {
+ return this._browserContext;
+ }
+
+ setViewportSize(viewportSize) { + setViewportSize(viewportSize) {
+ return setViewportSizeForBrowser(viewportSize, this._linkedBrowser); + return setViewportSizeForBrowser(viewportSize, this._linkedBrowser);
+ } + }
@ -5808,13 +5836,14 @@ index 0000000000000000000000000000000000000000..0b28a9568877d99967b2ad845df3eb59
+ +
diff --git a/juggler/protocol/NetworkHandler.js b/juggler/protocol/NetworkHandler.js diff --git a/juggler/protocol/NetworkHandler.js b/juggler/protocol/NetworkHandler.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..8003e1feb3f5b5faaff0a3699c024982d408dc01 index 0000000000000000000000000000000000000000..10ce1e9eb24879426ca11a21ffeb89f3567ea078
--- /dev/null --- /dev/null
+++ b/juggler/protocol/NetworkHandler.js +++ b/juggler/protocol/NetworkHandler.js
@@ -0,0 +1,158 @@ @@ -0,0 +1,158 @@
+"use strict"; +"use strict";
+ +
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); +const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
+const {NetworkObserver, PageNetwork} = ChromeUtils.import('chrome://juggler/content/NetworkObserver.js');
+ +
+const Cc = Components.classes; +const Cc = Components.classes;
+const Ci = Components.interfaces; +const Ci = Components.interfaces;
@ -5826,10 +5855,9 @@ index 0000000000000000000000000000000000000000..8003e1feb3f5b5faaff0a3699c024982
+ constructor(target, session, contentChannel) { + constructor(target, session, contentChannel) {
+ this._session = session; + this._session = session;
+ this._contentPage = contentChannel.connect(session.sessionId() + 'page'); + this._contentPage = contentChannel.connect(session.sessionId() + 'page');
+ this._networkObserver = ChromeUtils.import('chrome://juggler/content/NetworkObserver.js').NetworkObserver.instance();
+ this._httpActivity = new Map(); + this._httpActivity = new Map();
+ this._enabled = false; + this._enabled = false;
+ this._browser = target.linkedBrowser(); + this._pageNetwork = NetworkObserver.instance().pageNetworkForTarget(target);
+ this._requestInterception = false; + this._requestInterception = false;
+ this._eventListeners = []; + this._eventListeners = [];
+ this._pendingRequstWillBeSentEvents = new Set(); + this._pendingRequstWillBeSentEvents = new Set();
@ -5841,42 +5869,42 @@ index 0000000000000000000000000000000000000000..8003e1feb3f5b5faaff0a3699c024982
+ return; + return;
+ this._enabled = true; + this._enabled = true;
+ this._eventListeners = [ + this._eventListeners = [
+ helper.on(this._networkObserver, 'request', this._onRequest.bind(this)), + helper.on(this._pageNetwork, PageNetwork.Events.Request, this._onRequest.bind(this)),
+ helper.on(this._networkObserver, 'response', this._onResponse.bind(this)), + helper.on(this._pageNetwork, PageNetwork.Events.Response, this._onResponse.bind(this)),
+ helper.on(this._networkObserver, 'requestfinished', this._onRequestFinished.bind(this)), + helper.on(this._pageNetwork, PageNetwork.Events.RequestFinished, this._onRequestFinished.bind(this)),
+ helper.on(this._networkObserver, 'requestfailed', this._onRequestFailed.bind(this)), + helper.on(this._pageNetwork, PageNetwork.Events.RequestFailed, this._onRequestFailed.bind(this)),
+ this._networkObserver.startTrackingBrowserNetwork(this._browser), + this._pageNetwork.addSession(),
+ ]; + ];
+ } + }
+ +
+ async getResponseBody({requestId}) { + async getResponseBody({requestId}) {
+ return this._networkObserver.getResponseBody(this._browser, requestId); + return this._pageNetwork.getResponseBody(requestId);
+ } + }
+ +
+ async setExtraHTTPHeaders({headers}) { + async setExtraHTTPHeaders({headers}) {
+ this._networkObserver.setExtraHTTPHeaders(this._browser, headers); + this._pageNetwork.setExtraHTTPHeaders(headers);
+ } + }
+ +
+ async setRequestInterception({enabled}) { + async setRequestInterception({enabled}) {
+ if (enabled) + if (enabled)
+ this._networkObserver.enableRequestInterception(this._browser); + this._pageNetwork.enableRequestInterception();
+ else + else
+ this._networkObserver.disableRequestInterception(this._browser); + this._pageNetwork.disableRequestInterception();
+ // Right after we enable/disable request interception we need to await all pending + // Right after we enable/disable request interception we need to await all pending
+ // requestWillBeSent events before successfully returning from the method. + // requestWillBeSent events before successfully returning from the method.
+ await Promise.all(Array.from(this._pendingRequstWillBeSentEvents)); + await Promise.all(Array.from(this._pendingRequstWillBeSentEvents));
+ } + }
+ +
+ async resumeInterceptedRequest({requestId, method, headers, postData}) { + async resumeInterceptedRequest({requestId, method, headers, postData}) {
+ this._networkObserver.resumeInterceptedRequest(this._browser, requestId, method, headers, postData); + this._pageNetwork.resumeInterceptedRequest(requestId, method, headers, postData);
+ } + }
+ +
+ async abortInterceptedRequest({requestId, errorCode}) { + async abortInterceptedRequest({requestId, errorCode}) {
+ this._networkObserver.abortInterceptedRequest(this._browser, requestId, errorCode); + this._pageNetwork.abortInterceptedRequest(requestId, errorCode);
+ } + }
+ +
+ async fulfillInterceptedRequest({requestId, status, statusText, headers, base64body}) { + async fulfillInterceptedRequest({requestId, status, statusText, headers, base64body}) {
+ this._networkObserver.fulfillInterceptedRequest(this._browser, requestId, status, statusText, headers, base64body); + this._pageNetwork.fulfillInterceptedRequest(requestId, status, statusText, headers, base64body);
+ } + }
+ +
+ dispose() { + dispose() {