init commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 = {};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
154
bin/pac/tools/.playwright/package/lib/server/firefox/ffInput.js
Normal file
154
bin/pac/tools/.playwright/package/lib/server/firefox/ffInput.js
Normal 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;
|
||||
@@ -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;
|
||||
}
|
||||
560
bin/pac/tools/.playwright/package/lib/server/firefox/ffPage.js
Normal file
560
bin/pac/tools/.playwright/package/lib/server/firefox/ffPage.js
Normal 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');
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user