mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			333 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			333 lines
		
	
	
		
			13 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/. */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
 | 
						|
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 | 
						|
const {TargetRegistry} = ChromeUtils.import("chrome://juggler/content/TargetRegistry.js");
 | 
						|
const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
 | 
						|
const {PageHandler} = ChromeUtils.import("chrome://juggler/content/protocol/PageHandler.js");
 | 
						|
const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 | 
						|
 | 
						|
const helper = new Helper();
 | 
						|
 | 
						|
class BrowserHandler {
 | 
						|
  constructor(session, dispatcher, targetRegistry, onclose) {
 | 
						|
    this._session = session;
 | 
						|
    this._dispatcher = dispatcher;
 | 
						|
    this._targetRegistry = targetRegistry;
 | 
						|
    this._enabled = false;
 | 
						|
    this._attachToDefaultContext = false;
 | 
						|
    this._eventListeners = [];
 | 
						|
    this._createdBrowserContextIds = new Set();
 | 
						|
    this._attachedSessions = new Map();
 | 
						|
    this._onclose = onclose;
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.enable']({attachToDefaultContext}) {
 | 
						|
    if (this._enabled)
 | 
						|
      return;
 | 
						|
    this._enabled = true;
 | 
						|
    this._attachToDefaultContext = attachToDefaultContext;
 | 
						|
 | 
						|
    this._eventListeners = [
 | 
						|
      helper.on(this._targetRegistry, TargetRegistry.Events.TargetCreated, this._onTargetCreated.bind(this)),
 | 
						|
      helper.on(this._targetRegistry, TargetRegistry.Events.TargetDestroyed, this._onTargetDestroyed.bind(this)),
 | 
						|
      helper.on(this._targetRegistry, TargetRegistry.Events.DownloadCreated, this._onDownloadCreated.bind(this)),
 | 
						|
      helper.on(this._targetRegistry, TargetRegistry.Events.DownloadFinished, this._onDownloadFinished.bind(this)),
 | 
						|
      helper.on(this._targetRegistry, TargetRegistry.Events.ScreencastStopped, sessionId => {
 | 
						|
        this._session.emitEvent('Browser.videoRecordingFinished', {screencastId: '' + sessionId});
 | 
						|
      })
 | 
						|
    ];
 | 
						|
 | 
						|
    for (const target of this._targetRegistry.targets())
 | 
						|
      this._onTargetCreated(target);
 | 
						|
 | 
						|
    // Wait to complete initialization of addon manager and search
 | 
						|
    // service before returning from this method. Failing to do so will result
 | 
						|
    // in a broken shutdown sequence and multiple errors in browser STDERR log.
 | 
						|
    //
 | 
						|
    // NOTE: we have to put this here as well as in the `Browser.close` handler
 | 
						|
    // since browser shutdown can be initiated when the last tab is closed, e.g.
 | 
						|
    // with persistent context.
 | 
						|
    await Promise.all([
 | 
						|
      waitForAddonManager(),
 | 
						|
      waitForSearchService(),
 | 
						|
    ]);
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.createBrowserContext']({removeOnDetach}) {
 | 
						|
    if (!this._enabled)
 | 
						|
      throw new Error('Browser domain is not enabled');
 | 
						|
    const browserContext = this._targetRegistry.createBrowserContext(removeOnDetach);
 | 
						|
    this._createdBrowserContextIds.add(browserContext.browserContextId);
 | 
						|
    return {browserContextId: browserContext.browserContextId};
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.removeBrowserContext']({browserContextId}) {
 | 
						|
    if (!this._enabled)
 | 
						|
      throw new Error('Browser domain is not enabled');
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).destroy();
 | 
						|
    this._createdBrowserContextIds.delete(browserContextId);
 | 
						|
  }
 | 
						|
 | 
						|
  dispose() {
 | 
						|
    helper.removeListeners(this._eventListeners);
 | 
						|
    for (const [target, session] of this._attachedSessions)
 | 
						|
      this._dispatcher.destroySession(session);
 | 
						|
    this._attachedSessions.clear();
 | 
						|
    for (const browserContextId of this._createdBrowserContextIds) {
 | 
						|
      const browserContext = this._targetRegistry.browserContextForId(browserContextId);
 | 
						|
      if (browserContext.removeOnDetach)
 | 
						|
        browserContext.destroy();
 | 
						|
    }
 | 
						|
    this._createdBrowserContextIds.clear();
 | 
						|
  }
 | 
						|
 | 
						|
  _shouldAttachToTarget(target) {
 | 
						|
    if (this._createdBrowserContextIds.has(target._browserContext.browserContextId))
 | 
						|
      return true;
 | 
						|
    return this._attachToDefaultContext && target._browserContext === this._targetRegistry.defaultContext();
 | 
						|
  }
 | 
						|
 | 
						|
  _onTargetCreated(target) {
 | 
						|
    if (!this._shouldAttachToTarget(target))
 | 
						|
      return;
 | 
						|
    const channel = target.channel();
 | 
						|
    const session = this._dispatcher.createSession();
 | 
						|
    this._attachedSessions.set(target, session);
 | 
						|
    this._session.emitEvent('Browser.attachedToTarget', {
 | 
						|
      sessionId: session.sessionId(),
 | 
						|
      targetInfo: target.info()
 | 
						|
    });
 | 
						|
    session.setHandler(new PageHandler(target, session, channel));
 | 
						|
  }
 | 
						|
 | 
						|
  _onTargetDestroyed(target) {
 | 
						|
    const session = this._attachedSessions.get(target);
 | 
						|
    if (!session)
 | 
						|
      return;
 | 
						|
    this._attachedSessions.delete(target);
 | 
						|
    this._dispatcher.destroySession(session);
 | 
						|
    this._session.emitEvent('Browser.detachedFromTarget', {
 | 
						|
      sessionId: session.sessionId(),
 | 
						|
      targetId: target.id(),
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  _onDownloadCreated(downloadInfo) {
 | 
						|
    this._session.emitEvent('Browser.downloadCreated', downloadInfo);
 | 
						|
  }
 | 
						|
 | 
						|
  _onDownloadFinished(downloadInfo) {
 | 
						|
    this._session.emitEvent('Browser.downloadFinished', downloadInfo);
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.cancelDownload']({uuid}) {
 | 
						|
    await this._targetRegistry.cancelDownload({uuid});
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.newPage']({browserContextId}) {
 | 
						|
    const targetId = await this._targetRegistry.newPage({browserContextId});
 | 
						|
    return {targetId};
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.close']() {
 | 
						|
    let browserWindow = Services.wm.getMostRecentWindow(
 | 
						|
      "navigator:browser"
 | 
						|
    );
 | 
						|
    if (browserWindow && browserWindow.gBrowserInit) {
 | 
						|
      // idleTasksFinishedPromise does not resolve when the window
 | 
						|
      // is closed early enough, so we race against window closure.
 | 
						|
      await Promise.race([
 | 
						|
        browserWindow.gBrowserInit.idleTasksFinishedPromise,
 | 
						|
        waitForWindowClosed(browserWindow),
 | 
						|
      ]);
 | 
						|
    }
 | 
						|
    // Try to fully initialize browser before closing.
 | 
						|
    // See comment in `Browser.enable`.
 | 
						|
    await Promise.all([
 | 
						|
      waitForAddonManager(),
 | 
						|
      waitForSearchService(),
 | 
						|
    ]);
 | 
						|
    this._onclose();
 | 
						|
    Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.grantPermissions']({browserContextId, origin, permissions}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).grantPermissions(origin, permissions);
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.resetPermissions']({browserContextId}) {
 | 
						|
    this._targetRegistry.browserContextForId(browserContextId).resetPermissions();
 | 
						|
  }
 | 
						|
 | 
						|
  ['Browser.setExtraHTTPHeaders']({browserContextId, headers}) {
 | 
						|
    this._targetRegistry.browserContextForId(browserContextId).extraHTTPHeaders = headers;
 | 
						|
  }
 | 
						|
 | 
						|
  ['Browser.setHTTPCredentials']({browserContextId, credentials}) {
 | 
						|
    this._targetRegistry.browserContextForId(browserContextId).httpCredentials = nullToUndefined(credentials);
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setBrowserProxy']({type, host, port, bypass, username, password}) {
 | 
						|
    this._targetRegistry.setBrowserProxy({ type, host, port, bypass, username, password});
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setContextProxy']({browserContextId, type, host, port, bypass, username, password}) {
 | 
						|
    const browserContext = this._targetRegistry.browserContextForId(browserContextId);
 | 
						|
    browserContext.setProxy({ type, host, port, bypass, username, password });
 | 
						|
  }
 | 
						|
 | 
						|
  ['Browser.setRequestInterception']({browserContextId, enabled}) {
 | 
						|
    this._targetRegistry.browserContextForId(browserContextId).requestInterceptionEnabled = enabled;
 | 
						|
  }
 | 
						|
 | 
						|
  ['Browser.setIgnoreHTTPSErrors']({browserContextId, ignoreHTTPSErrors}) {
 | 
						|
    this._targetRegistry.browserContextForId(browserContextId).setIgnoreHTTPSErrors(nullToUndefined(ignoreHTTPSErrors));
 | 
						|
  }
 | 
						|
 | 
						|
  ['Browser.setDownloadOptions']({browserContextId, downloadOptions}) {
 | 
						|
    this._targetRegistry.browserContextForId(browserContextId).downloadOptions = nullToUndefined(downloadOptions);
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setGeolocationOverride']({browserContextId, geolocation}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).applySetting('geolocation', nullToUndefined(geolocation));
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setOnlineOverride']({browserContextId, override}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).applySetting('onlineOverride', nullToUndefined(override));
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setColorScheme']({browserContextId, colorScheme}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).setColorScheme(nullToUndefined(colorScheme));
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setReducedMotion']({browserContextId, reducedMotion}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).setReducedMotion(nullToUndefined(reducedMotion));
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setForcedColors']({browserContextId, forcedColors}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).setForcedColors(nullToUndefined(forcedColors));
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setVideoRecordingOptions']({browserContextId, options}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).setVideoRecordingOptions(options);
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setUserAgentOverride']({browserContextId, userAgent}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).setDefaultUserAgent(userAgent);
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setPlatformOverride']({browserContextId, platform}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).setDefaultPlatform(platform);
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setBypassCSP']({browserContextId, bypassCSP}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).applySetting('bypassCSP', nullToUndefined(bypassCSP));
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setJavaScriptDisabled']({browserContextId, javaScriptDisabled}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).setJavaScriptDisabled(javaScriptDisabled);
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setLocaleOverride']({browserContextId, locale}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).applySetting('locale', nullToUndefined(locale));
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setTimezoneOverride']({browserContextId, timezoneId}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).applySetting('timezoneId', nullToUndefined(timezoneId));
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setTouchOverride']({browserContextId, hasTouch}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).setTouchOverride(nullToUndefined(hasTouch));
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setDefaultViewport']({browserContextId, viewport}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).setDefaultViewport(nullToUndefined(viewport));
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setScrollbarsHidden']({browserContextId, hidden}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).applySetting('scrollbarsHidden', nullToUndefined(hidden));
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.setInitScripts']({browserContextId, scripts}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).setInitScripts(scripts);
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.addBinding']({browserContextId, worldName, name, script}) {
 | 
						|
    await this._targetRegistry.browserContextForId(browserContextId).addBinding(worldName, name, script);
 | 
						|
  }
 | 
						|
 | 
						|
  ['Browser.setCookies']({browserContextId, cookies}) {
 | 
						|
    this._targetRegistry.browserContextForId(browserContextId).setCookies(cookies);
 | 
						|
  }
 | 
						|
 | 
						|
  ['Browser.clearCookies']({browserContextId}) {
 | 
						|
    this._targetRegistry.browserContextForId(browserContextId).clearCookies();
 | 
						|
  }
 | 
						|
 | 
						|
  ['Browser.getCookies']({browserContextId}) {
 | 
						|
    const cookies = this._targetRegistry.browserContextForId(browserContextId).getCookies();
 | 
						|
    return {cookies};
 | 
						|
  }
 | 
						|
 | 
						|
  async ['Browser.getInfo']() {
 | 
						|
    const version = AppConstants.MOZ_APP_VERSION_DISPLAY;
 | 
						|
    const userAgent = Components.classes["@mozilla.org/network/protocol;1?name=http"]
 | 
						|
                                .getService(Components.interfaces.nsIHttpProtocolHandler)
 | 
						|
                                .userAgent;
 | 
						|
    return {version: 'Firefox/' + version, userAgent};
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
async function waitForSearchService() {
 | 
						|
  const searchService = Components.classes["@mozilla.org/browser/search-service;1"].getService(Components.interfaces.nsISearchService);
 | 
						|
  await searchService.init();
 | 
						|
}
 | 
						|
 | 
						|
async function waitForAddonManager() {
 | 
						|
  if (AddonManager.isReady)
 | 
						|
    return;
 | 
						|
  await new Promise(resolve => {
 | 
						|
    let listener = {
 | 
						|
      onStartup() {
 | 
						|
        AddonManager.removeManagerListener(listener);
 | 
						|
        resolve();
 | 
						|
      },
 | 
						|
      onShutdown() { },
 | 
						|
    };
 | 
						|
    AddonManager.addManagerListener(listener);
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
async function waitForWindowClosed(browserWindow) {
 | 
						|
  if (browserWindow.closed)
 | 
						|
    return;
 | 
						|
  await new Promise((resolve => {
 | 
						|
    const listener = {
 | 
						|
      onCloseWindow: window => {
 | 
						|
        let domWindow;
 | 
						|
        if (window instanceof Ci.nsIAppWindow)
 | 
						|
          domWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
 | 
						|
        else
 | 
						|
          domWindow = window;
 | 
						|
        if (domWindow === browserWindow) {
 | 
						|
          Services.wm.removeListener(listener);
 | 
						|
          resolve();
 | 
						|
        }
 | 
						|
      },
 | 
						|
    };
 | 
						|
    Services.wm.addListener(listener);
 | 
						|
  }));
 | 
						|
}
 | 
						|
 | 
						|
function nullToUndefined(value) {
 | 
						|
  return value === null ? undefined : value;
 | 
						|
}
 | 
						|
 | 
						|
var EXPORTED_SYMBOLS = ['BrowserHandler'];
 | 
						|
this.BrowserHandler = BrowserHandler;
 |