| 
									
										
										
										
											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/. */
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm'); | 
					
						
							|  |  |  | const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); | 
					
						
							|  |  |  | const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js'); | 
					
						
							|  |  |  | const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); | 
					
						
							|  |  |  | const {Preferences} = ChromeUtils.import("resource://gre/modules/Preferences.jsm"); | 
					
						
							|  |  |  | const {ContextualIdentityService} = ChromeUtils.import("resource://gre/modules/ContextualIdentityService.jsm"); | 
					
						
							|  |  |  | const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm'); | 
					
						
							|  |  |  | const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); | 
					
						
							| 
									
										
										
										
											2020-10-03 08:16:49 +08:00
										 |  |  | const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | const helper = new Helper(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const IDENTITY_NAME = 'JUGGLER '; | 
					
						
							|  |  |  | const HUNDRED_YEARS = 60 * 60 * 24 * 365 * 100; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ALL_PERMISSIONS = [ | 
					
						
							|  |  |  |   'geo', | 
					
						
							|  |  |  |   'desktop-notification', | 
					
						
							|  |  |  | ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class DownloadInterceptor { | 
					
						
							|  |  |  |   constructor(registry) { | 
					
						
							|  |  |  |     this._registry = registry | 
					
						
							|  |  |  |     this._handlerToUuid = new Map(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   //
 | 
					
						
							|  |  |  |   // nsIDownloadInterceptor implementation.
 | 
					
						
							|  |  |  |   //
 | 
					
						
							|  |  |  |   interceptDownloadRequest(externalAppHandler, request, browsingContext, outFile) { | 
					
						
							| 
									
										
										
										
											2020-10-06 15:15:24 +08:00
										 |  |  |     if (!(request instanceof Ci.nsIChannel)) | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     const channel = request.QueryInterface(Ci.nsIChannel); | 
					
						
							|  |  |  |     let pageTarget = this._registry._browserBrowsingContextToTarget.get(channel.loadInfo.browsingContext); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     if (!pageTarget) | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const browserContext = pageTarget.browserContext(); | 
					
						
							|  |  |  |     const options = browserContext.downloadOptions; | 
					
						
							|  |  |  |     if (!options) | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const uuid = helper.generateId(); | 
					
						
							|  |  |  |     let file = null; | 
					
						
							|  |  |  |     if (options.behavior === 'saveToDisk') { | 
					
						
							|  |  |  |       file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); | 
					
						
							|  |  |  |       file.initWithPath(options.downloadsDir); | 
					
						
							|  |  |  |       file.append(uuid); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); | 
					
						
							|  |  |  |       } catch (e) { | 
					
						
							|  |  |  |         dump(`interceptDownloadRequest failed to create file: ${e}\n`); | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     outFile.value = file; | 
					
						
							|  |  |  |     this._handlerToUuid.set(externalAppHandler, uuid); | 
					
						
							|  |  |  |     const downloadInfo = { | 
					
						
							|  |  |  |       uuid, | 
					
						
							|  |  |  |       browserContextId: browserContext.browserContextId, | 
					
						
							|  |  |  |       pageTargetId: pageTarget.id(), | 
					
						
							|  |  |  |       url: request.name, | 
					
						
							|  |  |  |       suggestedFileName: externalAppHandler.suggestedFileName, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     this._registry.emit(TargetRegistry.Events.DownloadCreated, downloadInfo); | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onDownloadComplete(externalAppHandler, canceled, errorName) { | 
					
						
							|  |  |  |     const uuid = this._handlerToUuid.get(externalAppHandler); | 
					
						
							|  |  |  |     if (!uuid) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     this._handlerToUuid.delete(externalAppHandler); | 
					
						
							|  |  |  |     const downloadInfo = { | 
					
						
							|  |  |  |       uuid, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     if (errorName === 'NS_BINDING_ABORTED') { | 
					
						
							|  |  |  |       downloadInfo.canceled = true; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       downloadInfo.error = errorName; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this._registry.emit(TargetRegistry.Events.DownloadFinished, downloadInfo); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TargetRegistry { | 
					
						
							|  |  |  |   constructor() { | 
					
						
							|  |  |  |     EventEmitter.decorate(this); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this._browserContextIdToBrowserContext = new Map(); | 
					
						
							|  |  |  |     this._userContextIdToBrowserContext = new Map(); | 
					
						
							|  |  |  |     this._browserToTarget = new Map(); | 
					
						
							|  |  |  |     this._browserBrowsingContextToTarget = new Map(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-08 06:38:06 +08:00
										 |  |  |     this._browserProxy = null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     // Cleanup containers from previous runs (if any)
 | 
					
						
							|  |  |  |     for (const identity of ContextualIdentityService.getPublicIdentities()) { | 
					
						
							|  |  |  |       if (identity.name && identity.name.startsWith(IDENTITY_NAME)) { | 
					
						
							|  |  |  |         ContextualIdentityService.remove(identity.userContextId); | 
					
						
							|  |  |  |         ContextualIdentityService.closeContainerTabs(identity.userContextId); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this._defaultContext = new BrowserContext(this, undefined, undefined); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Services.obs.addObserver({ | 
					
						
							|  |  |  |       observe: (subject, topic, data) => { | 
					
						
							|  |  |  |         const browser = subject.ownerElement; | 
					
						
							|  |  |  |         if (!browser) | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         const target = this._browserToTarget.get(browser); | 
					
						
							|  |  |  |         if (!target) | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         target.emit('crashed'); | 
					
						
							| 
									
										
										
										
											2020-08-26 05:50:40 +08:00
										 |  |  |         target.dispose(); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |       } | 
					
						
							|  |  |  |     }, 'oop-frameloader-crashed'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Services.mm.addMessageListener('juggler:content-ready', { | 
					
						
							|  |  |  |       receiveMessage: message => { | 
					
						
							|  |  |  |         const linkedBrowser = message.target; | 
					
						
							| 
									
										
										
										
											2020-09-30 17:10:34 +08:00
										 |  |  |         const target = this._browserToTarget.get(linkedBrowser); | 
					
						
							|  |  |  |         if (!target) | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |           return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return { | 
					
						
							| 
									
										
										
										
											2020-09-30 17:10:34 +08:00
										 |  |  |           scriptsToEvaluateOnNewDocument: target.browserContext().scriptsToEvaluateOnNewDocument, | 
					
						
							|  |  |  |           bindings: target.browserContext().bindings, | 
					
						
							|  |  |  |           settings: target.browserContext().settings, | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |         }; | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-30 15:36:46 +08:00
										 |  |  |     const onTabOpenListener = (appWindow, window, event) => { | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |       const tab = event.target; | 
					
						
							|  |  |  |       const userContextId = tab.userContextId; | 
					
						
							|  |  |  |       const browserContext = this._userContextIdToBrowserContext.get(userContextId); | 
					
						
							| 
									
										
										
										
											2020-10-02 19:13:42 +08:00
										 |  |  |       const hasExplicitSize = appWindow && (appWindow.chromeFlags & Ci.nsIWebBrowserChrome.JUGGLER_WINDOW_EXPLICIT_SIZE) !== 0; | 
					
						
							| 
									
										
										
										
											2020-09-30 17:10:34 +08:00
										 |  |  |       const openerContext = tab.linkedBrowser.browsingContext.opener; | 
					
						
							|  |  |  |       let openerTarget; | 
					
						
							|  |  |  |       if (openerContext) { | 
					
						
							|  |  |  |         // Popups usually have opener context.
 | 
					
						
							|  |  |  |         openerTarget = this._browserBrowsingContextToTarget.get(openerContext); | 
					
						
							|  |  |  |       } else if (tab.openerTab) { | 
					
						
							|  |  |  |         // Noopener popups from the same window have opener tab instead.
 | 
					
						
							|  |  |  |         openerTarget = this._browserToTarget.get(tab.openerTab.linkedBrowser); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (!browserContext) | 
					
						
							|  |  |  |         throw new Error(`Internal error: cannot find context for userContextId=${userContextId}`); | 
					
						
							|  |  |  |       const target = new PageTarget(this, window, tab, browserContext, openerTarget); | 
					
						
							| 
									
										
										
										
											2020-09-30 23:02:22 +08:00
										 |  |  |       target.updateUserAgent(); | 
					
						
							| 
									
										
										
										
											2020-09-30 17:10:34 +08:00
										 |  |  |       if (!hasExplicitSize) | 
					
						
							|  |  |  |         target.updateViewportSize(); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-20 03:52:43 +08:00
										 |  |  |     const onTabCloseListener = event => { | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |       const tab = event.target; | 
					
						
							|  |  |  |       const linkedBrowser = tab.linkedBrowser; | 
					
						
							|  |  |  |       const target = this._browserToTarget.get(linkedBrowser); | 
					
						
							| 
									
										
										
										
											2020-08-21 04:26:04 +08:00
										 |  |  |       if (target) | 
					
						
							| 
									
										
										
										
											2020-08-26 05:50:40 +08:00
										 |  |  |           target.dispose(); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-30 15:36:46 +08:00
										 |  |  |     const domWindowTabListeners = new Map(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const onOpenWindow = async (appWindow) => { | 
					
						
							| 
									
										
										
										
											2020-10-02 19:13:42 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       let domWindow; | 
					
						
							|  |  |  |       if (appWindow instanceof Ci.nsIAppWindow) { | 
					
						
							|  |  |  |         domWindow = appWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         domWindow = appWindow; | 
					
						
							|  |  |  |         appWindow = null; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2020-09-30 15:36:46 +08:00
										 |  |  |       if (!(domWindow instanceof Ci.nsIDOMChromeWindow)) | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2020-09-30 17:10:34 +08:00
										 |  |  |       // In persistent mode, window might be opened long ago and might be
 | 
					
						
							|  |  |  |       // already initialized.
 | 
					
						
							|  |  |  |       //
 | 
					
						
							|  |  |  |       // In this case, we want to keep this callback synchronous so that we will call
 | 
					
						
							|  |  |  |       // `onTabOpenListener` synchronously and before the sync IPc message `juggler:content-ready`.
 | 
					
						
							|  |  |  |       if (domWindow.document.readyState === 'uninitialized' || domWindow.document.readyState === 'loading') { | 
					
						
							|  |  |  |         // For non-initialized windows, DOMContentLoaded initializes gBrowser
 | 
					
						
							|  |  |  |         // and starts tab loading (see //browser/base/content/browser.js), so we
 | 
					
						
							|  |  |  |         // are guaranteed to call `onTabOpenListener` before the sync IPC message
 | 
					
						
							|  |  |  |         // `juggler:content-ready`.
 | 
					
						
							|  |  |  |         await helper.awaitEvent(domWindow, 'DOMContentLoaded'); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2020-09-30 15:36:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       if (!domWindow.gBrowser) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       const tabContainer = domWindow.gBrowser.tabContainer; | 
					
						
							|  |  |  |       domWindowTabListeners.set(domWindow, [ | 
					
						
							|  |  |  |         helper.addEventListener(tabContainer, 'TabOpen', event => onTabOpenListener(appWindow, domWindow, event)), | 
					
						
							|  |  |  |         helper.addEventListener(tabContainer, 'TabClose', onTabCloseListener), | 
					
						
							|  |  |  |       ]); | 
					
						
							|  |  |  |       for (const tab of domWindow.gBrowser.tabs) | 
					
						
							|  |  |  |         onTabOpenListener(appWindow, domWindow, { target: tab }); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const onCloseWindow = window => { | 
					
						
							|  |  |  |       const domWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow); | 
					
						
							|  |  |  |       if (!(domWindow instanceof Ci.nsIDOMChromeWindow)) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       if (!domWindow.gBrowser) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const listeners = domWindowTabListeners.get(domWindow) || []; | 
					
						
							|  |  |  |       domWindowTabListeners.delete(domWindow); | 
					
						
							|  |  |  |       helper.removeListeners(listeners); | 
					
						
							|  |  |  |       for (const tab of domWindow.gBrowser.tabs) | 
					
						
							|  |  |  |         onTabCloseListener({ target: tab }); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-30 17:10:34 +08:00
										 |  |  |     const extHelperAppSvc = Cc["@mozilla.org/uriloader/external-helper-app-service;1"].getService(Ci.nsIExternalHelperAppService); | 
					
						
							|  |  |  |     extHelperAppSvc.setDownloadInterceptor(new DownloadInterceptor(this)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-30 15:36:46 +08:00
										 |  |  |     Services.wm.addListener({ onOpenWindow, onCloseWindow }); | 
					
						
							|  |  |  |     for (const win of Services.wm.getEnumerator(null)) | 
					
						
							|  |  |  |       onOpenWindow(win); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-08 06:38:06 +08:00
										 |  |  |   setBrowserProxy(proxy) { | 
					
						
							|  |  |  |     this._browserProxy = proxy; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   getProxyInfo(channel) { | 
					
						
							|  |  |  |     const originAttributes = channel.loadInfo && channel.loadInfo.originAttributes; | 
					
						
							|  |  |  |     const browserContext = originAttributes ? this.browserContextForUserContextId(originAttributes.userContextId) : null; | 
					
						
							|  |  |  |     // Prefer context proxy and fallback to browser-level proxy.
 | 
					
						
							|  |  |  |     const proxyInfo = (browserContext && browserContext._proxy) || this._browserProxy; | 
					
						
							|  |  |  |     if (!proxyInfo || proxyInfo.bypass.some(domainSuffix => channel.URI.host.endsWith(domainSuffix))) | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     return proxyInfo; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |   defaultContext() { | 
					
						
							|  |  |  |     return this._defaultContext; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   createBrowserContext(removeOnDetach) { | 
					
						
							|  |  |  |     return new BrowserContext(this, helper.generateId(), removeOnDetach); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   browserContextForId(browserContextId) { | 
					
						
							|  |  |  |     return this._browserContextIdToBrowserContext.get(browserContextId); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 23:52:43 +08:00
										 |  |  |   browserContextForUserContextId(userContextId) { | 
					
						
							|  |  |  |     return this._userContextIdToBrowserContext.get(userContextId); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |   async newPage({browserContextId}) { | 
					
						
							|  |  |  |     const browserContext = this.browserContextForId(browserContextId); | 
					
						
							| 
									
										
										
										
											2020-07-03 05:46:57 +08:00
										 |  |  |     const features = "chrome,dialog=no,all"; | 
					
						
							|  |  |  |     // See _callWithURIToLoad in browser.js for the structure of window.arguments
 | 
					
						
							|  |  |  |     // window.arguments[1]: unused (bug 871161)
 | 
					
						
							|  |  |  |     //                 [2]: referrerInfo (nsIReferrerInfo)
 | 
					
						
							|  |  |  |     //                 [3]: postData (nsIInputStream)
 | 
					
						
							|  |  |  |     //                 [4]: allowThirdPartyFixup (bool)
 | 
					
						
							|  |  |  |     //                 [5]: userContextId (int)
 | 
					
						
							|  |  |  |     //                 [6]: originPrincipal (nsIPrincipal)
 | 
					
						
							|  |  |  |     //                 [7]: originStoragePrincipal (nsIPrincipal)
 | 
					
						
							|  |  |  |     //                 [8]: triggeringPrincipal (nsIPrincipal)
 | 
					
						
							|  |  |  |     //                 [9]: allowInheritPrincipal (bool)
 | 
					
						
							|  |  |  |     //                 [10]: csp (nsIContentSecurityPolicy)
 | 
					
						
							|  |  |  |     //                 [11]: nsOpenWindowInfo
 | 
					
						
							|  |  |  |     const args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); | 
					
						
							|  |  |  |     const urlSupports = Cc["@mozilla.org/supports-string;1"].createInstance( | 
					
						
							|  |  |  |       Ci.nsISupportsString | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     urlSupports.data = 'about:blank'; | 
					
						
							|  |  |  |     args.appendElement(urlSupports); // 0
 | 
					
						
							|  |  |  |     args.appendElement(undefined); // 1
 | 
					
						
							|  |  |  |     args.appendElement(undefined); // 2
 | 
					
						
							|  |  |  |     args.appendElement(undefined); // 3
 | 
					
						
							|  |  |  |     args.appendElement(undefined); // 4
 | 
					
						
							|  |  |  |     const userContextIdSupports = Cc[ | 
					
						
							|  |  |  |       "@mozilla.org/supports-PRUint32;1" | 
					
						
							|  |  |  |     ].createInstance(Ci.nsISupportsPRUint32); | 
					
						
							|  |  |  |     userContextIdSupports.data = browserContext.userContextId; | 
					
						
							|  |  |  |     args.appendElement(userContextIdSupports); // 5
 | 
					
						
							|  |  |  |     args.appendElement(undefined); // 6
 | 
					
						
							|  |  |  |     args.appendElement(undefined); // 7
 | 
					
						
							|  |  |  |     args.appendElement(Services.scriptSecurityManager.getSystemPrincipal()); // 8
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const window = Services.ww.openWindow(null, AppConstants.BROWSER_CHROME_URL, '_blank', features, args); | 
					
						
							|  |  |  |     await waitForWindowReady(window); | 
					
						
							|  |  |  |     if (window.gBrowser.browsers.length !== 1) | 
					
						
							| 
									
										
										
										
											2020-09-30 15:36:46 +08:00
										 |  |  |       throw new Error(`Unexpected number of tabs in the new window: ${window.gBrowser.browsers.length}`); | 
					
						
							| 
									
										
										
										
											2020-07-03 05:46:57 +08:00
										 |  |  |     const browser = window.gBrowser.browsers[0]; | 
					
						
							| 
									
										
										
										
											2020-09-30 17:10:34 +08:00
										 |  |  |     const target = this._browserToTarget.get(browser); | 
					
						
							| 
									
										
										
										
											2020-07-03 05:46:57 +08:00
										 |  |  |     browser.focus(); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     if (browserContext.settings.timezoneId) { | 
					
						
							|  |  |  |       if (await target.hasFailedToOverrideTimezone()) | 
					
						
							|  |  |  |         throw new Error('Failed to override timezone'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return target.id(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 19:13:42 +08:00
										 |  |  |   targets() { | 
					
						
							|  |  |  |     return Array.from(this._browserToTarget.values()); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   targetForBrowser(browser) { | 
					
						
							|  |  |  |     return this._browserToTarget.get(browser); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class PageTarget { | 
					
						
							| 
									
										
										
										
											2020-09-30 17:10:34 +08:00
										 |  |  |   constructor(registry, win, tab, browserContext, opener) { | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     EventEmitter.decorate(this); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this._targetId = helper.generateId(); | 
					
						
							|  |  |  |     this._registry = registry; | 
					
						
							| 
									
										
										
										
											2020-07-08 05:04:07 +08:00
										 |  |  |     this._window = win; | 
					
						
							| 
									
										
										
										
											2020-09-30 17:10:34 +08:00
										 |  |  |     this._gBrowser = win.gBrowser; | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     this._tab = tab; | 
					
						
							| 
									
										
										
										
											2020-09-30 17:10:34 +08:00
										 |  |  |     this._linkedBrowser = tab.linkedBrowser; | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     this._browserContext = browserContext; | 
					
						
							|  |  |  |     this._viewportSize = undefined; | 
					
						
							|  |  |  |     this._url = 'about:blank'; | 
					
						
							|  |  |  |     this._openerId = opener ? opener.id() : undefined; | 
					
						
							|  |  |  |     this._channel = SimpleChannel.createForMessageManager(`browser::page[${this._targetId}]`, this._linkedBrowser.messageManager); | 
					
						
							| 
									
										
										
										
											2020-10-03 08:16:49 +08:00
										 |  |  |     this._screencastInfo = undefined; | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const navigationListener = { | 
					
						
							| 
									
										
										
										
											2020-08-07 01:32:50 +08:00
										 |  |  |       QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]), | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |       onLocationChange: (aWebProgress, aRequest, aLocation) => this._onNavigated(aLocation), | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     this._eventListeners = [ | 
					
						
							|  |  |  |       helper.addProgressListener(tab.linkedBrowser, navigationListener, Ci.nsIWebProgress.NOTIFY_LOCATION), | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this._disposed = false; | 
					
						
							| 
									
										
										
										
											2020-08-20 06:39:46 +08:00
										 |  |  |     browserContext.pages.add(this); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     this._registry._browserToTarget.set(this._linkedBrowser, this); | 
					
						
							|  |  |  |     this._registry._browserBrowsingContextToTarget.set(this._linkedBrowser.browsingContext, this); | 
					
						
							| 
									
										
										
										
											2020-09-11 05:37:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 19:13:42 +08:00
										 |  |  |     this._registry.emit(TargetRegistry.Events.TargetCreated, this); | 
					
						
							| 
									
										
										
										
											2020-09-30 17:10:34 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-11 05:37:48 +08:00
										 |  |  |   async windowReady() { | 
					
						
							|  |  |  |     await waitForWindowReady(this._window); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   linkedBrowser() { | 
					
						
							|  |  |  |     return this._linkedBrowser; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   browserContext() { | 
					
						
							|  |  |  |     return this._browserContext; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-30 23:02:22 +08:00
										 |  |  |   updateUserAgent() { | 
					
						
							|  |  |  |     this._linkedBrowser.browsingContext.customUserAgent = this._browserContext.defaultUserAgent; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-30 17:10:34 +08:00
										 |  |  |   async updateViewportSize() { | 
					
						
							|  |  |  |     // Viewport size is defined by three arguments:
 | 
					
						
							|  |  |  |     // 1. default size. Could be explicit if set as part of `window.open` call, e.g.
 | 
					
						
							|  |  |  |     //   `window.open(url, title, 'width=400,height=400')`
 | 
					
						
							|  |  |  |     // 2. page viewport size
 | 
					
						
							|  |  |  |     // 3. browserContext viewport size
 | 
					
						
							|  |  |  |     //
 | 
					
						
							|  |  |  |     // The "default size" (1) is only respected when the page is opened.
 | 
					
						
							|  |  |  |     // Otherwise, explicitly set page viewport prevales over browser context
 | 
					
						
							|  |  |  |     // default viewport.
 | 
					
						
							|  |  |  |     const viewportSize = this._viewportSize || this._browserContext.defaultViewportSize; | 
					
						
							| 
									
										
										
										
											2020-10-02 19:13:42 +08:00
										 |  |  |     const actualSize = await setViewportSizeForBrowser(viewportSize, this._linkedBrowser, this._window); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     await this._channel.connect('').send('awaitViewportDimensions', { | 
					
						
							|  |  |  |       width: actualSize.width, | 
					
						
							|  |  |  |       height: actualSize.height | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-30 17:10:34 +08:00
										 |  |  |   async setViewportSize(viewportSize) { | 
					
						
							|  |  |  |     this._viewportSize = viewportSize; | 
					
						
							|  |  |  |     await this.updateViewportSize(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |   async close(runBeforeUnload = false) { | 
					
						
							|  |  |  |     await this._gBrowser.removeTab(this._tab, { | 
					
						
							|  |  |  |       skipPermitUnload: !runBeforeUnload, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 19:13:42 +08:00
										 |  |  |   channel() { | 
					
						
							|  |  |  |     return this._channel; | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   id() { | 
					
						
							|  |  |  |     return this._targetId; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   info() { | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       targetId: this.id(), | 
					
						
							|  |  |  |       type: 'page', | 
					
						
							| 
									
										
										
										
											2020-08-20 06:39:46 +08:00
										 |  |  |       browserContextId: this._browserContext.browserContextId, | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |       openerId: this._openerId, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _onNavigated(aLocation) { | 
					
						
							|  |  |  |     this._url = aLocation.spec; | 
					
						
							|  |  |  |     this._browserContext.grantPermissionsToOrigin(this._url); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async ensurePermissions() { | 
					
						
							|  |  |  |     await this._channel.connect('').send('ensurePermissions', {}).catch(e => void e); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async addScriptToEvaluateOnNewDocument(script) { | 
					
						
							|  |  |  |     await this._channel.connect('').send('addScriptToEvaluateOnNewDocument', script).catch(e => void e); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async addBinding(name, script) { | 
					
						
							|  |  |  |     await this._channel.connect('').send('addBinding', { name, script }).catch(e => void e); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async applyContextSetting(name, value) { | 
					
						
							|  |  |  |     await this._channel.connect('').send('applyContextSetting', { name, value }).catch(e => void e); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async hasFailedToOverrideTimezone() { | 
					
						
							|  |  |  |     return await this._channel.connect('').send('hasFailedToOverrideTimezone').catch(e => true); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-03 08:16:49 +08:00
										 |  |  |   async startVideoRecording({width, height, scale, dir}) { | 
					
						
							|  |  |  |     // On Mac the window may not yet be visible when TargetCreated and its
 | 
					
						
							|  |  |  |     // NSWindow.windowNumber may be -1, so we wait until the window is known
 | 
					
						
							|  |  |  |     // to be initialized and visible.
 | 
					
						
							|  |  |  |     await this.windowReady(); | 
					
						
							|  |  |  |     const file = OS.Path.join(dir, helper.generateId() + '.webm'); | 
					
						
							|  |  |  |     if (width < 10 || width > 10000 || height < 10 || height > 10000) | 
					
						
							|  |  |  |       throw new Error("Invalid size"); | 
					
						
							|  |  |  |     if (scale && (scale <= 0 || scale > 1)) | 
					
						
							|  |  |  |       throw new Error("Unsupported scale"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const screencast = Cc['@mozilla.org/juggler/screencast;1'].getService(Ci.nsIScreencastService); | 
					
						
							|  |  |  |     const docShell = this._gBrowser.ownerGlobal.docShell; | 
					
						
							|  |  |  |     // Exclude address bar and navigation control from the video.
 | 
					
						
							|  |  |  |     const rect = this.linkedBrowser().getBoundingClientRect(); | 
					
						
							|  |  |  |     const devicePixelRatio = this._window.devicePixelRatio; | 
					
						
							|  |  |  |     const videoSessionId = screencast.startVideoRecording(docShell, file, width, height, scale || 0, devicePixelRatio * rect.top); | 
					
						
							|  |  |  |     this._screencastInfo = { videoSessionId, file }; | 
					
						
							|  |  |  |     this.emit('screencastStarted'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async stopVideoRecording() { | 
					
						
							|  |  |  |     if (!this._screencastInfo) | 
					
						
							|  |  |  |       throw new Error('No video recording in progress'); | 
					
						
							|  |  |  |     const screencastInfo = this._screencastInfo; | 
					
						
							|  |  |  |     this._screencastInfo = undefined; | 
					
						
							|  |  |  |     const screencast = Cc['@mozilla.org/juggler/screencast;1'].getService(Ci.nsIScreencastService); | 
					
						
							|  |  |  |     const result = new Promise(resolve => | 
					
						
							|  |  |  |       Services.obs.addObserver(function onStopped(subject, topic, data) { | 
					
						
							|  |  |  |         if (screencastInfo.videoSessionId != data) | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Services.obs.removeObserver(onStopped, 'juggler-screencast-stopped'); | 
					
						
							|  |  |  |         resolve(); | 
					
						
							|  |  |  |       }, 'juggler-screencast-stopped') | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     screencast.stopVideoRecording(screencastInfo.videoSessionId); | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   screencastInfo() { | 
					
						
							|  |  |  |     return this._screencastInfo; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 05:50:40 +08:00
										 |  |  |   dispose() { | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     this._disposed = true; | 
					
						
							| 
									
										
										
										
											2020-08-26 05:50:40 +08:00
										 |  |  |     this._browserContext.pages.delete(this); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     this._registry._browserToTarget.delete(this._linkedBrowser); | 
					
						
							|  |  |  |     this._registry._browserBrowsingContextToTarget.delete(this._linkedBrowser.browsingContext); | 
					
						
							|  |  |  |     helper.removeListeners(this._eventListeners); | 
					
						
							| 
									
										
										
										
											2020-10-02 19:13:42 +08:00
										 |  |  |     this._registry.emit(TargetRegistry.Events.TargetDestroyed, this); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class BrowserContext { | 
					
						
							|  |  |  |   constructor(registry, browserContextId, removeOnDetach) { | 
					
						
							|  |  |  |     this._registry = registry; | 
					
						
							|  |  |  |     this.browserContextId = browserContextId; | 
					
						
							|  |  |  |     // Default context has userContextId === 0, but we pass undefined to many APIs just in case.
 | 
					
						
							|  |  |  |     this.userContextId = 0; | 
					
						
							|  |  |  |     if (browserContextId !== undefined) { | 
					
						
							|  |  |  |       const identity = ContextualIdentityService.create(IDENTITY_NAME + browserContextId); | 
					
						
							|  |  |  |       this.userContextId = identity.userContextId; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this._principals = []; | 
					
						
							|  |  |  |     // Maps origins to the permission lists.
 | 
					
						
							|  |  |  |     this._permissions = new Map(); | 
					
						
							|  |  |  |     this._registry._browserContextIdToBrowserContext.set(this.browserContextId, this); | 
					
						
							|  |  |  |     this._registry._userContextIdToBrowserContext.set(this.userContextId, this); | 
					
						
							| 
									
										
										
										
											2020-08-08 06:38:06 +08:00
										 |  |  |     this._proxy = null; | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     this.removeOnDetach = removeOnDetach; | 
					
						
							|  |  |  |     this.extraHTTPHeaders = undefined; | 
					
						
							|  |  |  |     this.httpCredentials = undefined; | 
					
						
							|  |  |  |     this.requestInterceptionEnabled = undefined; | 
					
						
							|  |  |  |     this.ignoreHTTPSErrors = undefined; | 
					
						
							|  |  |  |     this.downloadOptions = undefined; | 
					
						
							|  |  |  |     this.defaultViewportSize = undefined; | 
					
						
							| 
									
										
										
										
											2020-09-30 23:02:22 +08:00
										 |  |  |     this.defaultUserAgent = null; | 
					
						
							| 
									
										
										
										
											2020-08-20 03:43:53 +08:00
										 |  |  |     this.screencastOptions = undefined; | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     this.scriptsToEvaluateOnNewDocument = []; | 
					
						
							|  |  |  |     this.bindings = []; | 
					
						
							|  |  |  |     this.settings = {}; | 
					
						
							|  |  |  |     this.pages = new Set(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async destroy() { | 
					
						
							|  |  |  |     if (this.userContextId !== 0) { | 
					
						
							|  |  |  |       ContextualIdentityService.remove(this.userContextId); | 
					
						
							|  |  |  |       ContextualIdentityService.closeContainerTabs(this.userContextId); | 
					
						
							|  |  |  |       if (this.pages.size) { | 
					
						
							|  |  |  |         await new Promise(f => { | 
					
						
							|  |  |  |           const listener = helper.on(this._registry, TargetRegistry.Events.TargetDestroyed, () => { | 
					
						
							|  |  |  |             if (!this.pages.size) { | 
					
						
							|  |  |  |               helper.removeListeners([listener]); | 
					
						
							|  |  |  |               f(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this._registry._browserContextIdToBrowserContext.delete(this.browserContextId); | 
					
						
							|  |  |  |     this._registry._userContextIdToBrowserContext.delete(this.userContextId); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-08 06:38:06 +08:00
										 |  |  |   setProxy(proxy) { | 
					
						
							|  |  |  |     this._proxy = proxy; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |   setIgnoreHTTPSErrors(ignoreHTTPSErrors) { | 
					
						
							|  |  |  |     if (this.ignoreHTTPSErrors === ignoreHTTPSErrors) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     this.ignoreHTTPSErrors = ignoreHTTPSErrors; | 
					
						
							|  |  |  |     const certOverrideService = Cc[ | 
					
						
							|  |  |  |       "@mozilla.org/security/certoverride;1" | 
					
						
							|  |  |  |     ].getService(Ci.nsICertOverrideService); | 
					
						
							|  |  |  |     if (ignoreHTTPSErrors) { | 
					
						
							|  |  |  |       Preferences.set("network.stricttransportsecurity.preloadlist", false); | 
					
						
							|  |  |  |       Preferences.set("security.cert_pinning.enforcement_level", 0); | 
					
						
							|  |  |  |       certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(true, this.userContextId); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(false, this.userContextId); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-30 23:02:22 +08:00
										 |  |  |   async setDefaultUserAgent(userAgent) { | 
					
						
							|  |  |  |     this.defaultUserAgent = userAgent; | 
					
						
							|  |  |  |     for (const page of this.pages) | 
					
						
							|  |  |  |       page.updateUserAgent(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |   async setDefaultViewport(viewport) { | 
					
						
							|  |  |  |     this.defaultViewportSize = viewport ? viewport.viewportSize : undefined; | 
					
						
							| 
									
										
										
										
											2020-09-30 17:10:34 +08:00
										 |  |  |     const promises = Array.from(this.pages).map(page => page.updateViewportSize()); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     await Promise.all([ | 
					
						
							|  |  |  |       this.applySetting('deviceScaleFactor', viewport ? viewport.deviceScaleFactor : undefined), | 
					
						
							|  |  |  |       ...promises, | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async addScriptToEvaluateOnNewDocument(script) { | 
					
						
							|  |  |  |     this.scriptsToEvaluateOnNewDocument.push(script); | 
					
						
							|  |  |  |     await Promise.all(Array.from(this.pages).map(page => page.addScriptToEvaluateOnNewDocument(script))); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async addBinding(name, script) { | 
					
						
							|  |  |  |     this.bindings.push({ name, script }); | 
					
						
							|  |  |  |     await Promise.all(Array.from(this.pages).map(page => page.addBinding(name, script))); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async applySetting(name, value) { | 
					
						
							|  |  |  |     this.settings[name] = value; | 
					
						
							|  |  |  |     await Promise.all(Array.from(this.pages).map(page => page.applyContextSetting(name, value))); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async grantPermissions(origin, permissions) { | 
					
						
							|  |  |  |     this._permissions.set(origin, permissions); | 
					
						
							|  |  |  |     const promises = []; | 
					
						
							|  |  |  |     for (const page of this.pages) { | 
					
						
							|  |  |  |       if (origin === '*' || page._url.startsWith(origin)) { | 
					
						
							|  |  |  |         this.grantPermissionsToOrigin(page._url); | 
					
						
							|  |  |  |         promises.push(page.ensurePermissions()); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     await Promise.all(promises); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   resetPermissions() { | 
					
						
							|  |  |  |     for (const principal of this._principals) { | 
					
						
							|  |  |  |       for (const permission of ALL_PERMISSIONS) | 
					
						
							|  |  |  |         Services.perms.removeFromPrincipal(principal, permission); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this._principals = []; | 
					
						
							|  |  |  |     this._permissions.clear(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   grantPermissionsToOrigin(url) { | 
					
						
							|  |  |  |     let origin = Array.from(this._permissions.keys()).find(key => url.startsWith(key)); | 
					
						
							|  |  |  |     if (!origin) | 
					
						
							|  |  |  |       origin = '*'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const permissions = this._permissions.get(origin); | 
					
						
							|  |  |  |     if (!permissions) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const attrs = { userContextId: this.userContextId || undefined }; | 
					
						
							|  |  |  |     const principal = Services.scriptSecurityManager.createContentPrincipal(NetUtil.newURI(url), attrs); | 
					
						
							|  |  |  |     this._principals.push(principal); | 
					
						
							|  |  |  |     for (const permission of ALL_PERMISSIONS) { | 
					
						
							|  |  |  |       const action = permissions.includes(permission) ? Ci.nsIPermissionManager.ALLOW_ACTION : Ci.nsIPermissionManager.DENY_ACTION; | 
					
						
							|  |  |  |       Services.perms.addFromPrincipal(principal, permission, action, Ci.nsIPermissionManager.EXPIRE_NEVER, 0 /* expireTime */); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   setCookies(cookies) { | 
					
						
							|  |  |  |     const protocolToSameSite = { | 
					
						
							|  |  |  |       [undefined]: Ci.nsICookie.SAMESITE_NONE, | 
					
						
							|  |  |  |       'Lax': Ci.nsICookie.SAMESITE_LAX, | 
					
						
							|  |  |  |       'Strict': Ci.nsICookie.SAMESITE_STRICT, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     for (const cookie of cookies) { | 
					
						
							|  |  |  |       const uri = cookie.url ? NetUtil.newURI(cookie.url) : null; | 
					
						
							|  |  |  |       let domain = cookie.domain; | 
					
						
							|  |  |  |       if (!domain) { | 
					
						
							|  |  |  |         if (!uri) | 
					
						
							|  |  |  |           throw new Error('At least one of the url and domain needs to be specified'); | 
					
						
							|  |  |  |         domain = uri.host; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       let path = cookie.path; | 
					
						
							|  |  |  |       if (!path) | 
					
						
							|  |  |  |         path = uri ? dirPath(uri.filePath) : '/'; | 
					
						
							|  |  |  |       let secure = false; | 
					
						
							|  |  |  |       if (cookie.secure !== undefined) | 
					
						
							|  |  |  |         secure = cookie.secure; | 
					
						
							|  |  |  |       else if (uri && uri.scheme === 'https') | 
					
						
							|  |  |  |         secure = true; | 
					
						
							|  |  |  |       Services.cookies.add( | 
					
						
							|  |  |  |         domain, | 
					
						
							|  |  |  |         path, | 
					
						
							|  |  |  |         cookie.name, | 
					
						
							|  |  |  |         cookie.value, | 
					
						
							|  |  |  |         secure, | 
					
						
							|  |  |  |         cookie.httpOnly || false, | 
					
						
							|  |  |  |         cookie.expires === undefined || cookie.expires === -1 /* isSession */, | 
					
						
							|  |  |  |         cookie.expires === undefined ? Date.now() + HUNDRED_YEARS : cookie.expires, | 
					
						
							|  |  |  |         { userContextId: this.userContextId || undefined } /* originAttributes */, | 
					
						
							|  |  |  |         protocolToSameSite[cookie.sameSite], | 
					
						
							| 
									
										
										
										
											2020-08-07 01:32:50 +08:00
										 |  |  |         Ci.nsICookie.SCHEME_UNSET | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   clearCookies() { | 
					
						
							|  |  |  |     Services.cookies.removeCookiesWithOriginAttributes(JSON.stringify({ userContextId: this.userContextId || undefined })); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   getCookies() { | 
					
						
							|  |  |  |     const result = []; | 
					
						
							|  |  |  |     const sameSiteToProtocol = { | 
					
						
							|  |  |  |       [Ci.nsICookie.SAMESITE_NONE]: 'None', | 
					
						
							|  |  |  |       [Ci.nsICookie.SAMESITE_LAX]: 'Lax', | 
					
						
							|  |  |  |       [Ci.nsICookie.SAMESITE_STRICT]: 'Strict', | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     for (let cookie of Services.cookies.cookies) { | 
					
						
							|  |  |  |       if (cookie.originAttributes.userContextId !== this.userContextId) | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  |       if (cookie.host === 'addons.mozilla.org') | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  |       result.push({ | 
					
						
							|  |  |  |         name: cookie.name, | 
					
						
							|  |  |  |         value: cookie.value, | 
					
						
							|  |  |  |         domain: cookie.host, | 
					
						
							|  |  |  |         path: cookie.path, | 
					
						
							|  |  |  |         expires: cookie.isSession ? -1 : cookie.expiry, | 
					
						
							|  |  |  |         size: cookie.name.length + cookie.value.length, | 
					
						
							|  |  |  |         httpOnly: cookie.isHttpOnly, | 
					
						
							|  |  |  |         secure: cookie.isSecure, | 
					
						
							|  |  |  |         session: cookie.isSession, | 
					
						
							|  |  |  |         sameSite: sameSiteToProtocol[cookie.sameSite], | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-08-20 03:43:53 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-03 08:16:49 +08:00
										 |  |  |   async setScreencastOptions(options) { | 
					
						
							| 
									
										
										
										
											2020-08-20 03:43:53 +08:00
										 |  |  |     this.screencastOptions = options; | 
					
						
							| 
									
										
										
										
											2020-10-03 08:16:49 +08:00
										 |  |  |     if (!options) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     const promises = []; | 
					
						
							|  |  |  |     for (const page of this.pages) | 
					
						
							|  |  |  |       promises.push(page.startVideoRecording(options)); | 
					
						
							|  |  |  |     await Promise.all(promises); | 
					
						
							| 
									
										
										
										
											2020-08-20 03:43:53 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function dirPath(path) { | 
					
						
							|  |  |  |   return path.substring(0, path.lastIndexOf('/') + 1); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 13:48:10 +08:00
										 |  |  | async function waitForWindowReady(window) { | 
					
						
							|  |  |  |   if (window.delayedStartupPromise) { | 
					
						
							|  |  |  |     await window.delayedStartupPromise; | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     await new Promise((resolve => { | 
					
						
							|  |  |  |       Services.obs.addObserver(function observer(aSubject, aTopic) { | 
					
						
							|  |  |  |         if (window == aSubject) { | 
					
						
							|  |  |  |           Services.obs.removeObserver(observer, aTopic); | 
					
						
							|  |  |  |           resolve(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }, "browser-delayed-startup-finished"); | 
					
						
							|  |  |  |     })); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-30 15:36:46 +08:00
										 |  |  |   if (window.document.readyState !== 'complete') | 
					
						
							|  |  |  |     await helper.awaitEvent(window, 'load'); | 
					
						
							| 
									
										
										
										
											2020-06-10 13:48:10 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 19:13:42 +08:00
										 |  |  | async function setViewportSizeForBrowser(viewportSize, browser, window) { | 
					
						
							|  |  |  |   await waitForWindowReady(window); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |   if (viewportSize) { | 
					
						
							|  |  |  |     const {width, height} = viewportSize; | 
					
						
							| 
									
										
										
										
											2020-07-08 05:04:07 +08:00
										 |  |  |     const rect = browser.getBoundingClientRect(); | 
					
						
							|  |  |  |     window.resizeBy(width - rect.width, height - rect.height); | 
					
						
							| 
									
										
										
										
											2020-06-03 07:51:13 +08:00
										 |  |  |     browser.style.setProperty('min-width', width + 'px'); | 
					
						
							|  |  |  |     browser.style.setProperty('min-height', height + 'px'); | 
					
						
							|  |  |  |     browser.style.setProperty('max-width', width + 'px'); | 
					
						
							|  |  |  |     browser.style.setProperty('max-height', height + 'px'); | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     browser.style.removeProperty('min-width'); | 
					
						
							|  |  |  |     browser.style.removeProperty('min-height'); | 
					
						
							|  |  |  |     browser.style.removeProperty('max-width'); | 
					
						
							|  |  |  |     browser.style.removeProperty('max-height'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   const rect = browser.getBoundingClientRect(); | 
					
						
							|  |  |  |   return { width: rect.width, height: rect.height }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | TargetRegistry.Events = { | 
					
						
							|  |  |  |   TargetCreated: Symbol('TargetRegistry.Events.TargetCreated'), | 
					
						
							|  |  |  |   TargetDestroyed: Symbol('TargetRegistry.Events.TargetDestroyed'), | 
					
						
							|  |  |  |   DownloadCreated: Symbol('TargetRegistry.Events.DownloadCreated'), | 
					
						
							|  |  |  |   DownloadFinished: Symbol('TargetRegistry.Events.DownloadFinished'), | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var EXPORTED_SYMBOLS = ['TargetRegistry']; | 
					
						
							|  |  |  | this.TargetRegistry = TargetRegistry; |