browser(firefox): enable document channel (#4065)
In the current tip-of-tree Firefox, document channel is enabled by default, so we have to enable it in order to roll further. This patch: 1. Removes content disposition sniffing from content process since it crashes renderer with document channel. 2. Merges all page-related handlers in a single `PageHandler` and serializes network events wrt the `Page.frameAttached` event. The serialization mentioned in (2) is necessary: frame attachment is reported from the content process, and network events are reported from the browsers process. This is an inherent race, that becomes exposed by the document channel. On a side note, (2) makes it possible to synchronously report all buffered events in `SimpleChannel` (cc offline discussion with @dgozman that highlighted an unsighty approach that we currently employ there: reporting events in a subsequent microtask.) References #3995
This commit is contained in:
		
							parent
							
								
									e403fd3912
								
							
						
					
					
						commit
						c8a64b88e1
					
				|  | @ -1,2 +1,2 @@ | ||||||
| 1181 | 1182 | ||||||
| Changed: pavel.feldman@gmail.com Mon, Oct  5, 2020  5:57:35 PM | Changed: lushnikov@chromium.org Mon Oct  5 23:55:54 PDT 2020 | ||||||
|  |  | ||||||
|  | @ -128,11 +128,24 @@ class NetworkRequest { | ||||||
|     this.httpChannel = httpChannel; |     this.httpChannel = httpChannel; | ||||||
|     this._networkObserver._channelToRequest.set(this.httpChannel, this); |     this._networkObserver._channelToRequest.set(this.httpChannel, this); | ||||||
| 
 | 
 | ||||||
|  |     const loadInfo = this.httpChannel.loadInfo; | ||||||
|  |     let browsingContext = loadInfo?.frameBrowsingContext || loadInfo?.browsingContext; | ||||||
|  |     // TODO: Unfortunately, requests from web workers don't have frameBrowsingContext or
 | ||||||
|  |     // browsingContext.
 | ||||||
|  |     //
 | ||||||
|  |     // We fail to attribute them to the original frames on the browser side, but we
 | ||||||
|  |     // can use load context top frame to attribute them to the top frame at least.
 | ||||||
|  |     if (!browsingContext) { | ||||||
|  |       const loadContext = helper.getLoadContext(this.httpChannel); | ||||||
|  |       browsingContext = loadContext?.topFrameElement?.browsingContext; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this._frameId = helper.browsingContextToFrameId(browsingContext); | ||||||
|  | 
 | ||||||
|     this.requestId = httpChannel.channelId + ''; |     this.requestId = httpChannel.channelId + ''; | ||||||
|     this.navigationId = httpChannel.isMainDocumentChannel ? this.requestId : undefined; |     this.navigationId = httpChannel.isMainDocumentChannel ? this.requestId : undefined; | ||||||
| 
 | 
 | ||||||
|     const internalCauseType = this.httpChannel.loadInfo ? this.httpChannel.loadInfo.internalContentPolicyType : Ci.nsIContentPolicy.TYPE_OTHER; |     const internalCauseType = this.httpChannel.loadInfo ? this.httpChannel.loadInfo.internalContentPolicyType : Ci.nsIContentPolicy.TYPE_OTHER; | ||||||
|     this.channelKey = this.httpChannel.channelId + ':' + internalCauseType; |  | ||||||
| 
 | 
 | ||||||
|     this._redirectedIndex = 0; |     this._redirectedIndex = 0; | ||||||
|     const ignoredRedirect = redirectedFrom && !redirectedFrom._sentOnResponse; |     const ignoredRedirect = redirectedFrom && !redirectedFrom._sentOnResponse; | ||||||
|  | @ -140,13 +153,11 @@ class NetworkRequest { | ||||||
|       // We just ignore redirect that did not hit the network before being redirected.
 |       // We just ignore redirect that did not hit the network before being redirected.
 | ||||||
|       // This happens, for example, for automatic http->https redirects.
 |       // This happens, for example, for automatic http->https redirects.
 | ||||||
|       this.navigationId = redirectedFrom.navigationId; |       this.navigationId = redirectedFrom.navigationId; | ||||||
|       this.channelKey = redirectedFrom.channelKey; |  | ||||||
|     } else if (redirectedFrom) { |     } else if (redirectedFrom) { | ||||||
|       this.redirectedFromId = redirectedFrom.requestId; |       this.redirectedFromId = redirectedFrom.requestId; | ||||||
|       this._redirectedIndex = redirectedFrom._redirectedIndex + 1; |       this._redirectedIndex = redirectedFrom._redirectedIndex + 1; | ||||||
|       this.requestId = this.requestId + '-redirect' + this._redirectedIndex; |       this.requestId = this.requestId + '-redirect' + this._redirectedIndex; | ||||||
|       this.navigationId = redirectedFrom.navigationId; |       this.navigationId = redirectedFrom.navigationId; | ||||||
|       this.channelKey = redirectedFrom.channelKey; |  | ||||||
|       // Finish previous request now. Since we inherit the listener, we could in theory
 |       // Finish previous request now. Since we inherit the listener, we could in theory
 | ||||||
|       // use onStopRequest, but that will only happen after the last redirect has finished.
 |       // use onStopRequest, but that will only happen after the last redirect has finished.
 | ||||||
|       redirectedFrom._sendOnRequestFinished(); |       redirectedFrom._sendOnRequestFinished(); | ||||||
|  | @ -492,21 +503,9 @@ class NetworkRequest { | ||||||
|     const loadInfo = this.httpChannel.loadInfo; |     const loadInfo = this.httpChannel.loadInfo; | ||||||
|     const causeType = loadInfo?.externalContentPolicyType || Ci.nsIContentPolicy.TYPE_OTHER; |     const causeType = loadInfo?.externalContentPolicyType || Ci.nsIContentPolicy.TYPE_OTHER; | ||||||
|     const internalCauseType = loadInfo?.internalContentPolicyType || Ci.nsIContentPolicy.TYPE_OTHER; |     const internalCauseType = loadInfo?.internalContentPolicyType || Ci.nsIContentPolicy.TYPE_OTHER; | ||||||
| 
 |  | ||||||
|     let browsingContext = loadInfo?.frameBrowsingContext || loadInfo?.browsingContext; |  | ||||||
|     // TODO: Unfortunately, requests from web workers don't have frameBrowsingContext or
 |  | ||||||
|     // browsingContext.
 |  | ||||||
|     //
 |  | ||||||
|     // We fail to attribute them to the original frames on the browser side, but we
 |  | ||||||
|     // can use load context top frame to attribute them to the top frame at least.
 |  | ||||||
|     if (!browsingContext) { |  | ||||||
|       const loadContext = helper.getLoadContext(this.httpChannel); |  | ||||||
|       browsingContext = loadContext?.topFrameElement?.browsingContext; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pageNetwork.emit(PageNetwork.Events.Request, { |     pageNetwork.emit(PageNetwork.Events.Request, { | ||||||
|       url: this.httpChannel.URI.spec, |       url: this.httpChannel.URI.spec, | ||||||
|       frameId: helper.browsingContextToFrameId(browsingContext), |       frameId: this._frameId, | ||||||
|       isIntercepted, |       isIntercepted, | ||||||
|       requestId: this.requestId, |       requestId: this.requestId, | ||||||
|       redirectedFrom: this.redirectedFromId, |       redirectedFrom: this.redirectedFromId, | ||||||
|  | @ -516,7 +515,7 @@ class NetworkRequest { | ||||||
|       navigationId: this.navigationId, |       navigationId: this.navigationId, | ||||||
|       cause: causeTypeToString(causeType), |       cause: causeTypeToString(causeType), | ||||||
|       internalCause: causeTypeToString(internalCauseType), |       internalCause: causeTypeToString(internalCauseType), | ||||||
|     }, this.channelKey); |     }, this._frameId); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _sendOnResponse(fromCache) { |   _sendOnResponse(fromCache) { | ||||||
|  | @ -563,7 +562,7 @@ class NetworkRequest { | ||||||
|       remotePort, |       remotePort, | ||||||
|       status, |       status, | ||||||
|       statusText, |       statusText, | ||||||
|     }); |     }, this._frameId); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _sendOnRequestFailed(error) { |   _sendOnRequestFailed(error) { | ||||||
|  | @ -572,7 +571,7 @@ class NetworkRequest { | ||||||
|       pageNetwork.emit(PageNetwork.Events.RequestFailed, { |       pageNetwork.emit(PageNetwork.Events.RequestFailed, { | ||||||
|         requestId: this.requestId, |         requestId: this.requestId, | ||||||
|         errorCode: helper.getNetworkErrorStatusText(error), |         errorCode: helper.getNetworkErrorStatusText(error), | ||||||
|       }); |       }, this._frameId); | ||||||
|     } |     } | ||||||
|     this._networkObserver._channelToRequest.delete(this.httpChannel); |     this._networkObserver._channelToRequest.delete(this.httpChannel); | ||||||
|   } |   } | ||||||
|  | @ -582,7 +581,7 @@ class NetworkRequest { | ||||||
|     if (pageNetwork) { |     if (pageNetwork) { | ||||||
|       pageNetwork.emit(PageNetwork.Events.RequestFinished, { |       pageNetwork.emit(PageNetwork.Events.RequestFinished, { | ||||||
|         requestId: this.requestId, |         requestId: this.requestId, | ||||||
|       }); |       }, this._frameId); | ||||||
|     } |     } | ||||||
|     this._networkObserver._channelToRequest.delete(this.httpChannel); |     this._networkObserver._channelToRequest.delete(this.httpChannel); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -26,29 +26,16 @@ class DownloadInterceptor { | ||||||
|   constructor(registry) { |   constructor(registry) { | ||||||
|     this._registry = registry |     this._registry = registry | ||||||
|     this._handlerToUuid = new Map(); |     this._handlerToUuid = new Map(); | ||||||
|     helper.addObserver(this._onRequest.bind(this), 'http-on-modify-request'); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   _onRequest(httpChannel, topic) { |  | ||||||
|     let loadContext = helper.getLoadContext(httpChannel); |  | ||||||
|     if (!loadContext) |  | ||||||
|       return; |  | ||||||
|     if (!loadContext.topFrameElement) |  | ||||||
|       return; |  | ||||||
|     const target = this._registry.targetForBrowser(loadContext.topFrameElement); |  | ||||||
|     if (!target) |  | ||||||
|       return; |  | ||||||
|     target._channelIds.add(httpChannel.channelId); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   //
 |   //
 | ||||||
|   // nsIDownloadInterceptor implementation.
 |   // nsIDownloadInterceptor implementation.
 | ||||||
|   //
 |   //
 | ||||||
|   interceptDownloadRequest(externalAppHandler, request, browsingContext, outFile) { |   interceptDownloadRequest(externalAppHandler, request, browsingContext, outFile) { | ||||||
|     let pageTarget = this._registry._browserBrowsingContextToTarget.get(browsingContext); |     if (!(request instanceof Ci.nsIChannel)) | ||||||
|     // New page downloads won't have browsing contex.
 |       return false; | ||||||
|     if (!pageTarget) |     const channel = request.QueryInterface(Ci.nsIChannel); | ||||||
|       pageTarget = this._registry._targetForChannel(request); |     let pageTarget = this._registry._browserBrowsingContextToTarget.get(channel.loadInfo.browsingContext); | ||||||
|     if (!pageTarget) |     if (!pageTarget) | ||||||
|       return false; |       return false; | ||||||
| 
 | 
 | ||||||
|  | @ -324,15 +311,6 @@ class TargetRegistry { | ||||||
|   targetForBrowser(browser) { |   targetForBrowser(browser) { | ||||||
|     return this._browserToTarget.get(browser); |     return this._browserToTarget.get(browser); | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   _targetForChannel(channel) { |  | ||||||
|     const channelId = channel.channelId; |  | ||||||
|     for (const target of this._browserToTarget.values()) { |  | ||||||
|       if (target._channelIds.has(channelId)) |  | ||||||
|         return target; |  | ||||||
|     } |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class PageTarget { | class PageTarget { | ||||||
|  | @ -350,7 +328,6 @@ class PageTarget { | ||||||
|     this._url = 'about:blank'; |     this._url = 'about:blank'; | ||||||
|     this._openerId = opener ? opener.id() : undefined; |     this._openerId = opener ? opener.id() : undefined; | ||||||
|     this._channel = SimpleChannel.createForMessageManager(`browser::page[${this._targetId}]`, this._linkedBrowser.messageManager); |     this._channel = SimpleChannel.createForMessageManager(`browser::page[${this._targetId}]`, this._linkedBrowser.messageManager); | ||||||
|     this._channelIds = new Set(); |  | ||||||
|     this._screencastInfo = undefined; |     this._screencastInfo = undefined; | ||||||
| 
 | 
 | ||||||
|     const navigationListener = { |     const navigationListener = { | ||||||
|  |  | ||||||
|  | @ -73,7 +73,7 @@ CommandLineHandler.prototype = { | ||||||
|             if (silent) |             if (silent) | ||||||
|               Services.startup.exitLastWindowClosingSurvivalArea(); |               Services.startup.exitLastWindowClosingSurvivalArea(); | ||||||
|           }); |           }); | ||||||
|           dispatcher.rootSession().registerHandler('Browser', browserHandler); |           dispatcher.rootSession().setHandler(browserHandler); | ||||||
|         } |         } | ||||||
|       }); |       }); | ||||||
|       loadFrameScript(); |       loadFrameScript(); | ||||||
|  | @ -101,9 +101,9 @@ CommandLineHandler.prototype = { | ||||||
|           pipe.stop(); |           pipe.stop(); | ||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
|       dispatcher.rootSession().registerHandler('Browser', browserHandler); |       dispatcher.rootSession().setHandler(browserHandler); | ||||||
|       loadFrameScript(); |       loadFrameScript(); | ||||||
|       dump(`Juggler listening to the pipe\n`); |       dump(`\nJuggler listening to the pipe\n`); | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -197,19 +197,12 @@ class FrameTree { | ||||||
|     const isTransferring = flag & Ci.nsIWebProgressListener.STATE_TRANSFERRING; |     const isTransferring = flag & Ci.nsIWebProgressListener.STATE_TRANSFERRING; | ||||||
|     const isStop = flag & Ci.nsIWebProgressListener.STATE_STOP; |     const isStop = flag & Ci.nsIWebProgressListener.STATE_STOP; | ||||||
| 
 | 
 | ||||||
|     let isDownload = false; |  | ||||||
|     try { |  | ||||||
|       isDownload = (channel.contentDisposition === Ci.nsIChannel.DISPOSITION_ATTACHMENT); |  | ||||||
|     } catch(e) { |  | ||||||
|       // The method is expected to throw if it's not an attachment.
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (isStart) { |     if (isStart) { | ||||||
|       // Starting a new navigation.
 |       // Starting a new navigation.
 | ||||||
|       frame._pendingNavigationId = this._channelId(channel); |       frame._pendingNavigationId = this._channelId(channel); | ||||||
|       frame._pendingNavigationURL = channel.URI.spec; |       frame._pendingNavigationURL = channel.URI.spec; | ||||||
|       this.emit(FrameTree.Events.NavigationStarted, frame); |       this.emit(FrameTree.Events.NavigationStarted, frame); | ||||||
|     } else if (isTransferring || (isStop && frame._pendingNavigationId && !status && !isDownload)) { |     } else if (isTransferring || (isStop && frame._pendingNavigationId && !status)) { | ||||||
|       // Navigation is committed.
 |       // Navigation is committed.
 | ||||||
|       for (const subframe of frame._children) |       for (const subframe of frame._children) | ||||||
|         this._detachFrame(subframe); |         this._detachFrame(subframe); | ||||||
|  | @ -221,15 +214,15 @@ class FrameTree { | ||||||
|       this.emit(FrameTree.Events.NavigationCommitted, frame); |       this.emit(FrameTree.Events.NavigationCommitted, frame); | ||||||
|       if (frame === this._mainFrame) |       if (frame === this._mainFrame) | ||||||
|         this.forcePageReady(); |         this.forcePageReady(); | ||||||
|     } else if (isStop && frame._pendingNavigationId && (status || isDownload)) { |     } else if (isStop && frame._pendingNavigationId && status) { | ||||||
|       // Navigation is aborted.
 |       // Navigation is aborted.
 | ||||||
|       const navigationId = frame._pendingNavigationId; |       const navigationId = frame._pendingNavigationId; | ||||||
|       frame._pendingNavigationId = null; |       frame._pendingNavigationId = null; | ||||||
|       frame._pendingNavigationURL = null; |       frame._pendingNavigationURL = null; | ||||||
|       // Always report download navigation as failure to match other browsers.
 |       // Always report download navigation as failure to match other browsers.
 | ||||||
|       const errorText = isDownload ? 'Will download to file' : helper.getNetworkErrorStatusText(status); |       const errorText = helper.getNetworkErrorStatusText(status); | ||||||
|       this.emit(FrameTree.Events.NavigationAborted, frame, navigationId, errorText); |       this.emit(FrameTree.Events.NavigationAborted, frame, navigationId, errorText); | ||||||
|       if (frame === this._mainFrame && status !== Cr.NS_BINDING_ABORTED && !isDownload) |       if (frame === this._mainFrame && status !== Cr.NS_BINDING_ABORTED) | ||||||
|         this.forcePageReady(); |         this.forcePageReady(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | @ -245,9 +238,9 @@ class FrameTree { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _channelId(channel) { |   _channelId(channel) { | ||||||
|     if (channel instanceof Ci.nsIHttpChannel) { |     if (channel instanceof Ci.nsIIdentChannel) { | ||||||
|       const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel); |       const identChannel = channel.QueryInterface(Ci.nsIIdentChannel); | ||||||
|       return String(httpChannel.channelId); |       return String(identChannel.channelId); | ||||||
|     } |     } | ||||||
|     return helper.generateId(); |     return helper.generateId(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -12,10 +12,7 @@ juggler.jar: | ||||||
|   content/protocol/Protocol.js (protocol/Protocol.js) |   content/protocol/Protocol.js (protocol/Protocol.js) | ||||||
|   content/protocol/Dispatcher.js (protocol/Dispatcher.js) |   content/protocol/Dispatcher.js (protocol/Dispatcher.js) | ||||||
|   content/protocol/PageHandler.js (protocol/PageHandler.js) |   content/protocol/PageHandler.js (protocol/PageHandler.js) | ||||||
|   content/protocol/RuntimeHandler.js (protocol/RuntimeHandler.js) |  | ||||||
|   content/protocol/NetworkHandler.js (protocol/NetworkHandler.js) |  | ||||||
|   content/protocol/BrowserHandler.js (protocol/BrowserHandler.js) |   content/protocol/BrowserHandler.js (protocol/BrowserHandler.js) | ||||||
|   content/protocol/AccessibilityHandler.js (protocol/AccessibilityHandler.js) |  | ||||||
|   content/content/main.js (content/main.js) |   content/content/main.js (content/main.js) | ||||||
|   content/content/FrameTree.js (content/FrameTree.js) |   content/content/FrameTree.js (content/FrameTree.js) | ||||||
|   content/content/PageAgent.js (content/PageAgent.js) |   content/content/PageAgent.js (content/PageAgent.js) | ||||||
|  |  | ||||||
|  | @ -1,20 +0,0 @@ | ||||||
| /* 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/. */
 |  | ||||||
| 
 |  | ||||||
| class AccessibilityHandler { |  | ||||||
|   constructor(session, contentChannel) { |  | ||||||
|     this._contentPage = contentChannel.connect('page'); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async getFullAXTree(params) { |  | ||||||
|     return await this._contentPage.send('getFullAXTree', params); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   dispose() { |  | ||||||
|     this._contentPage.dispose(); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var EXPORTED_SYMBOLS = ['AccessibilityHandler']; |  | ||||||
| this.AccessibilityHandler = AccessibilityHandler; |  | ||||||
|  | @ -8,9 +8,6 @@ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); | ||||||
| const {TargetRegistry} = ChromeUtils.import("chrome://juggler/content/TargetRegistry.js"); | const {TargetRegistry} = ChromeUtils.import("chrome://juggler/content/TargetRegistry.js"); | ||||||
| const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); | const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); | ||||||
| const {PageHandler} = ChromeUtils.import("chrome://juggler/content/protocol/PageHandler.js"); | const {PageHandler} = ChromeUtils.import("chrome://juggler/content/protocol/PageHandler.js"); | ||||||
| const {NetworkHandler} = ChromeUtils.import("chrome://juggler/content/protocol/NetworkHandler.js"); |  | ||||||
| const {RuntimeHandler} = ChromeUtils.import("chrome://juggler/content/protocol/RuntimeHandler.js"); |  | ||||||
| const {AccessibilityHandler} = ChromeUtils.import("chrome://juggler/content/protocol/AccessibilityHandler.js"); |  | ||||||
| 
 | 
 | ||||||
| const helper = new Helper(); | const helper = new Helper(); | ||||||
| 
 | 
 | ||||||
|  | @ -27,7 +24,7 @@ class BrowserHandler { | ||||||
|     this._onclose = onclose; |     this._onclose = onclose; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async enable({attachToDefaultContext}) { |   async ['Browser.enable']({attachToDefaultContext}) { | ||||||
|     if (this._enabled) |     if (this._enabled) | ||||||
|       return; |       return; | ||||||
|     this._enabled = true; |     this._enabled = true; | ||||||
|  | @ -50,7 +47,7 @@ class BrowserHandler { | ||||||
|       this._onTargetCreated(target); |       this._onTargetCreated(target); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async createBrowserContext({removeOnDetach}) { |   async ['Browser.createBrowserContext']({removeOnDetach}) { | ||||||
|     if (!this._enabled) |     if (!this._enabled) | ||||||
|       throw new Error('Browser domain is not enabled'); |       throw new Error('Browser domain is not enabled'); | ||||||
|     const browserContext = this._targetRegistry.createBrowserContext(removeOnDetach); |     const browserContext = this._targetRegistry.createBrowserContext(removeOnDetach); | ||||||
|  | @ -58,7 +55,7 @@ class BrowserHandler { | ||||||
|     return {browserContextId: browserContext.browserContextId}; |     return {browserContextId: browserContext.browserContextId}; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async removeBrowserContext({browserContextId}) { |   async ['Browser.removeBrowserContext']({browserContextId}) { | ||||||
|     if (!this._enabled) |     if (!this._enabled) | ||||||
|       throw new Error('Browser domain is not enabled'); |       throw new Error('Browser domain is not enabled'); | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).destroy(); |     await this._targetRegistry.browserContextForId(browserContextId).destroy(); | ||||||
|  | @ -90,18 +87,11 @@ class BrowserHandler { | ||||||
|     const channel = target.channel(); |     const channel = target.channel(); | ||||||
|     const session = this._dispatcher.createSession(); |     const session = this._dispatcher.createSession(); | ||||||
|     this._attachedSessions.set(target, session); |     this._attachedSessions.set(target, session); | ||||||
|     const pageHandler = new PageHandler(target, session, channel); |  | ||||||
|     const networkHandler = new NetworkHandler(target, session, channel); |  | ||||||
|     session.registerHandler('Page', pageHandler); |  | ||||||
|     session.registerHandler('Network', networkHandler); |  | ||||||
|     session.registerHandler('Runtime', new RuntimeHandler(session, channel)); |  | ||||||
|     session.registerHandler('Accessibility', new AccessibilityHandler(session, channel)); |  | ||||||
|     pageHandler.enable(); |  | ||||||
|     networkHandler.enable(); |  | ||||||
|     this._session.emitEvent('Browser.attachedToTarget', { |     this._session.emitEvent('Browser.attachedToTarget', { | ||||||
|       sessionId: session.sessionId(), |       sessionId: session.sessionId(), | ||||||
|       targetInfo: target.info() |       targetInfo: target.info() | ||||||
|     }); |     }); | ||||||
|  |     session.setHandler(new PageHandler(target, session, channel)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _onTargetDestroyed(target) { |   _onTargetDestroyed(target) { | ||||||
|  | @ -124,12 +114,12 @@ class BrowserHandler { | ||||||
|     this._session.emitEvent('Browser.downloadFinished', downloadInfo); |     this._session.emitEvent('Browser.downloadFinished', downloadInfo); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async newPage({browserContextId}) { |   async ['Browser.newPage']({browserContextId}) { | ||||||
|     const targetId = await this._targetRegistry.newPage({browserContextId}); |     const targetId = await this._targetRegistry.newPage({browserContextId}); | ||||||
|     return {targetId}; |     return {targetId}; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async close() { |   async ['Browser.close']() { | ||||||
|     let browserWindow = Services.wm.getMostRecentWindow( |     let browserWindow = Services.wm.getMostRecentWindow( | ||||||
|       "navigator:browser" |       "navigator:browser" | ||||||
|     ); |     ); | ||||||
|  | @ -140,109 +130,109 @@ class BrowserHandler { | ||||||
|     Services.startup.quit(Ci.nsIAppStartup.eForceQuit); |     Services.startup.quit(Ci.nsIAppStartup.eForceQuit); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async grantPermissions({browserContextId, origin, permissions}) { |   async ['Browser.grantPermissions']({browserContextId, origin, permissions}) { | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).grantPermissions(origin, permissions); |     await this._targetRegistry.browserContextForId(browserContextId).grantPermissions(origin, permissions); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   resetPermissions({browserContextId}) { |   async ['Browser.resetPermissions']({browserContextId}) { | ||||||
|     this._targetRegistry.browserContextForId(browserContextId).resetPermissions(); |     this._targetRegistry.browserContextForId(browserContextId).resetPermissions(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   setExtraHTTPHeaders({browserContextId, headers}) { |   ['Browser.setExtraHTTPHeaders']({browserContextId, headers}) { | ||||||
|     this._targetRegistry.browserContextForId(browserContextId).extraHTTPHeaders = headers; |     this._targetRegistry.browserContextForId(browserContextId).extraHTTPHeaders = headers; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   setHTTPCredentials({browserContextId, credentials}) { |   ['Browser.setHTTPCredentials']({browserContextId, credentials}) { | ||||||
|     this._targetRegistry.browserContextForId(browserContextId).httpCredentials = nullToUndefined(credentials); |     this._targetRegistry.browserContextForId(browserContextId).httpCredentials = nullToUndefined(credentials); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setBrowserProxy({type, host, port, bypass, username, password}) { |   async ['Browser.setBrowserProxy']({type, host, port, bypass, username, password}) { | ||||||
|     this._targetRegistry.setBrowserProxy({ type, host, port, bypass, username, password}); |     this._targetRegistry.setBrowserProxy({ type, host, port, bypass, username, password}); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setContextProxy({browserContextId, type, host, port, bypass, username, password}) { |   async ['Browser.setContextProxy']({browserContextId, type, host, port, bypass, username, password}) { | ||||||
|     const browserContext = this._targetRegistry.browserContextForId(browserContextId); |     const browserContext = this._targetRegistry.browserContextForId(browserContextId); | ||||||
|     browserContext.setProxy({ type, host, port, bypass, username, password }); |     browserContext.setProxy({ type, host, port, bypass, username, password }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   setRequestInterception({browserContextId, enabled}) { |   ['Browser.setRequestInterception']({browserContextId, enabled}) { | ||||||
|     this._targetRegistry.browserContextForId(browserContextId).requestInterceptionEnabled = enabled; |     this._targetRegistry.browserContextForId(browserContextId).requestInterceptionEnabled = enabled; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   setIgnoreHTTPSErrors({browserContextId, ignoreHTTPSErrors}) { |   ['Browser.setIgnoreHTTPSErrors']({browserContextId, ignoreHTTPSErrors}) { | ||||||
|     this._targetRegistry.browserContextForId(browserContextId).setIgnoreHTTPSErrors(nullToUndefined(ignoreHTTPSErrors)); |     this._targetRegistry.browserContextForId(browserContextId).setIgnoreHTTPSErrors(nullToUndefined(ignoreHTTPSErrors)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   setDownloadOptions({browserContextId, downloadOptions}) { |   ['Browser.setDownloadOptions']({browserContextId, downloadOptions}) { | ||||||
|     this._targetRegistry.browserContextForId(browserContextId).downloadOptions = nullToUndefined(downloadOptions); |     this._targetRegistry.browserContextForId(browserContextId).downloadOptions = nullToUndefined(downloadOptions); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setGeolocationOverride({browserContextId, geolocation}) { |   async ['Browser.setGeolocationOverride']({browserContextId, geolocation}) { | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).applySetting('geolocation', nullToUndefined(geolocation)); |     await this._targetRegistry.browserContextForId(browserContextId).applySetting('geolocation', nullToUndefined(geolocation)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setOnlineOverride({browserContextId, override}) { |   async ['Browser.setOnlineOverride']({browserContextId, override}) { | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).applySetting('onlineOverride', nullToUndefined(override)); |     await this._targetRegistry.browserContextForId(browserContextId).applySetting('onlineOverride', nullToUndefined(override)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setColorScheme({browserContextId, colorScheme}) { |   async ['Browser.setColorScheme']({browserContextId, colorScheme}) { | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).applySetting('colorScheme', nullToUndefined(colorScheme)); |     await this._targetRegistry.browserContextForId(browserContextId).applySetting('colorScheme', nullToUndefined(colorScheme)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setScreencastOptions({browserContextId, dir, width, height, scale}) { |   async ['Browser.setScreencastOptions']({browserContextId, dir, width, height, scale}) { | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).setScreencastOptions({dir, width, height, scale}); |     await this._targetRegistry.browserContextForId(browserContextId).setScreencastOptions({dir, width, height, scale}); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setUserAgentOverride({browserContextId, userAgent}) { |   async ['Browser.setUserAgentOverride']({browserContextId, userAgent}) { | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).setDefaultUserAgent(userAgent); |     await this._targetRegistry.browserContextForId(browserContextId).setDefaultUserAgent(userAgent); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setBypassCSP({browserContextId, bypassCSP}) { |   async ['Browser.setBypassCSP']({browserContextId, bypassCSP}) { | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).applySetting('bypassCSP', nullToUndefined(bypassCSP)); |     await this._targetRegistry.browserContextForId(browserContextId).applySetting('bypassCSP', nullToUndefined(bypassCSP)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setJavaScriptDisabled({browserContextId, javaScriptDisabled}) { |   async ['Browser.setJavaScriptDisabled']({browserContextId, javaScriptDisabled}) { | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).applySetting('javaScriptDisabled', nullToUndefined(javaScriptDisabled)); |     await this._targetRegistry.browserContextForId(browserContextId).applySetting('javaScriptDisabled', nullToUndefined(javaScriptDisabled)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setLocaleOverride({browserContextId, locale}) { |   async ['Browser.setLocaleOverride']({browserContextId, locale}) { | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).applySetting('locale', nullToUndefined(locale)); |     await this._targetRegistry.browserContextForId(browserContextId).applySetting('locale', nullToUndefined(locale)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setTimezoneOverride({browserContextId, timezoneId}) { |   async ['Browser.setTimezoneOverride']({browserContextId, timezoneId}) { | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).applySetting('timezoneId', nullToUndefined(timezoneId)); |     await this._targetRegistry.browserContextForId(browserContextId).applySetting('timezoneId', nullToUndefined(timezoneId)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setTouchOverride({browserContextId, hasTouch}) { |   async ['Browser.setTouchOverride']({browserContextId, hasTouch}) { | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).applySetting('hasTouch', nullToUndefined(hasTouch)); |     await this._targetRegistry.browserContextForId(browserContextId).applySetting('hasTouch', nullToUndefined(hasTouch)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setDefaultViewport({browserContextId, viewport}) { |   async ['Browser.setDefaultViewport']({browserContextId, viewport}) { | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).setDefaultViewport(nullToUndefined(viewport)); |     await this._targetRegistry.browserContextForId(browserContextId).setDefaultViewport(nullToUndefined(viewport)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async addScriptToEvaluateOnNewDocument({browserContextId, script}) { |   async ['Browser.addScriptToEvaluateOnNewDocument']({browserContextId, script}) { | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).addScriptToEvaluateOnNewDocument(script); |     await this._targetRegistry.browserContextForId(browserContextId).addScriptToEvaluateOnNewDocument(script); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async addBinding({browserContextId, name, script}) { |   async ['Browser.addBinding']({browserContextId, name, script}) { | ||||||
|     await this._targetRegistry.browserContextForId(browserContextId).addBinding(name, script); |     await this._targetRegistry.browserContextForId(browserContextId).addBinding(name, script); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   setCookies({browserContextId, cookies}) { |   ['Browser.setCookies']({browserContextId, cookies}) { | ||||||
|     this._targetRegistry.browserContextForId(browserContextId).setCookies(cookies); |     this._targetRegistry.browserContextForId(browserContextId).setCookies(cookies); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   clearCookies({browserContextId}) { |   ['Browser.clearCookies']({browserContextId}) { | ||||||
|     this._targetRegistry.browserContextForId(browserContextId).clearCookies(); |     this._targetRegistry.browserContextForId(browserContextId).clearCookies(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getCookies({browserContextId}) { |   ['Browser.getCookies']({browserContextId}) { | ||||||
|     const cookies = this._targetRegistry.browserContextForId(browserContextId).getCookies(); |     const cookies = this._targetRegistry.browserContextForId(browserContextId).getCookies(); | ||||||
|     return {cookies}; |     return {cookies}; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async getInfo() { |   async ['Browser.getInfo']() { | ||||||
|     const version = Components.classes["@mozilla.org/xre/app-info;1"] |     const version = Components.classes["@mozilla.org/xre/app-info;1"] | ||||||
|                               .getService(Components.interfaces.nsIXULAppInfo) |                               .getService(Components.interfaces.nsIXULAppInfo) | ||||||
|                               .version; |                               .version; | ||||||
|  |  | ||||||
|  | @ -66,7 +66,7 @@ class Dispatcher { | ||||||
|       if (!checkScheme(descriptor.params || {}, params, details)) |       if (!checkScheme(descriptor.params || {}, params, details)) | ||||||
|         throw new Error(`ERROR: failed to call method '${method}' with parameters ${JSON.stringify(params, null, 2)}\n${details.error}`); |         throw new Error(`ERROR: failed to call method '${method}' with parameters ${JSON.stringify(params, null, 2)}\n${details.error}`); | ||||||
| 
 | 
 | ||||||
|       const result = await session.dispatch(domain, methodName, params); |       const result = await session.dispatch(method, params); | ||||||
| 
 | 
 | ||||||
|       details = {}; |       details = {}; | ||||||
|       if ((descriptor.returns || result) && !checkScheme(descriptor.returns, result, details)) |       if ((descriptor.returns || result) && !checkScheme(descriptor.returns, result, details)) | ||||||
|  | @ -97,24 +97,21 @@ class ProtocolSession { | ||||||
|   constructor(dispatcher, sessionId) { |   constructor(dispatcher, sessionId) { | ||||||
|     this._sessionId = sessionId; |     this._sessionId = sessionId; | ||||||
|     this._dispatcher = dispatcher; |     this._dispatcher = dispatcher; | ||||||
|     this._handlers = new Map(); |     this._handler = null; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   sessionId() { |   sessionId() { | ||||||
|     return this._sessionId; |     return this._sessionId; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   registerHandler(domainName, handler) { |   setHandler(handler) { | ||||||
|     this._handlers.set(domainName, handler); |     this._handler = handler; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _dispose() { |   _dispose() { | ||||||
|     for (const [domainName, handler] of this._handlers) { |     if (this._handler) | ||||||
|       if (typeof handler.dispose !== 'function') |       this._handler.dispose(); | ||||||
|         throw new Error(`Handler for "${domainName}" domain does not define |dispose| method!`); |     this._handler = null; | ||||||
|       handler.dispose(); |  | ||||||
|     } |  | ||||||
|     this._handlers.clear(); |  | ||||||
|     this._dispatcher = null; |     this._dispatcher = null; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -124,13 +121,12 @@ class ProtocolSession { | ||||||
|     this._dispatcher._emitEvent(this._sessionId, eventName, params); |     this._dispatcher._emitEvent(this._sessionId, eventName, params); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async dispatch(domainName, methodName, params) { |   async dispatch(method, params) { | ||||||
|     const handler = this._handlers.get(domainName); |     if (!this._handler) | ||||||
|     if (!handler) |       throw new Error(`Session does not have a handler!`); | ||||||
|       throw new Error(`Domain "${domainName}" does not exist`); |     if (!this._handler[method]) | ||||||
|     if (!handler[methodName]) |       throw new Error(`Handler for does not implement method "${method}"`); | ||||||
|       throw new Error(`Handler for domain "${domainName}" does not implement method "${methodName}"`); |     return await this._handler[method](params); | ||||||
|     return await handler[methodName](params); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,86 +0,0 @@ | ||||||
| /* 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 {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); |  | ||||||
| const {NetworkObserver, PageNetwork} = ChromeUtils.import('chrome://juggler/content/NetworkObserver.js'); |  | ||||||
| 
 |  | ||||||
| const Cc = Components.classes; |  | ||||||
| const Ci = Components.interfaces; |  | ||||||
| const Cu = Components.utils; |  | ||||||
| const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; |  | ||||||
| const helper = new Helper(); |  | ||||||
| 
 |  | ||||||
| class NetworkHandler { |  | ||||||
|   constructor(target, session, contentChannel) { |  | ||||||
|     this._session = session; |  | ||||||
|     this._enabled = false; |  | ||||||
|     this._pageNetwork = NetworkObserver.instance().pageNetworkForTarget(target); |  | ||||||
|     this._eventListeners = []; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async enable() { |  | ||||||
|     if (this._enabled) |  | ||||||
|       return; |  | ||||||
|     this._enabled = true; |  | ||||||
|     this._eventListeners = [ |  | ||||||
|       helper.on(this._pageNetwork, PageNetwork.Events.Request, this._onRequest.bind(this)), |  | ||||||
|       helper.on(this._pageNetwork, PageNetwork.Events.Response, this._onResponse.bind(this)), |  | ||||||
|       helper.on(this._pageNetwork, PageNetwork.Events.RequestFinished, this._onRequestFinished.bind(this)), |  | ||||||
|       helper.on(this._pageNetwork, PageNetwork.Events.RequestFailed, this._onRequestFailed.bind(this)), |  | ||||||
|       this._pageNetwork.addSession(), |  | ||||||
|     ]; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async getResponseBody({requestId}) { |  | ||||||
|     return this._pageNetwork.getResponseBody(requestId); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async setExtraHTTPHeaders({headers}) { |  | ||||||
|     this._pageNetwork.setExtraHTTPHeaders(headers); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async setRequestInterception({enabled}) { |  | ||||||
|     if (enabled) |  | ||||||
|       this._pageNetwork.enableRequestInterception(); |  | ||||||
|     else |  | ||||||
|     this._pageNetwork.disableRequestInterception(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async resumeInterceptedRequest({requestId, method, headers, postData}) { |  | ||||||
|     this._pageNetwork.resumeInterceptedRequest(requestId, method, headers, postData); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async abortInterceptedRequest({requestId, errorCode}) { |  | ||||||
|     this._pageNetwork.abortInterceptedRequest(requestId, errorCode); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async fulfillInterceptedRequest({requestId, status, statusText, headers, base64body}) { |  | ||||||
|     this._pageNetwork.fulfillInterceptedRequest(requestId, status, statusText, headers, base64body); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   dispose() { |  | ||||||
|     helper.removeListeners(this._eventListeners); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async _onRequest(eventDetails, channelKey) { |  | ||||||
|     this._session.emitEvent('Network.requestWillBeSent', eventDetails); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async _onResponse(eventDetails) { |  | ||||||
|     this._session.emitEvent('Network.responseReceived', eventDetails); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async _onRequestFinished(eventDetails) { |  | ||||||
|     this._session.emitEvent('Network.requestFinished', eventDetails); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async _onRequestFailed(eventDetails) { |  | ||||||
|     this._session.emitEvent('Network.requestFailed', eventDetails); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var EXPORTED_SYMBOLS = ['NetworkHandler']; |  | ||||||
| this.NetworkHandler = NetworkHandler; |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| 
 | 
 | ||||||
| const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); | const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); | ||||||
| const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); | const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); | ||||||
|  | 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; | ||||||
|  | @ -59,19 +60,28 @@ class PageHandler { | ||||||
|     this._session = session; |     this._session = session; | ||||||
|     this._contentChannel = contentChannel; |     this._contentChannel = contentChannel; | ||||||
|     this._contentPage = contentChannel.connect('page'); |     this._contentPage = contentChannel.connect('page'); | ||||||
|  |     this._contentRuntime = contentChannel.connect('runtime'); | ||||||
|     this._workers = new Map(); |     this._workers = new Map(); | ||||||
| 
 | 
 | ||||||
|  |     this._pageTarget = target; | ||||||
|  |     this._browser = target.linkedBrowser(); | ||||||
|  |     this._dialogs = new Map(); | ||||||
|  |     this._pageNetwork = NetworkObserver.instance().pageNetworkForTarget(target); | ||||||
|  | 
 | ||||||
|     const emitProtocolEvent = eventName => { |     const emitProtocolEvent = eventName => { | ||||||
|       return (...args) => this._session.emitEvent(eventName, ...args); |       return (...args) => this._session.emitEvent(eventName, ...args); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     this._reportedFrameIds = new Set(); | ||||||
|  |     this._networkEventsForUnreportedFrameIds = new Map(); | ||||||
|  | 
 | ||||||
|     this._eventListeners = [ |     this._eventListeners = [ | ||||||
|       contentChannel.register('page', { |       contentChannel.register('page', { | ||||||
|         pageBindingCalled: emitProtocolEvent('Page.bindingCalled'), |         pageBindingCalled: emitProtocolEvent('Page.bindingCalled'), | ||||||
|         pageDispatchMessageFromWorker: emitProtocolEvent('Page.dispatchMessageFromWorker'), |         pageDispatchMessageFromWorker: emitProtocolEvent('Page.dispatchMessageFromWorker'), | ||||||
|         pageEventFired: emitProtocolEvent('Page.eventFired'), |         pageEventFired: emitProtocolEvent('Page.eventFired'), | ||||||
|         pageFileChooserOpened: emitProtocolEvent('Page.fileChooserOpened'), |         pageFileChooserOpened: emitProtocolEvent('Page.fileChooserOpened'), | ||||||
|         pageFrameAttached: emitProtocolEvent('Page.frameAttached'), |         pageFrameAttached: this._onFrameAttached.bind(this), | ||||||
|         pageFrameDetached: emitProtocolEvent('Page.frameDetached'), |         pageFrameDetached: emitProtocolEvent('Page.frameDetached'), | ||||||
|         pageLinkClicked: emitProtocolEvent('Page.linkClicked'), |         pageLinkClicked: emitProtocolEvent('Page.linkClicked'), | ||||||
|         pageWillOpenNewWindowAsynchronously: emitProtocolEvent('Page.willOpenNewWindowAsynchronously'), |         pageWillOpenNewWindowAsynchronously: emitProtocolEvent('Page.willOpenNewWindowAsynchronously'), | ||||||
|  | @ -84,12 +94,35 @@ class PageHandler { | ||||||
|         pageWorkerCreated: this._onWorkerCreated.bind(this), |         pageWorkerCreated: this._onWorkerCreated.bind(this), | ||||||
|         pageWorkerDestroyed: this._onWorkerDestroyed.bind(this), |         pageWorkerDestroyed: this._onWorkerDestroyed.bind(this), | ||||||
|       }), |       }), | ||||||
|  |       contentChannel.register('runtime', { | ||||||
|  |         runtimeConsole: emitProtocolEvent('Runtime.console'), | ||||||
|  |         runtimeExecutionContextCreated: emitProtocolEvent('Runtime.executionContextCreated'), | ||||||
|  |         runtimeExecutionContextDestroyed: emitProtocolEvent('Runtime.executionContextDestroyed'), | ||||||
|  |       }), | ||||||
|  |       helper.addEventListener(this._browser, 'DOMWillOpenModalDialog', async (event) => { | ||||||
|  |         // wait for the dialog to be actually added to DOM.
 | ||||||
|  |         await Promise.resolve(); | ||||||
|  |         this._updateModalDialogs(); | ||||||
|  |       }), | ||||||
|  |       helper.addEventListener(this._browser, 'DOMModalDialogClosed', event => this._updateModalDialogs()), | ||||||
|  |       helper.on(this._pageTarget, 'crashed', () => { | ||||||
|  |         this._session.emitEvent('Page.crashed', {}); | ||||||
|  |       }), | ||||||
|  |       helper.on(this._pageTarget, 'screencastStarted', () => { | ||||||
|  |         const info = this._pageTarget.screencastInfo(); | ||||||
|  |         this._session.emitEvent('Page.screencastStarted', { screencastId: '' + info.videoSessionId, file: info.file }); | ||||||
|  |       }), | ||||||
|  |       helper.on(this._pageNetwork, PageNetwork.Events.Request, this._handleNetworkEvent.bind(this, 'Network.requestWillBeSent')), | ||||||
|  |       helper.on(this._pageNetwork, PageNetwork.Events.Response, this._handleNetworkEvent.bind(this, 'Network.responseReceived')), | ||||||
|  |       helper.on(this._pageNetwork, PageNetwork.Events.RequestFinished, this._handleNetworkEvent.bind(this, 'Network.requestFinished')), | ||||||
|  |       helper.on(this._pageNetwork, PageNetwork.Events.RequestFailed, this._handleNetworkEvent.bind(this, 'Network.requestFailed')), | ||||||
|  |       this._pageNetwork.addSession(), | ||||||
|     ]; |     ]; | ||||||
|     this._pageTarget = target; |  | ||||||
|     this._browser = target.linkedBrowser(); |  | ||||||
|     this._dialogs = new Map(); |  | ||||||
| 
 | 
 | ||||||
|     this._enabled = false; |     this._updateModalDialogs(); | ||||||
|  |     const options = this._pageTarget.browserContext().screencastOptions; | ||||||
|  |     if (options) | ||||||
|  |       this._pageTarget.startVideoRecording(options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _onWorkerCreated({workerId, frameId, url}) { |   _onWorkerCreated({workerId, frameId, url}) { | ||||||
|  | @ -107,49 +140,45 @@ class PageHandler { | ||||||
|     this._session.emitEvent('Page.workerDestroyed', {workerId}); |     this._session.emitEvent('Page.workerDestroyed', {workerId}); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async close({runBeforeUnload}) { |   _handleNetworkEvent(protocolEventName, eventDetails, frameId) { | ||||||
|  |     if (!this._reportedFrameIds.has(frameId)) { | ||||||
|  |       let events = this._networkEventsForUnreportedFrameIds.get(frameId); | ||||||
|  |       if (!events) { | ||||||
|  |         events = []; | ||||||
|  |         this._networkEventsForUnreportedFrameIds.set(frameId, events); | ||||||
|  |       } | ||||||
|  |       events.push({eventName: protocolEventName, eventDetails}); | ||||||
|  |     } else { | ||||||
|  |       this._session.emitEvent(protocolEventName, eventDetails); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _onFrameAttached({frameId, parentFrameId}) { | ||||||
|  |     this._session.emitEvent('Page.frameAttached', {frameId, parentFrameId}); | ||||||
|  |     this._reportedFrameIds.add(frameId); | ||||||
|  |     const events = this._networkEventsForUnreportedFrameIds.get(frameId) || []; | ||||||
|  |     this._networkEventsForUnreportedFrameIds.delete(frameId); | ||||||
|  |     for (const {eventName, eventDetails} of events) | ||||||
|  |       this._session.emitEvent(eventName, eventDetails); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async ['Page.close']({runBeforeUnload}) { | ||||||
|     // Postpone target close to deliver response in session.
 |     // Postpone target close to deliver response in session.
 | ||||||
|     Services.tm.dispatchToMainThread(() => { |     Services.tm.dispatchToMainThread(() => { | ||||||
|       this._pageTarget.close(runBeforeUnload); |       this._pageTarget.close(runBeforeUnload); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async enable() { |  | ||||||
|     if (this._enabled) |  | ||||||
|       return; |  | ||||||
|     this._enabled = true; |  | ||||||
|     this._updateModalDialogs(); |  | ||||||
| 
 |  | ||||||
|     this._eventListeners.push(...[ |  | ||||||
|       helper.addEventListener(this._browser, 'DOMWillOpenModalDialog', async (event) => { |  | ||||||
|         // wait for the dialog to be actually added to DOM.
 |  | ||||||
|         await Promise.resolve(); |  | ||||||
|         this._updateModalDialogs(); |  | ||||||
|       }), |  | ||||||
|       helper.addEventListener(this._browser, 'DOMModalDialogClosed', event => this._updateModalDialogs()), |  | ||||||
|       helper.on(this._pageTarget, 'crashed', () => { |  | ||||||
|         this._session.emitEvent('Page.crashed', {}); |  | ||||||
|       }), |  | ||||||
|       helper.on(this._pageTarget, 'screencastStarted', () => { |  | ||||||
|         const info = this._pageTarget.screencastInfo(); |  | ||||||
|         this._session.emitEvent('Page.screencastStarted', { screencastId: '' + info.videoSessionId, file: info.file }); |  | ||||||
|       }), |  | ||||||
|     ]); |  | ||||||
| 
 |  | ||||||
|     const options = this._pageTarget.browserContext().screencastOptions; |  | ||||||
|     if (options) |  | ||||||
|       await this._pageTarget.startVideoRecording(options); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async dispose() { |   async dispose() { | ||||||
|     this._contentPage.dispose(); |     this._contentPage.dispose(); | ||||||
|  |     this._contentRuntime.dispose(); | ||||||
|     helper.removeListeners(this._eventListeners); |     helper.removeListeners(this._eventListeners); | ||||||
| 
 | 
 | ||||||
|     if (this._pageTarget.screencastInfo()) |     if (this._pageTarget.screencastInfo()) | ||||||
|       await this._pageTarget.stopVideoRecording().catch(e => dump(`stopVideoRecording failed:\n${e}\n`)); |       await this._pageTarget.stopVideoRecording().catch(e => dump(`stopVideoRecording failed:\n${e}\n`)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setViewportSize({viewportSize}) { |   async ['Page.setViewportSize']({viewportSize}) { | ||||||
|     await this._pageTarget.setViewportSize(viewportSize === null ? undefined : viewportSize); |     await this._pageTarget.setViewportSize(viewportSize === null ? undefined : viewportSize); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -179,107 +208,154 @@ class PageHandler { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setFileInputFiles(options) { |   async ['Runtime.evaluate'](options) { | ||||||
|  |     return await this._contentRuntime.send('evaluate', options); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async ['Runtime.callFunction'](options) { | ||||||
|  |     return await this._contentRuntime.send('callFunction', options); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async ['Runtime.getObjectProperties'](options) { | ||||||
|  |     return await this._contentRuntime.send('getObjectProperties', options); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async ['Runtime.disposeObject'](options) { | ||||||
|  |     return await this._contentRuntime.send('disposeObject', options); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async ['Network.getResponseBody']({requestId}) { | ||||||
|  |     return this._pageNetwork.getResponseBody(requestId); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async ['Network.setExtraHTTPHeaders']({headers}) { | ||||||
|  |     this._pageNetwork.setExtraHTTPHeaders(headers); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async ['Network.setRequestInterception']({enabled}) { | ||||||
|  |     if (enabled) | ||||||
|  |       this._pageNetwork.enableRequestInterception(); | ||||||
|  |     else | ||||||
|  |       this._pageNetwork.disableRequestInterception(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async ['Network.resumeInterceptedRequest']({requestId, method, headers, postData}) { | ||||||
|  |     this._pageNetwork.resumeInterceptedRequest(requestId, method, headers, postData); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async ['Network.abortInterceptedRequest']({requestId, errorCode}) { | ||||||
|  |     this._pageNetwork.abortInterceptedRequest(requestId, errorCode); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async ['Network.fulfillInterceptedRequest']({requestId, status, statusText, headers, base64body}) { | ||||||
|  |     this._pageNetwork.fulfillInterceptedRequest(requestId, status, statusText, headers, base64body); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async ['Accessibility.getFullAXTree'](params) { | ||||||
|  |     return await this._contentPage.send('getFullAXTree', params); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async ['Page.setFileInputFiles'](options) { | ||||||
|     return await this._contentPage.send('setFileInputFiles', options); |     return await this._contentPage.send('setFileInputFiles', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setEmulatedMedia(options) { |   async ['Page.setEmulatedMedia'](options) { | ||||||
|     return await this._contentPage.send('setEmulatedMedia', options); |     return await this._contentPage.send('setEmulatedMedia', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async bringToFront(options) { |   async ['Page.bringToFront'](options) { | ||||||
|     this._pageTarget._window.focus(); |     this._pageTarget._window.focus(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setCacheDisabled(options) { |   async ['Page.setCacheDisabled'](options) { | ||||||
|     return await this._contentPage.send('setCacheDisabled', options); |     return await this._contentPage.send('setCacheDisabled', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async addBinding(options) { |   async ['Page.addBinding'](options) { | ||||||
|     return await this._contentPage.send('addBinding', options); |     return await this._contentPage.send('addBinding', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async adoptNode(options) { |   async ['Page.adoptNode'](options) { | ||||||
|     return await this._contentPage.send('adoptNode', options); |     return await this._contentPage.send('adoptNode', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async screenshot(options) { |   async ['Page.screenshot'](options) { | ||||||
|     return await this._contentPage.send('screenshot', options); |     return await this._contentPage.send('screenshot', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async getBoundingBox(options) { |   async ['Page.getBoundingBox'](options) { | ||||||
|     return await this._contentPage.send('getBoundingBox', options); |     return await this._contentPage.send('getBoundingBox', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async getContentQuads(options) { |   async ['Page.getContentQuads'](options) { | ||||||
|     return await this._contentPage.send('getContentQuads', options); |     return await this._contentPage.send('getContentQuads', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * @param {{frameId: string, url: string}} options |    * @param {{frameId: string, url: string}} options | ||||||
|    */ |    */ | ||||||
|   async navigate(options) { |   async ['Page.navigate'](options) { | ||||||
|     return await this._contentPage.send('navigate', options); |     return await this._contentPage.send('navigate', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * @param {{frameId: string, url: string}} options |    * @param {{frameId: string, url: string}} options | ||||||
|    */ |    */ | ||||||
|   async goBack(options) { |   async ['Page.goBack'](options) { | ||||||
|     return await this._contentPage.send('goBack', options); |     return await this._contentPage.send('goBack', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * @param {{frameId: string, url: string}} options |    * @param {{frameId: string, url: string}} options | ||||||
|    */ |    */ | ||||||
|   async goForward(options) { |   async ['Page.goForward'](options) { | ||||||
|     return await this._contentPage.send('goForward', options); |     return await this._contentPage.send('goForward', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * @param {{frameId: string, url: string}} options |    * @param {{frameId: string, url: string}} options | ||||||
|    */ |    */ | ||||||
|   async reload(options) { |   async ['Page.reload'](options) { | ||||||
|     return await this._contentPage.send('reload', options); |     return await this._contentPage.send('reload', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async describeNode(options) { |   async ['Page.describeNode'](options) { | ||||||
|     return await this._contentPage.send('describeNode', options); |     return await this._contentPage.send('describeNode', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async scrollIntoViewIfNeeded(options) { |   async ['Page.scrollIntoViewIfNeeded'](options) { | ||||||
|     return await this._contentPage.send('scrollIntoViewIfNeeded', options); |     return await this._contentPage.send('scrollIntoViewIfNeeded', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async addScriptToEvaluateOnNewDocument(options) { |   async ['Page.addScriptToEvaluateOnNewDocument'](options) { | ||||||
|     return await this._contentPage.send('addScriptToEvaluateOnNewDocument', options); |     return await this._contentPage.send('addScriptToEvaluateOnNewDocument', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async removeScriptToEvaluateOnNewDocument(options) { |   async ['Page.removeScriptToEvaluateOnNewDocument'](options) { | ||||||
|     return await this._contentPage.send('removeScriptToEvaluateOnNewDocument', options); |     return await this._contentPage.send('removeScriptToEvaluateOnNewDocument', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async dispatchKeyEvent(options) { |   async ['Page.dispatchKeyEvent'](options) { | ||||||
|     return await this._contentPage.send('dispatchKeyEvent', options); |     return await this._contentPage.send('dispatchKeyEvent', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async dispatchTouchEvent(options) { |   async ['Page.dispatchTouchEvent'](options) { | ||||||
|     return await this._contentPage.send('dispatchTouchEvent', options); |     return await this._contentPage.send('dispatchTouchEvent', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async dispatchMouseEvent(options) { |   async ['Page.dispatchMouseEvent'](options) { | ||||||
|     return await this._contentPage.send('dispatchMouseEvent', options); |     return await this._contentPage.send('dispatchMouseEvent', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async insertText(options) { |   async ['Page.insertText'](options) { | ||||||
|     return await this._contentPage.send('insertText', options); |     return await this._contentPage.send('insertText', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async crash(options) { |   async ['Page.crash'](options) { | ||||||
|     return await this._contentPage.send('crash', options); |     return await this._contentPage.send('crash', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async handleDialog({dialogId, accept, promptText}) { |   async ['Page.handleDialog']({dialogId, accept, promptText}) { | ||||||
|     const dialog = this._dialogs.get(dialogId); |     const dialog = this._dialogs.get(dialogId); | ||||||
|     if (!dialog) |     if (!dialog) | ||||||
|       throw new Error('Failed to find dialog with id = ' + dialogId); |       throw new Error('Failed to find dialog with id = ' + dialogId); | ||||||
|  | @ -289,18 +365,18 @@ class PageHandler { | ||||||
|       dialog.dismiss(); |       dialog.dismiss(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async setInterceptFileChooserDialog(options) { |   async ['Page.setInterceptFileChooserDialog'](options) { | ||||||
|     return await this._contentPage.send('setInterceptFileChooserDialog', options); |     return await this._contentPage.send('setInterceptFileChooserDialog', options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async sendMessageToWorker({workerId, message}) { |   async ['Page.sendMessageToWorker']({workerId, message}) { | ||||||
|     const worker = this._workers.get(workerId); |     const worker = this._workers.get(workerId); | ||||||
|     if (!worker) |     if (!worker) | ||||||
|       throw new Error('ERROR: cannot find worker with id ' + workerId); |       throw new Error('ERROR: cannot find worker with id ' + workerId); | ||||||
|     return await worker.sendMessage(JSON.parse(message)); |     return await worker.sendMessage(JSON.parse(message)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async stopVideoRecording() { |   async ['Page.stopVideoRecording']() { | ||||||
|     await this._pageTarget.stopVideoRecording(); |     await this._pageTarget.stopVideoRecording(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,55 +0,0 @@ | ||||||
| /* 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 {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); |  | ||||||
| const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); |  | ||||||
| 
 |  | ||||||
| const Cc = Components.classes; |  | ||||||
| const Ci = Components.interfaces; |  | ||||||
| const Cu = Components.utils; |  | ||||||
| const helper = new Helper(); |  | ||||||
| 
 |  | ||||||
| class RuntimeHandler { |  | ||||||
|   constructor(session, contentChannel) { |  | ||||||
|     this._contentRuntime = contentChannel.connect('runtime'); |  | ||||||
| 
 |  | ||||||
|     const emitProtocolEvent = eventName => { |  | ||||||
|       return (...args) => session.emitEvent(eventName, ...args); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     this._eventListeners = [ |  | ||||||
|       contentChannel.register('runtime', { |  | ||||||
|         runtimeConsole: emitProtocolEvent('Runtime.console'), |  | ||||||
|         runtimeExecutionContextCreated: emitProtocolEvent('Runtime.executionContextCreated'), |  | ||||||
|         runtimeExecutionContextDestroyed: emitProtocolEvent('Runtime.executionContextDestroyed'), |  | ||||||
|       }), |  | ||||||
|     ]; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async evaluate(options) { |  | ||||||
|     return await this._contentRuntime.send('evaluate', options); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async callFunction(options) { |  | ||||||
|     return await this._contentRuntime.send('callFunction', options); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async getObjectProperties(options) { |  | ||||||
|     return await this._contentRuntime.send('getObjectProperties', options); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async disposeObject(options) { |  | ||||||
|     return await this._contentRuntime.send('disposeObject', options); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   dispose() { |  | ||||||
|     this._contentRuntime.dispose(); |  | ||||||
|     helper.removeListeners(this._eventListeners); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var EXPORTED_SYMBOLS = ['RuntimeHandler']; |  | ||||||
| this.RuntimeHandler = RuntimeHandler; |  | ||||||
|  | @ -240,10 +240,6 @@ pref("security.notification_enable_delay", 0); | ||||||
| // Ensure blocklist updates do not hit the network | // Ensure blocklist updates do not hit the network | ||||||
| pref("services.settings.server", ""); | pref("services.settings.server", ""); | ||||||
| 
 | 
 | ||||||
| // Disable DocumentChannel. |  | ||||||
| // See https://github.com/microsoft/playwright/pull/451 |  | ||||||
| pref("browser.tabs.documentchannel", false); |  | ||||||
| 
 |  | ||||||
| // Do not automatically fill sign-in forms with known usernames and | // Do not automatically fill sign-in forms with known usernames and | ||||||
| // passwords | // passwords | ||||||
| pref("signon.autofillForms", false); | pref("signon.autofillForms", false); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue