first commit

This commit is contained in:
2026-09-08 20:28:54 +08:00
commit 2ada8d3d5a
37380 changed files with 4886169 additions and 0 deletions
+295
View File
@@ -0,0 +1,295 @@
"use strict";
/**
* Copyright 2018 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Accessibility = void 0;
class Accessibility {
constructor(client) {
this._client = client;
}
async snapshot(options = {}) {
const { interestingOnly = true, root = null } = options;
const { nodes } = await this._client.send('Accessibility.getFullAXTree');
let backendNodeId = null;
if (root) {
const { node } = await this._client.send('DOM.describeNode', {
objectId: root._remoteObject.objectId,
});
backendNodeId = node.backendNodeId;
}
const defaultRoot = AXNode.createTree(nodes);
let needle = defaultRoot;
if (backendNodeId) {
needle = defaultRoot.find((node) => node.payload.backendDOMNodeId === backendNodeId);
if (!needle)
return null;
}
if (!interestingOnly)
return this.serializeTree(needle)[0];
const interestingNodes = new Set();
this.collectInterestingNodes(interestingNodes, defaultRoot, false);
if (!interestingNodes.has(needle))
return null;
return this.serializeTree(needle, interestingNodes)[0];
}
serializeTree(node, whitelistedNodes) {
const children = [];
for (const child of node.children)
children.push(...this.serializeTree(child, whitelistedNodes));
if (whitelistedNodes && !whitelistedNodes.has(node))
return children;
const serializedNode = node.serialize();
if (children.length)
serializedNode.children = children;
return [serializedNode];
}
collectInterestingNodes(collection, node, insideControl) {
if (node.isInteresting(insideControl))
collection.add(node);
if (node.isLeafNode())
return;
insideControl = insideControl || node.isControl();
for (const child of node.children)
this.collectInterestingNodes(collection, child, insideControl);
}
}
exports.Accessibility = Accessibility;
class AXNode {
constructor(payload) {
this.children = [];
this._richlyEditable = false;
this._editable = false;
this._focusable = false;
this._hidden = false;
this.payload = payload;
this._name = this.payload.name ? this.payload.name.value : '';
this._role = this.payload.role ? this.payload.role.value : 'Unknown';
for (const property of this.payload.properties || []) {
if (property.name === 'editable') {
this._richlyEditable = property.value.value === 'richtext';
this._editable = true;
}
if (property.name === 'focusable')
this._focusable = property.value.value;
if (property.name === 'hidden')
this._hidden = property.value.value;
}
}
_isPlainTextField() {
if (this._richlyEditable)
return false;
if (this._editable)
return true;
return (this._role === 'textbox' ||
this._role === 'ComboBox' ||
this._role === 'searchbox');
}
_isTextOnlyObject() {
const role = this._role;
return role === 'LineBreak' || role === 'text' || role === 'InlineTextBox';
}
_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;
}
find(predicate) {
if (predicate(this))
return this;
for (const child of this.children) {
const result = child.find(predicate);
if (result)
return result;
}
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 'doc-cover':
case 'graphics-symbol':
case 'img':
case 'Meter':
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._name)
return true;
if (this._role === 'heading' && this._name)
return true;
return false;
}
isControl() {
switch (this._role) {
case 'button':
case 'checkbox':
case 'ColorWell':
case 'combobox':
case 'DisclosureTriangle':
case 'listbox':
case 'menu':
case 'menubar':
case 'menuitem':
case 'menuitemcheckbox':
case 'menuitemradio':
case 'radio':
case 'scrollbar':
case 'searchbox':
case 'slider':
case 'spinbutton':
case 'switch':
case 'tab':
case 'textbox':
case 'tree':
return true;
default:
return false;
}
}
isInteresting(insideControl) {
const role = this._role;
if (role === 'Ignored' || this._hidden)
return false;
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;
}
serialize() {
const properties = new Map();
for (const property of this.payload.properties || [])
properties.set(property.name.toLowerCase(), property.value.value);
if (this.payload.name)
properties.set('name', this.payload.name.value);
if (this.payload.value)
properties.set('value', this.payload.value.value);
if (this.payload.description)
properties.set('description', this.payload.description.value);
const node = {
role: this._role,
};
const userStringProperties = [
'name',
'value',
'description',
'keyshortcuts',
'roledescription',
'valuetext',
];
const getUserStringPropertyValue = (key) => properties.get(key);
for (const userStringProperty of userStringProperties) {
if (!properties.has(userStringProperty))
continue;
node[userStringProperty] = getUserStringPropertyValue(userStringProperty);
}
const booleanProperties = [
'disabled',
'expanded',
'focused',
'modal',
'multiline',
'multiselectable',
'readonly',
'required',
'selected',
];
const getBooleanPropertyValue = (key) => properties.get(key);
for (const booleanProperty of booleanProperties) {
// WebArea's treat focus differently than other nodes. They report whether their frame has focus,
// not whether focus is specifically on the root node.
if (booleanProperty === 'focused' && this._role === 'WebArea')
continue;
const value = getBooleanPropertyValue(booleanProperty);
if (!value)
continue;
node[booleanProperty] = getBooleanPropertyValue(booleanProperty);
}
const tristateProperties = ['checked', 'pressed'];
for (const tristateProperty of tristateProperties) {
if (!properties.has(tristateProperty))
continue;
const value = properties.get(tristateProperty);
node[tristateProperty] =
value === 'mixed' ? 'mixed' : value === 'true' ? true : false;
}
const numericalProperties = [
'level',
'valuemax',
'valuemin',
];
const getNumericalPropertyValue = (key) => properties.get(key);
for (const numericalProperty of numericalProperties) {
if (!properties.has(numericalProperty))
continue;
node[numericalProperty] = getNumericalPropertyValue(numericalProperty);
}
const tokenProperties = [
'autocomplete',
'haspopup',
'invalid',
'orientation',
];
const getTokenPropertyValue = (key) => properties.get(key);
for (const tokenProperty of tokenProperties) {
const value = getTokenPropertyValue(tokenProperty);
if (!value || value === 'false')
continue;
node[tokenProperty] = getTokenPropertyValue(tokenProperty);
}
return node;
}
static createTree(payloads) {
const nodeById = new Map();
for (const payload of payloads)
nodeById.set(payload.nodeId, new AXNode(payload));
for (const node of nodeById.values()) {
for (const childId of node.payload.childIds || [])
node.children.push(nodeById.get(childId));
}
return nodeById.values().next().value;
}
}
+264
View File
@@ -0,0 +1,264 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowserContext = exports.Browser = void 0;
const helper_1 = require("./helper");
const Target_1 = require("./Target");
const EventEmitter = require("events");
const Events_1 = require("./Events");
class Browser extends EventEmitter {
constructor(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {
super();
this._ignoreHTTPSErrors = ignoreHTTPSErrors;
this._defaultViewport = defaultViewport;
this._process = process;
this._connection = connection;
this._closeCallback = closeCallback || function () { };
this._defaultContext = new BrowserContext(this._connection, this, null);
this._contexts = new Map();
for (const contextId of contextIds)
this._contexts.set(contextId, new BrowserContext(this._connection, this, contextId));
this._targets = new Map();
this._connection.on(Events_1.Events.Connection.Disconnected, () => this.emit(Events_1.Events.Browser.Disconnected));
this._connection.on('Target.targetCreated', this._targetCreated.bind(this));
this._connection.on('Target.targetDestroyed', this._targetDestroyed.bind(this));
this._connection.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this));
}
static async create(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {
const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback);
await connection.send('Target.setDiscoverTargets', { discover: true });
return browser;
}
process() {
return this._process;
}
async createIncognitoBrowserContext() {
const { browserContextId } = await this._connection.send('Target.createBrowserContext');
const context = new BrowserContext(this._connection, this, browserContextId);
this._contexts.set(browserContextId, context);
return context;
}
browserContexts() {
return [this._defaultContext, ...Array.from(this._contexts.values())];
}
defaultBrowserContext() {
return this._defaultContext;
}
/**
* @param {?string} contextId
*/
async _disposeContext(contextId) {
await this._connection.send('Target.disposeBrowserContext', {
browserContextId: contextId || undefined,
});
this._contexts.delete(contextId);
}
async _targetCreated(event) {
const targetInfo = event.targetInfo;
const { browserContextId } = targetInfo;
const context = browserContextId && this._contexts.has(browserContextId)
? this._contexts.get(browserContextId)
: this._defaultContext;
const target = new Target_1.Target(targetInfo, context, () => this._connection.createSession(targetInfo), this._ignoreHTTPSErrors, this._defaultViewport);
helper_1.assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
this._targets.set(event.targetInfo.targetId, target);
if (await target._initializedPromise) {
this.emit(Events_1.Events.Browser.TargetCreated, target);
context.emit(Events_1.Events.BrowserContext.TargetCreated, target);
}
}
/**
* @param {{targetId: string}} event
*/
async _targetDestroyed(event) {
const target = this._targets.get(event.targetId);
target._initializedCallback(false);
this._targets.delete(event.targetId);
target._closedCallback();
if (await target._initializedPromise) {
this.emit(Events_1.Events.Browser.TargetDestroyed, target);
target
.browserContext()
.emit(Events_1.Events.BrowserContext.TargetDestroyed, target);
}
}
/**
* @param {!Protocol.Target.targetInfoChangedPayload} event
*/
_targetInfoChanged(event) {
const target = this._targets.get(event.targetInfo.targetId);
helper_1.assert(target, 'target should exist before targetInfoChanged');
const previousURL = target.url();
const wasInitialized = target._isInitialized;
target._targetInfoChanged(event.targetInfo);
if (wasInitialized && previousURL !== target.url()) {
this.emit(Events_1.Events.Browser.TargetChanged, target);
target.browserContext().emit(Events_1.Events.BrowserContext.TargetChanged, target);
}
}
wsEndpoint() {
return this._connection.url();
}
async newPage() {
return this._defaultContext.newPage();
}
async _createPageInContext(contextId) {
const { targetId } = await this._connection.send('Target.createTarget', {
url: 'about:blank',
browserContextId: contextId || undefined,
});
const target = await this._targets.get(targetId);
helper_1.assert(await target._initializedPromise, 'Failed to create target for page');
const page = await target.page();
return page;
}
targets() {
return Array.from(this._targets.values()).filter((target) => target._isInitialized);
}
target() {
return this.targets().find((target) => target.type() === 'browser');
}
/**
* @param {function(!Target):boolean} predicate
* @param {{timeout?: number}=} options
* @return {!Promise<!Target>}
*/
async waitForTarget(predicate, options = {}) {
const { timeout = 30000 } = options;
const existingTarget = this.targets().find(predicate);
if (existingTarget)
return existingTarget;
let resolve;
const targetPromise = new Promise((x) => (resolve = x));
this.on(Events_1.Events.Browser.TargetCreated, check);
this.on(Events_1.Events.Browser.TargetChanged, check);
try {
if (!timeout)
return await targetPromise;
return await helper_1.helper.waitWithTimeout(targetPromise, 'target', timeout);
}
finally {
this.removeListener(Events_1.Events.Browser.TargetCreated, check);
this.removeListener(Events_1.Events.Browser.TargetChanged, check);
}
function check(target) {
if (predicate(target))
resolve(target);
}
}
async pages() {
const contextPages = await Promise.all(this.browserContexts().map((context) => context.pages()));
// Flatten array.
return contextPages.reduce((acc, x) => acc.concat(x), []);
}
async version() {
const version = await this._getVersion();
return version.product;
}
async userAgent() {
const version = await this._getVersion();
return version.userAgent;
}
async close() {
await this._closeCallback.call(null);
this.disconnect();
}
disconnect() {
this._connection.dispose();
}
isConnected() {
return !this._connection._closed;
}
_getVersion() {
return this._connection.send('Browser.getVersion');
}
}
exports.Browser = Browser;
class BrowserContext extends EventEmitter {
constructor(connection, browser, contextId) {
super();
this._connection = connection;
this._browser = browser;
this._id = contextId;
}
targets() {
return this._browser
.targets()
.filter((target) => target.browserContext() === this);
}
waitForTarget(predicate, options) {
return this._browser.waitForTarget((target) => target.browserContext() === this && predicate(target), options);
}
async pages() {
const pages = await Promise.all(this.targets()
.filter((target) => target.type() === 'page')
.map((target) => target.page()));
return pages.filter((page) => !!page);
}
isIncognito() {
return !!this._id;
}
async overridePermissions(origin, permissions) {
const webPermissionToProtocol = new Map([
['geolocation', 'geolocation'],
['midi', 'midi'],
['notifications', 'notifications'],
// TODO: push isn't a valid type?
// ['push', 'push'],
['camera', 'videoCapture'],
['microphone', 'audioCapture'],
['background-sync', 'backgroundSync'],
['ambient-light-sensor', 'sensors'],
['accelerometer', 'sensors'],
['gyroscope', 'sensors'],
['magnetometer', 'sensors'],
['accessibility-events', 'accessibilityEvents'],
['clipboard-read', 'clipboardReadWrite'],
['clipboard-write', 'clipboardReadWrite'],
['payment-handler', 'paymentHandler'],
// chrome-specific permissions we have.
['midi-sysex', 'midiSysex'],
]);
permissions = permissions.map((permission) => {
const protocolPermission = webPermissionToProtocol.get(permission);
if (!protocolPermission)
throw new Error('Unknown permission: ' + permission);
return protocolPermission;
});
await this._connection.send('Browser.grantPermissions', {
origin,
browserContextId: this._id || undefined,
permissions,
});
}
async clearPermissionOverrides() {
await this._connection.send('Browser.resetPermissions', {
browserContextId: this._id || undefined,
});
}
newPage() {
return this._browser._createPageInContext(this._id);
}
browser() {
return this._browser;
}
async close() {
helper_1.assert(this._id, 'Non-incognito profiles cannot be closed!');
await this._browser._disposeContext(this._id);
}
}
exports.BrowserContext = BrowserContext;
+406
View File
@@ -0,0 +1,406 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowserFetcher = void 0;
const os = require("os");
const fs = require("fs");
const path = require("path");
const util = require("util");
const childProcess = require("child_process");
const https = require("https");
const http = require("http");
const extractZip = require("extract-zip");
const debug = require("debug");
const removeRecursive = require("rimraf");
const URL = require("url");
const ProxyAgent = require("https-proxy-agent");
const proxy_from_env_1 = require("proxy-from-env");
const helper_1 = require("./helper");
const debugFetcher = debug(`puppeteer:fetcher`);
const downloadURLs = {
chrome: {
linux: '%s/chromium-browser-snapshots/Linux_x64/%d/%s.zip',
mac: '%s/chromium-browser-snapshots/Mac/%d/%s.zip',
win32: '%s/chromium-browser-snapshots/Win/%d/%s.zip',
win64: '%s/chromium-browser-snapshots/Win_x64/%d/%s.zip',
},
firefox: {
linux: '%s/firefox-%s.en-US.%s-x86_64.tar.bz2',
mac: '%s/firefox-%s.en-US.%s.dmg',
win32: '%s/firefox-%s.en-US.%s.zip',
win64: '%s/firefox-%s.en-US.%s.zip',
},
};
const browserConfig = {
chrome: {
host: 'https://storage.googleapis.com',
destination: '.local-chromium',
},
firefox: {
host: 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central',
destination: '.local-firefox',
},
};
function archiveName(product, platform, revision) {
if (product === 'chrome') {
if (platform === 'linux')
return 'chrome-linux';
if (platform === 'mac')
return 'chrome-mac';
if (platform === 'win32' || platform === 'win64') {
// Windows archive name changed at r591479.
return parseInt(revision, 10) > 591479 ? 'chrome-win' : 'chrome-win32';
}
}
else if (product === 'firefox') {
return platform;
}
}
/**
* @param {string} product
* @param {string} platform
* @param {string} host
* @param {string} revision
* @return {string}
*/
function downloadURL(product, platform, host, revision) {
const url = util.format(downloadURLs[product][platform], host, revision, archiveName(product, platform, revision));
return url;
}
const readdirAsync = helper_1.helper.promisify(fs.readdir.bind(fs));
const mkdirAsync = helper_1.helper.promisify(fs.mkdir.bind(fs));
const unlinkAsync = helper_1.helper.promisify(fs.unlink.bind(fs));
const chmodAsync = helper_1.helper.promisify(fs.chmod.bind(fs));
function existsAsync(filePath) {
return new Promise((resolve) => {
fs.access(filePath, (err) => resolve(!err));
});
}
/**
*/
class BrowserFetcher {
constructor(projectRoot, options = {}) {
this._product = (options.product || 'chrome').toLowerCase();
helper_1.assert(this._product === 'chrome' || this._product === 'firefox', `Unknown product: "${options.product}"`);
this._downloadsFolder =
options.path ||
path.join(projectRoot, browserConfig[this._product].destination);
this._downloadHost = options.host || browserConfig[this._product].host;
this.setPlatform(options.platform);
helper_1.assert(downloadURLs[this._product][this._platform], 'Unsupported platform: ' + this._platform);
}
setPlatform(platformFromOptions) {
if (platformFromOptions) {
this._platform = platformFromOptions;
return;
}
const platform = os.platform();
if (platform === 'darwin')
this._platform = 'mac';
else if (platform === 'linux')
this._platform = 'linux';
else if (platform === 'win32')
this._platform = os.arch() === 'x64' ? 'win64' : 'win32';
else
helper_1.assert(this._platform, 'Unsupported platform: ' + os.platform());
}
platform() {
return this._platform;
}
product() {
return this._product;
}
host() {
return this._downloadHost;
}
canDownload(revision) {
const url = downloadURL(this._product, this._platform, this._downloadHost, revision);
return new Promise((resolve) => {
const request = httpRequest(url, 'HEAD', (response) => {
resolve(response.statusCode === 200);
});
request.on('error', (error) => {
console.error(error);
resolve(false);
});
});
}
/**
* @param {string} revision
* @param {?function(number, number):void} progressCallback
* @return {!Promise<!BrowserFetcher.RevisionInfo>}
*/
async download(revision, progressCallback) {
const url = downloadURL(this._product, this._platform, this._downloadHost, revision);
const fileName = url.split('/').pop();
const archivePath = path.join(this._downloadsFolder, fileName);
const outputPath = this._getFolderPath(revision);
if (await existsAsync(outputPath))
return this.revisionInfo(revision);
if (!(await existsAsync(this._downloadsFolder)))
await mkdirAsync(this._downloadsFolder);
try {
await downloadFile(url, archivePath, progressCallback);
await install(archivePath, outputPath);
}
finally {
if (await existsAsync(archivePath))
await unlinkAsync(archivePath);
}
const revisionInfo = this.revisionInfo(revision);
if (revisionInfo)
await chmodAsync(revisionInfo.executablePath, 0o755);
return revisionInfo;
}
async localRevisions() {
if (!(await existsAsync(this._downloadsFolder)))
return [];
const fileNames = await readdirAsync(this._downloadsFolder);
return fileNames
.map((fileName) => parseFolderPath(this._product, fileName))
.filter((entry) => entry && entry.platform === this._platform)
.map((entry) => entry.revision);
}
async remove(revision) {
const folderPath = this._getFolderPath(revision);
helper_1.assert(await existsAsync(folderPath), `Failed to remove: revision ${revision} is not downloaded`);
await new Promise((fulfill) => removeRecursive(folderPath, fulfill));
}
revisionInfo(revision) {
const folderPath = this._getFolderPath(revision);
let executablePath = '';
if (this._product === 'chrome') {
if (this._platform === 'mac')
executablePath = path.join(folderPath, archiveName(this._product, this._platform, revision), 'Chromium.app', 'Contents', 'MacOS', 'Chromium');
else if (this._platform === 'linux')
executablePath = path.join(folderPath, archiveName(this._product, this._platform, revision), 'chrome');
else if (this._platform === 'win32' || this._platform === 'win64')
executablePath = path.join(folderPath, archiveName(this._product, this._platform, revision), 'chrome.exe');
else
throw new Error('Unsupported platform: ' + this._platform);
}
else if (this._product === 'firefox') {
if (this._platform === 'mac')
executablePath = path.join(folderPath, 'Firefox Nightly.app', 'Contents', 'MacOS', 'firefox');
else if (this._platform === 'linux')
executablePath = path.join(folderPath, 'firefox', 'firefox');
else if (this._platform === 'win32' || this._platform === 'win64')
executablePath = path.join(folderPath, 'firefox', 'firefox.exe');
else
throw new Error('Unsupported platform: ' + this._platform);
}
else {
throw new Error('Unsupported product: ' + this._product);
}
const url = downloadURL(this._product, this._platform, this._downloadHost, revision);
const local = fs.existsSync(folderPath);
debugFetcher({
revision,
executablePath,
folderPath,
local,
url,
product: this._product,
});
return {
revision,
executablePath,
folderPath,
local,
url,
product: this._product,
};
}
/**
* @param {string} revision
* @return {string}
*/
_getFolderPath(revision) {
return path.join(this._downloadsFolder, this._platform + '-' + revision);
}
}
exports.BrowserFetcher = BrowserFetcher;
function parseFolderPath(product, folderPath) {
const name = path.basename(folderPath);
const splits = name.split('-');
if (splits.length !== 2)
return null;
const [platform, revision] = splits;
if (!downloadURLs[product][platform])
return null;
return { product, platform, revision };
}
/**
* @param {string} url
* @param {string} destinationPath
* @param {?function(number, number):void} progressCallback
* @return {!Promise}
*/
function downloadFile(url, destinationPath, progressCallback) {
debugFetcher(`Downloading binary from ${url}`);
let fulfill, reject;
let downloadedBytes = 0;
let totalBytes = 0;
const promise = new Promise((x, y) => {
fulfill = x;
reject = y;
});
const request = httpRequest(url, 'GET', (response) => {
if (response.statusCode !== 200) {
const error = new Error(`Download failed: server returned code ${response.statusCode}. URL: ${url}`);
// consume response data to free up memory
response.resume();
reject(error);
return;
}
const file = fs.createWriteStream(destinationPath);
file.on('finish', () => fulfill());
file.on('error', (error) => reject(error));
response.pipe(file);
totalBytes = parseInt(
/** @type {string} */ response.headers['content-length'], 10);
if (progressCallback)
response.on('data', onData);
});
request.on('error', (error) => reject(error));
return promise;
function onData(chunk) {
downloadedBytes += chunk.length;
progressCallback(downloadedBytes, totalBytes);
}
}
function install(archivePath, folderPath) {
debugFetcher(`Installing ${archivePath} to ${folderPath}`);
if (archivePath.endsWith('.zip'))
return extractZip(archivePath, { dir: folderPath });
else if (archivePath.endsWith('.tar.bz2'))
return extractTar(archivePath, folderPath);
else if (archivePath.endsWith('.dmg'))
return mkdirAsync(folderPath).then(() => installDMG(archivePath, folderPath));
else
throw new Error(`Unsupported archive format: ${archivePath}`);
}
/**
* @param {string} tarPath
* @param {string} folderPath
* @return {!Promise<?Error>}
*/
function extractTar(tarPath, folderPath) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const tar = require('tar-fs');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const bzip = require('unbzip2-stream');
return new Promise((fulfill, reject) => {
const tarStream = tar.extract(folderPath);
tarStream.on('error', reject);
tarStream.on('finish', fulfill);
const readStream = fs.createReadStream(tarPath);
readStream.on('data', () => {
process.stdout.write('\rExtracting...');
});
readStream.pipe(bzip()).pipe(tarStream);
});
}
/**
* Install *.app directory from dmg file
*
* @param {string} dmgPath
* @param {string} folderPath
* @return {!Promise<?Error>}
*/
function installDMG(dmgPath, folderPath) {
let mountPath;
function mountAndCopy(fulfill, reject) {
const mountCommand = `hdiutil attach -nobrowse -noautoopen "${dmgPath}"`;
childProcess.exec(mountCommand, (err, stdout) => {
if (err)
return reject(err);
const volumes = stdout.match(/\/Volumes\/(.*)/m);
if (!volumes)
return reject(new Error(`Could not find volume path in ${stdout}`));
mountPath = volumes[0];
readdirAsync(mountPath)
.then((fileNames) => {
const appName = fileNames.filter((item) => typeof item === 'string' && item.endsWith('.app'))[0];
if (!appName)
return reject(new Error(`Cannot find app in ${mountPath}`));
const copyPath = path.join(mountPath, appName);
debugFetcher(`Copying ${copyPath} to ${folderPath}`);
childProcess.exec(`cp -R "${copyPath}" "${folderPath}"`, (err) => {
if (err)
reject(err);
else
fulfill();
});
})
.catch(reject);
});
}
function unmount() {
if (!mountPath)
return;
const unmountCommand = `hdiutil detach "${mountPath}" -quiet`;
debugFetcher(`Unmounting ${mountPath}`);
childProcess.exec(unmountCommand, (err) => {
if (err)
console.error(`Error unmounting dmg: ${err}`);
});
}
return new Promise(mountAndCopy)
.catch((error) => {
console.error(error);
})
.finally(unmount);
}
function httpRequest(url, method, response) {
const urlParsed = URL.parse(url);
let options = {
...urlParsed,
method,
};
const proxyURL = proxy_from_env_1.getProxyForUrl(url);
if (proxyURL) {
if (url.startsWith('http:')) {
const proxy = URL.parse(proxyURL);
options = {
path: options.href,
host: proxy.hostname,
port: proxy.port,
};
}
else {
const parsedProxyURL = URL.parse(proxyURL);
const proxyOptions = {
...parsedProxyURL,
secureProxy: parsedProxyURL.protocol === 'https:',
};
options.agent = new ProxyAgent(proxyOptions);
options.rejectUnauthorized = false;
}
}
const requestCallback = (res) => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location)
httpRequest(res.headers.location, method, response);
else
response(res);
};
const request = options.protocol === 'https:'
? https.request(options, requestCallback)
: http.request(options, requestCallback);
request.end();
return request;
}
+205
View File
@@ -0,0 +1,205 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CDPSession = exports.Connection = void 0;
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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 helper_1 = require("./helper");
const Events_1 = require("./Events");
const debug = require("debug");
const debugProtocol = debug('puppeteer:protocol');
const EventEmitter = require("events");
class Connection extends EventEmitter {
constructor(url, transport, delay = 0) {
super();
this._lastId = 0;
this._sessions = new Map();
this._closed = false;
this._callbacks = new Map();
this._url = url;
this._delay = delay;
this._transport = transport;
this._transport.onmessage = this._onMessage.bind(this);
this._transport.onclose = this._onClose.bind(this);
}
static fromSession(session) {
return session._connection;
}
/**
* @param {string} sessionId
* @return {?CDPSession}
*/
session(sessionId) {
return this._sessions.get(sessionId) || null;
}
url() {
return this._url;
}
send(method, params) {
const id = this._rawSend({ method, params });
return new Promise((resolve, reject) => {
this._callbacks.set(id, { resolve, reject, error: new Error(), method });
});
}
_rawSend(message) {
const id = ++this._lastId;
message = JSON.stringify(Object.assign({}, message, { id }));
debugProtocol('SEND ► ' + message);
this._transport.send(message);
return id;
}
async _onMessage(message) {
if (this._delay)
await new Promise((f) => setTimeout(f, this._delay));
debugProtocol('◀ RECV ' + message);
const object = JSON.parse(message);
if (object.method === 'Target.attachedToTarget') {
const sessionId = object.params.sessionId;
const session = new CDPSession(this, object.params.targetInfo.type, sessionId);
this._sessions.set(sessionId, session);
}
else if (object.method === 'Target.detachedFromTarget') {
const session = this._sessions.get(object.params.sessionId);
if (session) {
session._onClosed();
this._sessions.delete(object.params.sessionId);
}
}
if (object.sessionId) {
const session = this._sessions.get(object.sessionId);
if (session)
session._onMessage(object);
}
else if (object.id) {
const callback = this._callbacks.get(object.id);
// Callbacks could be all rejected if someone has called `.dispose()`.
if (callback) {
this._callbacks.delete(object.id);
if (object.error)
callback.reject(createProtocolError(callback.error, callback.method, object));
else
callback.resolve(object.result);
}
}
else {
this.emit(object.method, object.params);
}
}
_onClose() {
if (this._closed)
return;
this._closed = true;
this._transport.onmessage = null;
this._transport.onclose = null;
for (const callback of this._callbacks.values())
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
this._callbacks.clear();
for (const session of this._sessions.values())
session._onClosed();
this._sessions.clear();
this.emit(Events_1.Events.Connection.Disconnected);
}
dispose() {
this._onClose();
this._transport.close();
}
/**
* @param {Protocol.Target.TargetInfo} targetInfo
* @return {!Promise<!CDPSession>}
*/
async createSession(targetInfo) {
const { sessionId } = await this.send('Target.attachToTarget', {
targetId: targetInfo.targetId,
flatten: true,
});
return this._sessions.get(sessionId);
}
}
exports.Connection = Connection;
class CDPSession extends EventEmitter {
constructor(connection, targetType, sessionId) {
super();
this._callbacks = new Map();
this._connection = connection;
this._targetType = targetType;
this._sessionId = sessionId;
}
send(method, params) {
if (!this._connection)
return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`));
const id = this._connection._rawSend({
sessionId: this._sessionId,
method,
/* TODO(jacktfranklin@): once this Firefox bug is solved
* we no longer need the `|| {}` check
* https://bugzilla.mozilla.org/show_bug.cgi?id=1631570
*/
params: params || {},
});
return new Promise((resolve, reject) => {
this._callbacks.set(id, { resolve, reject, error: new Error(), method });
});
}
_onMessage(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));
else
callback.resolve(object.result);
}
else {
helper_1.assert(!object.id);
this.emit(object.method, object.params);
}
}
async detach() {
if (!this._connection)
throw new Error(`Session already detached. Most likely the ${this._targetType} has been closed.`);
await this._connection.send('Target.detachFromTarget', {
sessionId: this._sessionId,
});
}
_onClosed() {
for (const callback of this._callbacks.values())
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
this._callbacks.clear();
this._connection = null;
this.emit(Events_1.Events.CDPSession.Disconnected);
}
}
exports.CDPSession = CDPSession;
/**
* @param {!Error} error
* @param {string} method
* @param {{error: {message: string, data: any}}} object
* @return {!Error}
*/
function createProtocolError(error, method, object) {
let message = `Protocol error (${method}): ${object.error.message}`;
if ('data' in object.error)
message += ` ${object.error.data}`;
return rewriteError(error, message);
}
/**
* @param {!Error} error
* @param {string} message
* @return {!Error}
*/
function rewriteError(error, message) {
error.message = message;
return error;
}
+17
View File
@@ -0,0 +1,17 @@
"use strict";
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
+39
View File
@@ -0,0 +1,39 @@
"use strict";
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConsoleMessage = void 0;
class ConsoleMessage {
constructor(type, text, args, location = {}) {
this._type = type;
this._text = text;
this._args = args;
this._location = location;
}
type() {
return this._type;
}
text() {
return this._text;
}
args() {
return this._args;
}
location() {
return this._location;
}
}
exports.ConsoleMessage = ConsoleMessage;
+252
View File
@@ -0,0 +1,252 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Coverage = void 0;
const helper_1 = require("./helper");
const ExecutionContext_1 = require("./ExecutionContext");
class Coverage {
constructor(client) {
this._jsCoverage = new JSCoverage(client);
this._cssCoverage = new CSSCoverage(client);
}
async startJSCoverage(options) {
return await this._jsCoverage.start(options);
}
async stopJSCoverage() {
return await this._jsCoverage.stop();
}
async startCSSCoverage(options) {
return await this._cssCoverage.start(options);
}
async stopCSSCoverage() {
return await this._cssCoverage.stop();
}
}
exports.Coverage = Coverage;
class JSCoverage {
constructor(client) {
this._enabled = false;
this._scriptURLs = new Map();
this._scriptSources = new Map();
this._eventListeners = [];
this._resetOnNavigation = false;
this._reportAnonymousScripts = false;
this._client = client;
}
async start(options = {}) {
helper_1.assert(!this._enabled, 'JSCoverage is already enabled');
const { resetOnNavigation = true, reportAnonymousScripts = false, } = options;
this._resetOnNavigation = resetOnNavigation;
this._reportAnonymousScripts = reportAnonymousScripts;
this._enabled = true;
this._scriptURLs.clear();
this._scriptSources.clear();
this._eventListeners = [
helper_1.helper.addEventListener(this._client, 'Debugger.scriptParsed', this._onScriptParsed.bind(this)),
helper_1.helper.addEventListener(this._client, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)),
];
await Promise.all([
this._client.send('Profiler.enable'),
this._client.send('Profiler.startPreciseCoverage', {
callCount: false,
detailed: true,
}),
this._client.send('Debugger.enable'),
this._client.send('Debugger.setSkipAllPauses', { skip: true }),
]);
}
_onExecutionContextsCleared() {
if (!this._resetOnNavigation)
return;
this._scriptURLs.clear();
this._scriptSources.clear();
}
async _onScriptParsed(event) {
// Ignore puppeteer-injected scripts
if (event.url === ExecutionContext_1.EVALUATION_SCRIPT_URL)
return;
// Ignore other anonymous scripts unless the reportAnonymousScripts option is true.
if (!event.url && !this._reportAnonymousScripts)
return;
try {
const response = await this._client.send('Debugger.getScriptSource', {
scriptId: event.scriptId,
});
this._scriptURLs.set(event.scriptId, event.url);
this._scriptSources.set(event.scriptId, response.scriptSource);
}
catch (error) {
// This might happen if the page has already navigated away.
helper_1.debugError(error);
}
}
async stop() {
helper_1.assert(this._enabled, 'JSCoverage is not enabled');
this._enabled = false;
const result = await Promise.all([
this._client.send('Profiler.takePreciseCoverage'),
this._client.send('Profiler.stopPreciseCoverage'),
this._client.send('Profiler.disable'),
this._client.send('Debugger.disable'),
]);
helper_1.helper.removeEventListeners(this._eventListeners);
const coverage = [];
const profileResponse = result[0];
for (const entry of profileResponse.result) {
let url = this._scriptURLs.get(entry.scriptId);
if (!url && this._reportAnonymousScripts)
url = 'debugger://VM' + entry.scriptId;
const text = this._scriptSources.get(entry.scriptId);
if (text === undefined || url === undefined)
continue;
const flattenRanges = [];
for (const func of entry.functions)
flattenRanges.push(...func.ranges);
const ranges = convertToDisjointRanges(flattenRanges);
coverage.push({ url, ranges, text });
}
return coverage;
}
}
class CSSCoverage {
constructor(client) {
this._enabled = false;
this._stylesheetURLs = new Map();
this._stylesheetSources = new Map();
this._eventListeners = [];
this._resetOnNavigation = false;
this._reportAnonymousScripts = false;
this._client = client;
}
async start(options = {}) {
helper_1.assert(!this._enabled, 'CSSCoverage is already enabled');
const { resetOnNavigation = true } = options;
this._resetOnNavigation = resetOnNavigation;
this._enabled = true;
this._stylesheetURLs.clear();
this._stylesheetSources.clear();
this._eventListeners = [
helper_1.helper.addEventListener(this._client, 'CSS.styleSheetAdded', this._onStyleSheet.bind(this)),
helper_1.helper.addEventListener(this._client, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)),
];
await Promise.all([
this._client.send('DOM.enable'),
this._client.send('CSS.enable'),
this._client.send('CSS.startRuleUsageTracking'),
]);
}
_onExecutionContextsCleared() {
if (!this._resetOnNavigation)
return;
this._stylesheetURLs.clear();
this._stylesheetSources.clear();
}
async _onStyleSheet(event) {
const header = event.header;
// Ignore anonymous scripts
if (!header.sourceURL)
return;
try {
const response = await this._client.send('CSS.getStyleSheetText', {
styleSheetId: header.styleSheetId,
});
this._stylesheetURLs.set(header.styleSheetId, header.sourceURL);
this._stylesheetSources.set(header.styleSheetId, response.text);
}
catch (error) {
// This might happen if the page has already navigated away.
helper_1.debugError(error);
}
}
async stop() {
helper_1.assert(this._enabled, 'CSSCoverage is not enabled');
this._enabled = false;
const ruleTrackingResponse = await this._client.send('CSS.stopRuleUsageTracking');
await Promise.all([
this._client.send('CSS.disable'),
this._client.send('DOM.disable'),
]);
helper_1.helper.removeEventListeners(this._eventListeners);
// aggregate by styleSheetId
const styleSheetIdToCoverage = new Map();
for (const entry of ruleTrackingResponse.ruleUsage) {
let ranges = styleSheetIdToCoverage.get(entry.styleSheetId);
if (!ranges) {
ranges = [];
styleSheetIdToCoverage.set(entry.styleSheetId, ranges);
}
ranges.push({
startOffset: entry.startOffset,
endOffset: entry.endOffset,
count: entry.used ? 1 : 0,
});
}
const coverage = [];
for (const styleSheetId of this._stylesheetURLs.keys()) {
const url = this._stylesheetURLs.get(styleSheetId);
const text = this._stylesheetSources.get(styleSheetId);
const ranges = convertToDisjointRanges(styleSheetIdToCoverage.get(styleSheetId) || []);
coverage.push({ url, ranges, text });
}
return coverage;
}
}
function convertToDisjointRanges(nestedRanges) {
const points = [];
for (const range of nestedRanges) {
points.push({ offset: range.startOffset, type: 0, range });
points.push({ offset: range.endOffset, type: 1, range });
}
// Sort points to form a valid parenthesis sequence.
points.sort((a, b) => {
// Sort with increasing offsets.
if (a.offset !== b.offset)
return a.offset - b.offset;
// All "end" points should go before "start" points.
if (a.type !== b.type)
return b.type - a.type;
const aLength = a.range.endOffset - a.range.startOffset;
const bLength = b.range.endOffset - b.range.startOffset;
// For two "start" points, the one with longer range goes first.
if (a.type === 0)
return bLength - aLength;
// For two "end" points, the one with shorter range goes first.
return aLength - bLength;
});
const hitCountStack = [];
const results = [];
let lastOffset = 0;
// Run scanning line to intersect all ranges.
for (const point of points) {
if (hitCountStack.length &&
lastOffset < point.offset &&
hitCountStack[hitCountStack.length - 1] > 0) {
const lastResult = results.length ? results[results.length - 1] : null;
if (lastResult && lastResult.end === lastOffset)
lastResult.end = point.offset;
else
results.push({ start: lastOffset, end: point.offset });
}
lastOffset = point.offset;
if (point.type === 0)
hitCountStack.push(point.range.count);
else
hitCountStack.pop();
}
// Filter out empty ranges.
return results.filter((range) => range.end - range.start > 1);
}
+514
View File
@@ -0,0 +1,514 @@
"use strict";
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DOMWorld = void 0;
const fs = require("fs");
const helper_1 = require("./helper");
const LifecycleWatcher_1 = require("./LifecycleWatcher");
const Errors_1 = require("./Errors");
const QueryHandler_1 = require("./QueryHandler");
const readFileAsync = helper_1.helper.promisify(fs.readFile);
class DOMWorld {
constructor(frameManager, frame, timeoutSettings) {
this._documentPromise = null;
this._contextPromise = null;
this._contextResolveCallback = null;
this._detached = false;
this._waitTasks = new Set();
this._frameManager = frameManager;
this._frame = frame;
this._timeoutSettings = timeoutSettings;
this._setContext(null);
}
frame() {
return this._frame;
}
/**
* @param {?ExecutionContext} context
*/
_setContext(context) {
if (context) {
this._contextResolveCallback.call(null, context);
this._contextResolveCallback = null;
for (const waitTask of this._waitTasks)
waitTask.rerun();
}
else {
this._documentPromise = null;
this._contextPromise = new Promise((fulfill) => {
this._contextResolveCallback = fulfill;
});
}
}
_hasContext() {
return !this._contextResolveCallback;
}
_detach() {
this._detached = true;
for (const waitTask of this._waitTasks)
waitTask.terminate(new Error('waitForFunction failed: frame got detached.'));
}
/**
* @return {!Promise<!ExecutionContext>}
*/
executionContext() {
if (this._detached)
throw new Error(`Execution Context is not available in detached frame "${this._frame.url()}" (are you trying to evaluate?)`);
return this._contextPromise;
}
/**
* @param {Function|string} pageFunction
* @param {!Array<*>} args
* @return {!Promise<!JSHandle>}
*/
async evaluateHandle(pageFunction, ...args) {
const context = await this.executionContext();
return context.evaluateHandle(pageFunction, ...args);
}
/**
* @param {Function|string} pageFunction
* @param {!Array<*>} args
* @return {!Promise<*>}
*/
async evaluate(pageFunction, ...args) {
const context = await this.executionContext();
return context.evaluate(pageFunction, ...args);
}
/**
* @param {string} selector
* @return {!Promise<?ElementHandle>}
*/
async $(selector) {
const document = await this._document();
const value = await document.$(selector);
return value;
}
async _document() {
if (this._documentPromise)
return this._documentPromise;
this._documentPromise = this.executionContext().then(async (context) => {
const document = await context.evaluateHandle('document');
return document.asElement();
});
return this._documentPromise;
}
async $x(expression) {
const document = await this._document();
const value = await document.$x(expression);
return value;
}
async $eval(selector, pageFunction, ...args) {
const document = await this._document();
return document.$eval(selector, pageFunction, ...args);
}
async $$eval(selector, pageFunction, ...args) {
const document = await this._document();
const value = await document.$$eval(selector, pageFunction, ...args);
return value;
}
/**
* @param {string} selector
* @return {!Promise<!Array<!ElementHandle>>}
*/
async $$(selector) {
const document = await this._document();
const value = await document.$$(selector);
return value;
}
async content() {
return await this.evaluate(() => {
let retVal = '';
if (document.doctype)
retVal = new XMLSerializer().serializeToString(document.doctype);
if (document.documentElement)
retVal += document.documentElement.outerHTML;
return retVal;
});
}
async setContent(html, options = {}) {
const { waitUntil = ['load'], timeout = this._timeoutSettings.navigationTimeout(), } = options;
// We rely upon the fact that document.open() will reset frame lifecycle with "init"
// lifecycle event. @see https://crrev.com/608658
await this.evaluate((html) => {
document.open();
document.write(html);
document.close();
}, html);
const watcher = new LifecycleWatcher_1.LifecycleWatcher(this._frameManager, this._frame, waitUntil, timeout);
const error = await Promise.race([
watcher.timeoutOrTerminationPromise(),
watcher.lifecyclePromise(),
]);
watcher.dispose();
if (error)
throw error;
}
/**
* @param {!{url?: string, path?: string, content?: string, type?: string}} options
* @return {!Promise<!ElementHandle>}
*/
async addScriptTag(options) {
const { url = null, path = null, content = null, type = '' } = options;
if (url !== null) {
try {
const context = await this.executionContext();
return (await context.evaluateHandle(addScriptUrl, url, type)).asElement();
}
catch (error) {
throw new Error(`Loading script from ${url} failed`);
}
}
if (path !== null) {
let contents = await readFileAsync(path, 'utf8');
contents += '//# sourceURL=' + path.replace(/\n/g, '');
const context = await this.executionContext();
return (await context.evaluateHandle(addScriptContent, contents, type)).asElement();
}
if (content !== null) {
const context = await this.executionContext();
return (await context.evaluateHandle(addScriptContent, content, type)).asElement();
}
throw new Error('Provide an object with a `url`, `path` or `content` property');
async function addScriptUrl(url, type) {
const script = document.createElement('script');
script.src = url;
if (type)
script.type = type;
const promise = new Promise((res, rej) => {
script.onload = res;
script.onerror = rej;
});
document.head.appendChild(script);
await promise;
return script;
}
function addScriptContent(content, type = 'text/javascript') {
const script = document.createElement('script');
script.type = type;
script.text = content;
let error = null;
script.onerror = (e) => (error = e);
document.head.appendChild(script);
if (error)
throw error;
return script;
}
}
async addStyleTag(options) {
const { url = null, path = null, content = null } = options;
if (url !== null) {
try {
const context = await this.executionContext();
return (await context.evaluateHandle(addStyleUrl, url)).asElement();
}
catch (error) {
throw new Error(`Loading style from ${url} failed`);
}
}
if (path !== null) {
let contents = await readFileAsync(path, 'utf8');
contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
const context = await this.executionContext();
return (await context.evaluateHandle(addStyleContent, contents)).asElement();
}
if (content !== null) {
const context = await this.executionContext();
return (await context.evaluateHandle(addStyleContent, content)).asElement();
}
throw new Error('Provide an object with a `url`, `path` or `content` property');
async function addStyleUrl(url) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
const promise = new Promise((res, rej) => {
link.onload = res;
link.onerror = rej;
});
document.head.appendChild(link);
await promise;
return link;
}
async function addStyleContent(content) {
const style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(content));
const promise = new Promise((res, rej) => {
style.onload = res;
style.onerror = rej;
});
document.head.appendChild(style);
await promise;
return style;
}
}
async click(selector, options) {
const handle = await this.$(selector);
helper_1.assert(handle, 'No node found for selector: ' + selector);
await handle.click(options);
await handle.dispose();
}
async focus(selector) {
const handle = await this.$(selector);
helper_1.assert(handle, 'No node found for selector: ' + selector);
await handle.focus();
await handle.dispose();
}
async hover(selector) {
const handle = await this.$(selector);
helper_1.assert(handle, 'No node found for selector: ' + selector);
await handle.hover();
await handle.dispose();
}
async select(selector, ...values) {
const handle = await this.$(selector);
helper_1.assert(handle, 'No node found for selector: ' + selector);
const result = await handle.select(...values);
await handle.dispose();
return result;
}
async tap(selector) {
const handle = await this.$(selector);
helper_1.assert(handle, 'No node found for selector: ' + selector);
await handle.tap();
await handle.dispose();
}
async type(selector, text, options) {
const handle = await this.$(selector);
helper_1.assert(handle, 'No node found for selector: ' + selector);
await handle.type(text, options);
await handle.dispose();
}
waitForSelector(selector, options) {
return this._waitForSelectorOrXPath(selector, false, options);
}
waitForXPath(xpath, options) {
return this._waitForSelectorOrXPath(xpath, true, options);
}
waitForFunction(pageFunction, options = {}, ...args) {
const { polling = 'raf', timeout = this._timeoutSettings.timeout(), } = options;
return new WaitTask(this, pageFunction, undefined, 'function', polling, timeout, ...args).promise;
}
async title() {
return this.evaluate(() => document.title);
}
async _waitForSelectorOrXPath(selectorOrXPath, isXPath, options = {}) {
const { visible: waitForVisible = false, hidden: waitForHidden = false, timeout = this._timeoutSettings.timeout(), } = options;
const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation';
const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`;
const { updatedSelector, queryHandler, } = QueryHandler_1.getQueryHandlerAndSelector(selectorOrXPath, (element, selector) => document.querySelector(selector));
const waitTask = new WaitTask(this, predicate, queryHandler, title, polling, timeout, updatedSelector, isXPath, waitForVisible, waitForHidden);
const handle = await waitTask.promise;
if (!handle.asElement()) {
await handle.dispose();
return null;
}
return handle.asElement();
/**
* @param {string} selectorOrXPath
* @param {boolean} isXPath
* @param {boolean} waitForVisible
* @param {boolean} waitForHidden
* @return {?Node|boolean}
*/
function predicate(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {
const node = isXPath
? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
: predicateQueryHandler
? predicateQueryHandler(document, selectorOrXPath)
: document.querySelector(selectorOrXPath);
if (!node)
return waitForHidden;
if (!waitForVisible && !waitForHidden)
return node;
const element = node.nodeType === Node.TEXT_NODE
? node.parentElement
: node;
const style = window.getComputedStyle(element);
const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
const success = waitForVisible === isVisible || waitForHidden === !isVisible;
return success ? node : null;
function hasVisibleBoundingBox() {
const rect = element.getBoundingClientRect();
return !!(rect.top || rect.bottom || rect.width || rect.height);
}
}
}
}
exports.DOMWorld = DOMWorld;
class WaitTask {
constructor(domWorld, predicateBody, predicateQueryHandlerBody, title, polling, timeout, ...args) {
this._runCount = 0;
this._terminated = false;
if (helper_1.helper.isString(polling))
helper_1.assert(polling === 'raf' || polling === 'mutation', 'Unknown polling option: ' + polling);
else if (helper_1.helper.isNumber(polling))
helper_1.assert(polling > 0, 'Cannot poll with non-positive interval: ' + polling);
else
throw new Error('Unknown polling options: ' + polling);
function getPredicateBody(predicateBody, predicateQueryHandlerBody) {
if (helper_1.helper.isString(predicateBody))
return `return (${predicateBody});`;
if (predicateQueryHandlerBody) {
return `
return (function wrapper(args) {
const predicateQueryHandler = ${predicateQueryHandlerBody};
return (${predicateBody})(...args);
})(args);`;
}
return `return (${predicateBody})(...args);`;
}
this._domWorld = domWorld;
this._polling = polling;
this._timeout = timeout;
this._predicateBody = getPredicateBody(predicateBody, predicateQueryHandlerBody);
this._args = args;
this._runCount = 0;
domWorld._waitTasks.add(this);
this.promise = new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
// Since page navigation requires us to re-install the pageScript, we should track
// timeout on our end.
if (timeout) {
const timeoutError = new Errors_1.TimeoutError(`waiting for ${title} failed: timeout ${timeout}ms exceeded`);
this._timeoutTimer = setTimeout(() => this.terminate(timeoutError), timeout);
}
this.rerun();
}
terminate(error) {
this._terminated = true;
this._reject(error);
this._cleanup();
}
async rerun() {
const runCount = ++this._runCount;
/** @type {?JSHandle} */
let success = null;
let error = null;
try {
success = await (await this._domWorld.executionContext()).evaluateHandle(waitForPredicatePageFunction, this._predicateBody, this._polling, this._timeout, ...this._args);
}
catch (error_) {
error = error_;
}
if (this._terminated || runCount !== this._runCount) {
if (success)
await success.dispose();
return;
}
// Ignore timeouts in pageScript - we track timeouts ourselves.
// If the frame's execution context has already changed, `frame.evaluate` will
// throw an error - ignore this predicate run altogether.
if (!error &&
(await this._domWorld.evaluate((s) => !s, success).catch(() => true))) {
await success.dispose();
return;
}
// When the page is navigated, the promise is rejected.
// We will try again in the new execution context.
if (error && error.message.includes('Execution context was destroyed'))
return;
// We could have tried to evaluate in a context which was already
// destroyed.
if (error &&
error.message.includes('Cannot find context with specified id'))
return;
if (error)
this._reject(error);
else
this._resolve(success);
this._cleanup();
}
_cleanup() {
clearTimeout(this._timeoutTimer);
this._domWorld._waitTasks.delete(this);
}
}
async function waitForPredicatePageFunction(predicateBody, polling, timeout, ...args) {
const predicate = new Function('...args', predicateBody);
let timedOut = false;
if (timeout)
setTimeout(() => (timedOut = true), timeout);
if (polling === 'raf')
return await pollRaf();
if (polling === 'mutation')
return await pollMutation();
if (typeof polling === 'number')
return await pollInterval(polling);
/**
* @return {!Promise<*>}
*/
async function pollMutation() {
const success = await predicate(...args);
if (success)
return Promise.resolve(success);
let fulfill;
const result = new Promise((x) => (fulfill = x));
const observer = new MutationObserver(async () => {
if (timedOut) {
observer.disconnect();
fulfill();
}
const success = await predicate(...args);
if (success) {
observer.disconnect();
fulfill(success);
}
});
observer.observe(document, {
childList: true,
subtree: true,
attributes: true,
});
return result;
}
async function pollRaf() {
let fulfill;
const result = new Promise((x) => (fulfill = x));
await onRaf();
return result;
async function onRaf() {
if (timedOut) {
fulfill();
return;
}
const success = await predicate(...args);
if (success)
fulfill(success);
else
requestAnimationFrame(onRaf);
}
}
async function pollInterval(pollInterval) {
let fulfill;
const result = new Promise((x) => (fulfill = x));
await onTimeout();
return result;
async function onTimeout() {
if (timedOut) {
fulfill();
return;
}
const success = await predicate(...args);
if (success)
fulfill(success);
else
setTimeout(onTimeout, pollInterval);
}
}
}
+876
View File
@@ -0,0 +1,876 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.devicesMap = void 0;
const devices = [
{
name: 'Blackberry PlayBook',
userAgent: 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+',
viewport: {
width: 600,
height: 1024,
deviceScaleFactor: 1,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Blackberry PlayBook landscape',
userAgent: 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+',
viewport: {
width: 1024,
height: 600,
deviceScaleFactor: 1,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'BlackBerry Z30',
userAgent: 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+',
viewport: {
width: 360,
height: 640,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'BlackBerry Z30 landscape',
userAgent: 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+',
viewport: {
width: 640,
height: 360,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Galaxy Note 3',
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
viewport: {
width: 360,
height: 640,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Galaxy Note 3 landscape',
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
viewport: {
width: 640,
height: 360,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Galaxy Note II',
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
viewport: {
width: 360,
height: 640,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Galaxy Note II landscape',
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
viewport: {
width: 640,
height: 360,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Galaxy S III',
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
viewport: {
width: 360,
height: 640,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Galaxy S III landscape',
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
viewport: {
width: 640,
height: 360,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Galaxy S5',
userAgent: 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 360,
height: 640,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Galaxy S5 landscape',
userAgent: 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 640,
height: 360,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'iPad',
userAgent: 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
viewport: {
width: 768,
height: 1024,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'iPad landscape',
userAgent: 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
viewport: {
width: 1024,
height: 768,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'iPad Mini',
userAgent: 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
viewport: {
width: 768,
height: 1024,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'iPad Mini landscape',
userAgent: 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
viewport: {
width: 1024,
height: 768,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'iPad Pro',
userAgent: 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
viewport: {
width: 1024,
height: 1366,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'iPad Pro landscape',
userAgent: 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
viewport: {
width: 1366,
height: 1024,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'iPhone 4',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53',
viewport: {
width: 320,
height: 480,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'iPhone 4 landscape',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53',
viewport: {
width: 480,
height: 320,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'iPhone 5',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',
viewport: {
width: 320,
height: 568,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'iPhone 5 landscape',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',
viewport: {
width: 568,
height: 320,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'iPhone 6',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
viewport: {
width: 375,
height: 667,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'iPhone 6 landscape',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
viewport: {
width: 667,
height: 375,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'iPhone 6 Plus',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
viewport: {
width: 414,
height: 736,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'iPhone 6 Plus landscape',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
viewport: {
width: 736,
height: 414,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'iPhone 7',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
viewport: {
width: 375,
height: 667,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'iPhone 7 landscape',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
viewport: {
width: 667,
height: 375,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'iPhone 7 Plus',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
viewport: {
width: 414,
height: 736,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'iPhone 7 Plus landscape',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
viewport: {
width: 736,
height: 414,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'iPhone 8',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
viewport: {
width: 375,
height: 667,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'iPhone 8 landscape',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
viewport: {
width: 667,
height: 375,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'iPhone 8 Plus',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
viewport: {
width: 414,
height: 736,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'iPhone 8 Plus landscape',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
viewport: {
width: 736,
height: 414,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'iPhone SE',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',
viewport: {
width: 320,
height: 568,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'iPhone SE landscape',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',
viewport: {
width: 568,
height: 320,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'iPhone X',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
viewport: {
width: 375,
height: 812,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'iPhone X landscape',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
viewport: {
width: 812,
height: 375,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'iPhone XR',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1',
viewport: {
width: 414,
height: 896,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'iPhone XR landscape',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1',
viewport: {
width: 896,
height: 414,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'JioPhone 2',
userAgent: 'Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5',
viewport: {
width: 240,
height: 320,
deviceScaleFactor: 1,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'JioPhone 2 landscape',
userAgent: 'Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5',
viewport: {
width: 320,
height: 240,
deviceScaleFactor: 1,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Kindle Fire HDX',
userAgent: 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true',
viewport: {
width: 800,
height: 1280,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Kindle Fire HDX landscape',
userAgent: 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true',
viewport: {
width: 1280,
height: 800,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'LG Optimus L70',
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 384,
height: 640,
deviceScaleFactor: 1.25,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'LG Optimus L70 landscape',
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 640,
height: 384,
deviceScaleFactor: 1.25,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Microsoft Lumia 550',
userAgent: 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',
viewport: {
width: 640,
height: 360,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Microsoft Lumia 950',
userAgent: 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',
viewport: {
width: 360,
height: 640,
deviceScaleFactor: 4,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Microsoft Lumia 950 landscape',
userAgent: 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',
viewport: {
width: 640,
height: 360,
deviceScaleFactor: 4,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Nexus 10',
userAgent: 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36',
viewport: {
width: 800,
height: 1280,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Nexus 10 landscape',
userAgent: 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36',
viewport: {
width: 1280,
height: 800,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Nexus 4',
userAgent: 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 384,
height: 640,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Nexus 4 landscape',
userAgent: 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 640,
height: 384,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Nexus 5',
userAgent: 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 360,
height: 640,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Nexus 5 landscape',
userAgent: 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 640,
height: 360,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Nexus 5X',
userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 412,
height: 732,
deviceScaleFactor: 2.625,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Nexus 5X landscape',
userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 732,
height: 412,
deviceScaleFactor: 2.625,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Nexus 6',
userAgent: 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 412,
height: 732,
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Nexus 6 landscape',
userAgent: 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 732,
height: 412,
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Nexus 6P',
userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 412,
height: 732,
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Nexus 6P landscape',
userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 732,
height: 412,
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Nexus 7',
userAgent: 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36',
viewport: {
width: 600,
height: 960,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Nexus 7 landscape',
userAgent: 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36',
viewport: {
width: 960,
height: 600,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Nokia Lumia 520',
userAgent: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)',
viewport: {
width: 320,
height: 533,
deviceScaleFactor: 1.5,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Nokia Lumia 520 landscape',
userAgent: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)',
viewport: {
width: 533,
height: 320,
deviceScaleFactor: 1.5,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Nokia N9',
userAgent: 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13',
viewport: {
width: 480,
height: 854,
deviceScaleFactor: 1,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Nokia N9 landscape',
userAgent: 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13',
viewport: {
width: 854,
height: 480,
deviceScaleFactor: 1,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Pixel 2',
userAgent: 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 411,
height: 731,
deviceScaleFactor: 2.625,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Pixel 2 landscape',
userAgent: 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 731,
height: 411,
deviceScaleFactor: 2.625,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
{
name: 'Pixel 2 XL',
userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 411,
height: 823,
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true,
isLandscape: false,
},
},
{
name: 'Pixel 2 XL landscape',
userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 823,
height: 411,
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true,
isLandscape: true,
},
},
];
const devicesMap = {};
exports.devicesMap = devicesMap;
for (const device of devices)
devicesMap[device.name] = device;
+67
View File
@@ -0,0 +1,67 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Dialog = exports.DialogType = void 0;
const helper_1 = require("./helper");
/* TODO(jacktfranklin): protocol.d.ts defines this
* so let's ditch this and avoid the duplication
*/
var DialogType;
(function (DialogType) {
DialogType["Alert"] = "alert";
DialogType["BeforeUnload"] = "beforeunload";
DialogType["Confirm"] = "confirm";
DialogType["Prompt"] = "prompt";
})(DialogType = exports.DialogType || (exports.DialogType = {}));
let Dialog = /** @class */ (() => {
class Dialog {
constructor(client, type, message, defaultValue = '') {
this._handled = false;
this._client = client;
this._type = type;
this._message = message;
this._defaultValue = defaultValue;
}
type() {
return this._type;
}
message() {
return this._message;
}
defaultValue() {
return this._defaultValue;
}
async accept(promptText) {
helper_1.assert(!this._handled, 'Cannot accept dialog which is already handled!');
this._handled = true;
await this._client.send('Page.handleJavaScriptDialog', {
accept: true,
promptText: promptText,
});
}
async dismiss() {
helper_1.assert(!this._handled, 'Cannot dismiss dialog which is already handled!');
this._handled = true;
await this._client.send('Page.handleJavaScriptDialog', {
accept: false,
});
}
}
Dialog.Type = DialogType;
return Dialog;
})();
exports.Dialog = Dialog;
+37
View File
@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmulationManager = void 0;
class EmulationManager {
constructor(client) {
this._emulatingMobile = false;
this._hasTouch = false;
this._client = client;
}
async emulateViewport(viewport) {
const mobile = viewport.isMobile || false;
const width = viewport.width;
const height = viewport.height;
const deviceScaleFactor = viewport.deviceScaleFactor || 1;
const screenOrientation = viewport.isLandscape
? { angle: 90, type: 'landscapePrimary' }
: { angle: 0, type: 'portraitPrimary' };
const hasTouch = viewport.hasTouch || false;
await Promise.all([
this._client.send('Emulation.setDeviceMetricsOverride', {
mobile,
width,
height,
deviceScaleFactor,
screenOrientation,
}),
this._client.send('Emulation.setTouchEmulationEnabled', {
enabled: hasTouch,
}),
]);
const reloadNeeded = this._emulatingMobile !== mobile || this._hasTouch !== hasTouch;
this._emulatingMobile = mobile;
this._hasTouch = hasTouch;
return reloadNeeded;
}
}
exports.EmulationManager = EmulationManager;
+31
View File
@@ -0,0 +1,31 @@
"use strict";
/**
* Copyright 2018 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.puppeteerErrors = exports.TimeoutError = void 0;
class CustomError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}
class TimeoutError extends CustomError {
}
exports.TimeoutError = TimeoutError;
exports.puppeteerErrors = {
TimeoutError,
};
+74
View File
@@ -0,0 +1,74 @@
"use strict";
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Events = void 0;
exports.Events = {
Page: {
Close: 'close',
Console: 'console',
Dialog: 'dialog',
DOMContentLoaded: 'domcontentloaded',
Error: 'error',
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
PageError: 'pageerror',
Request: 'request',
Response: 'response',
RequestFailed: 'requestfailed',
RequestFinished: 'requestfinished',
FrameAttached: 'frameattached',
FrameDetached: 'framedetached',
FrameNavigated: 'framenavigated',
Load: 'load',
Metrics: 'metrics',
Popup: 'popup',
WorkerCreated: 'workercreated',
WorkerDestroyed: 'workerdestroyed',
},
Browser: {
TargetCreated: 'targetcreated',
TargetDestroyed: 'targetdestroyed',
TargetChanged: 'targetchanged',
Disconnected: 'disconnected',
},
BrowserContext: {
TargetCreated: 'targetcreated',
TargetDestroyed: 'targetdestroyed',
TargetChanged: 'targetchanged',
},
NetworkManager: {
Request: Symbol('Events.NetworkManager.Request'),
Response: Symbol('Events.NetworkManager.Response'),
RequestFailed: Symbol('Events.NetworkManager.RequestFailed'),
RequestFinished: Symbol('Events.NetworkManager.RequestFinished'),
},
FrameManager: {
FrameAttached: Symbol('Events.FrameManager.FrameAttached'),
FrameNavigated: Symbol('Events.FrameManager.FrameNavigated'),
FrameDetached: Symbol('Events.FrameManager.FrameDetached'),
LifecycleEvent: Symbol('Events.FrameManager.LifecycleEvent'),
FrameNavigatedWithinDocument: Symbol('Events.FrameManager.FrameNavigatedWithinDocument'),
ExecutionContextCreated: Symbol('Events.FrameManager.ExecutionContextCreated'),
ExecutionContextDestroyed: Symbol('Events.FrameManager.ExecutionContextDestroyed'),
},
Connection: {
Disconnected: Symbol('Events.Connection.Disconnected'),
},
CDPSession: {
Disconnected: Symbol('Events.CDPSession.Disconnected'),
},
};
+174
View File
@@ -0,0 +1,174 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExecutionContext = exports.EVALUATION_SCRIPT_URL = void 0;
const helper_1 = require("./helper");
const JSHandle_1 = require("./JSHandle");
exports.EVALUATION_SCRIPT_URL = '__puppeteer_evaluation_script__';
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
class ExecutionContext {
constructor(client, contextPayload, world) {
this._client = client;
this._world = world;
this._contextId = contextPayload.id;
}
frame() {
return this._world ? this._world.frame() : null;
}
async evaluate(pageFunction, ...args) {
return await this._evaluateInternal(true, pageFunction, ...args);
}
async evaluateHandle(pageFunction, ...args) {
return this._evaluateInternal(false, pageFunction, ...args);
}
async _evaluateInternal(returnByValue, pageFunction, ...args) {
const suffix = `//# sourceURL=${exports.EVALUATION_SCRIPT_URL}`;
if (helper_1.helper.isString(pageFunction)) {
const contextId = this._contextId;
const expression = pageFunction;
const expressionWithSourceUrl = SOURCE_URL_REGEX.test(expression)
? expression
: expression + '\n' + suffix;
const { exceptionDetails, result: remoteObject } = await this._client
.send('Runtime.evaluate', {
expression: expressionWithSourceUrl,
contextId,
returnByValue,
awaitPromise: true,
userGesture: true,
})
.catch(rewriteError);
if (exceptionDetails)
throw new Error('Evaluation failed: ' + helper_1.helper.getExceptionMessage(exceptionDetails));
return returnByValue
? helper_1.helper.valueFromRemoteObject(remoteObject)
: JSHandle_1.createJSHandle(this, remoteObject);
}
if (typeof pageFunction !== 'function')
throw new Error(`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`);
let functionText = pageFunction.toString();
try {
new Function('(' + functionText + ')');
}
catch (error) {
// This means we might have a function shorthand. Try another
// time prefixing 'function '.
if (functionText.startsWith('async '))
functionText =
'async function ' + functionText.substring('async '.length);
else
functionText = 'function ' + functionText;
try {
new Function('(' + functionText + ')');
}
catch (error) {
// We tried hard to serialize, but there's a weird beast here.
throw new Error('Passed function is not well-serializable!');
}
}
let callFunctionOnPromise;
try {
callFunctionOnPromise = this._client.send('Runtime.callFunctionOn', {
functionDeclaration: functionText + '\n' + suffix + '\n',
executionContextId: this._contextId,
arguments: args.map(convertArgument.bind(this)),
returnByValue,
awaitPromise: true,
userGesture: true,
});
}
catch (error) {
if (error instanceof TypeError &&
error.message.startsWith('Converting circular structure to JSON'))
error.message += ' Are you passing a nested JSHandle?';
throw error;
}
const { exceptionDetails, result: remoteObject, } = await callFunctionOnPromise.catch(rewriteError);
if (exceptionDetails)
throw new Error('Evaluation failed: ' + helper_1.helper.getExceptionMessage(exceptionDetails));
return returnByValue
? helper_1.helper.valueFromRemoteObject(remoteObject)
: JSHandle_1.createJSHandle(this, remoteObject);
/**
* @param {*} arg
* @return {*}
* @this {ExecutionContext}
*/
function convertArgument(arg) {
if (typeof arg === 'bigint')
// eslint-disable-line valid-typeof
return { unserializableValue: `${arg.toString()}n` };
if (Object.is(arg, -0))
return { unserializableValue: '-0' };
if (Object.is(arg, Infinity))
return { unserializableValue: 'Infinity' };
if (Object.is(arg, -Infinity))
return { unserializableValue: '-Infinity' };
if (Object.is(arg, NaN))
return { unserializableValue: 'NaN' };
const objectHandle = arg && arg instanceof JSHandle_1.JSHandle ? arg : null;
if (objectHandle) {
if (objectHandle._context !== this)
throw new Error('JSHandles can be evaluated only in the context they were created!');
if (objectHandle._disposed)
throw new Error('JSHandle is disposed!');
if (objectHandle._remoteObject.unserializableValue)
return {
unserializableValue: objectHandle._remoteObject.unserializableValue,
};
if (!objectHandle._remoteObject.objectId)
return { value: objectHandle._remoteObject.value };
return { objectId: objectHandle._remoteObject.objectId };
}
return { value: arg };
}
function rewriteError(error) {
if (error.message.includes('Object reference chain is too long'))
return { result: { type: 'undefined' } };
if (error.message.includes("Object couldn't be returned by value"))
return { result: { type: 'undefined' } };
if (error.message.endsWith('Cannot find context with specified id') ||
error.message.endsWith('Inspected target navigated or closed'))
throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw error;
}
}
async queryObjects(prototypeHandle) {
helper_1.assert(!prototypeHandle._disposed, 'Prototype JSHandle is disposed!');
helper_1.assert(prototypeHandle._remoteObject.objectId, 'Prototype JSHandle must not be referencing primitive value');
const response = await this._client.send('Runtime.queryObjects', {
prototypeObjectId: prototypeHandle._remoteObject.objectId,
});
return JSHandle_1.createJSHandle(this, response.objects);
}
async _adoptBackendNodeId(backendNodeId) {
const { object } = await this._client.send('DOM.resolveNode', {
backendNodeId: backendNodeId,
executionContextId: this._contextId,
});
return JSHandle_1.createJSHandle(this, object);
}
async _adoptElementHandle(elementHandle) {
helper_1.assert(elementHandle.executionContext() !== this, 'Cannot adopt handle that already belongs to this execution context');
helper_1.assert(this._world, 'Cannot adopt handle without DOMWorld');
const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: elementHandle._remoteObject.objectId,
});
return this._adoptBackendNodeId(nodeInfo.node.backendNodeId);
}
}
exports.ExecutionContext = ExecutionContext;
+39
View File
@@ -0,0 +1,39 @@
"use strict";
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileChooser = void 0;
const helper_1 = require("./helper");
class FileChooser {
constructor(element, event) {
this._handled = false;
this._element = element;
this._multiple = event.mode !== 'selectSingle';
}
isMultiple() {
return this._multiple;
}
async accept(filePaths) {
helper_1.assert(!this._handled, 'Cannot accept FileChooser which is already handled!');
this._handled = true;
await this._element.uploadFile(...filePaths);
}
async cancel() {
helper_1.assert(!this._handled, 'Cannot cancel FileChooser which is already handled!');
this._handled = true;
}
}
exports.FileChooser = FileChooser;
+439
View File
@@ -0,0 +1,439 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Frame = exports.FrameManager = void 0;
const EventEmitter = require("events");
const helper_1 = require("./helper");
const Events_1 = require("./Events");
const ExecutionContext_1 = require("./ExecutionContext");
const LifecycleWatcher_1 = require("./LifecycleWatcher");
const DOMWorld_1 = require("./DOMWorld");
const NetworkManager_1 = require("./NetworkManager");
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
class FrameManager extends EventEmitter {
constructor(client, page, ignoreHTTPSErrors, timeoutSettings) {
super();
this._frames = new Map();
this._contextIdToContext = new Map();
this._isolatedWorlds = new Set();
this._client = client;
this._page = page;
this._networkManager = new NetworkManager_1.NetworkManager(client, ignoreHTTPSErrors, this);
this._timeoutSettings = timeoutSettings;
this._client.on('Page.frameAttached', (event) => this._onFrameAttached(event.frameId, event.parentFrameId));
this._client.on('Page.frameNavigated', (event) => this._onFrameNavigated(event.frame));
this._client.on('Page.navigatedWithinDocument', (event) => this._onFrameNavigatedWithinDocument(event.frameId, event.url));
this._client.on('Page.frameDetached', (event) => this._onFrameDetached(event.frameId));
this._client.on('Page.frameStoppedLoading', (event) => this._onFrameStoppedLoading(event.frameId));
this._client.on('Runtime.executionContextCreated', (event) => this._onExecutionContextCreated(event.context));
this._client.on('Runtime.executionContextDestroyed', (event) => this._onExecutionContextDestroyed(event.executionContextId));
this._client.on('Runtime.executionContextsCleared', () => this._onExecutionContextsCleared());
this._client.on('Page.lifecycleEvent', (event) => this._onLifecycleEvent(event));
}
async initialize() {
const result = await Promise.all([
this._client.send('Page.enable'),
this._client.send('Page.getFrameTree'),
]);
const { frameTree } = result[1];
this._handleFrameTree(frameTree);
await Promise.all([
this._client.send('Page.setLifecycleEventsEnabled', { enabled: true }),
this._client
.send('Runtime.enable', {})
.then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)),
this._networkManager.initialize(),
]);
}
networkManager() {
return this._networkManager;
}
async navigateFrame(frame, url, options = {}) {
assertNoLegacyNavigationOptions(options);
const { referer = this._networkManager.extraHTTPHeaders()['referer'], waitUntil = ['load'], timeout = this._timeoutSettings.navigationTimeout(), } = options;
const watcher = new LifecycleWatcher_1.LifecycleWatcher(this, frame, waitUntil, timeout);
let ensureNewDocumentNavigation = false;
let error = await Promise.race([
navigate(this._client, url, referer, frame._id),
watcher.timeoutOrTerminationPromise(),
]);
if (!error) {
error = await Promise.race([
watcher.timeoutOrTerminationPromise(),
ensureNewDocumentNavigation
? watcher.newDocumentNavigationPromise()
: watcher.sameDocumentNavigationPromise(),
]);
}
watcher.dispose();
if (error)
throw error;
return watcher.navigationResponse();
async function navigate(client, url, referrer, frameId) {
try {
const response = await client.send('Page.navigate', {
url,
referrer,
frameId,
});
ensureNewDocumentNavigation = !!response.loaderId;
return response.errorText
? new Error(`${response.errorText} at ${url}`)
: null;
}
catch (error) {
return error;
}
}
}
async waitForFrameNavigation(frame, options = {}) {
assertNoLegacyNavigationOptions(options);
const { waitUntil = ['load'], timeout = this._timeoutSettings.navigationTimeout(), } = options;
const watcher = new LifecycleWatcher_1.LifecycleWatcher(this, frame, waitUntil, timeout);
const error = await Promise.race([
watcher.timeoutOrTerminationPromise(),
watcher.sameDocumentNavigationPromise(),
watcher.newDocumentNavigationPromise(),
]);
watcher.dispose();
if (error)
throw error;
return watcher.navigationResponse();
}
_onLifecycleEvent(event) {
const frame = this._frames.get(event.frameId);
if (!frame)
return;
frame._onLifecycleEvent(event.loaderId, event.name);
this.emit(Events_1.Events.FrameManager.LifecycleEvent, frame);
}
_onFrameStoppedLoading(frameId) {
const frame = this._frames.get(frameId);
if (!frame)
return;
frame._onLoadingStopped();
this.emit(Events_1.Events.FrameManager.LifecycleEvent, frame);
}
_handleFrameTree(frameTree) {
if (frameTree.frame.parentId)
this._onFrameAttached(frameTree.frame.id, frameTree.frame.parentId);
this._onFrameNavigated(frameTree.frame);
if (!frameTree.childFrames)
return;
for (const child of frameTree.childFrames)
this._handleFrameTree(child);
}
page() {
return this._page;
}
mainFrame() {
return this._mainFrame;
}
frames() {
return Array.from(this._frames.values());
}
frame(frameId) {
return this._frames.get(frameId) || null;
}
_onFrameAttached(frameId, parentFrameId) {
if (this._frames.has(frameId))
return;
helper_1.assert(parentFrameId);
const parentFrame = this._frames.get(parentFrameId);
const frame = new Frame(this, this._client, parentFrame, frameId);
this._frames.set(frame._id, frame);
this.emit(Events_1.Events.FrameManager.FrameAttached, frame);
}
_onFrameNavigated(framePayload) {
const isMainFrame = !framePayload.parentId;
let frame = isMainFrame
? this._mainFrame
: this._frames.get(framePayload.id);
helper_1.assert(isMainFrame || frame, 'We either navigate top level or have old version of the navigated frame');
// Detach all child frames first.
if (frame) {
for (const child of frame.childFrames())
this._removeFramesRecursively(child);
}
// Update or create main frame.
if (isMainFrame) {
if (frame) {
// Update frame id to retain frame identity on cross-process navigation.
this._frames.delete(frame._id);
frame._id = framePayload.id;
}
else {
// Initial main frame navigation.
frame = new Frame(this, this._client, null, framePayload.id);
}
this._frames.set(framePayload.id, frame);
this._mainFrame = frame;
}
// Update frame payload.
frame._navigated(framePayload);
this.emit(Events_1.Events.FrameManager.FrameNavigated, frame);
}
async _ensureIsolatedWorld(name) {
if (this._isolatedWorlds.has(name))
return;
this._isolatedWorlds.add(name);
await this._client.send('Page.addScriptToEvaluateOnNewDocument', {
source: `//# sourceURL=${ExecutionContext_1.EVALUATION_SCRIPT_URL}`,
worldName: name,
}),
await Promise.all(this.frames().map((frame) => this._client
.send('Page.createIsolatedWorld', {
frameId: frame._id,
grantUniveralAccess: true,
worldName: name,
})
.catch(helper_1.debugError))); // frames might be removed before we send this
}
_onFrameNavigatedWithinDocument(frameId, url) {
const frame = this._frames.get(frameId);
if (!frame)
return;
frame._navigatedWithinDocument(url);
this.emit(Events_1.Events.FrameManager.FrameNavigatedWithinDocument, frame);
this.emit(Events_1.Events.FrameManager.FrameNavigated, frame);
}
_onFrameDetached(frameId) {
const frame = this._frames.get(frameId);
if (frame)
this._removeFramesRecursively(frame);
}
_onExecutionContextCreated(contextPayload) {
const auxData = contextPayload.auxData;
const frameId = auxData ? auxData.frameId : null;
const frame = this._frames.get(frameId) || null;
let world = null;
if (frame) {
if (contextPayload.auxData && !!contextPayload.auxData['isDefault']) {
world = frame._mainWorld;
}
else if (contextPayload.name === UTILITY_WORLD_NAME &&
!frame._secondaryWorld._hasContext()) {
// In case of multiple sessions to the same target, there's a race between
// connections so we might end up creating multiple isolated worlds.
// We can use either.
world = frame._secondaryWorld;
}
}
if (contextPayload.auxData && contextPayload.auxData['type'] === 'isolated')
this._isolatedWorlds.add(contextPayload.name);
const context = new ExecutionContext_1.ExecutionContext(this._client, contextPayload, world);
if (world)
world._setContext(context);
this._contextIdToContext.set(contextPayload.id, context);
}
/**
* @param {number} executionContextId
*/
_onExecutionContextDestroyed(executionContextId) {
const context = this._contextIdToContext.get(executionContextId);
if (!context)
return;
this._contextIdToContext.delete(executionContextId);
if (context._world)
context._world._setContext(null);
}
_onExecutionContextsCleared() {
for (const context of this._contextIdToContext.values()) {
if (context._world)
context._world._setContext(null);
}
this._contextIdToContext.clear();
}
executionContextById(contextId) {
const context = this._contextIdToContext.get(contextId);
helper_1.assert(context, 'INTERNAL ERROR: missing context with id = ' + contextId);
return context;
}
_removeFramesRecursively(frame) {
for (const child of frame.childFrames())
this._removeFramesRecursively(child);
frame._detach();
this._frames.delete(frame._id);
this.emit(Events_1.Events.FrameManager.FrameDetached, frame);
}
}
exports.FrameManager = FrameManager;
class Frame {
constructor(frameManager, client, parentFrame, frameId) {
this._url = '';
this._detached = false;
this._loaderId = '';
this._lifecycleEvents = new Set();
this._frameManager = frameManager;
this._client = client;
this._parentFrame = parentFrame;
this._url = '';
this._id = frameId;
this._detached = false;
this._loaderId = '';
this._mainWorld = new DOMWorld_1.DOMWorld(frameManager, this, frameManager._timeoutSettings);
this._secondaryWorld = new DOMWorld_1.DOMWorld(frameManager, this, frameManager._timeoutSettings);
this._childFrames = new Set();
if (this._parentFrame)
this._parentFrame._childFrames.add(this);
}
async goto(url, options) {
return await this._frameManager.navigateFrame(this, url, options);
}
async waitForNavigation(options) {
return await this._frameManager.waitForFrameNavigation(this, options);
}
executionContext() {
return this._mainWorld.executionContext();
}
async evaluateHandle(pageFunction, ...args) {
return this._mainWorld.evaluateHandle(pageFunction, ...args);
}
async evaluate(pageFunction, ...args) {
return this._mainWorld.evaluate(pageFunction, ...args);
}
async $(selector) {
return this._mainWorld.$(selector);
}
async $x(expression) {
return this._mainWorld.$x(expression);
}
async $eval(selector, pageFunction, ...args) {
return this._mainWorld.$eval(selector, pageFunction, ...args);
}
async $$eval(selector, pageFunction, ...args) {
return this._mainWorld.$$eval(selector, pageFunction, ...args);
}
async $$(selector) {
return this._mainWorld.$$(selector);
}
async content() {
return this._secondaryWorld.content();
}
async setContent(html, options = {}) {
return this._secondaryWorld.setContent(html, options);
}
name() {
return this._name || '';
}
url() {
return this._url;
}
parentFrame() {
return this._parentFrame;
}
childFrames() {
return Array.from(this._childFrames);
}
isDetached() {
return this._detached;
}
async addScriptTag(options) {
return this._mainWorld.addScriptTag(options);
}
async addStyleTag(options) {
return this._mainWorld.addStyleTag(options);
}
async click(selector, options) {
return this._secondaryWorld.click(selector, options);
}
async focus(selector) {
return this._secondaryWorld.focus(selector);
}
async hover(selector) {
return this._secondaryWorld.hover(selector);
}
select(selector, ...values) {
return this._secondaryWorld.select(selector, ...values);
}
async tap(selector) {
return this._secondaryWorld.tap(selector);
}
async type(selector, text, options) {
return this._mainWorld.type(selector, text, options);
}
waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
const xPathPattern = '//';
if (helper_1.helper.isString(selectorOrFunctionOrTimeout)) {
const string = selectorOrFunctionOrTimeout;
if (string.startsWith(xPathPattern))
return this.waitForXPath(string, options);
return this.waitForSelector(string, options);
}
if (helper_1.helper.isNumber(selectorOrFunctionOrTimeout))
return new Promise((fulfill) => setTimeout(fulfill, selectorOrFunctionOrTimeout));
if (typeof selectorOrFunctionOrTimeout === 'function')
return this.waitForFunction(selectorOrFunctionOrTimeout, options, ...args);
return Promise.reject(new Error('Unsupported target type: ' + typeof selectorOrFunctionOrTimeout));
}
async waitForSelector(selector, options) {
const handle = await this._secondaryWorld.waitForSelector(selector, options);
if (!handle)
return null;
const mainExecutionContext = await this._mainWorld.executionContext();
const result = await mainExecutionContext._adoptElementHandle(handle);
await handle.dispose();
return result;
}
async waitForXPath(xpath, options) {
const handle = await this._secondaryWorld.waitForXPath(xpath, options);
if (!handle)
return null;
const mainExecutionContext = await this._mainWorld.executionContext();
const result = await mainExecutionContext._adoptElementHandle(handle);
await handle.dispose();
return result;
}
waitForFunction(pageFunction, options = {}, ...args) {
return this._mainWorld.waitForFunction(pageFunction, options, ...args);
}
async title() {
return this._secondaryWorld.title();
}
_navigated(framePayload) {
this._name = framePayload.name;
this._url = framePayload.url;
}
_navigatedWithinDocument(url) {
this._url = url;
}
_onLifecycleEvent(loaderId, name) {
if (name === 'init') {
this._loaderId = loaderId;
this._lifecycleEvents.clear();
}
this._lifecycleEvents.add(name);
}
_onLoadingStopped() {
this._lifecycleEvents.add('DOMContentLoaded');
this._lifecycleEvents.add('load');
}
_detach() {
this._detached = true;
this._mainWorld._detach();
this._secondaryWorld._detach();
if (this._parentFrame)
this._parentFrame._childFrames.delete(this);
this._parentFrame = null;
}
}
exports.Frame = Frame;
function assertNoLegacyNavigationOptions(options) {
helper_1.assert(options['networkIdleTimeout'] === undefined, 'ERROR: networkIdleTimeout option is no longer supported.');
helper_1.assert(options['networkIdleInflight'] === undefined, 'ERROR: networkIdleInflight option is no longer supported.');
helper_1.assert(options.waitUntil !== 'networkidle', 'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead');
}
+230
View File
@@ -0,0 +1,230 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HTTPRequest = void 0;
const helper_1 = require("./helper");
class HTTPRequest {
constructor(client, frame, interceptionId, allowInterception, event, redirectChain) {
this._failureText = null;
this._response = null;
this._fromMemoryCache = false;
this._interceptionHandled = false;
this._headers = {};
this._client = client;
this._requestId = event.requestId;
this._isNavigationRequest =
event.requestId === event.loaderId && event.type === 'Document';
this._interceptionId = interceptionId;
this._allowInterception = allowInterception;
this._url = event.request.url;
this._resourceType = event.type.toLowerCase();
this._method = event.request.method;
this._postData = event.request.postData;
this._frame = frame;
this._redirectChain = redirectChain;
for (const key of Object.keys(event.request.headers))
this._headers[key.toLowerCase()] = event.request.headers[key];
}
url() {
return this._url;
}
resourceType() {
return this._resourceType;
}
method() {
return this._method;
}
postData() {
return this._postData;
}
headers() {
return this._headers;
}
response() {
return this._response;
}
frame() {
return this._frame;
}
isNavigationRequest() {
return this._isNavigationRequest;
}
redirectChain() {
return this._redirectChain.slice();
}
/**
* @return {?{errorText: string}}
*/
failure() {
if (!this._failureText)
return null;
return {
errorText: this._failureText,
};
}
async continue(overrides = {}) {
// Request interception is not supported for data: urls.
if (this._url.startsWith('data:'))
return;
helper_1.assert(this._allowInterception, 'Request Interception is not enabled!');
helper_1.assert(!this._interceptionHandled, 'Request is already handled!');
const { url, method, postData, headers } = overrides;
this._interceptionHandled = true;
await this._client
.send('Fetch.continueRequest', {
requestId: this._interceptionId,
url,
method,
postData,
headers: headers ? headersArray(headers) : undefined,
})
.catch((error) => {
// In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
helper_1.debugError(error);
});
}
async respond(response) {
// Mocking responses for dataURL requests is not currently supported.
if (this._url.startsWith('data:'))
return;
helper_1.assert(this._allowInterception, 'Request Interception is not enabled!');
helper_1.assert(!this._interceptionHandled, 'Request is already handled!');
this._interceptionHandled = true;
const responseBody = response.body && helper_1.helper.isString(response.body)
? Buffer.from(response.body)
: response.body || null;
const responseHeaders = {};
if (response.headers) {
for (const header of Object.keys(response.headers))
responseHeaders[header.toLowerCase()] = response.headers[header];
}
if (response.contentType)
responseHeaders['content-type'] = response.contentType;
if (responseBody && !('content-length' in responseHeaders))
responseHeaders['content-length'] = String(Buffer.byteLength(responseBody));
await this._client
.send('Fetch.fulfillRequest', {
requestId: this._interceptionId,
responseCode: response.status || 200,
responsePhrase: STATUS_TEXTS[response.status || 200],
responseHeaders: headersArray(responseHeaders),
body: responseBody ? responseBody.toString('base64') : undefined,
})
.catch((error) => {
// In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
helper_1.debugError(error);
});
}
async abort(errorCode = 'failed') {
// Request interception is not supported for data: urls.
if (this._url.startsWith('data:'))
return;
const errorReason = errorReasons[errorCode];
helper_1.assert(errorReason, 'Unknown error code: ' + errorCode);
helper_1.assert(this._allowInterception, 'Request Interception is not enabled!');
helper_1.assert(!this._interceptionHandled, 'Request is already handled!');
this._interceptionHandled = true;
await this._client
.send('Fetch.failRequest', {
requestId: this._interceptionId,
errorReason,
})
.catch((error) => {
// In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
helper_1.debugError(error);
});
}
}
exports.HTTPRequest = HTTPRequest;
const errorReasons = {
aborted: 'Aborted',
accessdenied: 'AccessDenied',
addressunreachable: 'AddressUnreachable',
blockedbyclient: 'BlockedByClient',
blockedbyresponse: 'BlockedByResponse',
connectionaborted: 'ConnectionAborted',
connectionclosed: 'ConnectionClosed',
connectionfailed: 'ConnectionFailed',
connectionrefused: 'ConnectionRefused',
connectionreset: 'ConnectionReset',
internetdisconnected: 'InternetDisconnected',
namenotresolved: 'NameNotResolved',
timedout: 'TimedOut',
failed: 'Failed',
};
function headersArray(headers) {
const result = [];
for (const name in headers) {
if (!Object.is(headers[name], undefined))
result.push({ name, value: headers[name] + '' });
}
return result;
}
// List taken from https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml with extra 306 and 418 codes.
const STATUS_TEXTS = {
'100': 'Continue',
'101': 'Switching Protocols',
'102': 'Processing',
'103': 'Early Hints',
'200': 'OK',
'201': 'Created',
'202': 'Accepted',
'203': 'Non-Authoritative Information',
'204': 'No Content',
'205': 'Reset Content',
'206': 'Partial Content',
'207': 'Multi-Status',
'208': 'Already Reported',
'226': 'IM Used',
'300': 'Multiple Choices',
'301': 'Moved Permanently',
'302': 'Found',
'303': 'See Other',
'304': 'Not Modified',
'305': 'Use Proxy',
'306': 'Switch Proxy',
'307': 'Temporary Redirect',
'308': 'Permanent Redirect',
'400': 'Bad Request',
'401': 'Unauthorized',
'402': 'Payment Required',
'403': 'Forbidden',
'404': 'Not Found',
'405': 'Method Not Allowed',
'406': 'Not Acceptable',
'407': 'Proxy Authentication Required',
'408': 'Request Timeout',
'409': 'Conflict',
'410': 'Gone',
'411': 'Length Required',
'412': 'Precondition Failed',
'413': 'Payload Too Large',
'414': 'URI Too Long',
'415': 'Unsupported Media Type',
'416': 'Range Not Satisfiable',
'417': 'Expectation Failed',
'418': "I'm a teapot",
'421': 'Misdirected Request',
'422': 'Unprocessable Entity',
'423': 'Locked',
'424': 'Failed Dependency',
'425': 'Too Early',
'426': 'Upgrade Required',
'428': 'Precondition Required',
'429': 'Too Many Requests',
'431': 'Request Header Fields Too Large',
'451': 'Unavailable For Legal Reasons',
'500': 'Internal Server Error',
'501': 'Not Implemented',
'502': 'Bad Gateway',
'503': 'Service Unavailable',
'504': 'Gateway Timeout',
'505': 'HTTP Version Not Supported',
'506': 'Variant Also Negotiates',
'507': 'Insufficient Storage',
'508': 'Loop Detected',
'510': 'Not Extended',
'511': 'Network Authentication Required',
};
+87
View File
@@ -0,0 +1,87 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HTTPResponse = void 0;
const SecurityDetails_1 = require("./SecurityDetails");
class HTTPResponse {
constructor(client, request, responsePayload) {
this._contentPromise = null;
this._headers = {};
this._client = client;
this._request = request;
this._bodyLoadedPromise = new Promise((fulfill) => {
this._bodyLoadedPromiseFulfill = fulfill;
});
this._remoteAddress = {
ip: responsePayload.remoteIPAddress,
port: responsePayload.remotePort,
};
this._status = responsePayload.status;
this._statusText = responsePayload.statusText;
this._url = request.url();
this._fromDiskCache = !!responsePayload.fromDiskCache;
this._fromServiceWorker = !!responsePayload.fromServiceWorker;
for (const key of Object.keys(responsePayload.headers))
this._headers[key.toLowerCase()] = responsePayload.headers[key];
this._securityDetails = responsePayload.securityDetails
? new SecurityDetails_1.SecurityDetails(responsePayload.securityDetails)
: null;
}
_resolveBody(err) {
return this._bodyLoadedPromiseFulfill(err);
}
remoteAddress() {
return this._remoteAddress;
}
url() {
return this._url;
}
ok() {
return this._status === 0 || (this._status >= 200 && this._status <= 299);
}
status() {
return this._status;
}
statusText() {
return this._statusText;
}
headers() {
return this._headers;
}
securityDetails() {
return this._securityDetails;
}
buffer() {
if (!this._contentPromise) {
this._contentPromise = this._bodyLoadedPromise.then(async (error) => {
if (error)
throw error;
const response = await this._client.send('Network.getResponseBody', {
requestId: this._request._requestId,
});
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
});
}
return this._contentPromise;
}
async text() {
const content = await this.buffer();
return content.toString('utf8');
}
async json() {
const content = await this.text();
return JSON.parse(content);
}
request() {
return this._request;
}
fromCache() {
return this._fromDiskCache || this._request._fromMemoryCache;
}
fromServiceWorker() {
return this._fromServiceWorker;
}
frame() {
return this._request.frame();
}
}
exports.HTTPResponse = HTTPResponse;
+233
View File
@@ -0,0 +1,233 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Touchscreen = exports.Mouse = exports.Keyboard = void 0;
const helper_1 = require("./helper");
const USKeyboardLayout_1 = require("./USKeyboardLayout");
class Keyboard {
constructor(client) {
this._modifiers = 0;
this._pressedKeys = new Set();
this._client = client;
}
async down(key, options = { text: undefined }) {
const description = this._keyDescriptionForString(key);
const autoRepeat = this._pressedKeys.has(description.code);
this._pressedKeys.add(description.code);
this._modifiers |= this._modifierBit(description.key);
const text = options.text === undefined ? description.text : options.text;
await this._client.send('Input.dispatchKeyEvent', {
type: text ? 'keyDown' : 'rawKeyDown',
modifiers: this._modifiers,
windowsVirtualKeyCode: description.keyCode,
code: description.code,
key: description.key,
text: text,
unmodifiedText: text,
autoRepeat,
location: description.location,
isKeypad: description.location === 3,
});
}
_modifierBit(key) {
if (key === 'Alt')
return 1;
if (key === 'Control')
return 2;
if (key === 'Meta')
return 4;
if (key === 'Shift')
return 8;
return 0;
}
_keyDescriptionForString(keyString) {
const shift = this._modifiers & 8;
const description = {
key: '',
keyCode: 0,
code: '',
text: '',
location: 0,
};
const definition = USKeyboardLayout_1.keyDefinitions[keyString];
helper_1.assert(definition, `Unknown key: "${keyString}"`);
if (definition.key)
description.key = definition.key;
if (shift && definition.shiftKey)
description.key = definition.shiftKey;
if (definition.keyCode)
description.keyCode = definition.keyCode;
if (shift && definition.shiftKeyCode)
description.keyCode = definition.shiftKeyCode;
if (definition.code)
description.code = definition.code;
if (definition.location)
description.location = definition.location;
if (description.key.length === 1)
description.text = description.key;
if (definition.text)
description.text = definition.text;
if (shift && definition.shiftText)
description.text = definition.shiftText;
// if any modifiers besides shift are pressed, no text should be sent
if (this._modifiers & ~8)
description.text = '';
return description;
}
async up(key) {
const description = this._keyDescriptionForString(key);
this._modifiers &= ~this._modifierBit(description.key);
this._pressedKeys.delete(description.code);
await this._client.send('Input.dispatchKeyEvent', {
type: 'keyUp',
modifiers: this._modifiers,
key: description.key,
windowsVirtualKeyCode: description.keyCode,
code: description.code,
location: description.location,
});
}
async sendCharacter(char) {
await this._client.send('Input.insertText', { text: char });
}
charIsKey(char) {
return !!USKeyboardLayout_1.keyDefinitions[char];
}
async type(text, options) {
const delay = (options && options.delay) || null;
for (const char of text) {
if (this.charIsKey(char)) {
await this.press(char, { delay });
}
else {
if (delay)
await new Promise((f) => setTimeout(f, delay));
await this.sendCharacter(char);
}
}
}
async press(key, options = {}) {
const { delay = null } = options;
await this.down(key, options);
if (delay)
await new Promise((f) => setTimeout(f, options.delay));
await this.up(key);
}
}
exports.Keyboard = Keyboard;
class Mouse {
/**
* @param {CDPSession} client
* @param {!Keyboard} keyboard
*/
constructor(client, keyboard) {
this._x = 0;
this._y = 0;
this._button = 'none';
this._client = client;
this._keyboard = keyboard;
}
async move(x, y, options = {}) {
const { steps = 1 } = options;
const fromX = this._x, fromY = this._y;
this._x = x;
this._y = y;
for (let i = 1; i <= steps; i++) {
await this._client.send('Input.dispatchMouseEvent', {
type: 'mouseMoved',
button: this._button,
x: fromX + (this._x - fromX) * (i / steps),
y: fromY + (this._y - fromY) * (i / steps),
modifiers: this._keyboard._modifiers,
});
}
}
async click(x, y, options = {}) {
const { delay = null } = options;
if (delay !== null) {
await Promise.all([this.move(x, y), this.down(options)]);
await new Promise((f) => setTimeout(f, delay));
await this.up(options);
}
else {
await Promise.all([
this.move(x, y),
this.down(options),
this.up(options),
]);
}
}
async down(options = {}) {
const { button = 'left', clickCount = 1 } = options;
this._button = button;
await this._client.send('Input.dispatchMouseEvent', {
type: 'mousePressed',
button,
x: this._x,
y: this._y,
modifiers: this._keyboard._modifiers,
clickCount,
});
}
/**
* @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
*/
async up(options = {}) {
const { button = 'left', clickCount = 1 } = options;
this._button = 'none';
await this._client.send('Input.dispatchMouseEvent', {
type: 'mouseReleased',
button,
x: this._x,
y: this._y,
modifiers: this._keyboard._modifiers,
clickCount,
});
}
}
exports.Mouse = Mouse;
class Touchscreen {
constructor(client, keyboard) {
this._client = client;
this._keyboard = keyboard;
}
/**
* @param {number} x
* @param {number} y
*/
async tap(x, y) {
// Touches appear to be lost during the first frame after navigation.
// This waits a frame before sending the tap.
// @see https://crbug.com/613219
await this._client.send('Runtime.evaluate', {
expression: 'new Promise(x => requestAnimationFrame(() => requestAnimationFrame(x)))',
awaitPromise: true,
});
const touchPoints = [{ x: Math.round(x), y: Math.round(y) }];
await this._client.send('Input.dispatchTouchEvent', {
type: 'touchStart',
touchPoints,
modifiers: this._keyboard._modifiers,
});
await this._client.send('Input.dispatchTouchEvent', {
type: 'touchEnd',
touchPoints: [],
modifiers: this._keyboard._modifiers,
});
}
}
exports.Touchscreen = Touchscreen;
+456
View File
@@ -0,0 +1,456 @@
"use strict";
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ElementHandle = exports.JSHandle = exports.createJSHandle = void 0;
const helper_1 = require("./helper");
const QueryHandler_1 = require("./QueryHandler");
function createJSHandle(context, remoteObject) {
const frame = context.frame();
if (remoteObject.subtype === 'node' && frame) {
const frameManager = frame._frameManager;
return new ElementHandle(context, context._client, remoteObject, frameManager.page(), frameManager);
}
return new JSHandle(context, context._client, remoteObject);
}
exports.createJSHandle = createJSHandle;
class JSHandle {
constructor(context, client, remoteObject) {
this._disposed = false;
this._context = context;
this._client = client;
this._remoteObject = remoteObject;
}
executionContext() {
return this._context;
}
async evaluate(pageFunction, ...args) {
return await this.executionContext().evaluate(pageFunction, this, ...args);
}
async evaluateHandle(pageFunction, ...args) {
return await this.executionContext().evaluateHandle(pageFunction, this, ...args);
}
async getProperty(propertyName) {
const objectHandle = await this.evaluateHandle((object, propertyName) => {
const result = { __proto__: null };
result[propertyName] = object[propertyName];
return result;
}, propertyName);
const properties = await objectHandle.getProperties();
const result = properties.get(propertyName) || null;
await objectHandle.dispose();
return result;
}
async getProperties() {
const response = await this._client.send('Runtime.getProperties', {
objectId: this._remoteObject.objectId,
ownProperties: true,
});
const result = new Map();
for (const property of response.result) {
if (!property.enumerable)
continue;
result.set(property.name, createJSHandle(this._context, property.value));
}
return result;
}
async jsonValue() {
if (this._remoteObject.objectId) {
const response = await this._client.send('Runtime.callFunctionOn', {
functionDeclaration: 'function() { return this; }',
objectId: this._remoteObject.objectId,
returnByValue: true,
awaitPromise: true,
});
return helper_1.helper.valueFromRemoteObject(response.result);
}
return helper_1.helper.valueFromRemoteObject(this._remoteObject);
}
/* This always returns null but children can define this and return an ElementHandle */
asElement() {
return null;
}
async dispose() {
if (this._disposed)
return;
this._disposed = true;
await helper_1.helper.releaseObject(this._client, this._remoteObject);
}
toString() {
if (this._remoteObject.objectId) {
const type = this._remoteObject.subtype || this._remoteObject.type;
return 'JSHandle@' + type;
}
return 'JSHandle:' + helper_1.helper.valueFromRemoteObject(this._remoteObject);
}
}
exports.JSHandle = JSHandle;
class ElementHandle extends JSHandle {
constructor(context, client, remoteObject, page, frameManager) {
super(context, client, remoteObject);
this._client = client;
this._remoteObject = remoteObject;
this._page = page;
this._frameManager = frameManager;
}
asElement() {
return this;
}
async contentFrame() {
const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: this._remoteObject.objectId,
});
if (typeof nodeInfo.node.frameId !== 'string')
return null;
return this._frameManager.frame(nodeInfo.node.frameId);
}
async _scrollIntoViewIfNeeded() {
const error = await this.evaluate(async (element, pageJavascriptEnabled) => {
if (!element.isConnected)
return 'Node is detached from document';
if (element.nodeType !== Node.ELEMENT_NODE)
return 'Node is not of type HTMLElement';
// force-scroll if page's javascript is disabled.
if (!pageJavascriptEnabled) {
element.scrollIntoView({
block: 'center',
inline: 'center',
// Chrome still supports behavior: instant but it's not in the spec so TS shouts
// We don't want to make this breaking change in Puppeteer yet so we'll ignore the line.
// @ts-ignore
behavior: 'instant',
});
return false;
}
const visibleRatio = await new Promise((resolve) => {
const observer = new IntersectionObserver((entries) => {
resolve(entries[0].intersectionRatio);
observer.disconnect();
});
observer.observe(element);
});
if (visibleRatio !== 1.0) {
element.scrollIntoView({
block: 'center',
inline: 'center',
// Chrome still supports behavior: instant but it's not in the spec so TS shouts
// We don't want to make this breaking change in Puppeteer yet so we'll ignore the line.
// @ts-ignore
behavior: 'instant',
});
}
return false;
}, this._page._javascriptEnabled);
if (error)
throw new Error(error);
}
async _clickablePoint() {
const [result, layoutMetrics] = await Promise.all([
this._client
.send('DOM.getContentQuads', {
objectId: this._remoteObject.objectId,
})
.catch(helper_1.debugError),
this._client.send('Page.getLayoutMetrics'),
]);
if (!result || !result.quads.length)
throw new Error('Node is either not visible or not an HTMLElement');
// Filter out quads that have too small area to click into.
const { clientWidth, clientHeight } = layoutMetrics.layoutViewport;
const quads = result.quads
.map((quad) => this._fromProtocolQuad(quad))
.map((quad) => this._intersectQuadWithViewport(quad, clientWidth, clientHeight))
.filter((quad) => computeQuadArea(quad) > 1);
if (!quads.length)
throw new Error('Node is either not visible or not an HTMLElement');
// Return the middle point of the first quad.
const quad = quads[0];
let x = 0;
let y = 0;
for (const point of quad) {
x += point.x;
y += point.y;
}
return {
x: x / 4,
y: y / 4,
};
}
_getBoxModel() {
return this._client
.send('DOM.getBoxModel', {
objectId: this._remoteObject.objectId,
})
.catch((error) => helper_1.debugError(error));
}
_fromProtocolQuad(quad) {
return [
{ x: quad[0], y: quad[1] },
{ x: quad[2], y: quad[3] },
{ x: quad[4], y: quad[5] },
{ x: quad[6], y: quad[7] },
];
}
_intersectQuadWithViewport(quad, width, height) {
return quad.map((point) => ({
x: Math.min(Math.max(point.x, 0), width),
y: Math.min(Math.max(point.y, 0), height),
}));
}
async hover() {
await this._scrollIntoViewIfNeeded();
const { x, y } = await this._clickablePoint();
await this._page.mouse.move(x, y);
}
async click(options) {
await this._scrollIntoViewIfNeeded();
const { x, y } = await this._clickablePoint();
await this._page.mouse.click(x, y, options);
}
async select(...values) {
for (const value of values)
helper_1.assert(helper_1.helper.isString(value), 'Values must be strings. Found value "' +
value +
'" of type "' +
typeof value +
'"');
/* TODO(jacktfranklin@): once ExecutionContext is TypeScript, and
* its evaluate function is properly typed with generics we can
* return here and remove the typecasting
*/
return this.evaluate((element, values) => {
if (element.nodeName.toLowerCase() !== 'select')
throw new Error('Element is not a <select> element.');
const options = Array.from(element.options);
element.value = undefined;
for (const option of options) {
option.selected = values.includes(option.value);
if (option.selected && !element.multiple)
break;
}
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
return options
.filter((option) => option.selected)
.map((option) => option.value);
}, values);
}
async uploadFile(...filePaths) {
const isMultiple = await this.evaluate((element) => element.multiple);
helper_1.assert(filePaths.length <= 1 || isMultiple, 'Multiple file uploads only work with <input type=file multiple>');
// This import is only needed for `uploadFile`, so keep it scoped here to avoid paying
// the cost unnecessarily.
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require('fs');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { promisify } = require('util');
const access = promisify(fs.access);
// Locate all files and confirm that they exist.
const files = await Promise.all(filePaths.map(async (filePath) => {
const resolvedPath = path.resolve(filePath);
try {
await access(resolvedPath, fs.constants.R_OK);
}
catch (error) {
if (error.code === 'ENOENT')
throw new Error(`${filePath} does not exist or is not readable`);
}
return resolvedPath;
}));
const { objectId } = this._remoteObject;
const { node } = await this._client.send('DOM.describeNode', { objectId });
const { backendNodeId } = node;
// The zero-length array is a special case, it seems that DOM.setFileInputFiles does
// not actually update the files in that case, so the solution is to eval the element
// value to a new FileList directly.
if (files.length === 0) {
await this.evaluate((element) => {
element.files = new DataTransfer().files;
// Dispatch events for this case because it should behave akin to a user action.
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
});
}
else {
await this._client.send('DOM.setFileInputFiles', {
objectId,
files,
backendNodeId,
});
}
}
async tap() {
await this._scrollIntoViewIfNeeded();
const { x, y } = await this._clickablePoint();
await this._page.touchscreen.tap(x, y);
}
async focus() {
await this.evaluate((element) => element.focus());
}
async type(text, options) {
await this.focus();
await this._page.keyboard.type(text, options);
}
async press(key, options) {
await this.focus();
await this._page.keyboard.press(key, options);
}
async boundingBox() {
const result = await this._getBoxModel();
if (!result)
return null;
const quad = result.model.border;
const x = Math.min(quad[0], quad[2], quad[4], quad[6]);
const y = Math.min(quad[1], quad[3], quad[5], quad[7]);
const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x;
const height = Math.max(quad[1], quad[3], quad[5], quad[7]) - y;
return { x, y, width, height };
}
/**
* @return {!Promise<?BoxModel>}
*/
async boxModel() {
const result = await this._getBoxModel();
if (!result)
return null;
const { content, padding, border, margin, width, height } = result.model;
return {
content: this._fromProtocolQuad(content),
padding: this._fromProtocolQuad(padding),
border: this._fromProtocolQuad(border),
margin: this._fromProtocolQuad(margin),
width,
height,
};
}
async screenshot(options = {}) {
let needsViewportReset = false;
let boundingBox = await this.boundingBox();
helper_1.assert(boundingBox, 'Node is either not visible or not an HTMLElement');
const viewport = this._page.viewport();
if (viewport &&
(boundingBox.width > viewport.width ||
boundingBox.height > viewport.height)) {
const newViewport = {
width: Math.max(viewport.width, Math.ceil(boundingBox.width)),
height: Math.max(viewport.height, Math.ceil(boundingBox.height)),
};
await this._page.setViewport(Object.assign({}, viewport, newViewport));
needsViewportReset = true;
}
await this._scrollIntoViewIfNeeded();
boundingBox = await this.boundingBox();
helper_1.assert(boundingBox, 'Node is either not visible or not an HTMLElement');
helper_1.assert(boundingBox.width !== 0, 'Node has 0 width.');
helper_1.assert(boundingBox.height !== 0, 'Node has 0 height.');
const { layoutViewport: { pageX, pageY }, } = await this._client.send('Page.getLayoutMetrics');
const clip = Object.assign({}, boundingBox);
clip.x += pageX;
clip.y += pageY;
const imageData = await this._page.screenshot(Object.assign({}, {
clip,
}, options));
if (needsViewportReset)
await this._page.setViewport(viewport);
return imageData;
}
async $(selector) {
const defaultHandler = (element, selector) => element.querySelector(selector);
const { updatedSelector, queryHandler } = QueryHandler_1.getQueryHandlerAndSelector(selector, defaultHandler);
const handle = await this.evaluateHandle(queryHandler, updatedSelector);
const element = handle.asElement();
if (element)
return element;
await handle.dispose();
return null;
}
async $$(selector) {
const defaultHandler = (element, selector) => element.querySelectorAll(selector);
const { updatedSelector, queryHandler } = QueryHandler_1.getQueryHandlerAndSelector(selector, defaultHandler);
const arrayHandle = await this.evaluateHandle(queryHandler, updatedSelector);
const properties = await arrayHandle.getProperties();
await arrayHandle.dispose();
const result = [];
for (const property of properties.values()) {
const elementHandle = property.asElement();
if (elementHandle)
result.push(elementHandle);
}
return result;
}
async $eval(selector, pageFunction, ...args) {
const elementHandle = await this.$(selector);
if (!elementHandle)
throw new Error(`Error: failed to find element matching selector "${selector}"`);
const result = await elementHandle.evaluate(pageFunction, ...args);
await elementHandle.dispose();
return result;
}
async $$eval(selector, pageFunction, ...args) {
const defaultHandler = (element, selector) => Array.from(element.querySelectorAll(selector));
const { updatedSelector, queryHandler } = QueryHandler_1.getQueryHandlerAndSelector(selector, defaultHandler);
const arrayHandle = await this.evaluateHandle(queryHandler, updatedSelector);
const result = await arrayHandle.evaluate(pageFunction, ...args);
await arrayHandle.dispose();
return result;
}
async $x(expression) {
const arrayHandle = await this.evaluateHandle((element, expression) => {
const document = element.ownerDocument || element;
const iterator = document.evaluate(expression, element, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
const array = [];
let item;
while ((item = iterator.iterateNext()))
array.push(item);
return array;
}, expression);
const properties = await arrayHandle.getProperties();
await arrayHandle.dispose();
const result = [];
for (const property of properties.values()) {
const elementHandle = property.asElement();
if (elementHandle)
result.push(elementHandle);
}
return result;
}
async isIntersectingViewport() {
return await this.evaluate(async (element) => {
const visibleRatio = await new Promise((resolve) => {
const observer = new IntersectionObserver((entries) => {
resolve(entries[0].intersectionRatio);
observer.disconnect();
});
observer.observe(element);
});
return visibleRatio > 0;
});
}
}
exports.ElementHandle = ElementHandle;
function computeQuadArea(quad) {
// Compute sum of all directed areas of adjacent triangles
// https://en.wikipedia.org/wiki/Polygon#Simple_polygons
let area = 0;
for (let i = 0; i < quad.length; ++i) {
const p1 = quad[i];
const p2 = quad[(i + 1) % quad.length];
area += (p1.x * p2.y - p2.x * p1.y) / 2;
}
return Math.abs(area);
}
+530
View File
@@ -0,0 +1,530 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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 os = require("os");
const path = require("path");
const http = require("http");
const https = require("https");
const URL = require("url");
const fs = require("fs");
const BrowserFetcher_1 = require("./BrowserFetcher");
const Connection_1 = require("./Connection");
const Browser_1 = require("./Browser");
const helper_1 = require("./helper");
const WebSocketTransport_1 = require("./WebSocketTransport");
const BrowserRunner_1 = require("./launcher/BrowserRunner");
const mkdtempAsync = helper_1.helper.promisify(fs.mkdtemp);
const writeFileAsync = helper_1.helper.promisify(fs.writeFile);
class ChromeLauncher {
constructor(projectRoot, preferredRevision, isPuppeteerCore) {
this._projectRoot = projectRoot;
this._preferredRevision = preferredRevision;
this._isPuppeteerCore = isPuppeteerCore;
}
async launch(options = {}) {
const { ignoreDefaultArgs = false, args = [], dumpio = false, executablePath = null, pipe = false, env = process.env, handleSIGINT = true, handleSIGTERM = true, handleSIGHUP = true, ignoreHTTPSErrors = false, defaultViewport = { width: 800, height: 600 }, slowMo = 0, timeout = 30000, } = options;
const profilePath = path.join(os.tmpdir(), 'puppeteer_dev_chrome_profile-');
const chromeArguments = [];
if (!ignoreDefaultArgs)
chromeArguments.push(...this.defaultArgs(options));
else if (Array.isArray(ignoreDefaultArgs))
chromeArguments.push(...this.defaultArgs(options).filter((arg) => !ignoreDefaultArgs.includes(arg)));
else
chromeArguments.push(...args);
let temporaryUserDataDir = null;
if (!chromeArguments.some((argument) => argument.startsWith('--remote-debugging-')))
chromeArguments.push(pipe ? '--remote-debugging-pipe' : '--remote-debugging-port=0');
if (!chromeArguments.some((arg) => arg.startsWith('--user-data-dir'))) {
temporaryUserDataDir = await mkdtempAsync(profilePath);
chromeArguments.push(`--user-data-dir=${temporaryUserDataDir}`);
}
let chromeExecutable = executablePath;
if (!executablePath) {
const { missingText, executablePath } = resolveExecutablePath(this);
if (missingText)
throw new Error(missingText);
chromeExecutable = executablePath;
}
const usePipe = chromeArguments.includes('--remote-debugging-pipe');
const runner = new BrowserRunner_1.BrowserRunner(chromeExecutable, chromeArguments, temporaryUserDataDir);
runner.start({
handleSIGHUP,
handleSIGTERM,
handleSIGINT,
dumpio,
env,
pipe: usePipe,
});
try {
const connection = await runner.setupConnection({
usePipe,
timeout,
slowMo,
preferredRevision: this._preferredRevision,
});
const browser = await Browser_1.Browser.create(connection, [], ignoreHTTPSErrors, defaultViewport, runner.proc, runner.close.bind(runner));
await browser.waitForTarget((t) => t.type() === 'page');
return browser;
}
catch (error) {
runner.kill();
throw error;
}
}
/**
* @param {!Launcher.ChromeArgOptions=} options
* @return {!Array<string>}
*/
defaultArgs(options = {}) {
const chromeArguments = [
'--disable-background-networking',
'--enable-features=NetworkService,NetworkServiceInProcess',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-breakpad',
'--disable-client-side-phishing-detection',
'--disable-component-extensions-with-background-pages',
'--disable-default-apps',
'--disable-dev-shm-usage',
'--disable-extensions',
'--disable-features=TranslateUI',
'--disable-hang-monitor',
'--disable-ipc-flooding-protection',
'--disable-popup-blocking',
'--disable-prompt-on-repost',
'--disable-renderer-backgrounding',
'--disable-sync',
'--force-color-profile=srgb',
'--metrics-recording-only',
'--no-first-run',
'--enable-automation',
'--password-store=basic',
'--use-mock-keychain',
];
const { devtools = false, headless = !devtools, args = [], userDataDir = null, } = options;
if (userDataDir)
chromeArguments.push(`--user-data-dir=${userDataDir}`);
if (devtools)
chromeArguments.push('--auto-open-devtools-for-tabs');
if (headless) {
chromeArguments.push('--headless', '--hide-scrollbars', '--mute-audio');
}
if (args.every((arg) => arg.startsWith('-')))
chromeArguments.push('about:blank');
chromeArguments.push(...args);
return chromeArguments;
}
executablePath() {
return resolveExecutablePath(this).executablePath;
}
get product() {
return 'chrome';
}
async connect(options) {
const { browserWSEndpoint, browserURL, ignoreHTTPSErrors = false, defaultViewport = { width: 800, height: 600 }, transport, slowMo = 0, } = options;
helper_1.assert(Number(!!browserWSEndpoint) +
Number(!!browserURL) +
Number(!!transport) ===
1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect');
let connection = null;
if (transport) {
connection = new Connection_1.Connection('', transport, slowMo);
}
else if (browserWSEndpoint) {
const connectionTransport = await WebSocketTransport_1.WebSocketTransport.create(browserWSEndpoint);
connection = new Connection_1.Connection(browserWSEndpoint, connectionTransport, slowMo);
}
else if (browserURL) {
const connectionURL = await getWSEndpoint(browserURL);
const connectionTransport = await WebSocketTransport_1.WebSocketTransport.create(connectionURL);
connection = new Connection_1.Connection(connectionURL, connectionTransport, slowMo);
}
const { browserContextIds } = await connection.send('Target.getBrowserContexts');
return Browser_1.Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, null, () => connection.send('Browser.close').catch(helper_1.debugError));
}
}
class FirefoxLauncher {
constructor(projectRoot, preferredRevision, isPuppeteerCore) {
this._projectRoot = projectRoot;
this._preferredRevision = preferredRevision;
this._isPuppeteerCore = isPuppeteerCore;
}
async launch(options = {}) {
const { ignoreDefaultArgs = false, args = [], dumpio = false, executablePath = null, pipe = false, env = process.env, handleSIGINT = true, handleSIGTERM = true, handleSIGHUP = true, ignoreHTTPSErrors = false, defaultViewport = { width: 800, height: 600 }, slowMo = 0, timeout = 30000, extraPrefsFirefox = {}, } = options;
const firefoxArguments = [];
if (!ignoreDefaultArgs)
firefoxArguments.push(...this.defaultArgs(options));
else if (Array.isArray(ignoreDefaultArgs))
firefoxArguments.push(...this.defaultArgs(options).filter((arg) => !ignoreDefaultArgs.includes(arg)));
else
firefoxArguments.push(...args);
if (!firefoxArguments.some((argument) => argument.startsWith('--remote-debugging-')))
firefoxArguments.push('--remote-debugging-port=0');
let temporaryUserDataDir = null;
if (!firefoxArguments.includes('-profile') &&
!firefoxArguments.includes('--profile')) {
temporaryUserDataDir = await this._createProfile(extraPrefsFirefox);
firefoxArguments.push('--profile');
firefoxArguments.push(temporaryUserDataDir);
}
await this._updateRevision();
let firefoxExecutable = executablePath;
if (!executablePath) {
const { missingText, executablePath } = resolveExecutablePath(this);
if (missingText)
throw new Error(missingText);
firefoxExecutable = executablePath;
}
const runner = new BrowserRunner_1.BrowserRunner(firefoxExecutable, firefoxArguments, temporaryUserDataDir);
runner.start({
handleSIGHUP,
handleSIGTERM,
handleSIGINT,
dumpio,
env,
pipe,
});
try {
const connection = await runner.setupConnection({
usePipe: pipe,
timeout,
slowMo,
preferredRevision: this._preferredRevision,
});
const browser = await Browser_1.Browser.create(connection, [], ignoreHTTPSErrors, defaultViewport, runner.proc, runner.close.bind(runner));
await browser.waitForTarget((t) => t.type() === 'page');
return browser;
}
catch (error) {
runner.kill();
throw error;
}
}
async connect(options) {
const { browserWSEndpoint, browserURL, ignoreHTTPSErrors = false, defaultViewport = { width: 800, height: 600 }, transport, slowMo = 0, } = options;
helper_1.assert(Number(!!browserWSEndpoint) +
Number(!!browserURL) +
Number(!!transport) ===
1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect');
let connection = null;
if (transport) {
connection = new Connection_1.Connection('', transport, slowMo);
}
else if (browserWSEndpoint) {
const connectionTransport = await WebSocketTransport_1.WebSocketTransport.create(browserWSEndpoint);
connection = new Connection_1.Connection(browserWSEndpoint, connectionTransport, slowMo);
}
else if (browserURL) {
const connectionURL = await getWSEndpoint(browserURL);
const connectionTransport = await WebSocketTransport_1.WebSocketTransport.create(connectionURL);
connection = new Connection_1.Connection(connectionURL, connectionTransport, slowMo);
}
const { browserContextIds } = await connection.send('Target.getBrowserContexts');
return Browser_1.Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, null, () => connection.send('Browser.close').catch(helper_1.debugError));
}
executablePath() {
return resolveExecutablePath(this).executablePath;
}
async _updateRevision() {
// replace 'latest' placeholder with actual downloaded revision
if (this._preferredRevision === 'latest') {
const browserFetcher = new BrowserFetcher_1.BrowserFetcher(this._projectRoot, {
product: this.product,
});
const localRevisions = await browserFetcher.localRevisions();
if (localRevisions[0])
this._preferredRevision = localRevisions[0];
}
}
get product() {
return 'firefox';
}
defaultArgs(options = {}) {
const firefoxArguments = ['--no-remote', '--foreground'];
const { devtools = false, headless = !devtools, args = [], userDataDir = null, } = options;
if (userDataDir) {
firefoxArguments.push('--profile');
firefoxArguments.push(userDataDir);
}
if (headless)
firefoxArguments.push('--headless');
if (devtools)
firefoxArguments.push('--devtools');
if (args.every((arg) => arg.startsWith('-')))
firefoxArguments.push('about:blank');
firefoxArguments.push(...args);
return firefoxArguments;
}
async _createProfile(extraPrefs) {
const profilePath = await mkdtempAsync(path.join(os.tmpdir(), 'puppeteer_dev_firefox_profile-'));
const prefsJS = [];
const userJS = [];
const server = 'dummy.test';
const defaultPreferences = {
// Make sure Shield doesn't hit the network.
'app.normandy.api_url': '',
// Disable Firefox old build background check
'app.update.checkInstallTime': false,
// Disable automatically upgrading Firefox
'app.update.disabledForTesting': true,
// Increase the APZ content response timeout to 1 minute
'apz.content_response_timeout': 60000,
// Prevent various error message on the console
// jest-puppeteer asserts that no error message is emitted by the console
'browser.contentblocking.features.standard': '-tp,tpPrivate,cookieBehavior0,-cm,-fp',
// Enable the dump function: which sends messages to the system
// console
// https://bugzilla.mozilla.org/show_bug.cgi?id=1543115
'browser.dom.window.dump.enabled': true,
// Disable topstories
'browser.newtabpage.activity-stream.feeds.section.topstories': false,
// Always display a blank page
'browser.newtabpage.enabled': false,
// Background thumbnails in particular cause grief: and disabling
// thumbnails in general cannot hurt
'browser.pagethumbnails.capturing_disabled': true,
// Disable safebrowsing components.
'browser.safebrowsing.blockedURIs.enabled': false,
'browser.safebrowsing.downloads.enabled': false,
'browser.safebrowsing.malware.enabled': false,
'browser.safebrowsing.passwords.enabled': false,
'browser.safebrowsing.phishing.enabled': false,
// Disable updates to search engines.
'browser.search.update': false,
// Do not restore the last open set of tabs if the browser has crashed
'browser.sessionstore.resume_from_crash': false,
// Skip check for default browser on startup
'browser.shell.checkDefaultBrowser': false,
// Disable newtabpage
'browser.startup.homepage': 'about:blank',
// Do not redirect user when a milstone upgrade of Firefox is detected
'browser.startup.homepage_override.mstone': 'ignore',
// Start with a blank page about:blank
'browser.startup.page': 0,
// Do not allow background tabs to be zombified on Android: otherwise for
// tests that open additional tabs: the test harness tab itself might get
// unloaded
'browser.tabs.disableBackgroundZombification': false,
// Do not warn when closing all other open tabs
'browser.tabs.warnOnCloseOtherTabs': false,
// Do not warn when multiple tabs will be opened
'browser.tabs.warnOnOpen': false,
// Disable the UI tour.
'browser.uitour.enabled': false,
// Turn off search suggestions in the location bar so as not to trigger
// network connections.
'browser.urlbar.suggest.searches': false,
// Disable first run splash page on Windows 10
'browser.usedOnWindows10.introURL': '',
// Do not warn on quitting Firefox
'browser.warnOnQuit': false,
// Do not show datareporting policy notifications which can
// interfere with tests
'datareporting.healthreport.about.reportUrl': `http://${server}/dummy/abouthealthreport/`,
'datareporting.healthreport.documentServerURI': `http://${server}/dummy/healthreport/`,
'datareporting.healthreport.logging.consoleEnabled': false,
'datareporting.healthreport.service.enabled': false,
'datareporting.healthreport.service.firstRun': false,
'datareporting.healthreport.uploadEnabled': false,
'datareporting.policy.dataSubmissionEnabled': false,
'datareporting.policy.dataSubmissionPolicyAccepted': false,
'datareporting.policy.dataSubmissionPolicyBypassNotification': true,
// DevTools JSONViewer sometimes fails to load dependencies with its require.js.
// This doesn't affect Puppeteer but spams console (Bug 1424372)
'devtools.jsonview.enabled': false,
// Disable popup-blocker
'dom.disable_open_during_load': false,
// Enable the support for File object creation in the content process
// Required for |Page.setFileInputFiles| protocol method.
'dom.file.createInChild': true,
// Disable the ProcessHangMonitor
'dom.ipc.reportProcessHangs': false,
// Disable slow script dialogues
'dom.max_chrome_script_run_time': 0,
'dom.max_script_run_time': 0,
// Only load extensions from the application and user profile
// AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
'extensions.autoDisableScopes': 0,
'extensions.enabledScopes': 5,
// Disable metadata caching for installed add-ons by default
'extensions.getAddons.cache.enabled': false,
// Disable installing any distribution extensions or add-ons.
'extensions.installDistroAddons': false,
// Disabled screenshots extension
'extensions.screenshots.disabled': true,
// Turn off extension updates so they do not bother tests
'extensions.update.enabled': false,
// Turn off extension updates so they do not bother tests
'extensions.update.notifyUser': false,
// Make sure opening about:addons will not hit the network
'extensions.webservice.discoverURL': `http://${server}/dummy/discoveryURL`,
// Allow the application to have focus even it runs in the background
'focusmanager.testmode': true,
// Disable useragent updates
'general.useragent.updates.enabled': false,
// Always use network provider for geolocation tests so we bypass the
// macOS dialog raised by the corelocation provider
'geo.provider.testing': true,
// Do not scan Wifi
'geo.wifi.scan': false,
// No hang monitor
'hangmonitor.timeout': 0,
// Show chrome errors and warnings in the error console
'javascript.options.showInConsole': true,
// Disable download and usage of OpenH264: and Widevine plugins
'media.gmp-manager.updateEnabled': false,
// Prevent various error message on the console
// jest-puppeteer asserts that no error message is emitted by the console
'network.cookie.cookieBehavior': 0,
// Do not prompt for temporary redirects
'network.http.prompt-temp-redirect': false,
// Disable speculative connections so they are not reported as leaking
// when they are hanging around
'network.http.speculative-parallel-limit': 0,
// Do not automatically switch between offline and online
'network.manage-offline-status': false,
// Make sure SNTP requests do not hit the network
'network.sntp.pools': server,
// Disable Flash.
'plugin.state.flash': 0,
'privacy.trackingprotection.enabled': false,
// Enable Remote Agent
// https://bugzilla.mozilla.org/show_bug.cgi?id=1544393
'remote.enabled': true,
// Don't do network connections for mitm priming
'security.certerrors.mitm.priming.enabled': false,
// Local documents have access to all other local documents,
// including directory listings
'security.fileuri.strict_origin_policy': false,
// Do not wait for the notification button security delay
'security.notification_enable_delay': 0,
// Ensure blocklist updates do not hit the network
'services.settings.server': `http://${server}/dummy/blocklist/`,
// Do not automatically fill sign-in forms with known usernames and
// passwords
'signon.autofillForms': false,
// Disable password capture, so that tests that include forms are not
// influenced by the presence of the persistent doorhanger notification
'signon.rememberSignons': false,
// Disable first-run welcome page
'startup.homepage_welcome_url': 'about:blank',
// Disable first-run welcome page
'startup.homepage_welcome_url.additional': '',
// Disable browser animations (tabs, fullscreen, sliding alerts)
'toolkit.cosmeticAnimations.enabled': false,
// We want to collect telemetry, but we don't want to send in the results
'toolkit.telemetry.server': `https://${server}/dummy/telemetry/`,
// Prevent starting into safe mode after application crashes
'toolkit.startup.max_resumed_crashes': -1,
};
Object.assign(defaultPreferences, extraPrefs);
for (const [key, value] of Object.entries(defaultPreferences))
userJS.push(`user_pref(${JSON.stringify(key)}, ${JSON.stringify(value)});`);
await writeFileAsync(path.join(profilePath, 'user.js'), userJS.join('\n'));
await writeFileAsync(path.join(profilePath, 'prefs.js'), prefsJS.join('\n'));
return profilePath;
}
}
function getWSEndpoint(browserURL) {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
const endpointURL = URL.resolve(browserURL, '/json/version');
const protocol = endpointURL.startsWith('https') ? https : http;
const requestOptions = Object.assign(URL.parse(endpointURL), {
method: 'GET',
});
const request = protocol.request(requestOptions, (res) => {
let data = '';
if (res.statusCode !== 200) {
// Consume response data to free up memory.
res.resume();
reject(new Error('HTTP ' + res.statusCode));
return;
}
res.setEncoding('utf8');
res.on('data', (chunk) => (data += chunk));
res.on('end', () => resolve(JSON.parse(data).webSocketDebuggerUrl));
});
request.on('error', reject);
request.end();
return promise.catch((error) => {
error.message =
`Failed to fetch browser webSocket url from ${endpointURL}: ` +
error.message;
throw error;
});
}
function resolveExecutablePath(launcher) {
// puppeteer-core doesn't take into account PUPPETEER_* env variables.
if (!launcher._isPuppeteerCore) {
const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH ||
process.env.npm_config_puppeteer_executable_path ||
process.env.npm_package_config_puppeteer_executable_path;
if (executablePath) {
const missingText = !fs.existsSync(executablePath)
? 'Tried to use PUPPETEER_EXECUTABLE_PATH env variable to launch browser but did not find any executable at: ' +
executablePath
: null;
return { executablePath, missingText };
}
}
const browserFetcher = new BrowserFetcher_1.BrowserFetcher(launcher._projectRoot, {
product: launcher.product,
});
if (!launcher._isPuppeteerCore && launcher.product === 'chrome') {
const revision = process.env['PUPPETEER_CHROMIUM_REVISION'];
if (revision) {
const revisionInfo = browserFetcher.revisionInfo(revision);
const missingText = !revisionInfo.local
? 'Tried to use PUPPETEER_CHROMIUM_REVISION env variable to launch browser but did not find executable at: ' +
revisionInfo.executablePath
: null;
return { executablePath: revisionInfo.executablePath, missingText };
}
}
const revisionInfo = browserFetcher.revisionInfo(launcher._preferredRevision);
const missingText = !revisionInfo.local
? `Could not find browser revision ${launcher._preferredRevision}. Run "npm install" or "yarn install" to download a browser binary.`
: null;
return { executablePath: revisionInfo.executablePath, missingText };
}
function Launcher(projectRoot, preferredRevision, isPuppeteerCore, product) {
// puppeteer-core doesn't take into account PUPPETEER_* env variables.
if (!product && !isPuppeteerCore)
product =
process.env.PUPPETEER_PRODUCT ||
process.env.npm_config_puppeteer_product ||
process.env.npm_package_config_puppeteer_product;
switch (product) {
case 'firefox':
return new FirefoxLauncher(projectRoot, preferredRevision, isPuppeteerCore);
case 'chrome':
default:
if (typeof product !== 'undefined' && product !== 'chrome') {
/* The user gave us an incorrect product name
* we'll default to launching Chrome, but log to the console
* to let the user know (they've probably typoed).
*/
console.warn(`Warning: unknown product name ${product}. Falling back to chrome.`);
}
return new ChromeLauncher(projectRoot, preferredRevision, isPuppeteerCore);
}
}
exports.default = Launcher;
+142
View File
@@ -0,0 +1,142 @@
"use strict";
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.LifecycleWatcher = void 0;
const helper_1 = require("./helper");
const Events_1 = require("./Events");
const Errors_1 = require("./Errors");
const puppeteerToProtocolLifecycle = new Map([
['load', 'load'],
['domcontentloaded', 'DOMContentLoaded'],
['networkidle0', 'networkIdle'],
['networkidle2', 'networkAlmostIdle'],
]);
class LifecycleWatcher {
constructor(frameManager, frame, waitUntil, timeout) {
if (Array.isArray(waitUntil))
waitUntil = waitUntil.slice();
else if (typeof waitUntil === 'string')
waitUntil = [waitUntil];
this._expectedLifecycle = waitUntil.map((value) => {
const protocolEvent = puppeteerToProtocolLifecycle.get(value);
helper_1.assert(protocolEvent, 'Unknown value for options.waitUntil: ' + value);
return protocolEvent;
});
this._frameManager = frameManager;
this._frame = frame;
this._initialLoaderId = frame._loaderId;
this._timeout = timeout;
this._navigationRequest = null;
this._eventListeners = [
helper_1.helper.addEventListener(frameManager._client, Events_1.Events.CDPSession.Disconnected, () => this._terminate(new Error('Navigation failed because browser has disconnected!'))),
helper_1.helper.addEventListener(this._frameManager, Events_1.Events.FrameManager.LifecycleEvent, this._checkLifecycleComplete.bind(this)),
helper_1.helper.addEventListener(this._frameManager, Events_1.Events.FrameManager.FrameNavigatedWithinDocument, this._navigatedWithinDocument.bind(this)),
helper_1.helper.addEventListener(this._frameManager, Events_1.Events.FrameManager.FrameDetached, this._onFrameDetached.bind(this)),
helper_1.helper.addEventListener(this._frameManager.networkManager(), Events_1.Events.NetworkManager.Request, this._onRequest.bind(this)),
];
this._sameDocumentNavigationPromise = new Promise((fulfill) => {
this._sameDocumentNavigationCompleteCallback = fulfill;
});
this._lifecyclePromise = new Promise((fulfill) => {
this._lifecycleCallback = fulfill;
});
this._newDocumentNavigationPromise = new Promise((fulfill) => {
this._newDocumentNavigationCompleteCallback = fulfill;
});
this._timeoutPromise = this._createTimeoutPromise();
this._terminationPromise = new Promise((fulfill) => {
this._terminationCallback = fulfill;
});
this._checkLifecycleComplete();
}
_onRequest(request) {
if (request.frame() !== this._frame || !request.isNavigationRequest())
return;
this._navigationRequest = request;
}
_onFrameDetached(frame) {
if (this._frame === frame) {
this._terminationCallback.call(null, new Error('Navigating frame was detached'));
return;
}
this._checkLifecycleComplete();
}
navigationResponse() {
return this._navigationRequest ? this._navigationRequest.response() : null;
}
_terminate(error) {
this._terminationCallback.call(null, error);
}
sameDocumentNavigationPromise() {
return this._sameDocumentNavigationPromise;
}
newDocumentNavigationPromise() {
return this._newDocumentNavigationPromise;
}
lifecyclePromise() {
return this._lifecyclePromise;
}
timeoutOrTerminationPromise() {
return Promise.race([this._timeoutPromise, this._terminationPromise]);
}
_createTimeoutPromise() {
if (!this._timeout)
return new Promise(() => { });
const errorMessage = 'Navigation timeout of ' + this._timeout + ' ms exceeded';
return new Promise((fulfill) => (this._maximumTimer = setTimeout(fulfill, this._timeout))).then(() => new Errors_1.TimeoutError(errorMessage));
}
_navigatedWithinDocument(frame) {
if (frame !== this._frame)
return;
this._hasSameDocumentNavigation = true;
this._checkLifecycleComplete();
}
_checkLifecycleComplete() {
// We expect navigation to commit.
if (!checkLifecycle(this._frame, this._expectedLifecycle))
return;
this._lifecycleCallback();
if (this._frame._loaderId === this._initialLoaderId &&
!this._hasSameDocumentNavigation)
return;
if (this._hasSameDocumentNavigation)
this._sameDocumentNavigationCompleteCallback();
if (this._frame._loaderId !== this._initialLoaderId)
this._newDocumentNavigationCompleteCallback();
/**
* @param {!Frame} frame
* @param {!Array<string>} expectedLifecycle
* @return {boolean}
*/
function checkLifecycle(frame, expectedLifecycle) {
for (const event of expectedLifecycle) {
if (!frame._lifecycleEvents.has(event))
return false;
}
for (const child of frame.childFrames()) {
if (!checkLifecycle(child, expectedLifecycle))
return false;
}
return true;
}
}
dispose() {
helper_1.helper.removeEventListeners(this._eventListeners);
clearTimeout(this._maximumTimer);
}
}
exports.LifecycleWatcher = LifecycleWatcher;
+252
View File
@@ -0,0 +1,252 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkManager = void 0;
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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 EventEmitter = require("events");
const helper_1 = require("./helper");
const Events_1 = require("./Events");
const HTTPRequest_1 = require("./HTTPRequest");
const HTTPResponse_1 = require("./HTTPResponse");
class NetworkManager extends EventEmitter {
constructor(client, ignoreHTTPSErrors, frameManager) {
super();
this._requestIdToRequest = new Map();
this._requestIdToRequestWillBeSentEvent = new Map();
this._extraHTTPHeaders = {};
this._offline = false;
this._credentials = null;
this._attemptedAuthentications = new Set();
this._userRequestInterceptionEnabled = false;
this._protocolRequestInterceptionEnabled = false;
this._userCacheDisabled = false;
this._requestIdToInterceptionId = new Map();
this._client = client;
this._ignoreHTTPSErrors = ignoreHTTPSErrors;
this._frameManager = frameManager;
this._client.on('Fetch.requestPaused', this._onRequestPaused.bind(this));
this._client.on('Fetch.authRequired', this._onAuthRequired.bind(this));
this._client.on('Network.requestWillBeSent', this._onRequestWillBeSent.bind(this));
this._client.on('Network.requestServedFromCache', this._onRequestServedFromCache.bind(this));
this._client.on('Network.responseReceived', this._onResponseReceived.bind(this));
this._client.on('Network.loadingFinished', this._onLoadingFinished.bind(this));
this._client.on('Network.loadingFailed', this._onLoadingFailed.bind(this));
}
async initialize() {
await this._client.send('Network.enable');
if (this._ignoreHTTPSErrors)
await this._client.send('Security.setIgnoreCertificateErrors', {
ignore: true,
});
}
async authenticate(credentials) {
this._credentials = credentials;
await this._updateProtocolRequestInterception();
}
async setExtraHTTPHeaders(extraHTTPHeaders) {
this._extraHTTPHeaders = {};
for (const key of Object.keys(extraHTTPHeaders)) {
const value = extraHTTPHeaders[key];
helper_1.assert(helper_1.helper.isString(value), `Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
this._extraHTTPHeaders[key.toLowerCase()] = value;
}
await this._client.send('Network.setExtraHTTPHeaders', {
headers: this._extraHTTPHeaders,
});
}
extraHTTPHeaders() {
return Object.assign({}, this._extraHTTPHeaders);
}
async setOfflineMode(value) {
if (this._offline === value)
return;
this._offline = value;
await this._client.send('Network.emulateNetworkConditions', {
offline: this._offline,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
});
}
async setUserAgent(userAgent) {
await this._client.send('Network.setUserAgentOverride', { userAgent });
}
async setCacheEnabled(enabled) {
this._userCacheDisabled = !enabled;
await this._updateProtocolCacheDisabled();
}
async setRequestInterception(value) {
this._userRequestInterceptionEnabled = value;
await this._updateProtocolRequestInterception();
}
async _updateProtocolRequestInterception() {
const enabled = this._userRequestInterceptionEnabled || !!this._credentials;
if (enabled === this._protocolRequestInterceptionEnabled)
return;
this._protocolRequestInterceptionEnabled = enabled;
if (enabled) {
await Promise.all([
this._updateProtocolCacheDisabled(),
this._client.send('Fetch.enable', {
handleAuthRequests: true,
patterns: [{ urlPattern: '*' }],
}),
]);
}
else {
await Promise.all([
this._updateProtocolCacheDisabled(),
this._client.send('Fetch.disable'),
]);
}
}
async _updateProtocolCacheDisabled() {
await this._client.send('Network.setCacheDisabled', {
cacheDisabled: this._userCacheDisabled || this._protocolRequestInterceptionEnabled,
});
}
_onRequestWillBeSent(event) {
// Request interception doesn't happen for data URLs with Network Service.
if (this._protocolRequestInterceptionEnabled &&
!event.request.url.startsWith('data:')) {
const requestId = event.requestId;
const interceptionId = this._requestIdToInterceptionId.get(requestId);
if (interceptionId) {
this._onRequest(event, interceptionId);
this._requestIdToInterceptionId.delete(requestId);
}
else {
this._requestIdToRequestWillBeSentEvent.set(event.requestId, event);
}
return;
}
this._onRequest(event, null);
}
/**
* @param {!Protocol.Fetch.authRequiredPayload} event
*/
_onAuthRequired(event) {
let response = 'Default';
if (this._attemptedAuthentications.has(event.requestId)) {
response = 'CancelAuth';
}
else if (this._credentials) {
response = 'ProvideCredentials';
this._attemptedAuthentications.add(event.requestId);
}
const { username, password } = this._credentials || {
username: undefined,
password: undefined,
};
this._client
.send('Fetch.continueWithAuth', {
requestId: event.requestId,
authChallengeResponse: { response, username, password },
})
.catch(helper_1.debugError);
}
_onRequestPaused(event) {
if (!this._userRequestInterceptionEnabled &&
this._protocolRequestInterceptionEnabled) {
this._client
.send('Fetch.continueRequest', {
requestId: event.requestId,
})
.catch(helper_1.debugError);
}
const requestId = event.networkId;
const interceptionId = event.requestId;
if (requestId && this._requestIdToRequestWillBeSentEvent.has(requestId)) {
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId);
this._onRequest(requestWillBeSentEvent, interceptionId);
this._requestIdToRequestWillBeSentEvent.delete(requestId);
}
else {
this._requestIdToInterceptionId.set(requestId, interceptionId);
}
}
_onRequest(event, interceptionId) {
let redirectChain = [];
if (event.redirectResponse) {
const request = this._requestIdToRequest.get(event.requestId);
// If we connect late to the target, we could have missed the requestWillBeSent event.
if (request) {
this._handleRequestRedirect(request, event.redirectResponse);
redirectChain = request._redirectChain;
}
}
const frame = event.frameId
? this._frameManager.frame(event.frameId)
: null;
const request = new HTTPRequest_1.HTTPRequest(this._client, frame, interceptionId, this._userRequestInterceptionEnabled, event, redirectChain);
this._requestIdToRequest.set(event.requestId, request);
this.emit(Events_1.Events.NetworkManager.Request, request);
}
_onRequestServedFromCache(event) {
const request = this._requestIdToRequest.get(event.requestId);
if (request)
request._fromMemoryCache = true;
}
_handleRequestRedirect(request, responsePayload) {
const response = new HTTPResponse_1.HTTPResponse(this._client, request, responsePayload);
request._response = response;
request._redirectChain.push(request);
response._resolveBody(new Error('Response body is unavailable for redirect responses'));
this._requestIdToRequest.delete(request._requestId);
this._attemptedAuthentications.delete(request._interceptionId);
this.emit(Events_1.Events.NetworkManager.Response, response);
this.emit(Events_1.Events.NetworkManager.RequestFinished, request);
}
_onResponseReceived(event) {
const request = this._requestIdToRequest.get(event.requestId);
// FileUpload sends a response without a matching request.
if (!request)
return;
const response = new HTTPResponse_1.HTTPResponse(this._client, request, event.response);
request._response = response;
this.emit(Events_1.Events.NetworkManager.Response, response);
}
_onLoadingFinished(event) {
const request = this._requestIdToRequest.get(event.requestId);
// For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469
if (!request)
return;
// Under certain conditions we never get the Network.responseReceived
// event from protocol. @see https://crbug.com/883475
if (request.response())
request.response()._resolveBody(null);
this._requestIdToRequest.delete(request._requestId);
this._attemptedAuthentications.delete(request._interceptionId);
this.emit(Events_1.Events.NetworkManager.RequestFinished, request);
}
_onLoadingFailed(event) {
const request = this._requestIdToRequest.get(event.requestId);
// For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469
if (!request)
return;
request._failureText = event.errorText;
const response = request.response();
if (response)
response._resolveBody(null);
this._requestIdToRequest.delete(request._requestId);
this._attemptedAuthentications.delete(request._interceptionId);
this.emit(Events_1.Events.NetworkManager.RequestFailed, request);
}
}
exports.NetworkManager = NetworkManager;
+859
View File
@@ -0,0 +1,859 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Page = void 0;
const fs = require("fs");
const EventEmitter = require("events");
const mime = require("mime");
const Events_1 = require("./Events");
const Connection_1 = require("./Connection");
const Dialog_1 = require("./Dialog");
const EmulationManager_1 = require("./EmulationManager");
const FrameManager_1 = require("./FrameManager");
const Input_1 = require("./Input");
const Tracing_1 = require("./Tracing");
const helper_1 = require("./helper");
const Coverage_1 = require("./Coverage");
const WebWorker_1 = require("./WebWorker");
const JSHandle_1 = require("./JSHandle");
const Accessibility_1 = require("./Accessibility");
const TimeoutSettings_1 = require("./TimeoutSettings");
const FileChooser_1 = require("./FileChooser");
const ConsoleMessage_1 = require("./ConsoleMessage");
const writeFileAsync = helper_1.helper.promisify(fs.writeFile);
const paperFormats = {
letter: { width: 8.5, height: 11 },
legal: { width: 8.5, height: 14 },
tabloid: { width: 11, height: 17 },
ledger: { width: 17, height: 11 },
a0: { width: 33.1, height: 46.8 },
a1: { width: 23.4, height: 33.1 },
a2: { width: 16.54, height: 23.4 },
a3: { width: 11.7, height: 16.54 },
a4: { width: 8.27, height: 11.7 },
a5: { width: 5.83, height: 8.27 },
a6: { width: 4.13, height: 5.83 },
};
var VisionDeficiency;
(function (VisionDeficiency) {
VisionDeficiency["none"] = "none";
VisionDeficiency["achromatopsia"] = "achromatopsia";
VisionDeficiency["blurredVision"] = "blurredVision";
VisionDeficiency["deuteranopia"] = "deuteranopia";
VisionDeficiency["protanopia"] = "protanopia";
VisionDeficiency["tritanopia"] = "tritanopia";
})(VisionDeficiency || (VisionDeficiency = {}));
class ScreenshotTaskQueue {
constructor() {
this._chain = Promise.resolve(undefined);
}
postTask(task) {
const result = this._chain.then(task);
this._chain = result.catch(() => { });
return result;
}
}
class Page extends EventEmitter {
constructor(client, target, ignoreHTTPSErrors) {
super();
this._closed = false;
this._timeoutSettings = new TimeoutSettings_1.TimeoutSettings();
this._pageBindings = new Map();
this._javascriptEnabled = true;
this._workers = new Map();
// TODO: improve this typedef - it's a function that takes a file chooser or something?
this._fileChooserInterceptors = new Set();
this._client = client;
this._target = target;
this._keyboard = new Input_1.Keyboard(client);
this._mouse = new Input_1.Mouse(client, this._keyboard);
this._touchscreen = new Input_1.Touchscreen(client, this._keyboard);
this._accessibility = new Accessibility_1.Accessibility(client);
this._frameManager = new FrameManager_1.FrameManager(client, this, ignoreHTTPSErrors, this._timeoutSettings);
this._emulationManager = new EmulationManager_1.EmulationManager(client);
this._tracing = new Tracing_1.Tracing(client);
this._coverage = new Coverage_1.Coverage(client);
this._screenshotTaskQueue = new ScreenshotTaskQueue();
this._viewport = null;
client.on('Target.attachedToTarget', (event) => {
if (event.targetInfo.type !== 'worker') {
// If we don't detach from service workers, they will never die.
client
.send('Target.detachFromTarget', {
sessionId: event.sessionId,
})
.catch(helper_1.debugError);
return;
}
const session = Connection_1.Connection.fromSession(client).session(event.sessionId);
const worker = new WebWorker_1.WebWorker(session, event.targetInfo.url, this._addConsoleMessage.bind(this), this._handleException.bind(this));
this._workers.set(event.sessionId, worker);
this.emit(Events_1.Events.Page.WorkerCreated, worker);
});
client.on('Target.detachedFromTarget', (event) => {
const worker = this._workers.get(event.sessionId);
if (!worker)
return;
this.emit(Events_1.Events.Page.WorkerDestroyed, worker);
this._workers.delete(event.sessionId);
});
this._frameManager.on(Events_1.Events.FrameManager.FrameAttached, (event) => this.emit(Events_1.Events.Page.FrameAttached, event));
this._frameManager.on(Events_1.Events.FrameManager.FrameDetached, (event) => this.emit(Events_1.Events.Page.FrameDetached, event));
this._frameManager.on(Events_1.Events.FrameManager.FrameNavigated, (event) => this.emit(Events_1.Events.Page.FrameNavigated, event));
const networkManager = this._frameManager.networkManager();
networkManager.on(Events_1.Events.NetworkManager.Request, (event) => this.emit(Events_1.Events.Page.Request, event));
networkManager.on(Events_1.Events.NetworkManager.Response, (event) => this.emit(Events_1.Events.Page.Response, event));
networkManager.on(Events_1.Events.NetworkManager.RequestFailed, (event) => this.emit(Events_1.Events.Page.RequestFailed, event));
networkManager.on(Events_1.Events.NetworkManager.RequestFinished, (event) => this.emit(Events_1.Events.Page.RequestFinished, event));
this._fileChooserInterceptors = new Set();
client.on('Page.domContentEventFired', () => this.emit(Events_1.Events.Page.DOMContentLoaded));
client.on('Page.loadEventFired', () => this.emit(Events_1.Events.Page.Load));
client.on('Runtime.consoleAPICalled', (event) => this._onConsoleAPI(event));
client.on('Runtime.bindingCalled', (event) => this._onBindingCalled(event));
client.on('Page.javascriptDialogOpening', (event) => this._onDialog(event));
client.on('Runtime.exceptionThrown', (exception) => this._handleException(exception.exceptionDetails));
client.on('Inspector.targetCrashed', () => this._onTargetCrashed());
client.on('Performance.metrics', (event) => this._emitMetrics(event));
client.on('Log.entryAdded', (event) => this._onLogEntryAdded(event));
client.on('Page.fileChooserOpened', (event) => this._onFileChooser(event));
this._target._isClosedPromise.then(() => {
this.emit(Events_1.Events.Page.Close);
this._closed = true;
});
}
static async create(client, target, ignoreHTTPSErrors, defaultViewport) {
const page = new Page(client, target, ignoreHTTPSErrors);
await page._initialize();
if (defaultViewport)
await page.setViewport(defaultViewport);
return page;
}
async _initialize() {
await Promise.all([
this._frameManager.initialize(),
this._client.send('Target.setAutoAttach', {
autoAttach: true,
waitForDebuggerOnStart: false,
flatten: true,
}),
this._client.send('Performance.enable', {}),
this._client.send('Log.enable', {}),
]);
}
async _onFileChooser(event) {
if (!this._fileChooserInterceptors.size)
return;
const frame = this._frameManager.frame(event.frameId);
const context = await frame.executionContext();
const element = await context._adoptBackendNodeId(event.backendNodeId);
const interceptors = Array.from(this._fileChooserInterceptors);
this._fileChooserInterceptors.clear();
const fileChooser = new FileChooser_1.FileChooser(element, event);
for (const interceptor of interceptors)
interceptor.call(null, fileChooser);
}
async waitForFileChooser(options = {}) {
if (!this._fileChooserInterceptors.size)
await this._client.send('Page.setInterceptFileChooserDialog', {
enabled: true,
});
const { timeout = this._timeoutSettings.timeout() } = options;
let callback;
const promise = new Promise((x) => (callback = x));
this._fileChooserInterceptors.add(callback);
return helper_1.helper
.waitWithTimeout(promise, 'waiting for file chooser', timeout)
.catch((error) => {
this._fileChooserInterceptors.delete(callback);
throw error;
});
}
async setGeolocation(options) {
const { longitude, latitude, accuracy = 0 } = options;
if (longitude < -180 || longitude > 180)
throw new Error(`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`);
if (latitude < -90 || latitude > 90)
throw new Error(`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`);
if (accuracy < 0)
throw new Error(`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`);
await this._client.send('Emulation.setGeolocationOverride', {
longitude,
latitude,
accuracy,
});
}
target() {
return this._target;
}
browser() {
return this._target.browser();
}
browserContext() {
return this._target.browserContext();
}
_onTargetCrashed() {
this.emit('error', new Error('Page crashed!'));
}
_onLogEntryAdded(event) {
const { level, text, args, source, url, lineNumber } = event.entry;
if (args)
args.map((arg) => helper_1.helper.releaseObject(this._client, arg));
if (source !== 'worker')
this.emit(Events_1.Events.Page.Console, new ConsoleMessage_1.ConsoleMessage(level, text, [], { url, lineNumber }));
}
mainFrame() {
return this._frameManager.mainFrame();
}
get keyboard() {
return this._keyboard;
}
get touchscreen() {
return this._touchscreen;
}
get coverage() {
return this._coverage;
}
get tracing() {
return this._tracing;
}
get accessibility() {
return this._accessibility;
}
frames() {
return this._frameManager.frames();
}
workers() {
return Array.from(this._workers.values());
}
async setRequestInterception(value) {
return this._frameManager.networkManager().setRequestInterception(value);
}
setOfflineMode(enabled) {
return this._frameManager.networkManager().setOfflineMode(enabled);
}
setDefaultNavigationTimeout(timeout) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
}
async $(selector) {
return this.mainFrame().$(selector);
}
async evaluateHandle(pageFunction, ...args) {
const context = await this.mainFrame().executionContext();
return context.evaluateHandle(pageFunction, ...args);
}
async queryObjects(prototypeHandle) {
const context = await this.mainFrame().executionContext();
return context.queryObjects(prototypeHandle);
}
async $eval(selector, pageFunction, ...args) {
return this.mainFrame().$eval(selector, pageFunction, ...args);
}
async $$eval(selector, pageFunction, ...args) {
return this.mainFrame().$$eval(selector, pageFunction, ...args);
}
async $$(selector) {
return this.mainFrame().$$(selector);
}
async $x(expression) {
return this.mainFrame().$x(expression);
}
async cookies(...urls) {
const originalCookies = (await this._client.send('Network.getCookies', {
urls: urls.length ? urls : [this.url()],
})).cookies;
const unsupportedCookieAttributes = ['priority'];
const filterUnsupportedAttributes = (cookie) => {
for (const attr of unsupportedCookieAttributes)
delete cookie[attr];
return cookie;
};
return originalCookies.map(filterUnsupportedAttributes);
}
async deleteCookie(...cookies) {
const pageURL = this.url();
for (const cookie of cookies) {
const item = Object.assign({}, cookie);
if (!cookie.url && pageURL.startsWith('http'))
item.url = pageURL;
await this._client.send('Network.deleteCookies', item);
}
}
async setCookie(...cookies) {
const pageURL = this.url();
const startsWithHTTP = pageURL.startsWith('http');
const items = cookies.map((cookie) => {
const item = Object.assign({}, cookie);
if (!item.url && startsWithHTTP)
item.url = pageURL;
helper_1.assert(item.url !== 'about:blank', `Blank page can not have cookie "${item.name}"`);
helper_1.assert(!String.prototype.startsWith.call(item.url || '', 'data:'), `Data URL page can not have cookie "${item.name}"`);
return item;
});
await this.deleteCookie(...items);
if (items.length)
await this._client.send('Network.setCookies', { cookies: items });
}
async addScriptTag(options) {
return this.mainFrame().addScriptTag(options);
}
async addStyleTag(options) {
return this.mainFrame().addStyleTag(options);
}
async exposeFunction(name, puppeteerFunction) {
if (this._pageBindings.has(name))
throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`);
this._pageBindings.set(name, puppeteerFunction);
const expression = helper_1.helper.evaluationString(addPageBinding, name);
await this._client.send('Runtime.addBinding', { name: name });
await this._client.send('Page.addScriptToEvaluateOnNewDocument', {
source: expression,
});
await Promise.all(this.frames().map((frame) => frame.evaluate(expression).catch(helper_1.debugError)));
function addPageBinding(bindingName) {
/* Cast window to any here as we're about to add properties to it
* via win[bindingName] which TypeScript doesn't like.
*/
const win = window;
const binding = win[bindingName];
win[bindingName] = (...args) => {
const me = window[bindingName];
let callbacks = me['callbacks'];
if (!callbacks) {
callbacks = new Map();
me['callbacks'] = callbacks;
}
const seq = (me['lastSeq'] || 0) + 1;
me['lastSeq'] = seq;
const promise = new Promise((resolve, reject) => callbacks.set(seq, { resolve, reject }));
binding(JSON.stringify({ name: bindingName, seq, args }));
return promise;
};
}
}
async authenticate(credentials) {
return this._frameManager.networkManager().authenticate(credentials);
}
async setExtraHTTPHeaders(headers) {
return this._frameManager.networkManager().setExtraHTTPHeaders(headers);
}
async setUserAgent(userAgent) {
return this._frameManager.networkManager().setUserAgent(userAgent);
}
async metrics() {
const response = await this._client.send('Performance.getMetrics');
return this._buildMetricsObject(response.metrics);
}
_emitMetrics(event) {
this.emit(Events_1.Events.Page.Metrics, {
title: event.title,
metrics: this._buildMetricsObject(event.metrics),
});
}
_buildMetricsObject(metrics) {
const result = {};
for (const metric of metrics || []) {
if (supportedMetrics.has(metric.name))
result[metric.name] = metric.value;
}
return result;
}
_handleException(exceptionDetails) {
const message = helper_1.helper.getExceptionMessage(exceptionDetails);
const err = new Error(message);
err.stack = ''; // Don't report clientside error with a node stack attached
this.emit(Events_1.Events.Page.PageError, err);
}
async _onConsoleAPI(event) {
if (event.executionContextId === 0) {
// DevTools protocol stores the last 1000 console messages. These
// messages are always reported even for removed execution contexts. In
// this case, they are marked with executionContextId = 0 and are
// reported upon enabling Runtime agent.
//
// Ignore these messages since:
// - there's no execution context we can use to operate with message
// arguments
// - these messages are reported before Puppeteer clients can subscribe
// to the 'console'
// page event.
//
// @see https://github.com/puppeteer/puppeteer/issues/3865
return;
}
const context = this._frameManager.executionContextById(event.executionContextId);
const values = event.args.map((arg) => JSHandle_1.createJSHandle(context, arg));
this._addConsoleMessage(event.type, values, event.stackTrace);
}
async _onBindingCalled(event) {
const { name, seq, args } = JSON.parse(event.payload);
let expression = null;
try {
const result = await this._pageBindings.get(name)(...args);
expression = helper_1.helper.evaluationString(deliverResult, name, seq, result);
}
catch (error) {
if (error instanceof Error)
expression = helper_1.helper.evaluationString(deliverError, name, seq, error.message, error.stack);
else
expression = helper_1.helper.evaluationString(deliverErrorValue, name, seq, error);
}
this._client
.send('Runtime.evaluate', {
expression,
contextId: event.executionContextId,
})
.catch(helper_1.debugError);
function deliverResult(name, seq, result) {
window[name]['callbacks'].get(seq).resolve(result);
window[name]['callbacks'].delete(seq);
}
function deliverError(name, seq, message, stack) {
const error = new Error(message);
error.stack = stack;
window[name]['callbacks'].get(seq).reject(error);
window[name]['callbacks'].delete(seq);
}
function deliverErrorValue(name, seq, value) {
window[name]['callbacks'].get(seq).reject(value);
window[name]['callbacks'].delete(seq);
}
}
_addConsoleMessage(type, args, stackTrace) {
if (!this.listenerCount(Events_1.Events.Page.Console)) {
args.forEach((arg) => arg.dispose());
return;
}
const textTokens = [];
for (const arg of args) {
const remoteObject = arg._remoteObject;
if (remoteObject.objectId)
textTokens.push(arg.toString());
else
textTokens.push(helper_1.helper.valueFromRemoteObject(remoteObject));
}
const location = stackTrace && stackTrace.callFrames.length
? {
url: stackTrace.callFrames[0].url,
lineNumber: stackTrace.callFrames[0].lineNumber,
columnNumber: stackTrace.callFrames[0].columnNumber,
}
: {};
const message = new ConsoleMessage_1.ConsoleMessage(type, textTokens.join(' '), args, location);
this.emit(Events_1.Events.Page.Console, message);
}
_onDialog(event) {
let dialogType = null;
if (event.type === 'alert')
dialogType = Dialog_1.Dialog.Type.Alert;
else if (event.type === 'confirm')
dialogType = Dialog_1.Dialog.Type.Confirm;
else if (event.type === 'prompt')
dialogType = Dialog_1.Dialog.Type.Prompt;
else if (event.type === 'beforeunload')
dialogType = Dialog_1.Dialog.Type.BeforeUnload;
helper_1.assert(dialogType, 'Unknown javascript dialog type: ' + event.type);
const dialog = new Dialog_1.Dialog(this._client, dialogType, event.message, event.defaultPrompt);
this.emit(Events_1.Events.Page.Dialog, dialog);
}
url() {
return this.mainFrame().url();
}
async content() {
return await this._frameManager.mainFrame().content();
}
async setContent(html, options) {
await this._frameManager.mainFrame().setContent(html, options);
}
async goto(url, options) {
return await this._frameManager.mainFrame().goto(url, options);
}
async reload(options) {
const result = await Promise.all([this.waitForNavigation(options), this._client.send('Page.reload')]);
return result[0];
}
async waitForNavigation(options = {}) {
return await this._frameManager.mainFrame().waitForNavigation(options);
}
_sessionClosePromise() {
if (!this._disconnectPromise)
this._disconnectPromise = new Promise((fulfill) => this._client.once(Events_1.Events.CDPSession.Disconnected, () => fulfill(new Error('Target closed'))));
return this._disconnectPromise;
}
async waitForRequest(urlOrPredicate, options = {}) {
const { timeout = this._timeoutSettings.timeout() } = options;
return helper_1.helper.waitForEvent(this._frameManager.networkManager(), Events_1.Events.NetworkManager.Request, (request) => {
if (helper_1.helper.isString(urlOrPredicate))
return urlOrPredicate === request.url();
if (typeof urlOrPredicate === 'function')
return !!urlOrPredicate(request);
return false;
}, timeout, this._sessionClosePromise());
}
async waitForResponse(urlOrPredicate, options = {}) {
const { timeout = this._timeoutSettings.timeout() } = options;
return helper_1.helper.waitForEvent(this._frameManager.networkManager(), Events_1.Events.NetworkManager.Response, (response) => {
if (helper_1.helper.isString(urlOrPredicate))
return urlOrPredicate === response.url();
if (typeof urlOrPredicate === 'function')
return !!urlOrPredicate(response);
return false;
}, timeout, this._sessionClosePromise());
}
async goBack(options) {
return this._go(-1, options);
}
async goForward(options) {
return this._go(+1, options);
}
async _go(delta, options) {
const history = await this._client.send('Page.getNavigationHistory');
const entry = history.entries[history.currentIndex + delta];
if (!entry)
return null;
const result = await Promise.all([
this.waitForNavigation(options),
this._client.send('Page.navigateToHistoryEntry', { entryId: entry.id }),
]);
return result[0];
}
async bringToFront() {
await this._client.send('Page.bringToFront');
}
async emulate(options) {
await Promise.all([
this.setViewport(options.viewport),
this.setUserAgent(options.userAgent),
]);
}
async setJavaScriptEnabled(enabled) {
if (this._javascriptEnabled === enabled)
return;
this._javascriptEnabled = enabled;
await this._client.send('Emulation.setScriptExecutionDisabled', {
value: !enabled,
});
}
async setBypassCSP(enabled) {
await this._client.send('Page.setBypassCSP', { enabled });
}
async emulateMediaType(type) {
helper_1.assert(type === 'screen' || type === 'print' || type === null, 'Unsupported media type: ' + type);
await this._client.send('Emulation.setEmulatedMedia', {
media: type || '',
});
}
async emulateMediaFeatures(features) {
if (features === null)
await this._client.send('Emulation.setEmulatedMedia', { features: null });
if (Array.isArray(features)) {
features.every((mediaFeature) => {
const name = mediaFeature.name;
helper_1.assert(/^prefers-(?:color-scheme|reduced-motion)$/.test(name), 'Unsupported media feature: ' + name);
return true;
});
await this._client.send('Emulation.setEmulatedMedia', {
features: features,
});
}
}
async emulateTimezone(timezoneId) {
try {
await this._client.send('Emulation.setTimezoneOverride', {
timezoneId: timezoneId || '',
});
}
catch (error) {
if (error.message.includes('Invalid timezone'))
throw new Error(`Invalid timezone ID: ${timezoneId}`);
throw error;
}
}
async emulateVisionDeficiency(type) {
const visionDeficiencies = new Set(Object.keys(VisionDeficiency));
try {
helper_1.assert(!type || visionDeficiencies.has(type), `Unsupported vision deficiency: ${type}`);
await this._client.send('Emulation.setEmulatedVisionDeficiency', {
type: type || 'none',
});
}
catch (error) {
throw error;
}
}
async setViewport(viewport) {
const needsReload = await this._emulationManager.emulateViewport(viewport);
this._viewport = viewport;
if (needsReload)
await this.reload();
}
viewport() {
return this._viewport;
}
async evaluate(pageFunction, ...args) {
return this._frameManager
.mainFrame()
.evaluate(pageFunction, ...args);
}
async evaluateOnNewDocument(pageFunction, ...args) {
const source = helper_1.helper.evaluationString(pageFunction, ...args);
await this._client.send('Page.addScriptToEvaluateOnNewDocument', {
source,
});
}
async setCacheEnabled(enabled = true) {
await this._frameManager.networkManager().setCacheEnabled(enabled);
}
async screenshot(options = {}) {
let screenshotType = null;
// options.type takes precedence over inferring the type from options.path
// because it may be a 0-length file with no extension created beforehand (i.e. as a temp file).
if (options.type) {
helper_1.assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type);
screenshotType = options.type;
}
else if (options.path) {
const mimeType = mime.getType(options.path);
if (mimeType === 'image/png')
screenshotType = 'png';
else if (mimeType === 'image/jpeg')
screenshotType = 'jpeg';
helper_1.assert(screenshotType, 'Unsupported screenshot mime type: ' + mimeType);
}
if (!screenshotType)
screenshotType = 'png';
if (options.quality) {
helper_1.assert(screenshotType === 'jpeg', 'options.quality is unsupported for the ' +
screenshotType +
' screenshots');
helper_1.assert(typeof options.quality === 'number', 'Expected options.quality to be a number but found ' +
typeof options.quality);
helper_1.assert(Number.isInteger(options.quality), 'Expected options.quality to be an integer');
helper_1.assert(options.quality >= 0 && options.quality <= 100, 'Expected options.quality to be between 0 and 100 (inclusive), got ' +
options.quality);
}
helper_1.assert(!options.clip || !options.fullPage, 'options.clip and options.fullPage are exclusive');
if (options.clip) {
helper_1.assert(typeof options.clip.x === 'number', 'Expected options.clip.x to be a number but found ' +
typeof options.clip.x);
helper_1.assert(typeof options.clip.y === 'number', 'Expected options.clip.y to be a number but found ' +
typeof options.clip.y);
helper_1.assert(typeof options.clip.width === 'number', 'Expected options.clip.width to be a number but found ' +
typeof options.clip.width);
helper_1.assert(typeof options.clip.height === 'number', 'Expected options.clip.height to be a number but found ' +
typeof options.clip.height);
helper_1.assert(options.clip.width !== 0, 'Expected options.clip.width not to be 0.');
helper_1.assert(options.clip.height !== 0, 'Expected options.clip.height not to be 0.');
}
return this._screenshotTaskQueue.postTask(() => this._screenshotTask(screenshotType, options));
}
async _screenshotTask(format, options) {
await this._client.send('Target.activateTarget', {
targetId: this._target._targetId,
});
let clip = options.clip ? processClip(options.clip) : undefined;
if (options.fullPage) {
const metrics = await this._client.send('Page.getLayoutMetrics');
const width = Math.ceil(metrics.contentSize.width);
const height = Math.ceil(metrics.contentSize.height);
// Overwrite clip for full page at all times.
clip = { x: 0, y: 0, width, height, scale: 1 };
const { isMobile = false, deviceScaleFactor = 1, isLandscape = false } = this._viewport || {};
const screenOrientation = isLandscape
? { angle: 90, type: 'landscapePrimary' }
: { angle: 0, type: 'portraitPrimary' };
await this._client.send('Emulation.setDeviceMetricsOverride', {
mobile: isMobile,
width,
height,
deviceScaleFactor,
screenOrientation,
});
}
const shouldSetDefaultBackground = options.omitBackground && format === 'png';
if (shouldSetDefaultBackground)
await this._client.send('Emulation.setDefaultBackgroundColorOverride', {
color: { r: 0, g: 0, b: 0, a: 0 },
});
const result = await this._client.send('Page.captureScreenshot', {
format,
quality: options.quality,
clip,
});
if (shouldSetDefaultBackground)
await this._client.send('Emulation.setDefaultBackgroundColorOverride');
if (options.fullPage && this._viewport)
await this.setViewport(this._viewport);
const buffer = options.encoding === 'base64'
? result.data
: Buffer.from(result.data, 'base64');
if (options.path)
await writeFileAsync(options.path, buffer);
return buffer;
function processClip(clip) {
const x = Math.round(clip.x);
const y = Math.round(clip.y);
const width = Math.round(clip.width + clip.x - x);
const height = Math.round(clip.height + clip.y - y);
return { x, y, width, height, scale: 1 };
}
}
async pdf(options = {}) {
const { scale = 1, displayHeaderFooter = false, headerTemplate = '', footerTemplate = '', printBackground = false, landscape = false, pageRanges = '', preferCSSPageSize = false, margin = {}, path = null, } = options;
let paperWidth = 8.5;
let paperHeight = 11;
if (options.format) {
const format = paperFormats[options.format.toLowerCase()];
helper_1.assert(format, 'Unknown paper format: ' + options.format);
paperWidth = format.width;
paperHeight = format.height;
}
else {
paperWidth = convertPrintParameterToInches(options.width) || paperWidth;
paperHeight =
convertPrintParameterToInches(options.height) || paperHeight;
}
const marginTop = convertPrintParameterToInches(margin.top) || 0;
const marginLeft = convertPrintParameterToInches(margin.left) || 0;
const marginBottom = convertPrintParameterToInches(margin.bottom) || 0;
const marginRight = convertPrintParameterToInches(margin.right) || 0;
const result = await this._client.send('Page.printToPDF', {
transferMode: 'ReturnAsStream',
landscape,
displayHeaderFooter,
headerTemplate,
footerTemplate,
printBackground,
scale,
paperWidth,
paperHeight,
marginTop,
marginBottom,
marginLeft,
marginRight,
pageRanges,
preferCSSPageSize,
});
return await helper_1.helper.readProtocolStream(this._client, result.stream, path);
}
async title() {
return this.mainFrame().title();
}
async close(options = { runBeforeUnload: undefined }) {
helper_1.assert(!!this._client._connection, 'Protocol error: Connection closed. Most likely the page has been closed.');
const runBeforeUnload = !!options.runBeforeUnload;
if (runBeforeUnload) {
await this._client.send('Page.close');
}
else {
await this._client._connection.send('Target.closeTarget', {
targetId: this._target._targetId,
});
await this._target._isClosedPromise;
}
}
isClosed() {
return this._closed;
}
get mouse() {
return this._mouse;
}
click(selector, options = {}) {
return this.mainFrame().click(selector, options);
}
focus(selector) {
return this.mainFrame().focus(selector);
}
hover(selector) {
return this.mainFrame().hover(selector);
}
select(selector, ...values) {
return this.mainFrame().select(selector, ...values);
}
tap(selector) {
return this.mainFrame().tap(selector);
}
type(selector, text, options) {
return this.mainFrame().type(selector, text, options);
}
waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
}
waitForSelector(selector, options = {}) {
return this.mainFrame().waitForSelector(selector, options);
}
waitForXPath(xpath, options = {}) {
return this.mainFrame().waitForXPath(xpath, options);
}
waitForFunction(pageFunction, options = {}, ...args) {
return this.mainFrame().waitForFunction(pageFunction, options, ...args);
}
}
exports.Page = Page;
const supportedMetrics = new Set([
'Timestamp',
'Documents',
'Frames',
'JSEventListeners',
'Nodes',
'LayoutCount',
'RecalcStyleCount',
'LayoutDuration',
'RecalcStyleDuration',
'ScriptDuration',
'TaskDuration',
'JSHeapUsedSize',
'JSHeapTotalSize',
]);
const unitToPixels = {
px: 1,
in: 96,
cm: 37.8,
mm: 3.78,
};
function convertPrintParameterToInches(parameter) {
if (typeof parameter === 'undefined')
return undefined;
let pixels;
if (helper_1.helper.isNumber(parameter)) {
// Treat numbers as pixel values to be aligned with phantom's paperSize.
pixels = /** @type {number} */ parameter;
}
else if (helper_1.helper.isString(parameter)) {
const text = /** @type {string} */ parameter;
let unit = text.substring(text.length - 2).toLowerCase();
let valueText = '';
if (unitToPixels.hasOwnProperty(unit)) {
valueText = text.substring(0, text.length - 2);
}
else {
// In case of unknown unit try to parse the whole parameter as number of pixels.
// This is consistent with phantom's paperSize behavior.
unit = 'px';
valueText = text;
}
const value = Number(valueText);
helper_1.assert(!isNaN(value), 'Failed to parse parameter value: ' + text);
pixels = value * unitToPixels[unit];
}
else {
throw new Error('page.pdf() Cannot handle parameter type: ' + typeof parameter);
}
return pixels / 96;
}
+64
View File
@@ -0,0 +1,64 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PipeTransport = void 0;
/**
* Copyright 2018 Google Inc. All rights reserved.
*
* 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 helper_1 = require("./helper");
class PipeTransport {
constructor(pipeWrite, pipeRead) {
this._pipeWrite = pipeWrite;
this._pendingMessage = '';
this._eventListeners = [
helper_1.helper.addEventListener(pipeRead, 'data', (buffer) => this._dispatch(buffer)),
helper_1.helper.addEventListener(pipeRead, 'close', () => {
if (this.onclose)
this.onclose.call(null);
}),
helper_1.helper.addEventListener(pipeRead, 'error', helper_1.debugError),
helper_1.helper.addEventListener(pipeWrite, 'error', helper_1.debugError),
];
this.onmessage = null;
this.onclose = null;
}
send(message) {
this._pipeWrite.write(message);
this._pipeWrite.write('\0');
}
_dispatch(buffer) {
let end = buffer.indexOf('\0');
if (end === -1) {
this._pendingMessage += buffer.toString();
return;
}
const message = this._pendingMessage + buffer.toString(undefined, 0, end);
if (this.onmessage)
this.onmessage.call(null, message);
let start = end + 1;
end = buffer.indexOf('\0', start);
while (end !== -1) {
if (this.onmessage)
this.onmessage.call(null, buffer.toString(undefined, start, end));
start = end + 1;
end = buffer.indexOf('\0', start);
}
this._pendingMessage = buffer.toString(undefined, start);
}
close() {
this._pipeWrite = null;
helper_1.helper.removeEventListeners(this._eventListeners);
}
}
exports.PipeTransport = PipeTransport;
+106
View File
@@ -0,0 +1,106 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Puppeteer = void 0;
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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 Launcher_1 = require("./Launcher");
const BrowserFetcher_1 = require("./BrowserFetcher");
const Errors_1 = require("./Errors");
const DeviceDescriptors_1 = require("./DeviceDescriptors");
const QueryHandler = require("./QueryHandler");
class Puppeteer {
constructor(projectRoot, preferredRevision, isPuppeteerCore, productName) {
this._changedProduct = false;
this._projectRoot = projectRoot;
this._preferredRevision = preferredRevision;
this._isPuppeteerCore = isPuppeteerCore;
// track changes to Launcher configuration via options or environment variables
this.__productName = productName;
}
launch(options = {}) {
if (options.product)
this._productName = options.product;
return this._launcher.launch(options);
}
connect(options) {
if (options.product)
this._productName = options.product;
return this._launcher.connect(options);
}
set _productName(name) {
if (this.__productName !== name)
this._changedProduct = true;
this.__productName = name;
}
get _productName() {
return this.__productName;
}
executablePath() {
return this._launcher.executablePath();
}
get _launcher() {
if (!this._lazyLauncher ||
this._lazyLauncher.product !== this._productName ||
this._changedProduct) {
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-var-requires
const packageJson = require('../package.json');
switch (this._productName) {
case 'firefox':
this._preferredRevision = packageJson.puppeteer.firefox_revision;
break;
case 'chrome':
default:
this._preferredRevision = packageJson.puppeteer.chromium_revision;
}
this._changedProduct = false;
this._lazyLauncher = Launcher_1.default(this._projectRoot, this._preferredRevision, this._isPuppeteerCore, this._productName);
}
return this._lazyLauncher;
}
get product() {
return this._launcher.product;
}
get devices() {
return DeviceDescriptors_1.devicesMap;
}
get errors() {
return Errors_1.puppeteerErrors;
}
defaultArgs(options) {
return this._launcher.defaultArgs(options);
}
createBrowserFetcher(options) {
return new BrowserFetcher_1.BrowserFetcher(this._projectRoot, options);
}
// eslint-disable-next-line @typescript-eslint/camelcase
__experimental_registerCustomQueryHandler(name, queryHandler) {
QueryHandler.registerCustomQueryHandler(name, queryHandler);
}
// eslint-disable-next-line @typescript-eslint/camelcase
__experimental_unregisterCustomQueryHandler(name) {
QueryHandler.unregisterCustomQueryHandler(name);
}
// eslint-disable-next-line @typescript-eslint/camelcase
__experimental_customQueryHandlers() {
return QueryHandler.customQueryHandlers();
}
// eslint-disable-next-line @typescript-eslint/camelcase
__experimental_clearQueryHandlers() {
QueryHandler.clearQueryHandlers();
}
}
exports.Puppeteer = Puppeteer;
+2
View File
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+66
View File
@@ -0,0 +1,66 @@
"use strict";
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getQueryHandlerAndSelector = exports.clearQueryHandlers = exports.customQueryHandlers = exports.unregisterCustomQueryHandler = exports.registerCustomQueryHandler = void 0;
const _customQueryHandlers = new Map();
function registerCustomQueryHandler(name, handler) {
if (_customQueryHandlers.get(name))
throw new Error(`A custom query handler named "${name}" already exists`);
const isValidName = /^[a-zA-Z]+$/.test(name);
if (!isValidName)
throw new Error(`Custom query handler names may only contain [a-zA-Z]`);
_customQueryHandlers.set(name, handler);
}
exports.registerCustomQueryHandler = registerCustomQueryHandler;
/**
* @param {string} name
*/
function unregisterCustomQueryHandler(name) {
_customQueryHandlers.delete(name);
}
exports.unregisterCustomQueryHandler = unregisterCustomQueryHandler;
function customQueryHandlers() {
return _customQueryHandlers;
}
exports.customQueryHandlers = customQueryHandlers;
function clearQueryHandlers() {
_customQueryHandlers.clear();
}
exports.clearQueryHandlers = clearQueryHandlers;
function getQueryHandlerAndSelector(selector, defaultQueryHandler) {
const hasCustomQueryHandler = /^[a-zA-Z]+\//.test(selector);
if (!hasCustomQueryHandler)
return { updatedSelector: selector, queryHandler: defaultQueryHandler };
const index = selector.indexOf('/');
const name = selector.slice(0, index);
const updatedSelector = selector.slice(index + 1);
const queryHandler = customQueryHandlers().get(name);
if (!queryHandler)
throw new Error(`Query set to use "${name}", but no query handler of that name was found`);
return {
updatedSelector,
queryHandler,
};
}
exports.getQueryHandlerAndSelector = getQueryHandlerAndSelector;
module.exports = {
registerCustomQueryHandler,
unregisterCustomQueryHandler,
customQueryHandlers,
getQueryHandlerAndSelector,
clearQueryHandlers,
};
+47
View File
@@ -0,0 +1,47 @@
"use strict";
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SecurityDetails = void 0;
class SecurityDetails {
constructor(securityPayload) {
this._subjectName = securityPayload.subjectName;
this._issuer = securityPayload.issuer;
this._validFrom = securityPayload.validFrom;
this._validTo = securityPayload.validTo;
this._protocol = securityPayload.protocol;
this._sanList = securityPayload.sanList;
}
subjectName() {
return this._subjectName;
}
issuer() {
return this._issuer;
}
validFrom() {
return this._validFrom;
}
validTo() {
return this._validTo;
}
protocol() {
return this._protocol;
}
subjectAlternativeNames() {
return this._sanList;
}
}
exports.SecurityDetails = SecurityDetails;
+111
View File
@@ -0,0 +1,111 @@
"use strict";
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Target = void 0;
const Events_1 = require("./Events");
const Page_1 = require("./Page");
const WebWorker_1 = require("./WebWorker");
class Target {
constructor(targetInfo, browserContext, sessionFactory, ignoreHTTPSErrors, defaultViewport) {
this._targetInfo = targetInfo;
this._browserContext = browserContext;
this._targetId = targetInfo.targetId;
this._sessionFactory = sessionFactory;
this._ignoreHTTPSErrors = ignoreHTTPSErrors;
this._defaultViewport = defaultViewport;
/** @type {?Promise<!Puppeteer.Page>} */
this._pagePromise = null;
/** @type {?Promise<!WebWorker>} */
this._workerPromise = null;
this._initializedPromise = new Promise((fulfill) => (this._initializedCallback = fulfill)).then(async (success) => {
if (!success)
return false;
const opener = this.opener();
if (!opener || !opener._pagePromise || this.type() !== 'page')
return true;
const openerPage = await opener._pagePromise;
if (!openerPage.listenerCount(Events_1.Events.Page.Popup))
return true;
const popupPage = await this.page();
openerPage.emit(Events_1.Events.Page.Popup, popupPage);
return true;
});
this._isClosedPromise = new Promise((fulfill) => (this._closedCallback = fulfill));
this._isInitialized =
this._targetInfo.type !== 'page' || this._targetInfo.url !== '';
if (this._isInitialized)
this._initializedCallback(true);
}
createCDPSession() {
return this._sessionFactory();
}
async page() {
if ((this._targetInfo.type === 'page' ||
this._targetInfo.type === 'background_page' ||
this._targetInfo.type === 'webview') &&
!this._pagePromise) {
this._pagePromise = this._sessionFactory().then((client) => Page_1.Page.create(client, this, this._ignoreHTTPSErrors, this._defaultViewport));
}
return this._pagePromise;
}
async worker() {
if (this._targetInfo.type !== 'service_worker' &&
this._targetInfo.type !== 'shared_worker')
return null;
if (!this._workerPromise) {
// TODO(einbinder): Make workers send their console logs.
this._workerPromise = this._sessionFactory().then((client) => new WebWorker_1.WebWorker(client, this._targetInfo.url, () => { } /* consoleAPICalled */, () => { } /* exceptionThrown */));
}
return this._workerPromise;
}
url() {
return this._targetInfo.url;
}
type() {
const type = this._targetInfo.type;
if (type === 'page' ||
type === 'background_page' ||
type === 'service_worker' ||
type === 'shared_worker' ||
type === 'browser' ||
type === 'webview')
return type;
return 'other';
}
browser() {
return this._browserContext.browser();
}
browserContext() {
return this._browserContext;
}
opener() {
const { openerId } = this._targetInfo;
if (!openerId)
return null;
return this.browser()._targets.get(openerId);
}
_targetInfoChanged(targetInfo) {
this._targetInfo = targetInfo;
if (!this._isInitialized &&
(this._targetInfo.type !== 'page' || this._targetInfo.url !== '')) {
this._isInitialized = true;
this._initializedCallback(true);
return;
}
}
}
exports.Target = Target;
+47
View File
@@ -0,0 +1,47 @@
"use strict";
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimeoutSettings = void 0;
const DEFAULT_TIMEOUT = 30000;
class TimeoutSettings {
constructor() {
this._defaultTimeout = null;
this._defaultNavigationTimeout = null;
}
setDefaultTimeout(timeout) {
this._defaultTimeout = timeout;
}
/**
* @param {number} timeout
*/
setDefaultNavigationTimeout(timeout) {
this._defaultNavigationTimeout = timeout;
}
navigationTimeout() {
if (this._defaultNavigationTimeout !== null)
return this._defaultNavigationTimeout;
if (this._defaultTimeout !== null)
return this._defaultTimeout;
return DEFAULT_TIMEOUT;
}
timeout() {
if (this._defaultTimeout !== null)
return this._defaultTimeout;
return DEFAULT_TIMEOUT;
}
}
exports.TimeoutSettings = TimeoutSettings;
+65
View File
@@ -0,0 +1,65 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tracing = void 0;
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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 helper_1 = require("./helper");
class Tracing {
constructor(client) {
this._recording = false;
this._path = '';
this._client = client;
}
async start(options = {}) {
helper_1.assert(!this._recording, 'Cannot start recording trace while already recording trace.');
const defaultCategories = [
'-*',
'devtools.timeline',
'v8.execute',
'disabled-by-default-devtools.timeline',
'disabled-by-default-devtools.timeline.frame',
'toplevel',
'blink.console',
'blink.user_timing',
'latencyInfo',
'disabled-by-default-devtools.timeline.stack',
'disabled-by-default-v8.cpu_profiler',
'disabled-by-default-v8.cpu_profiler.hires',
];
const { path = null, screenshots = false, categories = defaultCategories, } = options;
if (screenshots)
categories.push('disabled-by-default-devtools.screenshot');
this._path = path;
this._recording = true;
await this._client.send('Tracing.start', {
transferMode: 'ReturnAsStream',
categories: categories.join(','),
});
}
async stop() {
let fulfill;
const contentPromise = new Promise((x) => (fulfill = x));
this._client.once('Tracing.tracingComplete', (event) => {
helper_1.helper
.readProtocolStream(this._client, event.stream, this._path)
.then(fulfill);
});
await this._client.send('Tracing.end');
this._recording = false;
return contentPromise;
}
}
exports.Tracing = Tracing;
+403
View File
@@ -0,0 +1,403 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.keyDefinitions = void 0;
exports.keyDefinitions = {
'0': { keyCode: 48, key: '0', code: 'Digit0' },
'1': { keyCode: 49, key: '1', code: 'Digit1' },
'2': { keyCode: 50, key: '2', code: 'Digit2' },
'3': { keyCode: 51, key: '3', code: 'Digit3' },
'4': { keyCode: 52, key: '4', code: 'Digit4' },
'5': { keyCode: 53, key: '5', code: 'Digit5' },
'6': { keyCode: 54, key: '6', code: 'Digit6' },
'7': { keyCode: 55, key: '7', code: 'Digit7' },
'8': { keyCode: 56, key: '8', code: 'Digit8' },
'9': { keyCode: 57, key: '9', code: 'Digit9' },
Power: { key: 'Power', code: 'Power' },
Eject: { key: 'Eject', code: 'Eject' },
Abort: { keyCode: 3, code: 'Abort', key: 'Cancel' },
Help: { keyCode: 6, code: 'Help', key: 'Help' },
Backspace: { keyCode: 8, code: 'Backspace', key: 'Backspace' },
Tab: { keyCode: 9, code: 'Tab', key: 'Tab' },
Numpad5: {
keyCode: 12,
shiftKeyCode: 101,
key: 'Clear',
code: 'Numpad5',
shiftKey: '5',
location: 3,
},
NumpadEnter: {
keyCode: 13,
code: 'NumpadEnter',
key: 'Enter',
text: '\r',
location: 3,
},
Enter: { keyCode: 13, code: 'Enter', key: 'Enter', text: '\r' },
'\r': { keyCode: 13, code: 'Enter', key: 'Enter', text: '\r' },
'\n': { keyCode: 13, code: 'Enter', key: 'Enter', text: '\r' },
ShiftLeft: { keyCode: 16, code: 'ShiftLeft', key: 'Shift', location: 1 },
ShiftRight: { keyCode: 16, code: 'ShiftRight', key: 'Shift', location: 2 },
ControlLeft: {
keyCode: 17,
code: 'ControlLeft',
key: 'Control',
location: 1,
},
ControlRight: {
keyCode: 17,
code: 'ControlRight',
key: 'Control',
location: 2,
},
AltLeft: { keyCode: 18, code: 'AltLeft', key: 'Alt', location: 1 },
AltRight: { keyCode: 18, code: 'AltRight', key: 'Alt', location: 2 },
Pause: { keyCode: 19, code: 'Pause', key: 'Pause' },
CapsLock: { keyCode: 20, code: 'CapsLock', key: 'CapsLock' },
Escape: { keyCode: 27, code: 'Escape', key: 'Escape' },
Convert: { keyCode: 28, code: 'Convert', key: 'Convert' },
NonConvert: { keyCode: 29, code: 'NonConvert', key: 'NonConvert' },
Space: { keyCode: 32, code: 'Space', key: ' ' },
Numpad9: {
keyCode: 33,
shiftKeyCode: 105,
key: 'PageUp',
code: 'Numpad9',
shiftKey: '9',
location: 3,
},
PageUp: { keyCode: 33, code: 'PageUp', key: 'PageUp' },
Numpad3: {
keyCode: 34,
shiftKeyCode: 99,
key: 'PageDown',
code: 'Numpad3',
shiftKey: '3',
location: 3,
},
PageDown: { keyCode: 34, code: 'PageDown', key: 'PageDown' },
End: { keyCode: 35, code: 'End', key: 'End' },
Numpad1: {
keyCode: 35,
shiftKeyCode: 97,
key: 'End',
code: 'Numpad1',
shiftKey: '1',
location: 3,
},
Home: { keyCode: 36, code: 'Home', key: 'Home' },
Numpad7: {
keyCode: 36,
shiftKeyCode: 103,
key: 'Home',
code: 'Numpad7',
shiftKey: '7',
location: 3,
},
ArrowLeft: { keyCode: 37, code: 'ArrowLeft', key: 'ArrowLeft' },
Numpad4: {
keyCode: 37,
shiftKeyCode: 100,
key: 'ArrowLeft',
code: 'Numpad4',
shiftKey: '4',
location: 3,
},
Numpad8: {
keyCode: 38,
shiftKeyCode: 104,
key: 'ArrowUp',
code: 'Numpad8',
shiftKey: '8',
location: 3,
},
ArrowUp: { keyCode: 38, code: 'ArrowUp', key: 'ArrowUp' },
ArrowRight: { keyCode: 39, code: 'ArrowRight', key: 'ArrowRight' },
Numpad6: {
keyCode: 39,
shiftKeyCode: 102,
key: 'ArrowRight',
code: 'Numpad6',
shiftKey: '6',
location: 3,
},
Numpad2: {
keyCode: 40,
shiftKeyCode: 98,
key: 'ArrowDown',
code: 'Numpad2',
shiftKey: '2',
location: 3,
},
ArrowDown: { keyCode: 40, code: 'ArrowDown', key: 'ArrowDown' },
Select: { keyCode: 41, code: 'Select', key: 'Select' },
Open: { keyCode: 43, code: 'Open', key: 'Execute' },
PrintScreen: { keyCode: 44, code: 'PrintScreen', key: 'PrintScreen' },
Insert: { keyCode: 45, code: 'Insert', key: 'Insert' },
Numpad0: {
keyCode: 45,
shiftKeyCode: 96,
key: 'Insert',
code: 'Numpad0',
shiftKey: '0',
location: 3,
},
Delete: { keyCode: 46, code: 'Delete', key: 'Delete' },
NumpadDecimal: {
keyCode: 46,
shiftKeyCode: 110,
code: 'NumpadDecimal',
key: '\u0000',
shiftKey: '.',
location: 3,
},
Digit0: { keyCode: 48, code: 'Digit0', shiftKey: ')', key: '0' },
Digit1: { keyCode: 49, code: 'Digit1', shiftKey: '!', key: '1' },
Digit2: { keyCode: 50, code: 'Digit2', shiftKey: '@', key: '2' },
Digit3: { keyCode: 51, code: 'Digit3', shiftKey: '#', key: '3' },
Digit4: { keyCode: 52, code: 'Digit4', shiftKey: '$', key: '4' },
Digit5: { keyCode: 53, code: 'Digit5', shiftKey: '%', key: '5' },
Digit6: { keyCode: 54, code: 'Digit6', shiftKey: '^', key: '6' },
Digit7: { keyCode: 55, code: 'Digit7', shiftKey: '&', key: '7' },
Digit8: { keyCode: 56, code: 'Digit8', shiftKey: '*', key: '8' },
Digit9: { keyCode: 57, code: 'Digit9', shiftKey: '(', key: '9' },
KeyA: { keyCode: 65, code: 'KeyA', shiftKey: 'A', key: 'a' },
KeyB: { keyCode: 66, code: 'KeyB', shiftKey: 'B', key: 'b' },
KeyC: { keyCode: 67, code: 'KeyC', shiftKey: 'C', key: 'c' },
KeyD: { keyCode: 68, code: 'KeyD', shiftKey: 'D', key: 'd' },
KeyE: { keyCode: 69, code: 'KeyE', shiftKey: 'E', key: 'e' },
KeyF: { keyCode: 70, code: 'KeyF', shiftKey: 'F', key: 'f' },
KeyG: { keyCode: 71, code: 'KeyG', shiftKey: 'G', key: 'g' },
KeyH: { keyCode: 72, code: 'KeyH', shiftKey: 'H', key: 'h' },
KeyI: { keyCode: 73, code: 'KeyI', shiftKey: 'I', key: 'i' },
KeyJ: { keyCode: 74, code: 'KeyJ', shiftKey: 'J', key: 'j' },
KeyK: { keyCode: 75, code: 'KeyK', shiftKey: 'K', key: 'k' },
KeyL: { keyCode: 76, code: 'KeyL', shiftKey: 'L', key: 'l' },
KeyM: { keyCode: 77, code: 'KeyM', shiftKey: 'M', key: 'm' },
KeyN: { keyCode: 78, code: 'KeyN', shiftKey: 'N', key: 'n' },
KeyO: { keyCode: 79, code: 'KeyO', shiftKey: 'O', key: 'o' },
KeyP: { keyCode: 80, code: 'KeyP', shiftKey: 'P', key: 'p' },
KeyQ: { keyCode: 81, code: 'KeyQ', shiftKey: 'Q', key: 'q' },
KeyR: { keyCode: 82, code: 'KeyR', shiftKey: 'R', key: 'r' },
KeyS: { keyCode: 83, code: 'KeyS', shiftKey: 'S', key: 's' },
KeyT: { keyCode: 84, code: 'KeyT', shiftKey: 'T', key: 't' },
KeyU: { keyCode: 85, code: 'KeyU', shiftKey: 'U', key: 'u' },
KeyV: { keyCode: 86, code: 'KeyV', shiftKey: 'V', key: 'v' },
KeyW: { keyCode: 87, code: 'KeyW', shiftKey: 'W', key: 'w' },
KeyX: { keyCode: 88, code: 'KeyX', shiftKey: 'X', key: 'x' },
KeyY: { keyCode: 89, code: 'KeyY', shiftKey: 'Y', key: 'y' },
KeyZ: { keyCode: 90, code: 'KeyZ', shiftKey: 'Z', key: 'z' },
MetaLeft: { keyCode: 91, code: 'MetaLeft', key: 'Meta', location: 1 },
MetaRight: { keyCode: 92, code: 'MetaRight', key: 'Meta', location: 2 },
ContextMenu: { keyCode: 93, code: 'ContextMenu', key: 'ContextMenu' },
NumpadMultiply: {
keyCode: 106,
code: 'NumpadMultiply',
key: '*',
location: 3,
},
NumpadAdd: { keyCode: 107, code: 'NumpadAdd', key: '+', location: 3 },
NumpadSubtract: {
keyCode: 109,
code: 'NumpadSubtract',
key: '-',
location: 3,
},
NumpadDivide: { keyCode: 111, code: 'NumpadDivide', key: '/', location: 3 },
F1: { keyCode: 112, code: 'F1', key: 'F1' },
F2: { keyCode: 113, code: 'F2', key: 'F2' },
F3: { keyCode: 114, code: 'F3', key: 'F3' },
F4: { keyCode: 115, code: 'F4', key: 'F4' },
F5: { keyCode: 116, code: 'F5', key: 'F5' },
F6: { keyCode: 117, code: 'F6', key: 'F6' },
F7: { keyCode: 118, code: 'F7', key: 'F7' },
F8: { keyCode: 119, code: 'F8', key: 'F8' },
F9: { keyCode: 120, code: 'F9', key: 'F9' },
F10: { keyCode: 121, code: 'F10', key: 'F10' },
F11: { keyCode: 122, code: 'F11', key: 'F11' },
F12: { keyCode: 123, code: 'F12', key: 'F12' },
F13: { keyCode: 124, code: 'F13', key: 'F13' },
F14: { keyCode: 125, code: 'F14', key: 'F14' },
F15: { keyCode: 126, code: 'F15', key: 'F15' },
F16: { keyCode: 127, code: 'F16', key: 'F16' },
F17: { keyCode: 128, code: 'F17', key: 'F17' },
F18: { keyCode: 129, code: 'F18', key: 'F18' },
F19: { keyCode: 130, code: 'F19', key: 'F19' },
F20: { keyCode: 131, code: 'F20', key: 'F20' },
F21: { keyCode: 132, code: 'F21', key: 'F21' },
F22: { keyCode: 133, code: 'F22', key: 'F22' },
F23: { keyCode: 134, code: 'F23', key: 'F23' },
F24: { keyCode: 135, code: 'F24', key: 'F24' },
NumLock: { keyCode: 144, code: 'NumLock', key: 'NumLock' },
ScrollLock: { keyCode: 145, code: 'ScrollLock', key: 'ScrollLock' },
AudioVolumeMute: {
keyCode: 173,
code: 'AudioVolumeMute',
key: 'AudioVolumeMute',
},
AudioVolumeDown: {
keyCode: 174,
code: 'AudioVolumeDown',
key: 'AudioVolumeDown',
},
AudioVolumeUp: { keyCode: 175, code: 'AudioVolumeUp', key: 'AudioVolumeUp' },
MediaTrackNext: {
keyCode: 176,
code: 'MediaTrackNext',
key: 'MediaTrackNext',
},
MediaTrackPrevious: {
keyCode: 177,
code: 'MediaTrackPrevious',
key: 'MediaTrackPrevious',
},
MediaStop: { keyCode: 178, code: 'MediaStop', key: 'MediaStop' },
MediaPlayPause: {
keyCode: 179,
code: 'MediaPlayPause',
key: 'MediaPlayPause',
},
Semicolon: { keyCode: 186, code: 'Semicolon', shiftKey: ':', key: ';' },
Equal: { keyCode: 187, code: 'Equal', shiftKey: '+', key: '=' },
NumpadEqual: { keyCode: 187, code: 'NumpadEqual', key: '=', location: 3 },
Comma: { keyCode: 188, code: 'Comma', shiftKey: '<', key: ',' },
Minus: { keyCode: 189, code: 'Minus', shiftKey: '_', key: '-' },
Period: { keyCode: 190, code: 'Period', shiftKey: '>', key: '.' },
Slash: { keyCode: 191, code: 'Slash', shiftKey: '?', key: '/' },
Backquote: { keyCode: 192, code: 'Backquote', shiftKey: '~', key: '`' },
BracketLeft: { keyCode: 219, code: 'BracketLeft', shiftKey: '{', key: '[' },
Backslash: { keyCode: 220, code: 'Backslash', shiftKey: '|', key: '\\' },
BracketRight: { keyCode: 221, code: 'BracketRight', shiftKey: '}', key: ']' },
Quote: { keyCode: 222, code: 'Quote', shiftKey: '"', key: "'" },
AltGraph: { keyCode: 225, code: 'AltGraph', key: 'AltGraph' },
Props: { keyCode: 247, code: 'Props', key: 'CrSel' },
Cancel: { keyCode: 3, key: 'Cancel', code: 'Abort' },
Clear: { keyCode: 12, key: 'Clear', code: 'Numpad5', location: 3 },
Shift: { keyCode: 16, key: 'Shift', code: 'ShiftLeft', location: 1 },
Control: { keyCode: 17, key: 'Control', code: 'ControlLeft', location: 1 },
Alt: { keyCode: 18, key: 'Alt', code: 'AltLeft', location: 1 },
Accept: { keyCode: 30, key: 'Accept' },
ModeChange: { keyCode: 31, key: 'ModeChange' },
' ': { keyCode: 32, key: ' ', code: 'Space' },
Print: { keyCode: 42, key: 'Print' },
Execute: { keyCode: 43, key: 'Execute', code: 'Open' },
'\u0000': { keyCode: 46, key: '\u0000', code: 'NumpadDecimal', location: 3 },
a: { keyCode: 65, key: 'a', code: 'KeyA' },
b: { keyCode: 66, key: 'b', code: 'KeyB' },
c: { keyCode: 67, key: 'c', code: 'KeyC' },
d: { keyCode: 68, key: 'd', code: 'KeyD' },
e: { keyCode: 69, key: 'e', code: 'KeyE' },
f: { keyCode: 70, key: 'f', code: 'KeyF' },
g: { keyCode: 71, key: 'g', code: 'KeyG' },
h: { keyCode: 72, key: 'h', code: 'KeyH' },
i: { keyCode: 73, key: 'i', code: 'KeyI' },
j: { keyCode: 74, key: 'j', code: 'KeyJ' },
k: { keyCode: 75, key: 'k', code: 'KeyK' },
l: { keyCode: 76, key: 'l', code: 'KeyL' },
m: { keyCode: 77, key: 'm', code: 'KeyM' },
n: { keyCode: 78, key: 'n', code: 'KeyN' },
o: { keyCode: 79, key: 'o', code: 'KeyO' },
p: { keyCode: 80, key: 'p', code: 'KeyP' },
q: { keyCode: 81, key: 'q', code: 'KeyQ' },
r: { keyCode: 82, key: 'r', code: 'KeyR' },
s: { keyCode: 83, key: 's', code: 'KeyS' },
t: { keyCode: 84, key: 't', code: 'KeyT' },
u: { keyCode: 85, key: 'u', code: 'KeyU' },
v: { keyCode: 86, key: 'v', code: 'KeyV' },
w: { keyCode: 87, key: 'w', code: 'KeyW' },
x: { keyCode: 88, key: 'x', code: 'KeyX' },
y: { keyCode: 89, key: 'y', code: 'KeyY' },
z: { keyCode: 90, key: 'z', code: 'KeyZ' },
Meta: { keyCode: 91, key: 'Meta', code: 'MetaLeft', location: 1 },
'*': { keyCode: 106, key: '*', code: 'NumpadMultiply', location: 3 },
'+': { keyCode: 107, key: '+', code: 'NumpadAdd', location: 3 },
'-': { keyCode: 109, key: '-', code: 'NumpadSubtract', location: 3 },
'/': { keyCode: 111, key: '/', code: 'NumpadDivide', location: 3 },
';': { keyCode: 186, key: ';', code: 'Semicolon' },
'=': { keyCode: 187, key: '=', code: 'Equal' },
',': { keyCode: 188, key: ',', code: 'Comma' },
'.': { keyCode: 190, key: '.', code: 'Period' },
'`': { keyCode: 192, key: '`', code: 'Backquote' },
'[': { keyCode: 219, key: '[', code: 'BracketLeft' },
'\\': { keyCode: 220, key: '\\', code: 'Backslash' },
']': { keyCode: 221, key: ']', code: 'BracketRight' },
"'": { keyCode: 222, key: "'", code: 'Quote' },
Attn: { keyCode: 246, key: 'Attn' },
CrSel: { keyCode: 247, key: 'CrSel', code: 'Props' },
ExSel: { keyCode: 248, key: 'ExSel' },
EraseEof: { keyCode: 249, key: 'EraseEof' },
Play: { keyCode: 250, key: 'Play' },
ZoomOut: { keyCode: 251, key: 'ZoomOut' },
')': { keyCode: 48, key: ')', code: 'Digit0' },
'!': { keyCode: 49, key: '!', code: 'Digit1' },
'@': { keyCode: 50, key: '@', code: 'Digit2' },
'#': { keyCode: 51, key: '#', code: 'Digit3' },
$: { keyCode: 52, key: '$', code: 'Digit4' },
'%': { keyCode: 53, key: '%', code: 'Digit5' },
'^': { keyCode: 54, key: '^', code: 'Digit6' },
'&': { keyCode: 55, key: '&', code: 'Digit7' },
'(': { keyCode: 57, key: '(', code: 'Digit9' },
A: { keyCode: 65, key: 'A', code: 'KeyA' },
B: { keyCode: 66, key: 'B', code: 'KeyB' },
C: { keyCode: 67, key: 'C', code: 'KeyC' },
D: { keyCode: 68, key: 'D', code: 'KeyD' },
E: { keyCode: 69, key: 'E', code: 'KeyE' },
F: { keyCode: 70, key: 'F', code: 'KeyF' },
G: { keyCode: 71, key: 'G', code: 'KeyG' },
H: { keyCode: 72, key: 'H', code: 'KeyH' },
I: { keyCode: 73, key: 'I', code: 'KeyI' },
J: { keyCode: 74, key: 'J', code: 'KeyJ' },
K: { keyCode: 75, key: 'K', code: 'KeyK' },
L: { keyCode: 76, key: 'L', code: 'KeyL' },
M: { keyCode: 77, key: 'M', code: 'KeyM' },
N: { keyCode: 78, key: 'N', code: 'KeyN' },
O: { keyCode: 79, key: 'O', code: 'KeyO' },
P: { keyCode: 80, key: 'P', code: 'KeyP' },
Q: { keyCode: 81, key: 'Q', code: 'KeyQ' },
R: { keyCode: 82, key: 'R', code: 'KeyR' },
S: { keyCode: 83, key: 'S', code: 'KeyS' },
T: { keyCode: 84, key: 'T', code: 'KeyT' },
U: { keyCode: 85, key: 'U', code: 'KeyU' },
V: { keyCode: 86, key: 'V', code: 'KeyV' },
W: { keyCode: 87, key: 'W', code: 'KeyW' },
X: { keyCode: 88, key: 'X', code: 'KeyX' },
Y: { keyCode: 89, key: 'Y', code: 'KeyY' },
Z: { keyCode: 90, key: 'Z', code: 'KeyZ' },
':': { keyCode: 186, key: ':', code: 'Semicolon' },
'<': { keyCode: 188, key: '<', code: 'Comma' },
_: { keyCode: 189, key: '_', code: 'Minus' },
'>': { keyCode: 190, key: '>', code: 'Period' },
'?': { keyCode: 191, key: '?', code: 'Slash' },
'~': { keyCode: 192, key: '~', code: 'Backquote' },
'{': { keyCode: 219, key: '{', code: 'BracketLeft' },
'|': { keyCode: 220, key: '|', code: 'Backslash' },
'}': { keyCode: 221, key: '}', code: 'BracketRight' },
'"': { keyCode: 222, key: '"', code: 'Quote' },
SoftLeft: { key: 'SoftLeft', code: 'SoftLeft', location: 4 },
SoftRight: { key: 'SoftRight', code: 'SoftRight', location: 4 },
Camera: { keyCode: 44, key: 'Camera', code: 'Camera', location: 4 },
Call: { key: 'Call', code: 'Call', location: 4 },
EndCall: { keyCode: 95, key: 'EndCall', code: 'EndCall', location: 4 },
VolumeDown: {
keyCode: 182,
key: 'VolumeDown',
code: 'VolumeDown',
location: 4,
},
VolumeUp: { keyCode: 183, key: 'VolumeUp', code: 'VolumeUp', location: 4 },
};
+53
View File
@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebSocketTransport = void 0;
/**
* Copyright 2018 Google Inc. All rights reserved.
*
* 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 NodeWebSocket = require("ws");
class WebSocketTransport {
constructor(ws) {
this._ws = ws;
this._ws.addEventListener('message', (event) => {
if (this.onmessage)
this.onmessage.call(null, event.data);
});
this._ws.addEventListener('close', () => {
if (this.onclose)
this.onclose.call(null);
});
// Silently ignore all errors - we don't know what to do with them.
this._ws.addEventListener('error', () => { });
this.onmessage = null;
this.onclose = null;
}
static create(url) {
return new Promise((resolve, reject) => {
const ws = new NodeWebSocket(url, [], {
perMessageDeflate: false,
maxPayload: 256 * 1024 * 1024,
});
ws.addEventListener('open', () => resolve(new WebSocketTransport(ws)));
ws.addEventListener('error', reject);
});
}
send(message) {
this._ws.send(message);
}
close() {
this._ws.close();
}
}
exports.WebSocketTransport = WebSocketTransport;
+54
View File
@@ -0,0 +1,54 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebWorker = void 0;
/**
* Copyright 2018 Google Inc. All rights reserved.
*
* 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 events_1 = require("events");
const helper_1 = require("./helper");
const ExecutionContext_1 = require("./ExecutionContext");
const JSHandle_1 = require("./JSHandle");
class WebWorker extends events_1.EventEmitter {
constructor(client, url, consoleAPICalled, exceptionThrown) {
super();
this._client = client;
this._url = url;
this._executionContextPromise = new Promise((x) => (this._executionContextCallback = x));
let jsHandleFactory;
this._client.once('Runtime.executionContextCreated', async (event) => {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
jsHandleFactory = (remoteObject) => new JSHandle_1.JSHandle(executionContext, client, remoteObject);
const executionContext = new ExecutionContext_1.ExecutionContext(client, event.context, null);
this._executionContextCallback(executionContext);
});
// This might fail if the target is closed before we recieve all execution contexts.
this._client.send('Runtime.enable', {}).catch(helper_1.debugError);
this._client.on('Runtime.consoleAPICalled', (event) => consoleAPICalled(event.type, event.args.map(jsHandleFactory), event.stackTrace));
this._client.on('Runtime.exceptionThrown', (exception) => exceptionThrown(exception.exceptionDetails));
}
url() {
return this._url;
}
async executionContext() {
return this._executionContextPromise;
}
async evaluate(pageFunction, ...args) {
return (await this._executionContextPromise).evaluate(pageFunction, ...args);
}
async evaluateHandle(pageFunction, ...args) {
return (await this._executionContextPromise).evaluateHandle(pageFunction, ...args);
}
}
exports.WebWorker = WebWorker;
+46
View File
@@ -0,0 +1,46 @@
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* 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.
*/
/* This file is used in two places:
* 1) the coverage-utils use it to gain a list of all methods we check for test coverage on
* 2) index.js uses it to iterate through all methods and call helper.installAsyncStackHooks on
*/
module.exports = {
Accessibility: require('./Accessibility').Accessibility,
Browser: require('./Browser').Browser,
BrowserContext: require('./Browser').BrowserContext,
BrowserFetcher: require('./BrowserFetcher').BrowserFetcher,
CDPSession: require('./Connection').CDPSession,
ConsoleMessage: require('./ConsoleMessage').ConsoleMessage,
Coverage: require('./Coverage').Coverage,
Dialog: require('./Dialog').Dialog,
ElementHandle: require('./JSHandle').ElementHandle,
ExecutionContext: require('./ExecutionContext').ExecutionContext,
FileChooser: require('./FileChooser').FileChooser,
Frame: require('./FrameManager').Frame,
JSHandle: require('./JSHandle').JSHandle,
Keyboard: require('./Input').Keyboard,
Mouse: require('./Input').Mouse,
Page: require('./Page').Page,
Puppeteer: require('./Puppeteer').Puppeteer,
HTTPRequest: require('./HTTPRequest').HTTPRequest,
HTTPResponse: require('./HTTPResponse').HTTPResponse,
SecurityDetails: require('./SecurityDetails').SecurityDetails,
Target: require('./Target').Target,
TimeoutError: require('./Errors').TimeoutError,
Touchscreen: require('./Input').Touchscreen,
Tracing: require('./Tracing').Tracing,
WebWorker: require('./WebWorker').WebWorker,
};
+219
View File
@@ -0,0 +1,219 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.helper = exports.assert = exports.debugError = void 0;
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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 Errors_1 = require("./Errors");
const debug = require("debug");
const fs = require("fs");
const util_1 = require("util");
const openAsync = util_1.promisify(fs.open);
const writeAsync = util_1.promisify(fs.write);
const closeAsync = util_1.promisify(fs.close);
exports.debugError = debug('puppeteer:error');
function assert(value, message) {
if (!value)
throw new Error(message);
}
exports.assert = assert;
function getExceptionMessage(exceptionDetails) {
if (exceptionDetails.exception)
return (exceptionDetails.exception.description || exceptionDetails.exception.value);
let message = exceptionDetails.text;
if (exceptionDetails.stackTrace) {
for (const callframe of exceptionDetails.stackTrace.callFrames) {
const location = callframe.url +
':' +
callframe.lineNumber +
':' +
callframe.columnNumber;
const functionName = callframe.functionName || '<anonymous>';
message += `\n at ${functionName} (${location})`;
}
}
return message;
}
function valueFromRemoteObject(remoteObject) {
assert(!remoteObject.objectId, 'Cannot extract value when objectId is given');
if (remoteObject.unserializableValue) {
if (remoteObject.type === 'bigint' && typeof BigInt !== 'undefined')
return BigInt(remoteObject.unserializableValue.replace('n', ''));
switch (remoteObject.unserializableValue) {
case '-0':
return -0;
case 'NaN':
return NaN;
case 'Infinity':
return Infinity;
case '-Infinity':
return -Infinity;
default:
throw new Error('Unsupported unserializable value: ' +
remoteObject.unserializableValue);
}
}
return remoteObject.value;
}
async function releaseObject(client, remoteObject) {
if (!remoteObject.objectId)
return;
await client
.send('Runtime.releaseObject', { objectId: remoteObject.objectId })
.catch((error) => {
// Exceptions might happen in case of a page been navigated or closed.
// Swallow these since they are harmless and we don't leak anything in this case.
exports.debugError(error);
});
}
function installAsyncStackHooks(classType) {
for (const methodName of Reflect.ownKeys(classType.prototype)) {
const method = Reflect.get(classType.prototype, methodName);
if (methodName === 'constructor' ||
typeof methodName !== 'string' ||
methodName.startsWith('_') ||
typeof method !== 'function' ||
method.constructor.name !== 'AsyncFunction')
continue;
Reflect.set(classType.prototype, methodName, function (...args) {
const syncStack = {
stack: '',
};
Error.captureStackTrace(syncStack);
return method.call(this, ...args).catch((error) => {
const stack = syncStack.stack.substring(syncStack.stack.indexOf('\n') + 1);
const clientStack = stack.substring(stack.indexOf('\n'));
if (error instanceof Error &&
error.stack &&
!error.stack.includes(clientStack))
error.stack += '\n -- ASYNC --\n' + stack;
throw error;
});
});
}
}
function addEventListener(emitter, eventName, handler) {
emitter.on(eventName, handler);
return { emitter, eventName, handler };
}
function removeEventListeners(listeners) {
for (const listener of listeners)
listener.emitter.removeListener(listener.eventName, listener.handler);
listeners.length = 0;
}
function isString(obj) {
return typeof obj === 'string' || obj instanceof String;
}
function isNumber(obj) {
return typeof obj === 'number' || obj instanceof Number;
}
async function waitForEvent(emitter, eventName, predicate, timeout, abortPromise) {
let eventTimeout, resolveCallback, rejectCallback;
const promise = new Promise((resolve, reject) => {
resolveCallback = resolve;
rejectCallback = reject;
});
const listener = addEventListener(emitter, eventName, (event) => {
if (!predicate(event))
return;
resolveCallback(event);
});
if (timeout) {
eventTimeout = setTimeout(() => {
rejectCallback(new Errors_1.TimeoutError('Timeout exceeded while waiting for event'));
}, timeout);
}
function cleanup() {
removeEventListeners([listener]);
clearTimeout(eventTimeout);
}
const result = await Promise.race([promise, abortPromise]).then((r) => {
cleanup();
return r;
}, (error) => {
cleanup();
throw error;
});
if (result instanceof Error)
throw result;
return result;
}
function evaluationString(fun, ...args) {
if (isString(fun)) {
assert(args.length === 0, 'Cannot evaluate a string with arguments');
return fun;
}
function serializeArgument(arg) {
if (Object.is(arg, undefined))
return 'undefined';
return JSON.stringify(arg);
}
return `(${fun})(${args.map(serializeArgument).join(',')})`;
}
async function waitWithTimeout(promise, taskName, timeout) {
let reject;
const timeoutError = new Errors_1.TimeoutError(`waiting for ${taskName} failed: timeout ${timeout}ms exceeded`);
const timeoutPromise = new Promise((resolve, x) => (reject = x));
let timeoutTimer = null;
if (timeout)
timeoutTimer = setTimeout(() => reject(timeoutError), timeout);
try {
return await Promise.race([promise, timeoutPromise]);
}
finally {
if (timeoutTimer)
clearTimeout(timeoutTimer);
}
}
async function readProtocolStream(client, handle, path) {
let eof = false;
let file;
if (path)
file = await openAsync(path, 'w');
const bufs = [];
while (!eof) {
const response = await client.send('IO.read', { handle });
eof = response.eof;
const buf = Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
bufs.push(buf);
if (path)
await writeAsync(file, buf);
}
if (path)
await closeAsync(file);
await client.send('IO.close', { handle });
let resultBuffer = null;
try {
resultBuffer = Buffer.concat(bufs);
}
finally {
return resultBuffer;
}
}
exports.helper = {
promisify: util_1.promisify,
evaluationString,
readProtocolStream,
waitWithTimeout,
waitForEvent,
isString,
isNumber,
addEventListener,
removeEventListeners,
valueFromRemoteObject,
installAsyncStackHooks,
getExceptionMessage,
releaseObject,
};
+186
View File
@@ -0,0 +1,186 @@
"use strict";
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowserRunner = void 0;
const debug = require("debug");
const removeFolder = require("rimraf");
const childProcess = require("child_process");
const helper_1 = require("../helper");
const Connection_1 = require("../Connection");
const WebSocketTransport_1 = require("../WebSocketTransport");
const PipeTransport_1 = require("../PipeTransport");
const readline = require("readline");
const Errors_1 = require("../Errors");
const removeFolderAsync = helper_1.helper.promisify(removeFolder);
const debugLauncher = debug('puppeteer:launcher');
class BrowserRunner {
constructor(executablePath, processArguments, tempDirectory) {
this.proc = null;
this.connection = null;
this._closed = true;
this._listeners = [];
this._executablePath = executablePath;
this._processArguments = processArguments;
this._tempDirectory = tempDirectory;
}
start(options = {}) {
const { handleSIGINT, handleSIGTERM, handleSIGHUP, dumpio, env, pipe, } = options;
let stdio = ['pipe', 'pipe', 'pipe'];
if (pipe) {
if (dumpio)
stdio = ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'];
else
stdio = ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'];
}
helper_1.assert(!this.proc, 'This process has previously been started.');
debugLauncher(`Calling ${this._executablePath} ${this._processArguments.join(' ')}`);
this.proc = childProcess.spawn(this._executablePath, this._processArguments, {
// On non-windows platforms, `detached: true` makes child process a leader of a new
// process group, making it possible to kill child process tree with `.kill(-pid)` command.
// @see https://nodejs.org/api/child_process.html#child_process_options_detached
detached: process.platform !== 'win32',
env,
stdio,
});
if (dumpio) {
this.proc.stderr.pipe(process.stderr);
this.proc.stdout.pipe(process.stdout);
}
this._closed = false;
this._processClosing = new Promise((fulfill) => {
this.proc.once('exit', () => {
this._closed = true;
// Cleanup as processes exit.
if (this._tempDirectory) {
removeFolderAsync(this._tempDirectory)
.then(() => fulfill())
.catch((error) => console.error(error));
}
else {
fulfill();
}
});
});
this._listeners = [
helper_1.helper.addEventListener(process, 'exit', this.kill.bind(this)),
];
if (handleSIGINT)
this._listeners.push(helper_1.helper.addEventListener(process, 'SIGINT', () => {
this.kill();
process.exit(130);
}));
if (handleSIGTERM)
this._listeners.push(helper_1.helper.addEventListener(process, 'SIGTERM', this.close.bind(this)));
if (handleSIGHUP)
this._listeners.push(helper_1.helper.addEventListener(process, 'SIGHUP', this.close.bind(this)));
}
close() {
if (this._closed)
return Promise.resolve();
helper_1.helper.removeEventListeners(this._listeners);
if (this._tempDirectory) {
this.kill();
}
else if (this.connection) {
// Attempt to close the browser gracefully
this.connection.send('Browser.close').catch((error) => {
helper_1.debugError(error);
this.kill();
});
}
return this._processClosing;
}
kill() {
helper_1.helper.removeEventListeners(this._listeners);
if (this.proc && this.proc.pid && !this.proc.killed && !this._closed) {
try {
if (process.platform === 'win32')
childProcess.execSync(`taskkill /pid ${this.proc.pid} /T /F`);
else
process.kill(-this.proc.pid, 'SIGKILL');
}
catch (error) {
// the process might have already stopped
}
}
// Attempt to remove temporary profile directory to avoid littering.
try {
removeFolder.sync(this._tempDirectory);
}
catch (error) { }
}
async setupConnection(options) {
const { usePipe, timeout, slowMo, preferredRevision } = options;
if (!usePipe) {
const browserWSEndpoint = await waitForWSEndpoint(this.proc, timeout, preferredRevision);
const transport = await WebSocketTransport_1.WebSocketTransport.create(browserWSEndpoint);
this.connection = new Connection_1.Connection(browserWSEndpoint, transport, slowMo);
}
else {
// stdio was assigned during start(), and the 'pipe' option there adds the 4th and 5th items to stdio array
const { 3: pipeWrite, 4: pipeRead } = this.proc.stdio;
const transport = new PipeTransport_1.PipeTransport(pipeWrite, pipeRead);
this.connection = new Connection_1.Connection('', transport, slowMo);
}
return this.connection;
}
}
exports.BrowserRunner = BrowserRunner;
function waitForWSEndpoint(browserProcess, timeout, preferredRevision) {
return new Promise((resolve, reject) => {
const rl = readline.createInterface({ input: browserProcess.stderr });
let stderr = '';
const listeners = [
helper_1.helper.addEventListener(rl, 'line', onLine),
helper_1.helper.addEventListener(rl, 'close', () => onClose()),
helper_1.helper.addEventListener(browserProcess, 'exit', () => onClose()),
helper_1.helper.addEventListener(browserProcess, 'error', (error) => onClose(error)),
];
const timeoutId = timeout ? setTimeout(onTimeout, timeout) : 0;
/**
* @param {!Error=} error
*/
function onClose(error) {
cleanup();
reject(new Error([
'Failed to launch the browser process!' +
(error ? ' ' + error.message : ''),
stderr,
'',
'TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md',
'',
].join('\n')));
}
function onTimeout() {
cleanup();
reject(new Errors_1.TimeoutError(`Timed out after ${timeout} ms while trying to connect to the browser! Only Chrome at revision r${preferredRevision} is guaranteed to work.`));
}
function onLine(line) {
stderr += line + '\n';
const match = line.match(/^DevTools listening on (ws:\/\/.*)$/);
if (!match)
return;
cleanup();
resolve(match[1]);
}
function cleanup() {
if (timeoutId)
clearTimeout(timeoutId);
helper_1.helper.removeEventListeners(listeners);
}
});
}
+17
View File
@@ -0,0 +1,17 @@
"use strict";
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
+15503
View File
File diff suppressed because it is too large Load Diff