init commit

This commit is contained in:
iFlip721
2025-04-16 09:55:57 -04:00
commit a03209dc9e
935 changed files with 177492 additions and 0 deletions

View File

@@ -0,0 +1,215 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getAccessibilityTree = getAccessibilityTree;
/**
* Copyright 2018 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
async function getAccessibilityTree(session, needle) {
const objectId = needle ? needle._objectId : undefined;
const {
tree
} = await session.send('Accessibility.getFullAXTree', {
objectId
});
const axNode = new FFAXNode(tree);
return {
tree: axNode,
needle: needle ? axNode._findNeedle() : null
};
}
const FFRoleToARIARole = new Map(Object.entries({
'pushbutton': 'button',
'checkbutton': 'checkbox',
'editcombobox': 'combobox',
'content deletion': 'deletion',
'footnote': 'doc-footnote',
'non-native document': 'document',
'grouping': 'group',
'graphic': 'img',
'content insertion': 'insertion',
'animation': 'marquee',
'flat equation': 'math',
'menupopup': 'menu',
'check menu item': 'menuitemcheckbox',
'radio menu item': 'menuitemradio',
'listbox option': 'option',
'radiobutton': 'radio',
'statusbar': 'status',
'pagetab': 'tab',
'pagetablist': 'tablist',
'propertypage': 'tabpanel',
'entry': 'textbox',
'outline': 'tree',
'tree table': 'treegrid',
'outlineitem': 'treeitem'
}));
class FFAXNode {
constructor(payload) {
this._children = void 0;
this._payload = void 0;
this._editable = void 0;
this._richlyEditable = void 0;
this._focusable = void 0;
this._expanded = void 0;
this._name = void 0;
this._role = void 0;
this._cachedHasFocusableChild = void 0;
this._payload = payload;
this._children = (payload.children || []).map(x => new FFAXNode(x));
this._editable = !!payload.editable;
this._richlyEditable = this._editable && payload.tag !== 'textarea' && payload.tag !== 'input';
this._focusable = !!payload.focusable;
this._expanded = !!payload.expanded;
this._name = this._payload.name;
this._role = this._payload.role;
}
_isPlainTextField() {
if (this._richlyEditable) return false;
if (this._editable) return true;
return this._role === 'entry';
}
_isTextOnlyObject() {
const role = this._role;
return role === 'text leaf' || role === 'text' || role === 'statictext';
}
_hasFocusableChild() {
if (this._cachedHasFocusableChild === undefined) {
this._cachedHasFocusableChild = false;
for (const child of this._children) {
if (child._focusable || child._hasFocusableChild()) {
this._cachedHasFocusableChild = true;
break;
}
}
}
return this._cachedHasFocusableChild;
}
children() {
return this._children;
}
_findNeedle() {
if (this._payload.foundObject) return this;
for (const child of this._children) {
const found = child._findNeedle();
if (found) return found;
}
return null;
}
isLeafNode() {
if (!this._children.length) return true;
// These types of objects may have children that we use as internal
// implementation details, but we want to expose them as leaves to platform
// accessibility APIs because screen readers might be confused if they find
// any children.
if (this._isPlainTextField() || this._isTextOnlyObject()) return true;
// Roles whose children are only presentational according to the ARIA and
// HTML5 Specs should be hidden from screen readers.
// (Note that whilst ARIA buttons can have only presentational children, HTML5
// buttons are allowed to have content.)
switch (this._role) {
case 'graphic':
case 'scrollbar':
case 'slider':
case 'separator':
case 'progressbar':
return true;
default:
break;
}
// Here and below: Android heuristics
if (this._hasFocusableChild()) return false;
if (this._focusable && this._role !== 'document' && this._name) return true;
if (this._role === 'heading' && this._name) return true;
return false;
}
isControl() {
switch (this._role) {
case 'checkbutton':
case 'check menu item':
case 'check rich option':
case 'combobox':
case 'combobox option':
case 'color chooser':
case 'listbox':
case 'listbox option':
case 'listbox rich option':
case 'popup menu':
case 'menupopup':
case 'menuitem':
case 'menubar':
case 'button':
case 'pushbutton':
case 'radiobutton':
case 'radio menuitem':
case 'scrollbar':
case 'slider':
case 'spinbutton':
case 'switch':
case 'pagetab':
case 'entry':
case 'tree table':
return true;
default:
return false;
}
}
isInteresting(insideControl) {
if (this._focusable || this._richlyEditable) return true;
// If it's not focusable but has a control role, then it's interesting.
if (this.isControl()) return true;
// A non focusable child of a control is not interesting
if (insideControl) return false;
return this.isLeafNode() && !!this._name.trim();
}
serialize() {
const node = {
role: FFRoleToARIARole.get(this._role) || this._role,
name: this._name || ''
};
const userStringProperties = ['name', 'description', 'roledescription', 'valuetext', 'keyshortcuts'];
for (const userStringProperty of userStringProperties) {
if (!(userStringProperty in this._payload)) continue;
node[userStringProperty] = this._payload[userStringProperty];
}
const booleanProperties = ['disabled', 'expanded', 'focused', 'modal', 'multiline', 'multiselectable', 'readonly', 'required', 'selected'];
for (const booleanProperty of booleanProperties) {
if (this._role === 'document' && booleanProperty === 'focused') continue; // document focusing is strange
const value = this._payload[booleanProperty];
if (!value) continue;
node[booleanProperty] = value;
}
const numericalProperties = ['level'];
for (const numericalProperty of numericalProperties) {
if (!(numericalProperty in this._payload)) continue;
node[numericalProperty] = this._payload[numericalProperty];
}
const tokenProperties = ['autocomplete', 'haspopup', 'invalid', 'orientation'];
for (const tokenProperty of tokenProperties) {
const value = this._payload[tokenProperty];
if (!value || value === 'false') continue;
node[tokenProperty] = value;
}
const axNode = node;
axNode.valueString = this._payload.value;
if ('checked' in this._payload) axNode.checked = this._payload.checked === true ? 'checked' : this._payload.checked === 'mixed' ? 'mixed' : 'unchecked';
if ('pressed' in this._payload) axNode.pressed = this._payload.pressed === true ? 'pressed' : 'released';
return axNode;
}
}

View File

@@ -0,0 +1,439 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FFBrowserContext = exports.FFBrowser = void 0;
var _errors = require("../../common/errors");
var _utils = require("../../utils");
var _browser = require("../browser");
var _browserContext = require("../browserContext");
var network = _interopRequireWildcard(require("../network"));
var _ffConnection = require("./ffConnection");
var _ffPage = require("./ffPage");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright 2018 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class FFBrowser extends _browser.Browser {
static async connect(parent, transport, options) {
var _options$originalLaun;
const connection = new _ffConnection.FFConnection(transport, options.protocolLogger, options.browserLogsCollector);
const browser = new FFBrowser(parent, connection, options);
if (options.__testHookOnConnectToBrowser) await options.__testHookOnConnectToBrowser();
let firefoxUserPrefs = options.persistent ? {} : (_options$originalLaun = options.originalLaunchOptions.firefoxUserPrefs) !== null && _options$originalLaun !== void 0 ? _options$originalLaun : {};
if (Object.keys(kBandaidFirefoxUserPrefs).length) firefoxUserPrefs = {
...kBandaidFirefoxUserPrefs,
...firefoxUserPrefs
};
const promises = [connection.send('Browser.enable', {
attachToDefaultContext: !!options.persistent,
userPrefs: Object.entries(firefoxUserPrefs).map(([name, value]) => ({
name,
value
}))
}), browser._initVersion()];
if (options.persistent) {
browser._defaultContext = new FFBrowserContext(browser, undefined, options.persistent);
promises.push(browser._defaultContext._initialize());
}
if (options.proxy) promises.push(browser._connection.send('Browser.setBrowserProxy', toJugglerProxyOptions(options.proxy)));
await Promise.all(promises);
return browser;
}
constructor(parent, connection, options) {
super(parent, options);
this._connection = void 0;
this._ffPages = void 0;
this._contexts = void 0;
this._version = '';
this._userAgent = '';
this._connection = connection;
this._ffPages = new Map();
this._contexts = new Map();
this._connection.on(_ffConnection.ConnectionEvents.Disconnected, () => this._onDisconnect());
this._connection.on('Browser.attachedToTarget', this._onAttachedToTarget.bind(this));
this._connection.on('Browser.detachedFromTarget', this._onDetachedFromTarget.bind(this));
this._connection.on('Browser.downloadCreated', this._onDownloadCreated.bind(this));
this._connection.on('Browser.downloadFinished', this._onDownloadFinished.bind(this));
this._connection.on('Browser.videoRecordingFinished', this._onVideoRecordingFinished.bind(this));
}
async _initVersion() {
const result = await this._connection.send('Browser.getInfo');
this._version = result.version.substring(result.version.indexOf('/') + 1);
this._userAgent = result.userAgent;
}
isConnected() {
return !this._connection._closed;
}
async doCreateNewContext(options) {
if (options.isMobile) throw new Error('options.isMobile is not supported in Firefox');
const {
browserContextId
} = await this._connection.send('Browser.createBrowserContext', {
removeOnDetach: true
});
const context = new FFBrowserContext(this, browserContextId, options);
await context._initialize();
this._contexts.set(browserContextId, context);
return context;
}
contexts() {
return Array.from(this._contexts.values());
}
version() {
return this._version;
}
userAgent() {
return this._userAgent;
}
_onDetachedFromTarget(payload) {
const ffPage = this._ffPages.get(payload.targetId);
this._ffPages.delete(payload.targetId);
ffPage.didClose();
}
_onAttachedToTarget(payload) {
const {
targetId,
browserContextId,
openerId,
type
} = payload.targetInfo;
(0, _utils.assert)(type === 'page');
const context = browserContextId ? this._contexts.get(browserContextId) : this._defaultContext;
(0, _utils.assert)(context, `Unknown context id:${browserContextId}, _defaultContext: ${this._defaultContext}`);
const session = this._connection.createSession(payload.sessionId);
const opener = openerId ? this._ffPages.get(openerId) : null;
const ffPage = new _ffPage.FFPage(session, context, opener);
this._ffPages.set(targetId, ffPage);
}
_onDownloadCreated(payload) {
const ffPage = this._ffPages.get(payload.pageTargetId);
if (!ffPage) return;
// Abort the navigation that turned into download.
ffPage._page._frameManager.frameAbortedNavigation(payload.frameId, 'Download is starting');
let originPage = ffPage._initializedPage;
// If it's a new window download, report it on the opener page.
if (!originPage) {
// Resume the page creation with an error. The page will automatically close right
// after the download begins.
ffPage._markAsError(new Error('Starting new page download'));
if (ffPage._opener) originPage = ffPage._opener._initializedPage;
}
if (!originPage) return;
this._downloadCreated(originPage, payload.uuid, payload.url, payload.suggestedFileName);
}
_onDownloadFinished(payload) {
const error = payload.canceled ? 'canceled' : payload.error;
this._downloadFinished(payload.uuid, error);
}
_onVideoRecordingFinished(payload) {
var _this$_takeVideo;
(_this$_takeVideo = this._takeVideo(payload.screencastId)) === null || _this$_takeVideo === void 0 ? void 0 : _this$_takeVideo.reportFinished();
}
_onDisconnect() {
for (const video of this._idToVideo.values()) video.artifact.reportFinished(_errors.kBrowserClosedError);
this._idToVideo.clear();
this._didClose();
}
}
exports.FFBrowser = FFBrowser;
class FFBrowserContext extends _browserContext.BrowserContext {
constructor(browser, browserContextId, options) {
super(browser, options, browserContextId);
}
async _initialize() {
(0, _utils.assert)(!this._ffPages().length);
const browserContextId = this._browserContextId;
const promises = [super._initialize()];
promises.push(this._browser._connection.send('Browser.setDownloadOptions', {
browserContextId,
downloadOptions: {
behavior: this._options.acceptDownloads ? 'saveToDisk' : 'cancel',
downloadsDir: this._browser.options.downloadsPath
}
}));
if (this._options.viewport) {
const viewport = {
viewportSize: {
width: this._options.viewport.width,
height: this._options.viewport.height
},
deviceScaleFactor: this._options.deviceScaleFactor || 1
};
promises.push(this._browser._connection.send('Browser.setDefaultViewport', {
browserContextId,
viewport
}));
}
if (this._options.hasTouch) promises.push(this._browser._connection.send('Browser.setTouchOverride', {
browserContextId,
hasTouch: true
}));
if (this._options.userAgent) promises.push(this._browser._connection.send('Browser.setUserAgentOverride', {
browserContextId,
userAgent: this._options.userAgent
}));
if (this._options.bypassCSP) promises.push(this._browser._connection.send('Browser.setBypassCSP', {
browserContextId,
bypassCSP: true
}));
if (this._options.ignoreHTTPSErrors) promises.push(this._browser._connection.send('Browser.setIgnoreHTTPSErrors', {
browserContextId,
ignoreHTTPSErrors: true
}));
if (this._options.javaScriptEnabled === false) promises.push(this._browser._connection.send('Browser.setJavaScriptDisabled', {
browserContextId,
javaScriptDisabled: true
}));
if (this._options.locale) promises.push(this._browser._connection.send('Browser.setLocaleOverride', {
browserContextId,
locale: this._options.locale
}));
if (this._options.timezoneId) promises.push(this._browser._connection.send('Browser.setTimezoneOverride', {
browserContextId,
timezoneId: this._options.timezoneId
}));
if (this._options.extraHTTPHeaders || this._options.locale) promises.push(this.setExtraHTTPHeaders(this._options.extraHTTPHeaders || []));
if (this._options.httpCredentials) promises.push(this.setHTTPCredentials(this._options.httpCredentials));
if (this._options.geolocation) promises.push(this.setGeolocation(this._options.geolocation));
if (this._options.offline) promises.push(this.setOffline(this._options.offline));
if (this._options.colorScheme !== 'no-override') {
promises.push(this._browser._connection.send('Browser.setColorScheme', {
browserContextId,
colorScheme: this._options.colorScheme !== undefined ? this._options.colorScheme : 'light'
}));
}
if (this._options.reducedMotion !== 'no-override') {
promises.push(this._browser._connection.send('Browser.setReducedMotion', {
browserContextId,
reducedMotion: this._options.reducedMotion !== undefined ? this._options.reducedMotion : 'no-preference'
}));
}
if (this._options.forcedColors !== 'no-override') {
promises.push(this._browser._connection.send('Browser.setForcedColors', {
browserContextId,
forcedColors: this._options.forcedColors !== undefined ? this._options.forcedColors : 'none'
}));
}
if (this._options.recordVideo) {
promises.push(this._ensureVideosPath().then(() => {
return this._browser._connection.send('Browser.setVideoRecordingOptions', {
// validateBrowserContextOptions ensures correct video size.
options: {
...this._options.recordVideo.size,
dir: this._options.recordVideo.dir
},
browserContextId: this._browserContextId
});
}));
}
if (this._options.proxy) {
promises.push(this._browser._connection.send('Browser.setContextProxy', {
browserContextId: this._browserContextId,
...toJugglerProxyOptions(this._options.proxy)
}));
}
await Promise.all(promises);
}
_ffPages() {
return Array.from(this._browser._ffPages.values()).filter(ffPage => ffPage._browserContext === this);
}
pages() {
return this._ffPages().map(ffPage => ffPage._initializedPage).filter(pageOrNull => !!pageOrNull);
}
async newPageDelegate() {
(0, _browserContext.assertBrowserContextIsNotOwned)(this);
const {
targetId
} = await this._browser._connection.send('Browser.newPage', {
browserContextId: this._browserContextId
}).catch(e => {
if (e.message.includes('Failed to override timezone')) throw new Error(`Invalid timezone ID: ${this._options.timezoneId}`);
throw e;
});
return this._browser._ffPages.get(targetId);
}
async doGetCookies(urls) {
const {
cookies
} = await this._browser._connection.send('Browser.getCookies', {
browserContextId: this._browserContextId
});
return network.filterCookies(cookies.map(c => {
const copy = {
...c
};
delete copy.size;
delete copy.session;
return copy;
}), urls);
}
async addCookies(cookies) {
const cc = network.rewriteCookies(cookies).map(c => ({
...c,
expires: c.expires && c.expires !== -1 ? c.expires : undefined
}));
await this._browser._connection.send('Browser.setCookies', {
browserContextId: this._browserContextId,
cookies: cc
});
}
async clearCookies() {
await this._browser._connection.send('Browser.clearCookies', {
browserContextId: this._browserContextId
});
}
async doGrantPermissions(origin, permissions) {
const webPermissionToProtocol = new Map([['geolocation', 'geo'], ['persistent-storage', 'persistent-storage'], ['push', 'push'], ['notifications', 'desktop-notification']]);
const filtered = permissions.map(permission => {
const protocolPermission = webPermissionToProtocol.get(permission);
if (!protocolPermission) throw new Error('Unknown permission: ' + permission);
return protocolPermission;
});
await this._browser._connection.send('Browser.grantPermissions', {
origin: origin,
browserContextId: this._browserContextId,
permissions: filtered
});
}
async doClearPermissions() {
await this._browser._connection.send('Browser.resetPermissions', {
browserContextId: this._browserContextId
});
}
async setGeolocation(geolocation) {
(0, _browserContext.verifyGeolocation)(geolocation);
this._options.geolocation = geolocation;
await this._browser._connection.send('Browser.setGeolocationOverride', {
browserContextId: this._browserContextId,
geolocation: geolocation || null
});
}
async setExtraHTTPHeaders(headers) {
this._options.extraHTTPHeaders = headers;
let allHeaders = this._options.extraHTTPHeaders;
if (this._options.locale) allHeaders = network.mergeHeaders([allHeaders, network.singleHeader('Accept-Language', this._options.locale)]);
await this._browser._connection.send('Browser.setExtraHTTPHeaders', {
browserContextId: this._browserContextId,
headers: allHeaders
});
}
async setUserAgent(userAgent) {
await this._browser._connection.send('Browser.setUserAgentOverride', {
browserContextId: this._browserContextId,
userAgent: userAgent || null
});
}
async setOffline(offline) {
this._options.offline = offline;
await this._browser._connection.send('Browser.setOnlineOverride', {
browserContextId: this._browserContextId,
override: offline ? 'offline' : 'online'
});
}
async doSetHTTPCredentials(httpCredentials) {
this._options.httpCredentials = httpCredentials;
await this._browser._connection.send('Browser.setHTTPCredentials', {
browserContextId: this._browserContextId,
credentials: httpCredentials || null
});
}
async doAddInitScript(source) {
await this._browser._connection.send('Browser.setInitScripts', {
browserContextId: this._browserContextId,
scripts: this.initScripts.map(script => ({
script
}))
});
}
async doRemoveInitScripts() {
await this._browser._connection.send('Browser.setInitScripts', {
browserContextId: this._browserContextId,
scripts: []
});
}
async doExposeBinding(binding) {
await this._browser._connection.send('Browser.addBinding', {
browserContextId: this._browserContextId,
name: binding.name,
script: binding.source
});
}
async doRemoveExposedBindings() {
// TODO: implement me.
// This is not a critical problem, what ends up happening is
// an old binding will be restored upon page reload and will point nowhere.
}
async doUpdateRequestInterception() {
await this._browser._connection.send('Browser.setRequestInterception', {
browserContextId: this._browserContextId,
enabled: !!this._requestInterceptor
});
}
onClosePersistent() {}
async clearCache() {
// Clearing only the context cache does not work: https://bugzilla.mozilla.org/show_bug.cgi?id=1819147
await this._browser._connection.send('Browser.clearCache');
}
async doClose() {
if (!this._browserContextId) {
if (this._options.recordVideo) {
await this._browser._connection.send('Browser.setVideoRecordingOptions', {
options: undefined,
browserContextId: this._browserContextId
});
}
// Closing persistent context should close the browser.
await this._browser.close();
} else {
await this._browser._connection.send('Browser.removeBrowserContext', {
browserContextId: this._browserContextId
});
this._browser._contexts.delete(this._browserContextId);
}
}
async cancelDownload(uuid) {
await this._browser._connection.send('Browser.cancelDownload', {
uuid
});
}
}
exports.FFBrowserContext = FFBrowserContext;
function toJugglerProxyOptions(proxy) {
const proxyServer = new URL(proxy.server);
let port = parseInt(proxyServer.port, 10);
let type = 'http';
if (proxyServer.protocol === 'socks5:') type = 'socks';else if (proxyServer.protocol === 'socks4:') type = 'socks4';else if (proxyServer.protocol === 'https:') type = 'https';
if (proxyServer.port === '') {
if (proxyServer.protocol === 'http:') port = 80;else if (proxyServer.protocol === 'https:') port = 443;
}
return {
type,
bypass: proxy.bypass ? proxy.bypass.split(',').map(domain => domain.trim()) : [],
host: proxyServer.hostname,
port,
username: proxy.username,
password: proxy.password
};
}
// Prefs for quick fixes that didn't make it to the build.
// Should all be moved to `playwright.cfg`.
const kBandaidFirefoxUserPrefs = {};

View File

@@ -0,0 +1,223 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.kBrowserCloseMessageId = exports.FFSessionEvents = exports.FFSession = exports.FFConnection = exports.ConnectionEvents = void 0;
var _events = require("events");
var _utils = require("../../utils");
var _stackTrace = require("../../utils/stackTrace");
var _debugLogger = require("../../common/debugLogger");
var _helper = require("../helper");
var _protocolError = require("../protocolError");
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const ConnectionEvents = {
Disconnected: Symbol('Disconnected')
};
// FFPlaywright uses this special id to issue Browser.close command which we
// should ignore.
exports.ConnectionEvents = ConnectionEvents;
const kBrowserCloseMessageId = -9999;
exports.kBrowserCloseMessageId = kBrowserCloseMessageId;
class FFConnection extends _events.EventEmitter {
constructor(transport, protocolLogger, browserLogsCollector) {
super();
this._lastId = void 0;
this._callbacks = void 0;
this._transport = void 0;
this._protocolLogger = void 0;
this._browserLogsCollector = void 0;
this._sessions = void 0;
this._closed = void 0;
this.on = void 0;
this.addListener = void 0;
this.off = void 0;
this.removeListener = void 0;
this.once = void 0;
this.setMaxListeners(0);
this._transport = transport;
this._protocolLogger = protocolLogger;
this._browserLogsCollector = browserLogsCollector;
this._lastId = 0;
this._callbacks = new Map();
this._sessions = new Map();
this._closed = false;
this.on = super.on;
this.addListener = super.addListener;
this.off = super.removeListener;
this.removeListener = super.removeListener;
this.once = super.once;
this._transport.onmessage = this._onMessage.bind(this);
// onclose should be set last, since it can be immediately called.
this._transport.onclose = this._onClose.bind(this);
}
async send(method, params) {
this._checkClosed(method);
const id = this.nextMessageId();
this._rawSend({
id,
method,
params
});
return new Promise((resolve, reject) => {
this._callbacks.set(id, {
resolve,
reject,
error: new _protocolError.ProtocolError(false),
method
});
});
}
nextMessageId() {
return ++this._lastId;
}
_checkClosed(method) {
if (this._closed) throw new _protocolError.ProtocolError(true, `${method}): Browser closed.` + _helper.helper.formatBrowserLogs(this._browserLogsCollector.recentLogs()));
}
_rawSend(message) {
this._protocolLogger('send', message);
this._transport.send(message);
}
async _onMessage(message) {
this._protocolLogger('receive', message);
if (message.id === kBrowserCloseMessageId) return;
if (message.sessionId) {
const session = this._sessions.get(message.sessionId);
if (session) session.dispatchMessage(message);
} else if (message.id) {
const callback = this._callbacks.get(message.id);
// Callbacks could be all rejected if someone has called `.dispose()`.
if (callback) {
this._callbacks.delete(message.id);
if (message.error) callback.reject(createProtocolError(callback.error, callback.method, message.error));else callback.resolve(message.result);
}
} else {
Promise.resolve().then(() => this.emit(message.method, message.params));
}
}
_onClose() {
this._closed = true;
this._transport.onmessage = undefined;
this._transport.onclose = undefined;
const formattedBrowserLogs = _helper.helper.formatBrowserLogs(this._browserLogsCollector.recentLogs());
for (const session of this._sessions.values()) session.dispose();
this._sessions.clear();
for (const callback of this._callbacks.values()) {
const error = (0, _stackTrace.rewriteErrorMessage)(callback.error, `Protocol error (${callback.method}): Browser closed.` + formattedBrowserLogs);
error.sessionClosed = true;
callback.reject(error);
}
this._callbacks.clear();
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
}
close() {
if (!this._closed) this._transport.close();
}
createSession(sessionId) {
const session = new FFSession(this, sessionId, message => this._rawSend({
...message,
sessionId
}));
this._sessions.set(sessionId, session);
return session;
}
}
exports.FFConnection = FFConnection;
const FFSessionEvents = {
Disconnected: Symbol('Disconnected')
};
exports.FFSessionEvents = FFSessionEvents;
class FFSession extends _events.EventEmitter {
constructor(connection, sessionId, rawSend) {
super();
this._connection = void 0;
this._disposed = false;
this._callbacks = void 0;
this._sessionId = void 0;
this._rawSend = void 0;
this._crashed = false;
this.on = void 0;
this.addListener = void 0;
this.off = void 0;
this.removeListener = void 0;
this.once = void 0;
this.setMaxListeners(0);
this._callbacks = new Map();
this._connection = connection;
this._sessionId = sessionId;
this._rawSend = rawSend;
this.on = super.on;
this.addListener = super.addListener;
this.off = super.removeListener;
this.removeListener = super.removeListener;
this.once = super.once;
}
markAsCrashed() {
this._crashed = true;
}
async send(method, params) {
if (this._crashed) throw new _protocolError.ProtocolError(true, 'Target crashed');
this._connection._checkClosed(method);
if (this._disposed) throw new _protocolError.ProtocolError(true, 'Target closed');
const id = this._connection.nextMessageId();
this._rawSend({
method,
params,
id
});
return new Promise((resolve, reject) => {
this._callbacks.set(id, {
resolve,
reject,
error: new _protocolError.ProtocolError(false),
method
});
});
}
sendMayFail(method, params) {
return this.send(method, params).catch(error => _debugLogger.debugLogger.log('error', error));
}
dispatchMessage(object) {
if (object.id && this._callbacks.has(object.id)) {
const callback = this._callbacks.get(object.id);
this._callbacks.delete(object.id);
if (object.error) callback.reject(createProtocolError(callback.error, callback.method, object.error));else callback.resolve(object.result);
} else {
(0, _utils.assert)(!object.id);
Promise.resolve().then(() => this.emit(object.method, object.params));
}
}
dispose() {
for (const callback of this._callbacks.values()) {
callback.error.sessionClosed = true;
callback.reject((0, _stackTrace.rewriteErrorMessage)(callback.error, 'Target closed'));
}
this._callbacks.clear();
this._disposed = true;
this._connection._sessions.delete(this._sessionId);
Promise.resolve().then(() => this.emit(FFSessionEvents.Disconnected));
}
}
exports.FFSession = FFSession;
function createProtocolError(error, method, protocolError) {
let message = `Protocol error (${method}): ${protocolError.message}`;
if ('data' in protocolError) message += ` ${protocolError.data}`;
return (0, _stackTrace.rewriteErrorMessage)(error, message);
}

View File

@@ -0,0 +1,138 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FFExecutionContext = void 0;
var js = _interopRequireWildcard(require("../javascript"));
var _stackTrace = require("../../utils/stackTrace");
var _utilityScriptSerializers = require("../isomorphic/utilityScriptSerializers");
var _protocolError = require("../protocolError");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright 2019 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class FFExecutionContext {
constructor(session, executionContextId) {
this._session = void 0;
this._executionContextId = void 0;
this._session = session;
this._executionContextId = executionContextId;
}
async rawEvaluateJSON(expression) {
const payload = await this._session.send('Runtime.evaluate', {
expression,
returnByValue: true,
executionContextId: this._executionContextId
}).catch(rewriteError);
checkException(payload.exceptionDetails);
return payload.result.value;
}
async rawEvaluateHandle(expression) {
const payload = await this._session.send('Runtime.evaluate', {
expression,
returnByValue: false,
executionContextId: this._executionContextId
}).catch(rewriteError);
checkException(payload.exceptionDetails);
return payload.result.objectId;
}
rawCallFunctionNoReply(func, ...args) {
this._session.send('Runtime.callFunction', {
functionDeclaration: func.toString(),
args: args.map(a => a instanceof js.JSHandle ? {
objectId: a._objectId
} : {
value: a
}),
returnByValue: true,
executionContextId: this._executionContextId
}).catch(() => {});
}
async evaluateWithArguments(expression, returnByValue, utilityScript, values, objectIds) {
const payload = await this._session.send('Runtime.callFunction', {
functionDeclaration: expression,
args: [{
objectId: utilityScript._objectId,
value: undefined
}, ...values.map(value => ({
value
})), ...objectIds.map(objectId => ({
objectId,
value: undefined
}))],
returnByValue,
executionContextId: this._executionContextId
}).catch(rewriteError);
checkException(payload.exceptionDetails);
if (returnByValue) return (0, _utilityScriptSerializers.parseEvaluationResultValue)(payload.result.value);
return utilityScript._context.createHandle(payload.result);
}
async getProperties(context, objectId) {
const response = await this._session.send('Runtime.getObjectProperties', {
executionContextId: this._executionContextId,
objectId
});
const result = new Map();
for (const property of response.properties) result.set(property.name, context.createHandle(property.value));
return result;
}
createHandle(context, remoteObject) {
return new js.JSHandle(context, remoteObject.subtype || remoteObject.type || '', renderPreview(remoteObject), remoteObject.objectId, potentiallyUnserializableValue(remoteObject));
}
async releaseHandle(objectId) {
await this._session.send('Runtime.disposeObject', {
executionContextId: this._executionContextId,
objectId
});
}
objectCount(objectId) {
throw new Error('Method not implemented in Firefox.');
}
}
exports.FFExecutionContext = FFExecutionContext;
function checkException(exceptionDetails) {
if (!exceptionDetails) return;
if (exceptionDetails.value) throw new js.JavaScriptErrorInEvaluate(JSON.stringify(exceptionDetails.value));else throw new js.JavaScriptErrorInEvaluate(exceptionDetails.text + (exceptionDetails.stack ? '\n' + exceptionDetails.stack : ''));
}
function rewriteError(error) {
if (error.message.includes('cyclic object value') || error.message.includes('Object is not serializable')) return {
result: {
type: 'undefined',
value: undefined
}
};
if (error instanceof TypeError && error.message.startsWith('Converting circular structure to JSON')) (0, _stackTrace.rewriteErrorMessage)(error, error.message + ' Are you passing a nested JSHandle?');
if (!js.isJavaScriptErrorInEvaluate(error) && !(0, _protocolError.isSessionClosedError)(error)) throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw error;
}
function potentiallyUnserializableValue(remoteObject) {
const value = remoteObject.value;
const unserializableValue = remoteObject.unserializableValue;
return unserializableValue ? js.parseUnserializableValue(unserializableValue) : value;
}
function renderPreview(object) {
if (object.type === 'undefined') return 'undefined';
if (object.unserializableValue) return String(object.unserializableValue);
if (object.type === 'symbol') return 'Symbol()';
if (object.subtype === 'regexp') return 'RegExp';
if (object.subtype === 'weakmap') return 'WeakMap';
if (object.subtype === 'weakset') return 'WeakSet';
if (object.subtype) return object.subtype[0].toUpperCase() + object.subtype.slice(1);
if ('value' in object) return String(object.value);
}

View File

@@ -0,0 +1,154 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.RawTouchscreenImpl = exports.RawMouseImpl = exports.RawKeyboardImpl = void 0;
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function toModifiersMask(modifiers) {
let mask = 0;
if (modifiers.has('Alt')) mask |= 1;
if (modifiers.has('Control')) mask |= 2;
if (modifiers.has('Shift')) mask |= 4;
if (modifiers.has('Meta')) mask |= 8;
return mask;
}
function toButtonNumber(button) {
if (button === 'left') return 0;
if (button === 'middle') return 1;
if (button === 'right') return 2;
return 0;
}
function toButtonsMask(buttons) {
let mask = 0;
if (buttons.has('left')) mask |= 1;
if (buttons.has('right')) mask |= 2;
if (buttons.has('middle')) mask |= 4;
return mask;
}
class RawKeyboardImpl {
constructor(client) {
this._client = void 0;
this._client = client;
}
async keydown(modifiers, code, keyCode, keyCodeWithoutLocation, key, location, autoRepeat, text) {
if (code === 'MetaLeft') code = 'OSLeft';
if (code === 'MetaRight') code = 'OSRight';
// Firefox will figure out Enter by itself
if (text === '\r') text = '';
await this._client.send('Page.dispatchKeyEvent', {
type: 'keydown',
keyCode: keyCodeWithoutLocation,
code,
key,
repeat: autoRepeat,
location,
text
});
}
async keyup(modifiers, code, keyCode, keyCodeWithoutLocation, key, location) {
if (code === 'MetaLeft') code = 'OSLeft';
if (code === 'MetaRight') code = 'OSRight';
await this._client.send('Page.dispatchKeyEvent', {
type: 'keyup',
key,
keyCode: keyCodeWithoutLocation,
code,
location,
repeat: false
});
}
async sendText(text) {
await this._client.send('Page.insertText', {
text
});
}
}
exports.RawKeyboardImpl = RawKeyboardImpl;
class RawMouseImpl {
constructor(client) {
this._client = void 0;
this._page = void 0;
this._client = client;
}
async move(x, y, button, buttons, modifiers, forClick) {
await this._client.send('Page.dispatchMouseEvent', {
type: 'mousemove',
button: 0,
buttons: toButtonsMask(buttons),
x: Math.floor(x),
y: Math.floor(y),
modifiers: toModifiersMask(modifiers)
});
}
async down(x, y, button, buttons, modifiers, clickCount) {
await this._client.send('Page.dispatchMouseEvent', {
type: 'mousedown',
button: toButtonNumber(button),
buttons: toButtonsMask(buttons),
x: Math.floor(x),
y: Math.floor(y),
modifiers: toModifiersMask(modifiers),
clickCount
});
}
async up(x, y, button, buttons, modifiers, clickCount) {
await this._client.send('Page.dispatchMouseEvent', {
type: 'mouseup',
button: toButtonNumber(button),
buttons: toButtonsMask(buttons),
x: Math.floor(x),
y: Math.floor(y),
modifiers: toModifiersMask(modifiers),
clickCount
});
}
async wheel(x, y, buttons, modifiers, deltaX, deltaY) {
// Wheel events hit the compositor first, so wait one frame for it to be synced.
await this._page.mainFrame().evaluateExpression(`new Promise(requestAnimationFrame)`, {
world: 'utility'
});
await this._client.send('Page.dispatchWheelEvent', {
deltaX,
deltaY,
x: Math.floor(x),
y: Math.floor(y),
deltaZ: 0,
modifiers: toModifiersMask(modifiers)
});
}
setPage(page) {
this._page = page;
}
}
exports.RawMouseImpl = RawMouseImpl;
class RawTouchscreenImpl {
constructor(client) {
this._client = void 0;
this._client = client;
}
async tap(x, y, modifiers) {
await this._client.send('Page.dispatchTapEvent', {
x,
y,
modifiers: toModifiersMask(modifiers)
});
}
}
exports.RawTouchscreenImpl = RawTouchscreenImpl;

View File

@@ -0,0 +1,231 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FFNetworkManager = void 0;
var _eventsHelper = require("../../utils/eventsHelper");
var network = _interopRequireWildcard(require("../network"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright 2019 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class FFNetworkManager {
constructor(session, page) {
this._session = void 0;
this._requests = void 0;
this._page = void 0;
this._eventListeners = void 0;
this._session = session;
this._requests = new Map();
this._page = page;
this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(session, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.responseReceived', this._onResponseReceived.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.requestFinished', this._onRequestFinished.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.requestFailed', this._onRequestFailed.bind(this))];
}
dispose() {
_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
}
async setRequestInterception(enabled) {
await this._session.send('Network.setRequestInterception', {
enabled
});
}
_onRequestWillBeSent(event) {
const redirectedFrom = event.redirectedFrom ? this._requests.get(event.redirectedFrom) || null : null;
const frame = redirectedFrom ? redirectedFrom.request.frame() : event.frameId ? this._page._frameManager.frame(event.frameId) : null;
if (!frame) return;
if (redirectedFrom) this._requests.delete(redirectedFrom._id);
const request = new InterceptableRequest(frame, redirectedFrom, event);
let route;
if (event.isIntercepted) route = new FFRouteImpl(this._session, request);
this._requests.set(request._id, request);
this._page._frameManager.requestStarted(request.request, route);
}
_onResponseReceived(event) {
var _event$securityDetail, _event$securityDetail2, _event$securityDetail3, _event$securityDetail4, _event$securityDetail5;
const request = this._requests.get(event.requestId);
if (!request) return;
const getResponseBody = async () => {
const response = await this._session.send('Network.getResponseBody', {
requestId: request._id
});
if (response.evicted) throw new Error(`Response body for ${request.request.method()} ${request.request.url()} was evicted!`);
return Buffer.from(response.base64body, 'base64');
};
const startTime = event.timing.startTime;
function relativeToStart(time) {
if (!time) return -1;
return (time - startTime) / 1000;
}
const timing = {
startTime: startTime / 1000,
domainLookupStart: relativeToStart(event.timing.domainLookupStart),
domainLookupEnd: relativeToStart(event.timing.domainLookupEnd),
connectStart: relativeToStart(event.timing.connectStart),
secureConnectionStart: relativeToStart(event.timing.secureConnectionStart),
connectEnd: relativeToStart(event.timing.connectEnd),
requestStart: relativeToStart(event.timing.requestStart),
responseStart: relativeToStart(event.timing.responseStart)
};
const response = new network.Response(request.request, event.status, event.statusText, parseMultivalueHeaders(event.headers), timing, getResponseBody, event.fromServiceWorker);
if (event !== null && event !== void 0 && event.remoteIPAddress && typeof (event === null || event === void 0 ? void 0 : event.remotePort) === 'number') {
response._serverAddrFinished({
ipAddress: event.remoteIPAddress,
port: event.remotePort
});
} else {
response._serverAddrFinished();
}
response._securityDetailsFinished({
protocol: event === null || event === void 0 ? void 0 : (_event$securityDetail = event.securityDetails) === null || _event$securityDetail === void 0 ? void 0 : _event$securityDetail.protocol,
subjectName: event === null || event === void 0 ? void 0 : (_event$securityDetail2 = event.securityDetails) === null || _event$securityDetail2 === void 0 ? void 0 : _event$securityDetail2.subjectName,
issuer: event === null || event === void 0 ? void 0 : (_event$securityDetail3 = event.securityDetails) === null || _event$securityDetail3 === void 0 ? void 0 : _event$securityDetail3.issuer,
validFrom: event === null || event === void 0 ? void 0 : (_event$securityDetail4 = event.securityDetails) === null || _event$securityDetail4 === void 0 ? void 0 : _event$securityDetail4.validFrom,
validTo: event === null || event === void 0 ? void 0 : (_event$securityDetail5 = event.securityDetails) === null || _event$securityDetail5 === void 0 ? void 0 : _event$securityDetail5.validTo
});
// "raw" headers are the same as "provisional" headers in Firefox.
response.setRawResponseHeaders(null);
// Headers size are not available in Firefox.
response.setResponseHeadersSize(null);
this._page._frameManager.requestReceivedResponse(response);
}
_onRequestFinished(event) {
const request = this._requests.get(event.requestId);
if (!request) return;
const response = request.request._existingResponse();
response.setTransferSize(event.transferSize);
response.setEncodedBodySize(event.encodedBodySize);
// Keep redirected requests in the map for future reference as redirectedFrom.
const isRedirected = response.status() >= 300 && response.status() <= 399;
const responseEndTime = event.responseEndTime ? event.responseEndTime / 1000 - response.timing().startTime : -1;
if (isRedirected) {
response._requestFinished(responseEndTime);
} else {
this._requests.delete(request._id);
response._requestFinished(responseEndTime);
}
if (event.protocolVersion) response._setHttpVersion(event.protocolVersion);
this._page._frameManager.reportRequestFinished(request.request, response);
}
_onRequestFailed(event) {
const request = this._requests.get(event.requestId);
if (!request) return;
this._requests.delete(request._id);
const response = request.request._existingResponse();
if (response) {
response.setTransferSize(null);
response.setEncodedBodySize(null);
response._requestFinished(-1);
}
request.request._setFailureText(event.errorCode);
this._page._frameManager.requestFailed(request.request, event.errorCode === 'NS_BINDING_ABORTED');
}
}
exports.FFNetworkManager = FFNetworkManager;
const causeToResourceType = {
TYPE_INVALID: 'other',
TYPE_OTHER: 'other',
TYPE_SCRIPT: 'script',
TYPE_IMAGE: 'image',
TYPE_STYLESHEET: 'stylesheet',
TYPE_OBJECT: 'other',
TYPE_DOCUMENT: 'document',
TYPE_SUBDOCUMENT: 'document',
TYPE_REFRESH: 'document',
TYPE_XBL: 'other',
TYPE_PING: 'other',
TYPE_XMLHTTPREQUEST: 'xhr',
TYPE_OBJECT_SUBREQUEST: 'other',
TYPE_DTD: 'other',
TYPE_FONT: 'font',
TYPE_MEDIA: 'media',
TYPE_WEBSOCKET: 'websocket',
TYPE_CSP_REPORT: 'other',
TYPE_XSLT: 'other',
TYPE_BEACON: 'other',
TYPE_FETCH: 'fetch',
TYPE_IMAGESET: 'images',
TYPE_WEB_MANIFEST: 'manifest'
};
const internalCauseToResourceType = {
TYPE_INTERNAL_EVENTSOURCE: 'eventsource'
};
class InterceptableRequest {
constructor(frame, redirectedFrom, payload) {
this.request = void 0;
this._id = void 0;
this._redirectedTo = void 0;
this._id = payload.requestId;
if (redirectedFrom) redirectedFrom._redirectedTo = this;
let postDataBuffer = null;
if (payload.postData) postDataBuffer = Buffer.from(payload.postData, 'base64');
this.request = new network.Request(frame._page._browserContext, frame, null, redirectedFrom ? redirectedFrom.request : null, payload.navigationId, payload.url, internalCauseToResourceType[payload.internalCause] || causeToResourceType[payload.cause] || 'other', payload.method, postDataBuffer, payload.headers);
// "raw" headers are the same as "provisional" headers in Firefox.
this.request.setRawRequestHeaders(null);
}
_finalRequest() {
let request = this;
while (request._redirectedTo) request = request._redirectedTo;
return request;
}
}
class FFRouteImpl {
constructor(session, request) {
this._request = void 0;
this._session = void 0;
this._session = session;
this._request = request;
}
async continue(request, overrides) {
await this._session.sendMayFail('Network.resumeInterceptedRequest', {
requestId: this._request._id,
url: overrides.url,
method: overrides.method,
headers: overrides.headers,
postData: overrides.postData ? Buffer.from(overrides.postData).toString('base64') : undefined
});
}
async fulfill(response) {
const base64body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64');
await this._session.sendMayFail('Network.fulfillInterceptedRequest', {
requestId: this._request._id,
status: response.status,
statusText: network.STATUS_TEXTS[String(response.status)] || '',
headers: response.headers,
base64body
});
}
async abort(errorCode) {
await this._session.sendMayFail('Network.abortInterceptedRequest', {
requestId: this._request._id,
errorCode
});
}
}
function parseMultivalueHeaders(headers) {
const result = [];
for (const header of headers) {
const separator = header.name.toLowerCase() === 'set-cookie' ? '\n' : ',';
const tokens = header.value.split(separator).map(s => s.trim());
for (const token of tokens) result.push({
name: header.name,
value: token
});
}
return result;
}

View File

@@ -0,0 +1,560 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.UTILITY_WORLD_NAME = exports.FFPage = void 0;
var dialog = _interopRequireWildcard(require("../dialog"));
var dom = _interopRequireWildcard(require("../dom"));
var _eventsHelper = require("../../utils/eventsHelper");
var _page = require("../page");
var _ffAccessibility = require("./ffAccessibility");
var _ffConnection = require("./ffConnection");
var _ffExecutionContext = require("./ffExecutionContext");
var _ffInput = require("./ffInput");
var _ffNetworkManager = require("./ffNetworkManager");
var _stackTrace = require("../../utils/stackTrace");
var _debugLogger = require("../../common/debugLogger");
var _manualPromise = require("../../utils/manualPromise");
var _browserContext = require("../browserContext");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright 2019 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
exports.UTILITY_WORLD_NAME = UTILITY_WORLD_NAME;
class FFPage {
constructor(session, browserContext, opener) {
this.cspErrorsAsynchronousForInlineScipts = true;
this.rawMouse = void 0;
this.rawKeyboard = void 0;
this.rawTouchscreen = void 0;
this._session = void 0;
this._page = void 0;
this._networkManager = void 0;
this._browserContext = void 0;
this._pagePromise = new _manualPromise.ManualPromise();
this._initializedPage = null;
this._initializationFailed = false;
this._opener = void 0;
this._contextIdToContext = void 0;
this._eventListeners = void 0;
this._workers = new Map();
this._screencastId = void 0;
this._initScripts = [];
this._session = session;
this._opener = opener;
this.rawKeyboard = new _ffInput.RawKeyboardImpl(session);
this.rawMouse = new _ffInput.RawMouseImpl(session);
this.rawTouchscreen = new _ffInput.RawTouchscreenImpl(session);
this._contextIdToContext = new Map();
this._browserContext = browserContext;
this._page = new _page.Page(this, browserContext);
this.rawMouse.setPage(this._page);
this._networkManager = new _ffNetworkManager.FFNetworkManager(session, this._page);
this._page.on(_page.Page.Events.FrameDetached, frame => this._removeContextsForFrame(frame));
// TODO: remove Page.willOpenNewWindowAsynchronously from the protocol.
this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(this._session, 'Page.eventFired', this._onEventFired.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.frameAttached', this._onFrameAttached.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.frameDetached', this._onFrameDetached.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.navigationAborted', this._onNavigationAborted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.navigationCommitted', this._onNavigationCommitted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.navigationStarted', this._onNavigationStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.sameDocumentNavigation', this._onSameDocumentNavigation.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.executionContextCreated', this._onExecutionContextCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.executionContextDestroyed', this._onExecutionContextDestroyed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.linkClicked', event => this._onLinkClicked(event.phase)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.uncaughtError', this._onUncaughtError.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.console', this._onConsole.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.dialogOpened', this._onDialogOpened.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.bindingCalled', this._onBindingCalled.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.fileChooserOpened', this._onFileChooserOpened.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.workerCreated', this._onWorkerCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.workerDestroyed', this._onWorkerDestroyed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.dispatchMessageFromWorker', this._onDispatchMessageFromWorker.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.crashed', this._onCrashed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.videoRecordingStarted', this._onVideoRecordingStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketCreated', this._onWebSocketCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketClosed', this._onWebSocketClosed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketFrameReceived', this._onWebSocketFrameReceived.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketFrameSent', this._onWebSocketFrameSent.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.screencastFrame', this._onScreencastFrame.bind(this))];
session.once(_ffConnection.FFSessionEvents.Disconnected, () => {
this._markAsError(new Error('Page closed'));
this._page._didDisconnect();
});
this._session.once('Page.ready', async () => {
await this._page.initOpener(this._opener);
if (this._initializationFailed) return;
// Note: it is important to call |reportAsNew| before resolving pageOrError promise,
// so that anyone who awaits pageOrError got a ready and reported page.
this._initializedPage = this._page;
this._page.reportAsNew();
this._pagePromise.resolve(this._page);
});
// Ideally, we somehow ensure that utility world is created before Page.ready arrives, but currently it is racy.
// Therefore, we can end up with an initialized page without utility world, although very unlikely.
this.addInitScript('', UTILITY_WORLD_NAME).catch(e => this._markAsError(e));
}
potentiallyUninitializedPage() {
return this._page;
}
async _markAsError(error) {
// Same error may be report twice: channer disconnected and session.send fails.
if (this._initializationFailed) return;
this._initializationFailed = true;
if (!this._initializedPage) {
await this._page.initOpener(this._opener);
this._page.reportAsNew(error);
this._pagePromise.resolve(error);
}
}
async pageOrError() {
return this._pagePromise;
}
_onWebSocketCreated(event) {
this._page._frameManager.onWebSocketCreated(webSocketId(event.frameId, event.wsid), event.requestURL);
this._page._frameManager.onWebSocketRequest(webSocketId(event.frameId, event.wsid));
}
_onWebSocketClosed(event) {
if (event.error) this._page._frameManager.webSocketError(webSocketId(event.frameId, event.wsid), event.error);
this._page._frameManager.webSocketClosed(webSocketId(event.frameId, event.wsid));
}
_onWebSocketFrameReceived(event) {
this._page._frameManager.webSocketFrameReceived(webSocketId(event.frameId, event.wsid), event.opcode, event.data);
}
_onWebSocketFrameSent(event) {
this._page._frameManager.onWebSocketFrameSent(webSocketId(event.frameId, event.wsid), event.opcode, event.data);
}
_onExecutionContextCreated(payload) {
const {
executionContextId,
auxData
} = payload;
const frame = this._page._frameManager.frame(auxData.frameId);
if (!frame) return;
const delegate = new _ffExecutionContext.FFExecutionContext(this._session, executionContextId);
let worldName = null;
if (auxData.name === UTILITY_WORLD_NAME) worldName = 'utility';else if (!auxData.name) worldName = 'main';
const context = new dom.FrameExecutionContext(delegate, frame, worldName);
context[contextDelegateSymbol] = delegate;
if (worldName) frame._contextCreated(worldName, context);
this._contextIdToContext.set(executionContextId, context);
}
_onExecutionContextDestroyed(payload) {
const {
executionContextId
} = payload;
const context = this._contextIdToContext.get(executionContextId);
if (!context) return;
this._contextIdToContext.delete(executionContextId);
context.frame._contextDestroyed(context);
}
_onExecutionContextsCleared() {
for (const executionContextId of Array.from(this._contextIdToContext.keys())) this._onExecutionContextDestroyed({
executionContextId
});
}
_removeContextsForFrame(frame) {
for (const [contextId, context] of this._contextIdToContext) {
if (context.frame === frame) this._contextIdToContext.delete(contextId);
}
}
_onLinkClicked(phase) {
if (phase === 'before') this._page._frameManager.frameWillPotentiallyRequestNavigation();else this._page._frameManager.frameDidPotentiallyRequestNavigation();
}
_onNavigationStarted(params) {
this._page._frameManager.frameRequestedNavigation(params.frameId, params.navigationId);
}
_onNavigationAborted(params) {
this._page._frameManager.frameAbortedNavigation(params.frameId, params.errorText, params.navigationId);
}
_onNavigationCommitted(params) {
for (const [workerId, worker] of this._workers) {
if (worker.frameId === params.frameId) this._onWorkerDestroyed({
workerId
});
}
this._page._frameManager.frameCommittedNewDocumentNavigation(params.frameId, params.url, params.name || '', params.navigationId || '', false);
}
_onSameDocumentNavigation(params) {
this._page._frameManager.frameCommittedSameDocumentNavigation(params.frameId, params.url);
}
_onFrameAttached(params) {
this._page._frameManager.frameAttached(params.frameId, params.parentFrameId);
}
_onFrameDetached(params) {
this._page._frameManager.frameDetached(params.frameId);
}
_onEventFired(payload) {
const {
frameId,
name
} = payload;
if (name === 'load') this._page._frameManager.frameLifecycleEvent(frameId, 'load');
if (name === 'DOMContentLoaded') this._page._frameManager.frameLifecycleEvent(frameId, 'domcontentloaded');
}
_onUncaughtError(params) {
const {
name,
message
} = (0, _stackTrace.splitErrorMessage)(params.message);
const error = new Error(message);
error.stack = params.message + '\n' + params.stack.split('\n').filter(Boolean).map(a => a.replace(/([^@]*)@(.*)/, ' at $1 ($2)')).join('\n');
error.name = name;
this._page.firePageError(error);
}
_onConsole(payload) {
const {
type,
args,
executionContextId,
location
} = payload;
const context = this._contextIdToContext.get(executionContextId);
if (!context) return;
this._page._addConsoleMessage(type, args.map(arg => context.createHandle(arg)), location);
}
_onDialogOpened(params) {
this._page.emitOnContext(_browserContext.BrowserContext.Events.Dialog, new dialog.Dialog(this._page, params.type, params.message, async (accept, promptText) => {
await this._session.sendMayFail('Page.handleDialog', {
dialogId: params.dialogId,
accept,
promptText
});
}, params.defaultValue));
}
async _onBindingCalled(event) {
const pageOrError = await this.pageOrError();
if (!(pageOrError instanceof Error)) {
const context = this._contextIdToContext.get(event.executionContextId);
if (context) await this._page._onBindingCalled(event.payload, context);
}
}
async _onFileChooserOpened(payload) {
const {
executionContextId,
element
} = payload;
const context = this._contextIdToContext.get(executionContextId);
if (!context) return;
const handle = context.createHandle(element).asElement();
await this._page._onFileChooserOpened(handle);
}
async _onWorkerCreated(event) {
const workerId = event.workerId;
const worker = new _page.Worker(this._page, event.url);
const workerSession = new _ffConnection.FFSession(this._session._connection, workerId, message => {
this._session.send('Page.sendMessageToWorker', {
frameId: event.frameId,
workerId: workerId,
message: JSON.stringify(message)
}).catch(e => {
workerSession.dispatchMessage({
id: message.id,
method: '',
params: {},
error: {
message: e.message,
data: undefined
}
});
});
});
this._workers.set(workerId, {
session: workerSession,
frameId: event.frameId
});
this._page._addWorker(workerId, worker);
workerSession.once('Runtime.executionContextCreated', event => {
worker._createExecutionContext(new _ffExecutionContext.FFExecutionContext(workerSession, event.executionContextId));
});
workerSession.on('Runtime.console', event => {
const {
type,
args,
location
} = event;
const context = worker._existingExecutionContext;
this._page._addConsoleMessage(type, args.map(arg => context.createHandle(arg)), location);
});
// Note: we receive worker exceptions directly from the page.
}
_onWorkerDestroyed(event) {
const workerId = event.workerId;
const worker = this._workers.get(workerId);
if (!worker) return;
worker.session.dispose();
this._workers.delete(workerId);
this._page._removeWorker(workerId);
}
async _onDispatchMessageFromWorker(event) {
const worker = this._workers.get(event.workerId);
if (!worker) return;
worker.session.dispatchMessage(JSON.parse(event.message));
}
async _onCrashed(event) {
this._session.markAsCrashed();
this._page._didCrash();
}
_onVideoRecordingStarted(event) {
this._browserContext._browser._videoStarted(this._browserContext, event.screencastId, event.file, this.pageOrError());
}
async exposeBinding(binding) {
await this._session.send('Page.addBinding', {
name: binding.name,
script: binding.source
});
}
async removeExposedBindings() {
// TODO: implement me.
}
didClose() {
this._session.dispose();
_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
this._networkManager.dispose();
this._page._didClose();
}
async navigateFrame(frame, url, referer) {
const response = await this._session.send('Page.navigate', {
url,
referer,
frameId: frame._id
});
return {
newDocumentId: response.navigationId || undefined
};
}
async updateExtraHTTPHeaders() {
await this._session.send('Network.setExtraHTTPHeaders', {
headers: this._page.extraHTTPHeaders() || []
});
}
async updateEmulatedViewportSize() {
const viewportSize = this._page.viewportSize();
await this._session.send('Page.setViewportSize', {
viewportSize
});
}
async bringToFront() {
await this._session.send('Page.bringToFront', {});
}
async updateEmulateMedia() {
const emulatedMedia = this._page.emulatedMedia();
const colorScheme = emulatedMedia.colorScheme === 'no-override' ? undefined : emulatedMedia.colorScheme;
const reducedMotion = emulatedMedia.reducedMotion === 'no-override' ? undefined : emulatedMedia.reducedMotion;
const forcedColors = emulatedMedia.forcedColors === 'no-override' ? undefined : emulatedMedia.forcedColors;
await this._session.send('Page.setEmulatedMedia', {
// Empty string means reset.
type: emulatedMedia.media === 'no-override' ? '' : emulatedMedia.media,
colorScheme,
reducedMotion,
forcedColors
});
}
async updateRequestInterception() {
await this._networkManager.setRequestInterception(this._page.needsRequestInterception());
}
async updateFileChooserInterception() {
const enabled = this._page.fileChooserIntercepted();
await this._session.send('Page.setInterceptFileChooserDialog', {
enabled
}).catch(() => {}); // target can be closed.
}
async reload() {
await this._session.send('Page.reload');
}
async goBack() {
const {
success
} = await this._session.send('Page.goBack', {
frameId: this._page.mainFrame()._id
});
return success;
}
async goForward() {
const {
success
} = await this._session.send('Page.goForward', {
frameId: this._page.mainFrame()._id
});
return success;
}
async addInitScript(script, worldName) {
this._initScripts.push({
script,
worldName
});
await this._session.send('Page.setInitScripts', {
scripts: this._initScripts
});
}
async removeInitScripts() {
this._initScripts = [];
await this._session.send('Page.setInitScripts', {
scripts: []
});
}
async closePage(runBeforeUnload) {
await this._session.send('Page.close', {
runBeforeUnload
});
}
async setBackgroundColor(color) {
if (color) throw new Error('Not implemented');
}
async takeScreenshot(progress, format, documentRect, viewportRect, quality, fitsViewport, scale) {
if (!documentRect) {
const scrollOffset = await this._page.mainFrame().waitForFunctionValueInUtility(progress, () => ({
x: window.scrollX,
y: window.scrollY
}));
documentRect = {
x: viewportRect.x + scrollOffset.x,
y: viewportRect.y + scrollOffset.y,
width: viewportRect.width,
height: viewportRect.height
};
}
progress.throwIfAborted();
const {
data
} = await this._session.send('Page.screenshot', {
mimeType: 'image/' + format,
clip: documentRect,
quality,
omitDeviceScaleFactor: scale === 'css'
});
return Buffer.from(data, 'base64');
}
async getContentFrame(handle) {
const {
contentFrameId
} = await this._session.send('Page.describeNode', {
frameId: handle._context.frame._id,
objectId: handle._objectId
});
if (!contentFrameId) return null;
return this._page._frameManager.frame(contentFrameId);
}
async getOwnerFrame(handle) {
const {
ownerFrameId
} = await this._session.send('Page.describeNode', {
frameId: handle._context.frame._id,
objectId: handle._objectId
});
return ownerFrameId || null;
}
isElementHandle(remoteObject) {
return remoteObject.subtype === 'node';
}
async getBoundingBox(handle) {
const quads = await this.getContentQuads(handle);
if (!quads || !quads.length) return null;
let minX = Infinity;
let maxX = -Infinity;
let minY = Infinity;
let maxY = -Infinity;
for (const quad of quads) {
for (const point of quad) {
minX = Math.min(minX, point.x);
maxX = Math.max(maxX, point.x);
minY = Math.min(minY, point.y);
maxY = Math.max(maxY, point.y);
}
}
return {
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY
};
}
async scrollRectIntoViewIfNeeded(handle, rect) {
return await this._session.send('Page.scrollIntoViewIfNeeded', {
frameId: handle._context.frame._id,
objectId: handle._objectId,
rect
}).then(() => 'done').catch(e => {
if (e instanceof Error && e.message.includes('Node is detached from document')) return 'error:notconnected';
if (e instanceof Error && e.message.includes('Node does not have a layout object')) return 'error:notvisible';
throw e;
});
}
async setScreencastOptions(options) {
if (options) {
const {
screencastId
} = await this._session.send('Page.startScreencast', options);
this._screencastId = screencastId;
} else {
await this._session.send('Page.stopScreencast');
}
}
_onScreencastFrame(event) {
if (!this._screencastId) return;
const screencastId = this._screencastId;
this._page.throttleScreencastFrameAck(() => {
this._session.send('Page.screencastFrameAck', {
screencastId
}).catch(e => _debugLogger.debugLogger.log('error', e));
});
const buffer = Buffer.from(event.data, 'base64');
this._page.emit(_page.Page.Events.ScreencastFrame, {
buffer,
width: event.deviceWidth,
height: event.deviceHeight
});
}
rafCountForStablePosition() {
return 1;
}
async getContentQuads(handle) {
const result = await this._session.sendMayFail('Page.getContentQuads', {
frameId: handle._context.frame._id,
objectId: handle._objectId
});
if (!result) return null;
return result.quads.map(quad => [quad.p1, quad.p2, quad.p3, quad.p4]);
}
async setInputFiles(handle, files) {
await handle.evaluateInUtility(([injected, node, files]) => injected.setInputFiles(node, files), files);
}
async setInputFilePaths(handle, files) {
await Promise.all([this._session.send('Page.setFileInputFiles', {
frameId: handle._context.frame._id,
objectId: handle._objectId,
files
}), handle.dispatchEvent('input'), handle.dispatchEvent('change')]);
}
async adoptElementHandle(handle, to) {
const result = await this._session.send('Page.adoptNode', {
frameId: handle._context.frame._id,
objectId: handle._objectId,
executionContextId: to[contextDelegateSymbol]._executionContextId
});
if (!result.remoteObject) throw new Error(dom.kUnableToAdoptErrorMessage);
return to.createHandle(result.remoteObject);
}
async getAccessibilityTree(needle) {
return (0, _ffAccessibility.getAccessibilityTree)(this._session, needle);
}
async inputActionEpilogue() {}
async resetForReuse() {
// Firefox sometimes keeps the last mouse position in the page,
// which affects things like hovered state.
// See https://github.com/microsoft/playwright/issues/22432.
// Move mouse to (-1, -1) to avoid anything being hovered.
await this.rawMouse.move(-1, -1, 'none', new Set(), new Set(), false);
}
async getFrameElement(frame) {
const parent = frame.parentFrame();
if (!parent) throw new Error('Frame has been detached.');
const context = await parent._mainContext();
const result = await this._session.send('Page.adoptNode', {
frameId: frame._id,
executionContextId: context[contextDelegateSymbol]._executionContextId
});
if (!result.remoteObject) throw new Error('Frame has been detached.');
return context.createHandle(result.remoteObject);
}
}
exports.FFPage = FFPage;
function webSocketId(frameId, wsid) {
return `${frameId}---${wsid}`;
}
const contextDelegateSymbol = Symbol('delegate');

View File

@@ -0,0 +1,89 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Firefox = void 0;
var os = _interopRequireWildcard(require("os"));
var _path = _interopRequireDefault(require("path"));
var _ffBrowser = require("./ffBrowser");
var _ffConnection = require("./ffConnection");
var _browserType = require("../browserType");
var _stackTrace = require("../../utils/stackTrace");
var _utils = require("../../utils");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class Firefox extends _browserType.BrowserType {
constructor(parent) {
super(parent, 'firefox');
}
_connectToTransport(transport, options) {
return _ffBrowser.FFBrowser.connect(this.attribution.playwright, transport, options);
}
_rewriteStartupError(error) {
if (error.message.includes('no DISPLAY environment variable specified')) return (0, _stackTrace.rewriteErrorMessage)(error, '\n' + (0, _utils.wrapInASCIIBox)(_browserType.kNoXServerRunningError, 1));
return error;
}
_amendEnvironment(env, userDataDir, executable, browserArguments) {
if (!_path.default.isAbsolute(os.homedir())) throw new Error(`Cannot launch Firefox with relative home directory. Did you set ${os.platform() === 'win32' ? 'USERPROFILE' : 'HOME'} to a relative path?`);
if (os.platform() === 'linux') {
// Always remove SNAP_NAME and SNAP_INSTANCE_NAME env variables since they
// confuse Firefox: in our case, builds never come from SNAP.
// See https://github.com/microsoft/playwright/issues/20555
return {
...env,
SNAP_NAME: undefined,
SNAP_INSTANCE_NAME: undefined
};
}
return env;
}
_attemptToGracefullyCloseBrowser(transport) {
const message = {
method: 'Browser.close',
params: {},
id: _ffConnection.kBrowserCloseMessageId
};
transport.send(message);
}
_defaultArgs(options, isPersistent, userDataDir) {
const {
args = [],
headless
} = options;
const userDataDirArg = args.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile'));
if (userDataDirArg) throw new Error('Pass userDataDir parameter to `browserType.launchPersistentContext(userDataDir, ...)` instead of specifying --profile argument');
if (args.find(arg => arg.startsWith('-juggler'))) throw new Error('Use the port parameter instead of -juggler argument');
const firefoxArguments = ['-no-remote'];
if (headless) {
firefoxArguments.push('-headless');
} else {
firefoxArguments.push('-wait-for-browser');
firefoxArguments.push('-foreground');
}
firefoxArguments.push(`-profile`, userDataDir);
firefoxArguments.push('-juggler-pipe');
firefoxArguments.push(...args);
if (isPersistent) firefoxArguments.push('about:blank');else firefoxArguments.push('-silent');
return firefoxArguments;
}
}
exports.Firefox = Firefox;