| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  | /* This Source Code Form is subject to the terms of the Mozilla Public | 
					
						
							|  |  |  |  * License, v. 2.0. If a copy of the MPL was not distributed with this | 
					
						
							|  |  |  |  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm'); | 
					
						
							|  |  |  | const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); | 
					
						
							|  |  |  | const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); | 
					
						
							|  |  |  | const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm'); | 
					
						
							|  |  |  | const {CommonUtils} = ChromeUtils.import("resource://services-common/utils.js"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const Cc = Components.classes; | 
					
						
							|  |  |  | const Ci = Components.interfaces; | 
					
						
							|  |  |  | const Cu = Components.utils; | 
					
						
							|  |  |  | const Cr = Components.results; | 
					
						
							|  |  |  | const Cm = Components.manager; | 
					
						
							|  |  |  | const CC = Components.Constructor; | 
					
						
							|  |  |  | const helper = new Helper(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 23:52:43 +08:00
										 |  |  | const UINT32_MAX = Math.pow(2, 32)-1; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  | const BinaryInputStream = CC('@mozilla.org/binaryinputstream;1', 'nsIBinaryInputStream', 'setInputStream'); | 
					
						
							|  |  |  | const BinaryOutputStream = CC('@mozilla.org/binaryoutputstream;1', 'nsIBinaryOutputStream', 'setOutputStream'); | 
					
						
							|  |  |  | const StorageStream = CC('@mozilla.org/storagestream;1', 'nsIStorageStream', 'init'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Cap response storage with 100Mb per tracked tab.
 | 
					
						
							|  |  |  | const MAX_RESPONSE_STORAGE_SIZE = 100 * 1024 * 1024; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * This is a nsIChannelEventSink implementation that monitors channel redirects. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const SINK_CLASS_DESCRIPTION = "Juggler NetworkMonitor Channel Event Sink"; | 
					
						
							|  |  |  | const SINK_CLASS_ID = Components.ID("{c2b4c83e-607a-405a-beab-0ef5dbfb7617}"); | 
					
						
							|  |  |  | const SINK_CONTRACT_ID = "@mozilla.org/network/monitor/channeleventsink;1"; | 
					
						
							|  |  |  | 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 { | 
					
						
							|  |  |  |   static instance() { | 
					
						
							|  |  |  |     return NetworkObserver._instance || null; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor(targetRegistry) { | 
					
						
							|  |  |  |     EventEmitter.decorate(this); | 
					
						
							|  |  |  |     NetworkObserver._instance = this; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this._targetRegistry = targetRegistry; | 
					
						
							|  |  |  |     this._activityDistributor = Cc["@mozilla.org/network/http-activity-distributor;1"].getService(Ci.nsIHttpActivityDistributor); | 
					
						
							|  |  |  |     this._activityDistributor.addObserver(this); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this._redirectMap = new Map();  // oldId => newId
 | 
					
						
							|  |  |  |     this._resumedRequestIdToHeaders = new Map();  // requestId => { headers }
 | 
					
						
							|  |  |  |     this._postResumeChannelIdToRequestId = new Map();  // post-resume channel id => pre-resume request id
 | 
					
						
							|  |  |  |     this._pendingAuthentication = new Set();  // pre-auth id
 | 
					
						
							|  |  |  |     this._postAuthChannelIdToRequestId = new Map();  // pre-auth id => post-auth id
 | 
					
						
							|  |  |  |     this._bodyListeners = new Map();  // channel id => ResponseBodyListener.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 23:52:43 +08:00
										 |  |  |     const protocolProxyService = Cc['@mozilla.org/network/protocol-proxy-service;1'].getService(); | 
					
						
							|  |  |  |     this._channelProxyFilter = { | 
					
						
							|  |  |  |       QueryInterface: ChromeUtils.generateQI([Ci.nsIProtocolProxyChannelFilter]), | 
					
						
							|  |  |  |       applyFilter: (channel, defaultProxyInfo, proxyFilter) => { | 
					
						
							|  |  |  |         const originAttributes = channel.loadInfo && channel.loadInfo.originAttributes; | 
					
						
							|  |  |  |         const browserContext = originAttributes ? this._targetRegistry.browserContextForUserContextId(originAttributes.userContextId) : null; | 
					
						
							|  |  |  |         const proxy = browserContext ? browserContext.proxy : null; | 
					
						
							|  |  |  |         if (!proxy) { | 
					
						
							|  |  |  |           proxyFilter.onProxyFilterResult(defaultProxyInfo); | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         proxyFilter.onProxyFilterResult(protocolProxyService.newProxyInfo( | 
					
						
							|  |  |  |             proxy.type, | 
					
						
							|  |  |  |             proxy.host, | 
					
						
							|  |  |  |             proxy.port, | 
					
						
							|  |  |  |             '', /* aProxyAuthorizationHeader */ | 
					
						
							|  |  |  |             '', /* aConnectionIsolationKey */ | 
					
						
							|  |  |  |             0, /* aFlags */ | 
					
						
							|  |  |  |             UINT32_MAX, /* aFailoverTimeout */ | 
					
						
							|  |  |  |             null, /* failover proxy */ | 
					
						
							|  |  |  |         )); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     protocolProxyService.registerChannelFilter(this._channelProxyFilter, 0 /* position */); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     this._channelSink = { | 
					
						
							|  |  |  |       QueryInterface: ChromeUtils.generateQI([Ci.nsIChannelEventSink]), | 
					
						
							|  |  |  |       asyncOnChannelRedirect: (oldChannel, newChannel, flags, callback) => { | 
					
						
							|  |  |  |         this._onRedirect(oldChannel, newChannel, flags); | 
					
						
							|  |  |  |         callback.onRedirectVerifyCallback(Cr.NS_OK); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     this._channelSinkFactory = { | 
					
						
							|  |  |  |       QueryInterface: ChromeUtils.generateQI([Ci.nsIFactory]), | 
					
						
							|  |  |  |       createInstance: (aOuter, aIID) => this._channelSink.QueryInterface(aIID), | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     // Register self as ChannelEventSink to track redirects.
 | 
					
						
							|  |  |  |     const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); | 
					
						
							|  |  |  |     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); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this._eventListeners = [ | 
					
						
							|  |  |  |       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, true /* fromCache */), 'http-on-examine-cached-response'), | 
					
						
							|  |  |  |       helper.addObserver(this._onResponse.bind(this, true /* fromCache */), 'http-on-examine-merged-response'), | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _requestAuthenticated(httpChannel) { | 
					
						
							|  |  |  |     this._pendingAuthentication.add(httpChannel.channelId + ''); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _requestIdBeforeAuthentication(httpChannel) { | 
					
						
							|  |  |  |     const id = httpChannel.channelId + ''; | 
					
						
							|  |  |  |     return this._postAuthChannelIdToRequestId.has(id) ? id : undefined; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _requestId(httpChannel) { | 
					
						
							|  |  |  |     const id = httpChannel.channelId + ''; | 
					
						
							|  |  |  |     return this._postResumeChannelIdToRequestId.get(id) || this._postAuthChannelIdToRequestId.get(id) || id; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _onRedirect(oldChannel, newChannel, flags) { | 
					
						
							|  |  |  |     if (!(oldChannel instanceof Ci.nsIHttpChannel) || !(newChannel instanceof Ci.nsIHttpChannel)) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     const oldHttpChannel = oldChannel.QueryInterface(Ci.nsIHttpChannel); | 
					
						
							|  |  |  |     const newHttpChannel = newChannel.QueryInterface(Ci.nsIHttpChannel); | 
					
						
							|  |  |  |     const pageNetwork = this._pageNetworkForChannel(oldHttpChannel); | 
					
						
							|  |  |  |     if (!pageNetwork) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     const oldRequestId = this._requestId(oldHttpChannel); | 
					
						
							|  |  |  |     const newRequestId = this._requestId(newHttpChannel); | 
					
						
							|  |  |  |     if (this._resumedRequestIdToHeaders.has(oldRequestId)) { | 
					
						
							|  |  |  |       // When we call resetInterception on a request, we get a new "redirected" request for it.
 | 
					
						
							|  |  |  |       const { method, headers, postData } = this._resumedRequestIdToHeaders.get(oldRequestId); | 
					
						
							|  |  |  |       if (headers) { | 
					
						
							|  |  |  |         // Apply new request headers from interception resume.
 | 
					
						
							|  |  |  |         for (const header of requestHeaders(newChannel)) | 
					
						
							|  |  |  |           newChannel.setRequestHeader(header.name, '', false /* merge */); | 
					
						
							|  |  |  |         for (const header of headers) | 
					
						
							|  |  |  |           newChannel.setRequestHeader(header.name, header.value, false /* merge */); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (method) | 
					
						
							|  |  |  |         newChannel.requestMethod = method; | 
					
						
							|  |  |  |       if (postData && newChannel instanceof Ci.nsIUploadChannel) { | 
					
						
							|  |  |  |         const synthesized = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); | 
					
						
							|  |  |  |         synthesized.data = atob(postData); | 
					
						
							|  |  |  |         newChannel.setUploadStream(synthesized, 'application/octet-stream', -1); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       // Use the old request id for the new "redirected" request for protocol consistency.
 | 
					
						
							|  |  |  |       this._resumedRequestIdToHeaders.delete(oldRequestId); | 
					
						
							|  |  |  |       this._postResumeChannelIdToRequestId.set(newRequestId, oldRequestId); | 
					
						
							|  |  |  |     } else if (!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL)) { | 
					
						
							|  |  |  |       // Regular (non-internal) redirect.
 | 
					
						
							|  |  |  |       this._redirectMap.set(newRequestId, oldRequestId); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   observeActivity(channel, activityType, activitySubtype, timestamp, extraSizeData, extraStringData) { | 
					
						
							|  |  |  |     if (activityType !== Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     if (!(channel instanceof Ci.nsIHttpChannel)) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel); | 
					
						
							|  |  |  |     const pageNetwork = this._pageNetworkForChannel(httpChannel); | 
					
						
							|  |  |  |     if (!pageNetwork) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     if (activitySubtype !== Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     if (this._isResumedChannel(httpChannel)) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     if (this._requestIdBeforeAuthentication(httpChannel)) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     this._sendOnRequestFinished(pageNetwork, httpChannel); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   pageNetworkForTarget(target) { | 
					
						
							|  |  |  |     return PageNetwork._forPageTarget(this, target); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _pageNetworkForChannel(httpChannel) { | 
					
						
							|  |  |  |     let loadContext = helper.getLoadContext(httpChannel); | 
					
						
							|  |  |  |     if (!loadContext) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     const target = this._targetRegistry.targetForBrowser(loadContext.topFrameElement); | 
					
						
							|  |  |  |     if (!target) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     const pageNetwork = PageNetwork._forPageTarget(this, target); | 
					
						
							|  |  |  |     if (!pageNetwork._isActive()) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     return pageNetwork; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _isResumedChannel(httpChannel) { | 
					
						
							|  |  |  |     return this._postResumeChannelIdToRequestId.has(httpChannel.channelId + ''); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _onRequest(channel, topic) { | 
					
						
							|  |  |  |     if (!(channel instanceof Ci.nsIHttpChannel)) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel); | 
					
						
							|  |  |  |     const pageNetwork = this._pageNetworkForChannel(httpChannel); | 
					
						
							|  |  |  |     if (!pageNetwork) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     if (this._isResumedChannel(httpChannel)) { | 
					
						
							|  |  |  |       // Ignore onRequest for resumed requests, but listen to their response.
 | 
					
						
							|  |  |  |       new ResponseBodyListener(this, pageNetwork, httpChannel); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // Convert pending auth bit into auth mapping.
 | 
					
						
							|  |  |  |     const channelId = httpChannel.channelId + ''; | 
					
						
							|  |  |  |     if (this._pendingAuthentication.has(channelId)) { | 
					
						
							|  |  |  |       this._postAuthChannelIdToRequestId.set(channelId, channelId + '-auth'); | 
					
						
							|  |  |  |       this._redirectMap.set(channelId + '-auth', channelId); | 
					
						
							|  |  |  |       this._pendingAuthentication.delete(channelId); | 
					
						
							|  |  |  |       const bodyListener = this._bodyListeners.get(channelId); | 
					
						
							|  |  |  |       if (bodyListener) | 
					
						
							|  |  |  |         bodyListener.dispose(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const browserContext = pageNetwork._target.browserContext(); | 
					
						
							|  |  |  |     if (browserContext) | 
					
						
							|  |  |  |       this._appendExtraHTTPHeaders(httpChannel, browserContext.extraHTTPHeaders); | 
					
						
							|  |  |  |     this._appendExtraHTTPHeaders(httpChannel, pageNetwork._extraHTTPHeaders); | 
					
						
							|  |  |  |     const requestId = this._requestId(httpChannel); | 
					
						
							|  |  |  |     const isRedirect = this._redirectMap.has(requestId); | 
					
						
							|  |  |  |     const interceptionEnabled = this._isInterceptionEnabledForPage(pageNetwork); | 
					
						
							|  |  |  |     if (!interceptionEnabled) { | 
					
						
							|  |  |  |       new NotificationCallbacks(this, pageNetwork, httpChannel, false); | 
					
						
							|  |  |  |       this._sendOnRequest(httpChannel, false); | 
					
						
							|  |  |  |       new ResponseBodyListener(this, pageNetwork, httpChannel); | 
					
						
							|  |  |  |     } else if (isRedirect) { | 
					
						
							|  |  |  |       // We pretend that redirect is interceptable in the protocol, although it's actually not
 | 
					
						
							|  |  |  |       // and therefore we do not instantiate the interceptor.
 | 
					
						
							|  |  |  |       // TODO: look into REDIRECT_MODE_MANUAL.
 | 
					
						
							|  |  |  |       const interceptors = pageNetwork._ensureInterceptors(); | 
					
						
							|  |  |  |       interceptors.set(requestId, { | 
					
						
							|  |  |  |         _resume: () => {}, | 
					
						
							|  |  |  |         _abort: () => {}, | 
					
						
							|  |  |  |         _fulfill: () => {}, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       new NotificationCallbacks(this, pageNetwork, httpChannel, false); | 
					
						
							|  |  |  |       this._sendOnRequest(httpChannel, true); | 
					
						
							|  |  |  |       new ResponseBodyListener(this, pageNetwork, httpChannel); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       const previousCallbacks = httpChannel.notificationCallbacks; | 
					
						
							|  |  |  |       if (previousCallbacks instanceof Ci.nsIInterfaceRequestor) { | 
					
						
							|  |  |  |         const interceptor = previousCallbacks.getInterface(Ci.nsINetworkInterceptController); | 
					
						
							|  |  |  |         // We assume that interceptor is a service worker if there is one.
 | 
					
						
							|  |  |  |         if (interceptor && interceptor.shouldPrepareForIntercept(httpChannel.URI, httpChannel)) { | 
					
						
							|  |  |  |           new NotificationCallbacks(this, pageNetwork, httpChannel, false); | 
					
						
							|  |  |  |           this._sendOnRequest(httpChannel, false); | 
					
						
							|  |  |  |           new ResponseBodyListener(this, pageNetwork, httpChannel); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           // We'll issue onRequest once it's intercepted.
 | 
					
						
							|  |  |  |           new NotificationCallbacks(this, pageNetwork, httpChannel, true); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         // We'll issue onRequest once it's intercepted.
 | 
					
						
							|  |  |  |         new NotificationCallbacks(this, pageNetwork, httpChannel, true); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _isInterceptionEnabledForPage(pageNetwork) { | 
					
						
							|  |  |  |     if (pageNetwork._requestInterceptionEnabled) | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     const browserContext = pageNetwork._target.browserContext(); | 
					
						
							|  |  |  |     if (browserContext && browserContext.requestInterceptionEnabled) | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     if (browserContext && browserContext.settings.onlineOverride === 'offline') | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _appendExtraHTTPHeaders(httpChannel, headers) { | 
					
						
							|  |  |  |     if (!headers) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     for (const header of headers) | 
					
						
							|  |  |  |       httpChannel.setRequestHeader(header.name, header.value, false /* merge */); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _onIntercepted(httpChannel, interceptor) { | 
					
						
							|  |  |  |     const pageNetwork = this._pageNetworkForChannel(httpChannel); | 
					
						
							|  |  |  |     if (!pageNetwork) { | 
					
						
							|  |  |  |       interceptor._resume(); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const browserContext = pageNetwork._target.browserContext(); | 
					
						
							|  |  |  |     if (browserContext && browserContext.settings.onlineOverride === 'offline') { | 
					
						
							|  |  |  |       interceptor._abort(Cr.NS_ERROR_OFFLINE); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const interceptionEnabled = this._isInterceptionEnabledForPage(pageNetwork); | 
					
						
							|  |  |  |     this._sendOnRequest(httpChannel, !!interceptionEnabled); | 
					
						
							|  |  |  |     if (interceptionEnabled) | 
					
						
							|  |  |  |       pageNetwork._ensureInterceptors().set(this._requestId(httpChannel), interceptor); | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       interceptor._resume(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _sendOnRequest(httpChannel, isIntercepted) { | 
					
						
							|  |  |  |     const pageNetwork = this._pageNetworkForChannel(httpChannel); | 
					
						
							|  |  |  |     if (!pageNetwork) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     const causeType = httpChannel.loadInfo ? httpChannel.loadInfo.externalContentPolicyType : Ci.nsIContentPolicy.TYPE_OTHER; | 
					
						
							|  |  |  |     const internalCauseType = httpChannel.loadInfo ? httpChannel.loadInfo.internalContentPolicyType : Ci.nsIContentPolicy.TYPE_OTHER; | 
					
						
							|  |  |  |     const requestId = this._requestId(httpChannel); | 
					
						
							|  |  |  |     const redirectedFrom = this._redirectMap.get(requestId); | 
					
						
							|  |  |  |     this._redirectMap.delete(requestId); | 
					
						
							|  |  |  |     pageNetwork.emit(PageNetwork.Events.Request, httpChannel, { | 
					
						
							|  |  |  |       url: httpChannel.URI.spec, | 
					
						
							|  |  |  |       isIntercepted, | 
					
						
							|  |  |  |       requestId, | 
					
						
							|  |  |  |       redirectedFrom, | 
					
						
							|  |  |  |       postData: readRequestPostData(httpChannel), | 
					
						
							|  |  |  |       headers: requestHeaders(httpChannel), | 
					
						
							|  |  |  |       method: httpChannel.requestMethod, | 
					
						
							|  |  |  |       navigationId: httpChannel.isMainDocumentChannel ? this._requestIdBeforeAuthentication(httpChannel) || this._requestId(httpChannel) : undefined, | 
					
						
							|  |  |  |       cause: causeTypeToString(causeType), | 
					
						
							|  |  |  |       internalCause: causeTypeToString(internalCauseType), | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _sendOnRequestFinished(pageNetwork, httpChannel) { | 
					
						
							|  |  |  |     pageNetwork.emit(PageNetwork.Events.RequestFinished, httpChannel, { | 
					
						
							|  |  |  |       requestId: this._requestId(httpChannel), | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     this._cleanupChannelState(httpChannel); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _sendOnRequestFailed(pageNetwork, httpChannel, error) { | 
					
						
							|  |  |  |     pageNetwork.emit(PageNetwork.Events.RequestFailed, httpChannel, { | 
					
						
							|  |  |  |       requestId: this._requestId(httpChannel), | 
					
						
							|  |  |  |       errorCode: helper.getNetworkErrorStatusText(error), | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     this._cleanupChannelState(httpChannel); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _cleanupChannelState(httpChannel) { | 
					
						
							|  |  |  |     const id = httpChannel.channelId + ''; | 
					
						
							|  |  |  |     this._postResumeChannelIdToRequestId.delete(id); | 
					
						
							|  |  |  |     this._postAuthChannelIdToRequestId.delete(id); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _onResponse(fromCache, httpChannel, topic) { | 
					
						
							|  |  |  |     const pageNetwork = this._pageNetworkForChannel(httpChannel); | 
					
						
							|  |  |  |     if (!pageNetwork) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     httpChannel.QueryInterface(Ci.nsIHttpChannelInternal); | 
					
						
							|  |  |  |     const headers = []; | 
					
						
							|  |  |  |     httpChannel.visitResponseHeaders({ | 
					
						
							|  |  |  |       visitHeader: (name, value) => headers.push({name, value}), | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let remoteIPAddress = undefined; | 
					
						
							|  |  |  |     let remotePort = undefined; | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       remoteIPAddress = httpChannel.remoteAddress; | 
					
						
							|  |  |  |       remotePort = httpChannel.remotePort; | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |       // remoteAddress is not defined for cached requests.
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     pageNetwork.emit(PageNetwork.Events.Response, httpChannel, { | 
					
						
							|  |  |  |       requestId: this._requestId(httpChannel), | 
					
						
							|  |  |  |       securityDetails: getSecurityDetails(httpChannel), | 
					
						
							|  |  |  |       fromCache, | 
					
						
							|  |  |  |       headers, | 
					
						
							|  |  |  |       remoteIPAddress, | 
					
						
							|  |  |  |       remotePort, | 
					
						
							|  |  |  |       status: httpChannel.responseStatus, | 
					
						
							|  |  |  |       statusText: httpChannel.responseStatusText, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _onResponseFinished(pageNetwork, httpChannel, body) { | 
					
						
							|  |  |  |     if (!pageNetwork._isActive()) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     pageNetwork._responseStorage.addResponseBody(httpChannel, body); | 
					
						
							|  |  |  |     this._sendOnRequestFinished(pageNetwork, httpChannel); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   dispose() { | 
					
						
							|  |  |  |     this._activityDistributor.removeObserver(this); | 
					
						
							|  |  |  |     const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); | 
					
						
							|  |  |  |     registrar.unregisterFactory(SINK_CLASS_ID, this._channelSinkFactory); | 
					
						
							|  |  |  |     Services.catMan.deleteCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID, false); | 
					
						
							|  |  |  |     helper.removeListeners(this._eventListeners); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const protocolVersionNames = { | 
					
						
							|  |  |  |   [Ci.nsITransportSecurityInfo.TLS_VERSION_1]: 'TLS 1', | 
					
						
							|  |  |  |   [Ci.nsITransportSecurityInfo.TLS_VERSION_1_1]: 'TLS 1.1', | 
					
						
							|  |  |  |   [Ci.nsITransportSecurityInfo.TLS_VERSION_1_2]: 'TLS 1.2', | 
					
						
							|  |  |  |   [Ci.nsITransportSecurityInfo.TLS_VERSION_1_3]: 'TLS 1.3', | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getSecurityDetails(httpChannel) { | 
					
						
							|  |  |  |   const securityInfo = httpChannel.securityInfo; | 
					
						
							|  |  |  |   if (!securityInfo) | 
					
						
							|  |  |  |     return null; | 
					
						
							|  |  |  |   securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); | 
					
						
							|  |  |  |   if (!securityInfo.serverCert) | 
					
						
							|  |  |  |     return null; | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     protocol: protocolVersionNames[securityInfo.protocolVersion] || '<unknown>', | 
					
						
							|  |  |  |     subjectName: securityInfo.serverCert.commonName, | 
					
						
							|  |  |  |     issuer: securityInfo.serverCert.issuerCommonName, | 
					
						
							|  |  |  |     // Convert to seconds.
 | 
					
						
							|  |  |  |     validFrom: securityInfo.serverCert.validity.notBefore / 1000 / 1000, | 
					
						
							|  |  |  |     validTo: securityInfo.serverCert.validity.notAfter / 1000 / 1000, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function readRequestPostData(httpChannel) { | 
					
						
							|  |  |  |   if (!(httpChannel instanceof Ci.nsIUploadChannel)) | 
					
						
							|  |  |  |     return undefined; | 
					
						
							|  |  |  |   const iStream = httpChannel.uploadStream; | 
					
						
							|  |  |  |   if (!iStream) | 
					
						
							|  |  |  |     return undefined; | 
					
						
							|  |  |  |   const isSeekableStream = iStream instanceof Ci.nsISeekableStream; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let prevOffset; | 
					
						
							|  |  |  |   if (isSeekableStream) { | 
					
						
							|  |  |  |     prevOffset = iStream.tell(); | 
					
						
							|  |  |  |     iStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Read data from the stream.
 | 
					
						
							|  |  |  |   let text = undefined; | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     text = NetUtil.readInputStreamToString(iStream, iStream.available()); | 
					
						
							|  |  |  |     const converter = Cc['@mozilla.org/intl/scriptableunicodeconverter'] | 
					
						
							|  |  |  |         .createInstance(Ci.nsIScriptableUnicodeConverter); | 
					
						
							|  |  |  |     converter.charset = 'UTF-8'; | 
					
						
							|  |  |  |     text = converter.ConvertToUnicode(text); | 
					
						
							|  |  |  |   } catch (err) { | 
					
						
							|  |  |  |     text = undefined; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Seek locks the file, so seek to the beginning only if necko hasn't
 | 
					
						
							|  |  |  |   // read it yet, since necko doesn't seek to 0 before reading (at lest
 | 
					
						
							|  |  |  |   // not till 459384 is fixed).
 | 
					
						
							|  |  |  |   if (isSeekableStream && prevOffset == 0) | 
					
						
							|  |  |  |     iStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); | 
					
						
							|  |  |  |   return text; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function requestHeaders(httpChannel) { | 
					
						
							|  |  |  |   const headers = []; | 
					
						
							|  |  |  |   httpChannel.visitRequestHeaders({ | 
					
						
							|  |  |  |     visitHeader: (name, value) => headers.push({name, value}), | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   return headers; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function causeTypeToString(causeType) { | 
					
						
							|  |  |  |   for (let key in Ci.nsIContentPolicy) { | 
					
						
							|  |  |  |     if (Ci.nsIContentPolicy[key] === causeType) | 
					
						
							|  |  |  |       return key; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return 'TYPE_OTHER'; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ResponseStorage { | 
					
						
							|  |  |  |   constructor(networkObserver, maxTotalSize, maxResponseSize) { | 
					
						
							|  |  |  |     this._networkObserver = networkObserver; | 
					
						
							|  |  |  |     this._totalSize = 0; | 
					
						
							|  |  |  |     this._maxResponseSize = maxResponseSize; | 
					
						
							|  |  |  |     this._maxTotalSize = maxTotalSize; | 
					
						
							|  |  |  |     this._responses = new Map(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   addResponseBody(httpChannel, body) { | 
					
						
							|  |  |  |     if (body.length > this._maxResponseSize) { | 
					
						
							|  |  |  |       this._responses.set(requestId, { | 
					
						
							|  |  |  |         evicted: true, | 
					
						
							|  |  |  |         body: '', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     let encodings = []; | 
					
						
							|  |  |  |     if ((httpChannel instanceof Ci.nsIEncodedChannel) && httpChannel.contentEncodings && !httpChannel.applyConversion) { | 
					
						
							|  |  |  |       const encodingHeader = httpChannel.getResponseHeader("Content-Encoding"); | 
					
						
							|  |  |  |       encodings = encodingHeader.split(/\s*\t*,\s*\t*/); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this._responses.set(this._networkObserver._requestId(httpChannel), {body, encodings}); | 
					
						
							|  |  |  |     this._totalSize += body.length; | 
					
						
							|  |  |  |     if (this._totalSize > this._maxTotalSize) { | 
					
						
							|  |  |  |       for (let [requestId, response] of this._responses) { | 
					
						
							|  |  |  |         this._totalSize -= response.body.length; | 
					
						
							|  |  |  |         response.body = ''; | 
					
						
							|  |  |  |         response.evicted = true; | 
					
						
							|  |  |  |         if (this._totalSize < this._maxTotalSize) | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   getBase64EncodedResponse(requestId) { | 
					
						
							|  |  |  |     const response = this._responses.get(requestId); | 
					
						
							|  |  |  |     if (!response) | 
					
						
							|  |  |  |       throw new Error(`Request "${requestId}" is not found`); | 
					
						
							|  |  |  |     if (response.evicted) | 
					
						
							|  |  |  |       return {base64body: '', evicted: true}; | 
					
						
							|  |  |  |     let result = response.body; | 
					
						
							|  |  |  |     if (response.encodings && response.encodings.length) { | 
					
						
							|  |  |  |       for (const encoding of response.encodings) | 
					
						
							|  |  |  |         result = CommonUtils.convertString(result, encoding, 'uncompressed'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return {base64body: btoa(result)}; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ResponseBodyListener { | 
					
						
							|  |  |  |   constructor(networkObserver, pageNetwork, httpChannel) { | 
					
						
							|  |  |  |     this._networkObserver = networkObserver; | 
					
						
							|  |  |  |     this._pageNetwork = pageNetwork; | 
					
						
							|  |  |  |     this._httpChannel = httpChannel; | 
					
						
							|  |  |  |     this._chunks = []; | 
					
						
							|  |  |  |     this.QueryInterface = ChromeUtils.generateQI([Ci.nsIStreamListener]); | 
					
						
							|  |  |  |     httpChannel.QueryInterface(Ci.nsITraceableChannel); | 
					
						
							|  |  |  |     this.originalListener = httpChannel.setNewListener(this); | 
					
						
							|  |  |  |     this._disposed = false; | 
					
						
							|  |  |  |     this._networkObserver._bodyListeners.set(this._httpChannel.channelId + '', this); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onDataAvailable(aRequest, aInputStream, aOffset, aCount) { | 
					
						
							|  |  |  |     if (this._disposed) { | 
					
						
							|  |  |  |       this.originalListener.onDataAvailable(aRequest, aInputStream, aOffset, aCount); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const iStream = new BinaryInputStream(aInputStream); | 
					
						
							|  |  |  |     const sStream = new StorageStream(8192, aCount, null); | 
					
						
							|  |  |  |     const oStream = new BinaryOutputStream(sStream.getOutputStream(0)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Copy received data as they come.
 | 
					
						
							|  |  |  |     const data = iStream.readBytes(aCount); | 
					
						
							|  |  |  |     this._chunks.push(data); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     oStream.writeBytes(data, aCount); | 
					
						
							|  |  |  |     this.originalListener.onDataAvailable(aRequest, sStream.newInputStream(0), aOffset, aCount); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onStartRequest(aRequest) { | 
					
						
							|  |  |  |     this.originalListener.onStartRequest(aRequest); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onStopRequest(aRequest, aStatusCode) { | 
					
						
							|  |  |  |     this.originalListener.onStopRequest(aRequest, aStatusCode); | 
					
						
							|  |  |  |     if (this._disposed) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (aStatusCode === 0) { | 
					
						
							|  |  |  |       const body = this._chunks.join(''); | 
					
						
							|  |  |  |       this._networkObserver._onResponseFinished(this._pageNetwork, this._httpChannel, body); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       this._networkObserver._sendOnRequestFailed(this._pageNetwork, this._httpChannel, aStatusCode); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     delete this._chunks; | 
					
						
							|  |  |  |     this.dispose(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   dispose() { | 
					
						
							|  |  |  |     this._disposed = true; | 
					
						
							|  |  |  |     this._networkObserver._bodyListeners.delete(this._httpChannel.channelId + ''); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class NotificationCallbacks { | 
					
						
							|  |  |  |   constructor(networkObserver, pageNetwork, httpChannel, shouldIntercept) { | 
					
						
							|  |  |  |     this._networkObserver = networkObserver; | 
					
						
							|  |  |  |     this._pageNetwork = pageNetwork; | 
					
						
							|  |  |  |     this._shouldIntercept = shouldIntercept; | 
					
						
							|  |  |  |     this._httpChannel = httpChannel; | 
					
						
							|  |  |  |     this._previousCallbacks = httpChannel.notificationCallbacks; | 
					
						
							|  |  |  |     httpChannel.notificationCallbacks = this; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const qis = [ | 
					
						
							|  |  |  |       Ci.nsIAuthPrompt2, | 
					
						
							|  |  |  |       Ci.nsIAuthPromptProvider, | 
					
						
							|  |  |  |       Ci.nsIInterfaceRequestor, | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  |     if (shouldIntercept) | 
					
						
							|  |  |  |       qis.push(Ci.nsINetworkInterceptController); | 
					
						
							|  |  |  |     this.QueryInterface = ChromeUtils.generateQI(qis); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   getInterface(iid) { | 
					
						
							|  |  |  |     if (iid.equals(Ci.nsIAuthPrompt2) || iid.equals(Ci.nsIAuthPromptProvider)) | 
					
						
							|  |  |  |       return this; | 
					
						
							|  |  |  |     if (this._shouldIntercept && iid.equals(Ci.nsINetworkInterceptController)) | 
					
						
							|  |  |  |       return this; | 
					
						
							|  |  |  |     if (iid.equals(Ci.nsIAuthPrompt))  // Block nsIAuthPrompt - we want nsIAuthPrompt2 to be used instead.
 | 
					
						
							|  |  |  |       throw Cr.NS_ERROR_NO_INTERFACE; | 
					
						
							|  |  |  |     if (this._previousCallbacks) | 
					
						
							|  |  |  |       return this._previousCallbacks.getInterface(iid); | 
					
						
							|  |  |  |     throw Cr.NS_ERROR_NO_INTERFACE; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _forward(iid, method, args) { | 
					
						
							|  |  |  |     if (!this._previousCallbacks) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const impl = this._previousCallbacks.getInterface(iid); | 
					
						
							|  |  |  |       impl[method].apply(impl, args); | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |       if (e.result != Cr.NS_ERROR_NO_INTERFACE) | 
					
						
							|  |  |  |         throw e; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // nsIAuthPromptProvider
 | 
					
						
							|  |  |  |   getAuthPrompt(aPromptReason, iid) { | 
					
						
							|  |  |  |     return this; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // nsIAuthPrompt2
 | 
					
						
							|  |  |  |   asyncPromptAuth(aChannel, aCallback, aContext, level, authInfo) { | 
					
						
							|  |  |  |     let canceled = false; | 
					
						
							|  |  |  |     Promise.resolve().then(() => { | 
					
						
							|  |  |  |       if (canceled) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       const hasAuth = this.promptAuth(aChannel, level, authInfo); | 
					
						
							|  |  |  |       if (hasAuth) | 
					
						
							|  |  |  |         aCallback.onAuthAvailable(aContext, authInfo); | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         aCallback.onAuthCancelled(aContext, true); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       QueryInterface: ChromeUtils.generateQI([Ci.nsICancelable]), | 
					
						
							|  |  |  |       cancel: () => { | 
					
						
							|  |  |  |         aCallback.onAuthCancelled(aContext, false); | 
					
						
							|  |  |  |         canceled = true; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // nsIAuthPrompt2
 | 
					
						
							|  |  |  |   promptAuth(aChannel, level, authInfo) { | 
					
						
							|  |  |  |     if (authInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED) | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     const browserContext = this._pageNetwork._target.browserContext(); | 
					
						
							|  |  |  |     const credentials = browserContext ? browserContext.httpCredentials : undefined; | 
					
						
							|  |  |  |     if (!credentials) | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     authInfo.username = credentials.username; | 
					
						
							|  |  |  |     authInfo.password = credentials.password; | 
					
						
							|  |  |  |     this._networkObserver._requestAuthenticated(this._httpChannel); | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // nsINetworkInterceptController
 | 
					
						
							|  |  |  |   shouldPrepareForIntercept(aURI, channel) { | 
					
						
							|  |  |  |     if (!(channel instanceof Ci.nsIHttpChannel)) | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel); | 
					
						
							|  |  |  |     return httpChannel.channelId === this._httpChannel.channelId; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // nsINetworkInterceptController
 | 
					
						
							|  |  |  |   channelIntercepted(intercepted) { | 
					
						
							|  |  |  |     this._intercepted = intercepted.QueryInterface(Ci.nsIInterceptedChannel); | 
					
						
							|  |  |  |     const httpChannel = this._intercepted.channel.QueryInterface(Ci.nsIHttpChannel); | 
					
						
							|  |  |  |     this._networkObserver._onIntercepted(httpChannel, this); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _resume(method, headers, postData) { | 
					
						
							|  |  |  |     this._networkObserver._resumedRequestIdToHeaders.set(this._networkObserver._requestId(this._httpChannel), { method, headers, postData }); | 
					
						
							|  |  |  |     this._intercepted.resetInterception(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _fulfill(status, statusText, headers, base64body) { | 
					
						
							|  |  |  |     this._intercepted.synthesizeStatus(status, statusText); | 
					
						
							|  |  |  |     for (const header of headers) | 
					
						
							|  |  |  |       this._intercepted.synthesizeHeader(header.name, header.value); | 
					
						
							|  |  |  |     const synthesized = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); | 
					
						
							|  |  |  |     const body = base64body ? atob(base64body) : ''; | 
					
						
							|  |  |  |     synthesized.data = body; | 
					
						
							|  |  |  |     this._intercepted.startSynthesizedResponse(synthesized, null, null, '', false); | 
					
						
							|  |  |  |     this._intercepted.finishSynthesizedResponse(); | 
					
						
							|  |  |  |     this._pageNetwork.emit(PageNetwork.Events.Response, this._httpChannel, { | 
					
						
							|  |  |  |       requestId: this._networkObserver._requestId(this._httpChannel), | 
					
						
							|  |  |  |       securityDetails: null, | 
					
						
							|  |  |  |       fromCache: false, | 
					
						
							|  |  |  |       headers, | 
					
						
							|  |  |  |       status, | 
					
						
							|  |  |  |       statusText, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     this._networkObserver._onResponseFinished(this._pageNetwork, this._httpChannel, body); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _abort(errorCode) { | 
					
						
							|  |  |  |     const error = errorMap[errorCode] || Cr.NS_ERROR_FAILURE; | 
					
						
							|  |  |  |     this._intercepted.cancelInterception(error); | 
					
						
							|  |  |  |     this._networkObserver._sendOnRequestFailed(this._pageNetwork, this._httpChannel, error); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const errorMap = { | 
					
						
							|  |  |  |   'aborted': Cr.NS_ERROR_ABORT, | 
					
						
							|  |  |  |   'accessdenied': Cr.NS_ERROR_PORT_ACCESS_NOT_ALLOWED, | 
					
						
							|  |  |  |   'addressunreachable': Cr.NS_ERROR_UNKNOWN_HOST, | 
					
						
							|  |  |  |   'blockedbyclient': Cr.NS_ERROR_FAILURE, | 
					
						
							|  |  |  |   'blockedbyresponse': Cr.NS_ERROR_FAILURE, | 
					
						
							|  |  |  |   'connectionaborted': Cr.NS_ERROR_NET_INTERRUPT, | 
					
						
							|  |  |  |   'connectionclosed': Cr.NS_ERROR_FAILURE, | 
					
						
							|  |  |  |   'connectionfailed': Cr.NS_ERROR_FAILURE, | 
					
						
							|  |  |  |   'connectionrefused': Cr.NS_ERROR_CONNECTION_REFUSED, | 
					
						
							|  |  |  |   'connectionreset': Cr.NS_ERROR_NET_RESET, | 
					
						
							|  |  |  |   'internetdisconnected': Cr.NS_ERROR_OFFLINE, | 
					
						
							|  |  |  |   'namenotresolved': Cr.NS_ERROR_UNKNOWN_HOST, | 
					
						
							|  |  |  |   'timedout': Cr.NS_ERROR_NET_TIMEOUT, | 
					
						
							|  |  |  |   'failed': Cr.NS_ERROR_FAILURE, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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.PageNetwork = PageNetwork; |