mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			702 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			702 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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 {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 {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 | |
| 
 | |
| 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();
 | |
|     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.
 | |
|   //
 | |
|   interceptDownloadRequest(externalAppHandler, request, browsingContext, outFile) {
 | |
|     let pageTarget = this._registry._browserBrowsingContextToTarget.get(browsingContext);
 | |
|     // New page downloads won't have browsing contex.
 | |
|     if (!pageTarget)
 | |
|       pageTarget = this._registry._targetForChannel(request);
 | |
|     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();
 | |
| 
 | |
|     // 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');
 | |
|         target.dispose();
 | |
|         this.emit(TargetRegistry.Events.TargetDestroyed, target);
 | |
|       }
 | |
|     }, 'oop-frameloader-crashed');
 | |
| 
 | |
|     Services.mm.addMessageListener('juggler:content-ready', {
 | |
|       receiveMessage: message => {
 | |
|         const linkedBrowser = message.target;
 | |
|         if (this._browserToTarget.has(linkedBrowser))
 | |
|           throw new Error(`Internal error: two targets per linkedBrowser`);
 | |
| 
 | |
|         let tab;
 | |
|         let gBrowser;
 | |
|         const windowsIt = Services.wm.getEnumerator('navigator:browser');
 | |
|         while (windowsIt.hasMoreElements()) {
 | |
|           const window = windowsIt.getNext();
 | |
|           // gBrowser is always created before tabs. If gBrowser is not
 | |
|           // initialized yet the browser belongs to another window.
 | |
|           if (!window.gBrowser)
 | |
|             continue;
 | |
|           tab = window.gBrowser.getTabForBrowser(linkedBrowser);
 | |
|           if (tab) {
 | |
|             gBrowser = window.gBrowser;
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|         if (!tab)
 | |
|           return;
 | |
| 
 | |
|         const { userContextId } = message.data;
 | |
|         const openerContext = 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);
 | |
|         }
 | |
|         const browserContext = this._userContextIdToBrowserContext.get(userContextId);
 | |
|         const target = new PageTarget(this, gBrowser, tab, linkedBrowser, browserContext, openerTarget);
 | |
| 
 | |
|         const sessions = [];
 | |
|         const readyData = { sessions, target };
 | |
|         this.emit(TargetRegistry.Events.TargetCreated, readyData);
 | |
|         sessions.forEach(session => target._initSession(session));
 | |
|         return {
 | |
|           scriptsToEvaluateOnNewDocument: browserContext ? browserContext.scriptsToEvaluateOnNewDocument : [],
 | |
|           bindings: browserContext ? browserContext.bindings : [],
 | |
|           settings: browserContext ? browserContext.settings : {},
 | |
|           sessionIds: sessions.map(session => session.sessionId()),
 | |
|         };
 | |
|       },
 | |
|     });
 | |
| 
 | |
|     const onTabOpenListener = event => {
 | |
|       const tab = event.target;
 | |
|       const userContextId = tab.userContextId;
 | |
|       const browserContext = this._userContextIdToBrowserContext.get(userContextId);
 | |
|       if (browserContext && browserContext.defaultViewportSize)
 | |
|         setViewportSizeForBrowser(browserContext.defaultViewportSize, tab.linkedBrowser);
 | |
|     };
 | |
| 
 | |
|     const onTabCloseListener = event => {
 | |
|       const tab = event.target;
 | |
|       const linkedBrowser = tab.linkedBrowser;
 | |
|       const target = this._browserToTarget.get(linkedBrowser);
 | |
|       if (target) {
 | |
|         target.dispose();
 | |
|         this.emit(TargetRegistry.Events.TargetDestroyed, target);
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     Services.wm.addListener({
 | |
|       onOpenWindow: async window => {
 | |
|         const domWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
 | |
|         if (!(domWindow instanceof Ci.nsIDOMChromeWindow))
 | |
|           return;
 | |
|         if (domWindow.document.readyState !== 'uninitialized')
 | |
|           throw new Error('DOMWindow should not be loaded yet');
 | |
|         await new Promise(fulfill => {
 | |
|           domWindow.addEventListener('DOMContentLoaded', function listener() {
 | |
|             domWindow.removeEventListener('DOMContentLoaded', listener);
 | |
|             fulfill();
 | |
|           });
 | |
|         });
 | |
|         if (!domWindow.gBrowser)
 | |
|           return;
 | |
|         domWindow.gBrowser.tabContainer.addEventListener('TabOpen', onTabOpenListener);
 | |
|         domWindow.gBrowser.tabContainer.addEventListener('TabClose', onTabCloseListener);
 | |
|       },
 | |
|       onCloseWindow: window => {
 | |
|         const domWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
 | |
|         if (!(domWindow instanceof Ci.nsIDOMChromeWindow))
 | |
|           return;
 | |
|         if (!domWindow.gBrowser)
 | |
|           return;
 | |
|         domWindow.gBrowser.tabContainer.removeEventListener('TabOpen', onTabOpenListener);
 | |
|         domWindow.gBrowser.tabContainer.removeEventListener('TabClose', onTabCloseListener);
 | |
|         for (const tab of domWindow.gBrowser.tabs)
 | |
|           onTabCloseListener({ target: tab });
 | |
|       },
 | |
|     });
 | |
| 
 | |
|     const extHelperAppSvc = Cc["@mozilla.org/uriloader/external-helper-app-service;1"].getService(Ci.nsIExternalHelperAppService);
 | |
|     extHelperAppSvc.setDownloadInterceptor(new DownloadInterceptor(this));
 | |
|   }
 | |
| 
 | |
|   defaultContext() {
 | |
|     return this._defaultContext;
 | |
|   }
 | |
| 
 | |
|   createBrowserContext(removeOnDetach) {
 | |
|     return new BrowserContext(this, helper.generateId(), removeOnDetach);
 | |
|   }
 | |
| 
 | |
|   browserContextForId(browserContextId) {
 | |
|     return this._browserContextIdToBrowserContext.get(browserContextId);
 | |
|   }
 | |
| 
 | |
|   browserContextForUserContextId(userContextId) {
 | |
|     return this._userContextIdToBrowserContext.get(userContextId);
 | |
|   }
 | |
| 
 | |
|   async newPage({browserContextId}) {
 | |
|     let window;
 | |
|     let created = false;
 | |
|     const windowsIt = Services.wm.getEnumerator('navigator:browser');
 | |
|     if (windowsIt.hasMoreElements()) {
 | |
|       window = windowsIt.getNext();
 | |
|     } else {
 | |
|       const features = "chrome,dialog=no,all";
 | |
|       const args = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
 | |
|       args.data = 'about:blank';
 | |
|       window = Services.ww.openWindow(null, AppConstants.BROWSER_CHROME_URL, '_blank', features, args);
 | |
|       created = true;
 | |
|     }
 | |
|     await waitForWindowReady(window);
 | |
|     const browserContext = this.browserContextForId(browserContextId);
 | |
|     const tab = window.gBrowser.addTab('about:blank', {
 | |
|       userContextId: browserContext.userContextId,
 | |
|       triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
 | |
|     });
 | |
|     const target = await new Promise(fulfill => {
 | |
|       const listener = helper.on(this, TargetRegistry.Events.TargetCreated, ({target}) => {
 | |
|         if (target._tab === tab) {
 | |
|           helper.removeListeners([listener]);
 | |
|           fulfill(target);
 | |
|         }
 | |
|       });
 | |
|     });
 | |
|     if (created) {
 | |
|       window.gBrowser.removeTab(window.gBrowser.getTabForBrowser(window.gBrowser.getBrowserAtIndex(0)), {
 | |
|         skipPermitUnload: true,
 | |
|       });
 | |
|     }
 | |
|     window.gBrowser.selectedTab = tab;
 | |
|     if (browserContext.settings.timezoneId) {
 | |
|       if (await target.hasFailedToOverrideTimezone())
 | |
|         throw new Error('Failed to override timezone');
 | |
|     }
 | |
|     return target.id();
 | |
|   }
 | |
| 
 | |
|   targets() {
 | |
|     return Array.from(this._browserToTarget.values());
 | |
|   }
 | |
| 
 | |
|   targetForBrowser(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 {
 | |
|   constructor(registry, gBrowser, tab, linkedBrowser, browserContext, opener) {
 | |
|     EventEmitter.decorate(this);
 | |
| 
 | |
|     this._targetId = helper.generateId();
 | |
|     this._registry = registry;
 | |
|     this._gBrowser = gBrowser;
 | |
|     this._tab = tab;
 | |
|     this._linkedBrowser = linkedBrowser;
 | |
|     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);
 | |
|     this._channelIds = new Set();
 | |
| 
 | |
|     const navigationListener = {
 | |
|       QueryInterface: ChromeUtils.generateQI([ Ci.nsIWebProgressListener]),
 | |
|       onLocationChange: (aWebProgress, aRequest, aLocation) => this._onNavigated(aLocation),
 | |
|     };
 | |
|     this._eventListeners = [
 | |
|       helper.addProgressListener(tab.linkedBrowser, navigationListener, Ci.nsIWebProgress.NOTIFY_LOCATION),
 | |
|     ];
 | |
| 
 | |
|     this._disposed = false;
 | |
|     if (browserContext) {
 | |
|       browserContext.pages.add(this);
 | |
|       browserContext._firstPageCallback();
 | |
|     }
 | |
|     this._registry._browserToTarget.set(this._linkedBrowser, this);
 | |
|     this._registry._browserBrowsingContextToTarget.set(this._linkedBrowser.browsingContext, this);
 | |
|   }
 | |
| 
 | |
|   linkedBrowser() {
 | |
|     return this._linkedBrowser;
 | |
|   }
 | |
| 
 | |
|   browserContext() {
 | |
|     return this._browserContext;
 | |
|   }
 | |
| 
 | |
|   async setViewportSize(viewportSize) {
 | |
|     this._viewportSize = viewportSize;
 | |
|     const actualSize = setViewportSizeForBrowser(viewportSize, this._linkedBrowser);
 | |
|     await this._channel.connect('').send('awaitViewportDimensions', {
 | |
|       width: actualSize.width,
 | |
|       height: actualSize.height
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   connectSession(session) {
 | |
|     this._initSession(session);
 | |
|     this._channel.connect('').send('attach', { sessionId: session.sessionId() });
 | |
|   }
 | |
| 
 | |
|   disconnectSession(session) {
 | |
|     if (!this._disposed)
 | |
|       this._channel.connect('').emit('detach', { sessionId: session.sessionId() });
 | |
|   }
 | |
| 
 | |
|   async close(runBeforeUnload = false) {
 | |
|     await this._gBrowser.removeTab(this._tab, {
 | |
|       skipPermitUnload: !runBeforeUnload,
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   _initSession(session) {
 | |
|     const pageHandler = new PageHandler(this, session, this._channel);
 | |
|     const networkHandler = new NetworkHandler(this, session, this._channel);
 | |
|     session.registerHandler('Page', pageHandler);
 | |
|     session.registerHandler('Network', networkHandler);
 | |
|     session.registerHandler('Runtime', new RuntimeHandler(session, this._channel));
 | |
|     session.registerHandler('Accessibility', new AccessibilityHandler(session, this._channel));
 | |
|     pageHandler.enable();
 | |
|     networkHandler.enable();
 | |
|   }
 | |
| 
 | |
|   id() {
 | |
|     return this._targetId;
 | |
|   }
 | |
| 
 | |
|   info() {
 | |
|     return {
 | |
|       targetId: this.id(),
 | |
|       type: 'page',
 | |
|       browserContextId: this._browserContext ? this._browserContext.browserContextId : undefined,
 | |
|       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);
 | |
|   }
 | |
| 
 | |
|   dispose() {
 | |
|     this._disposed = true;
 | |
|     if (this._browserContext)
 | |
|       this._browserContext.pages.delete(this);
 | |
|     this._registry._browserToTarget.delete(this._linkedBrowser);
 | |
|     this._registry._browserBrowsingContextToTarget.delete(this._linkedBrowser.browsingContext);
 | |
|     helper.removeListeners(this._eventListeners);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 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);
 | |
|     this.removeOnDetach = removeOnDetach;
 | |
|     this.proxy = undefined;
 | |
|     this.extraHTTPHeaders = undefined;
 | |
|     this.httpCredentials = undefined;
 | |
|     this.requestInterceptionEnabled = undefined;
 | |
|     this.ignoreHTTPSErrors = undefined;
 | |
|     this.downloadOptions = undefined;
 | |
|     this.defaultViewportSize = undefined;
 | |
|     this.scriptsToEvaluateOnNewDocument = [];
 | |
|     this.bindings = [];
 | |
|     this.settings = {};
 | |
|     this.pages = new Set();
 | |
|     this._firstPagePromise = new Promise(f => this._firstPageCallback = f);
 | |
|   }
 | |
| 
 | |
|   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);
 | |
|   }
 | |
| 
 | |
|   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);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async setDefaultViewport(viewport) {
 | |
|     this.defaultViewportSize = viewport ? viewport.viewportSize : undefined;
 | |
|     if (!this.userContextId) {
 | |
|       // First page in the default context comes before onTabOpenListener
 | |
|       // so we don't set default viewport. Wait for it here and ensure the viewport.
 | |
|       await this._firstPagePromise;
 | |
|     }
 | |
|     const promises = Array.from(this.pages).map(async page => {
 | |
|       // Resize to new default, unless the page has a custom viewport.
 | |
|       if (!page._viewportSize)
 | |
|         await page.setViewportSize(this.defaultViewportSize);
 | |
|     });
 | |
|     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],
 | |
|       );
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   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;
 | |
|   }
 | |
| }
 | |
| 
 | |
| function dirPath(path) {
 | |
|   return path.substring(0, path.lastIndexOf('/') + 1);
 | |
| }
 | |
| 
 | |
| 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");
 | |
|     }));
 | |
|   }
 | |
|   if (window.document.readyState !== 'complete') {
 | |
|     await new Promise(fulfill => {
 | |
|       window.addEventListener('load', function listener() {
 | |
|         window.removeEventListener('load', listener);
 | |
|         fulfill();
 | |
|       });
 | |
|     });
 | |
|   }
 | |
| }
 | |
| 
 | |
| function setViewportSizeForBrowser(viewportSize, browser) {
 | |
|   if (viewportSize) {
 | |
|     const {width, height} = viewportSize;
 | |
|     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;
 | 
