mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-15 14:11:50 +03:00
08a447e90f
Notable changes: - javaScriptEnabled moved to the browser side. - enabling new UI since it no longer helps to fix screencast Review branch: https://github.com/aslushnikov/juggler/tree/roll-to-91-0-b10 Note: this will have 2 screencast tests failing. Investigation is in a follow-up.
324 lines
12 KiB
JavaScript
324 lines
12 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, dir, width, height, scale}) {
|
|
await this._targetRegistry.browserContextForId(browserContextId).setVideoRecordingOptions({dir, width, height, scale});
|
|
}
|
|
|
|
async ['Browser.setUserAgentOverride']({browserContextId, userAgent}) {
|
|
await this._targetRegistry.browserContextForId(browserContextId).setDefaultUserAgent(userAgent);
|
|
}
|
|
|
|
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.addScriptToEvaluateOnNewDocument']({browserContextId, script}) {
|
|
await this._targetRegistry.browserContextForId(browserContextId).addScriptToEvaluateOnNewDocument(script);
|
|
}
|
|
|
|
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 => {
|
|
if (window === 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;
|