screenshot/test_1
This commit is contained in:
BIN
node_modules/playwright-core/lib/server/chromium/appIcon.png
generated
vendored
Normal file
BIN
node_modules/playwright-core/lib/server/chromium/appIcon.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
354
node_modules/playwright-core/lib/server/chromium/chromium.js
generated
vendored
Normal file
354
node_modules/playwright-core/lib/server/chromium/chromium.js
generated
vendored
Normal file
@@ -0,0 +1,354 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.Chromium = void 0;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _os = _interopRequireDefault(require("os"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _chromiumSwitches = require("./chromiumSwitches");
|
||||
var _crBrowser = require("./crBrowser");
|
||||
var _crConnection = require("./crConnection");
|
||||
var _timeoutSettings = require("../timeoutSettings");
|
||||
var _utils = require("../../utils");
|
||||
var _ascii = require("../utils/ascii");
|
||||
var _debugLogger = require("../utils/debugLogger");
|
||||
var _manualPromise = require("../../utils/isomorphic/manualPromise");
|
||||
var _network = require("../utils/network");
|
||||
var _userAgent = require("../utils/userAgent");
|
||||
var _browserContext = require("../browserContext");
|
||||
var _browserType = require("../browserType");
|
||||
var _helper = require("../helper");
|
||||
var _registry = require("../registry");
|
||||
var _transport = require("../transport");
|
||||
var _crDevTools = require("./crDevTools");
|
||||
var _browser = require("../browser");
|
||||
var _fileUtils = require("../utils/fileUtils");
|
||||
var _processLauncher = require("../utils/processLauncher");
|
||||
var _progress = require("../progress");
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const ARTIFACTS_FOLDER = _path.default.join(_os.default.tmpdir(), 'playwright-artifacts-');
|
||||
class Chromium extends _browserType.BrowserType {
|
||||
constructor(parent) {
|
||||
super(parent, 'chromium');
|
||||
this._devtools = void 0;
|
||||
if ((0, _utils.debugMode)()) this._devtools = this._createDevTools();
|
||||
}
|
||||
async connectOverCDP(metadata, endpointURL, options, timeout) {
|
||||
const controller = new _progress.ProgressController(metadata, this);
|
||||
controller.setLogName('browser');
|
||||
return controller.run(async progress => {
|
||||
return await this._connectOverCDPInternal(progress, endpointURL, options);
|
||||
}, _timeoutSettings.TimeoutSettings.timeout({
|
||||
timeout
|
||||
}));
|
||||
}
|
||||
async _connectOverCDPInternal(progress, endpointURL, options, onClose) {
|
||||
let headersMap;
|
||||
if (options.headers) headersMap = (0, _utils.headersArrayToObject)(options.headers, false);
|
||||
if (!headersMap) headersMap = {
|
||||
'User-Agent': (0, _userAgent.getUserAgent)()
|
||||
};else if (headersMap && !Object.keys(headersMap).some(key => key.toLowerCase() === 'user-agent')) headersMap['User-Agent'] = (0, _userAgent.getUserAgent)();
|
||||
const artifactsDir = await _fs.default.promises.mkdtemp(ARTIFACTS_FOLDER);
|
||||
const wsEndpoint = await urlToWSEndpoint(progress, endpointURL, headersMap);
|
||||
progress.throwIfAborted();
|
||||
const chromeTransport = await _transport.WebSocketTransport.connect(progress, wsEndpoint, headersMap);
|
||||
const cleanedUp = new _manualPromise.ManualPromise();
|
||||
const doCleanup = async () => {
|
||||
await (0, _fileUtils.removeFolders)([artifactsDir]);
|
||||
await (onClose === null || onClose === void 0 ? void 0 : onClose());
|
||||
cleanedUp.resolve();
|
||||
};
|
||||
const doClose = async () => {
|
||||
await chromeTransport.closeAndWait();
|
||||
await cleanedUp;
|
||||
};
|
||||
const browserProcess = {
|
||||
close: doClose,
|
||||
kill: doClose
|
||||
};
|
||||
const persistent = {
|
||||
noDefaultViewport: true
|
||||
};
|
||||
const browserOptions = {
|
||||
slowMo: options.slowMo,
|
||||
name: 'chromium',
|
||||
isChromium: true,
|
||||
persistent,
|
||||
browserProcess,
|
||||
protocolLogger: _helper.helper.debugProtocolLogger(),
|
||||
browserLogsCollector: new _debugLogger.RecentLogsCollector(),
|
||||
artifactsDir,
|
||||
downloadsPath: options.downloadsPath || artifactsDir,
|
||||
tracesDir: options.tracesDir || artifactsDir,
|
||||
originalLaunchOptions: {}
|
||||
};
|
||||
(0, _browserContext.validateBrowserContextOptions)(persistent, browserOptions);
|
||||
progress.throwIfAborted();
|
||||
const browser = await _crBrowser.CRBrowser.connect(this.attribution.playwright, chromeTransport, browserOptions);
|
||||
browser._isCollocatedWithServer = false;
|
||||
browser.on(_browser.Browser.Events.Disconnected, doCleanup);
|
||||
return browser;
|
||||
}
|
||||
_createDevTools() {
|
||||
// TODO: this is totally wrong when using channels.
|
||||
const directory = _registry.registry.findExecutable('chromium').directory;
|
||||
return directory ? new _crDevTools.CRDevTools(_path.default.join(directory, 'devtools-preferences.json')) : undefined;
|
||||
}
|
||||
async connectToTransport(transport, options) {
|
||||
let devtools = this._devtools;
|
||||
if (options.__testHookForDevTools) {
|
||||
devtools = this._createDevTools();
|
||||
await options.__testHookForDevTools(devtools);
|
||||
}
|
||||
return _crBrowser.CRBrowser.connect(this.attribution.playwright, transport, options, devtools);
|
||||
}
|
||||
doRewriteStartupLog(error) {
|
||||
if (!error.logs) return error;
|
||||
if (error.logs.includes('Missing X server')) error.logs = '\n' + (0, _ascii.wrapInASCIIBox)(_browserType.kNoXServerRunningError, 1);
|
||||
// These error messages are taken from Chromium source code as of July, 2020:
|
||||
// https://github.com/chromium/chromium/blob/70565f67e79f79e17663ad1337dc6e63ee207ce9/content/browser/zygote_host/zygote_host_impl_linux.cc
|
||||
if (!error.logs.includes('crbug.com/357670') && !error.logs.includes('No usable sandbox!') && !error.logs.includes('crbug.com/638180')) return error;
|
||||
error.logs = [`Chromium sandboxing failed!`, `================================`, `To avoid the sandboxing issue, do either of the following:`, ` - (preferred): Configure your environment to support sandboxing`, ` - (alternative): Launch Chromium without sandbox using 'chromiumSandbox: false' option`, `================================`, ``].join('\n');
|
||||
return error;
|
||||
}
|
||||
amendEnvironment(env, userDataDir, executable, browserArguments) {
|
||||
return env;
|
||||
}
|
||||
attemptToGracefullyCloseBrowser(transport) {
|
||||
const message = {
|
||||
method: 'Browser.close',
|
||||
id: _crConnection.kBrowserCloseMessageId,
|
||||
params: {}
|
||||
};
|
||||
transport.send(message);
|
||||
}
|
||||
async _launchWithSeleniumHub(progress, hubUrl, options) {
|
||||
await this._createArtifactDirs(options);
|
||||
if (!hubUrl.endsWith('/')) hubUrl = hubUrl + '/';
|
||||
const args = this._innerDefaultArgs(options);
|
||||
args.push('--remote-debugging-port=0');
|
||||
const isEdge = options.channel && options.channel.startsWith('msedge');
|
||||
let desiredCapabilities = {
|
||||
'browserName': isEdge ? 'MicrosoftEdge' : 'chrome',
|
||||
[isEdge ? 'ms:edgeOptions' : 'goog:chromeOptions']: {
|
||||
args
|
||||
}
|
||||
};
|
||||
if (process.env.SELENIUM_REMOTE_CAPABILITIES) {
|
||||
const remoteCapabilities = parseSeleniumRemoteParams({
|
||||
name: 'capabilities',
|
||||
value: process.env.SELENIUM_REMOTE_CAPABILITIES
|
||||
}, progress);
|
||||
if (remoteCapabilities) desiredCapabilities = {
|
||||
...desiredCapabilities,
|
||||
...remoteCapabilities
|
||||
};
|
||||
}
|
||||
let headers = {};
|
||||
if (process.env.SELENIUM_REMOTE_HEADERS) {
|
||||
const remoteHeaders = parseSeleniumRemoteParams({
|
||||
name: 'headers',
|
||||
value: process.env.SELENIUM_REMOTE_HEADERS
|
||||
}, progress);
|
||||
if (remoteHeaders) headers = remoteHeaders;
|
||||
}
|
||||
progress.log(`<selenium> connecting to ${hubUrl}`);
|
||||
const response = await (0, _network.fetchData)({
|
||||
url: hubUrl + 'session',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
...headers
|
||||
},
|
||||
data: JSON.stringify({
|
||||
capabilities: {
|
||||
alwaysMatch: desiredCapabilities
|
||||
}
|
||||
}),
|
||||
timeout: progress.timeUntilDeadline()
|
||||
}, seleniumErrorHandler);
|
||||
const value = JSON.parse(response).value;
|
||||
const sessionId = value.sessionId;
|
||||
progress.log(`<selenium> connected to sessionId=${sessionId}`);
|
||||
const disconnectFromSelenium = async () => {
|
||||
progress.log(`<selenium> disconnecting from sessionId=${sessionId}`);
|
||||
await (0, _network.fetchData)({
|
||||
url: hubUrl + 'session/' + sessionId,
|
||||
method: 'DELETE',
|
||||
headers
|
||||
}).catch(error => progress.log(`<error disconnecting from selenium>: ${error}`));
|
||||
progress.log(`<selenium> disconnected from sessionId=${sessionId}`);
|
||||
_processLauncher.gracefullyCloseSet.delete(disconnectFromSelenium);
|
||||
};
|
||||
_processLauncher.gracefullyCloseSet.add(disconnectFromSelenium);
|
||||
try {
|
||||
const capabilities = value.capabilities;
|
||||
let endpointURL;
|
||||
if (capabilities['se:cdp']) {
|
||||
// Selenium 4 - use built-in CDP websocket proxy.
|
||||
progress.log(`<selenium> using selenium v4`);
|
||||
const endpointURLString = addProtocol(capabilities['se:cdp']);
|
||||
endpointURL = new URL(endpointURLString);
|
||||
if (endpointURL.hostname === 'localhost' || endpointURL.hostname === '127.0.0.1') endpointURL.hostname = new URL(hubUrl).hostname;
|
||||
progress.log(`<selenium> retrieved endpoint ${endpointURL.toString()} for sessionId=${sessionId}`);
|
||||
} else {
|
||||
// Selenium 3 - resolve target node IP to use instead of localhost ws url.
|
||||
progress.log(`<selenium> using selenium v3`);
|
||||
const maybeChromeOptions = capabilities['goog:chromeOptions'];
|
||||
const chromeOptions = maybeChromeOptions && typeof maybeChromeOptions === 'object' ? maybeChromeOptions : undefined;
|
||||
const debuggerAddress = chromeOptions && typeof chromeOptions.debuggerAddress === 'string' ? chromeOptions.debuggerAddress : undefined;
|
||||
const chromeOptionsURL = typeof maybeChromeOptions === 'string' ? maybeChromeOptions : undefined;
|
||||
// TODO(dgozman): figure out if we can make ChromeDriver to return 127.0.0.1 instead of localhost.
|
||||
const endpointURLString = addProtocol(debuggerAddress || chromeOptionsURL).replace('localhost', '127.0.0.1');
|
||||
progress.log(`<selenium> retrieved endpoint ${endpointURLString} for sessionId=${sessionId}`);
|
||||
endpointURL = new URL(endpointURLString);
|
||||
if (endpointURL.hostname === 'localhost' || endpointURL.hostname === '127.0.0.1') {
|
||||
const sessionInfoUrl = new URL(hubUrl).origin + '/grid/api/testsession?session=' + sessionId;
|
||||
try {
|
||||
const sessionResponse = await (0, _network.fetchData)({
|
||||
url: sessionInfoUrl,
|
||||
method: 'GET',
|
||||
timeout: progress.timeUntilDeadline(),
|
||||
headers
|
||||
}, seleniumErrorHandler);
|
||||
const proxyId = JSON.parse(sessionResponse).proxyId;
|
||||
endpointURL.hostname = new URL(proxyId).hostname;
|
||||
progress.log(`<selenium> resolved endpoint ip ${endpointURL.toString()} for sessionId=${sessionId}`);
|
||||
} catch (e) {
|
||||
progress.log(`<selenium> unable to resolve endpoint ip for sessionId=${sessionId}, running in standalone?`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return await this._connectOverCDPInternal(progress, endpointURL.toString(), {
|
||||
...options,
|
||||
headers: (0, _utils.headersObjectToArray)(headers)
|
||||
}, disconnectFromSelenium);
|
||||
} catch (e) {
|
||||
await disconnectFromSelenium();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
defaultArgs(options, isPersistent, userDataDir) {
|
||||
const chromeArguments = this._innerDefaultArgs(options);
|
||||
chromeArguments.push(`--user-data-dir=${userDataDir}`);
|
||||
if (options.useWebSocket) chromeArguments.push('--remote-debugging-port=0');else chromeArguments.push('--remote-debugging-pipe');
|
||||
if (isPersistent) chromeArguments.push('about:blank');else chromeArguments.push('--no-startup-window');
|
||||
return chromeArguments;
|
||||
}
|
||||
_innerDefaultArgs(options) {
|
||||
const {
|
||||
args = []
|
||||
} = options;
|
||||
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));
|
||||
if (userDataDirArg) throw this._createUserDataDirArgMisuseError('--user-data-dir');
|
||||
if (args.find(arg => arg.startsWith('--remote-debugging-pipe'))) throw new Error('Playwright manages remote debugging connection itself.');
|
||||
if (args.find(arg => !arg.startsWith('-'))) throw new Error('Arguments can not specify page to be opened');
|
||||
const chromeArguments = [..._chromiumSwitches.chromiumSwitches];
|
||||
if (_os.default.platform() === 'darwin') {
|
||||
// See https://github.com/microsoft/playwright/issues/7362
|
||||
chromeArguments.push('--enable-use-zoom-for-dsf=false');
|
||||
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1407025.
|
||||
if (options.headless && (!options.channel || options.channel === 'chromium-headless-shell')) chromeArguments.push('--use-angle');
|
||||
}
|
||||
if (options.devtools) chromeArguments.push('--auto-open-devtools-for-tabs');
|
||||
if (options.headless) {
|
||||
chromeArguments.push('--headless');
|
||||
chromeArguments.push('--hide-scrollbars', '--mute-audio', '--blink-settings=primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4');
|
||||
}
|
||||
if (options.chromiumSandbox !== true) chromeArguments.push('--no-sandbox');
|
||||
const proxy = options.proxyOverride || options.proxy;
|
||||
if (proxy) {
|
||||
const proxyURL = new URL(proxy.server);
|
||||
const isSocks = proxyURL.protocol === 'socks5:';
|
||||
// https://www.chromium.org/developers/design-documents/network-settings
|
||||
if (isSocks && !this.attribution.playwright.options.socksProxyPort) {
|
||||
// https://www.chromium.org/developers/design-documents/network-stack/socks-proxy
|
||||
chromeArguments.push(`--host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE ${proxyURL.hostname}"`);
|
||||
}
|
||||
chromeArguments.push(`--proxy-server=${proxy.server}`);
|
||||
const proxyBypassRules = [];
|
||||
// https://source.chromium.org/chromium/chromium/src/+/master:net/docs/proxy.md;l=548;drc=71698e610121078e0d1a811054dcf9fd89b49578
|
||||
if (this.attribution.playwright.options.socksProxyPort) proxyBypassRules.push('<-loopback>');
|
||||
if (proxy.bypass) proxyBypassRules.push(...proxy.bypass.split(',').map(t => t.trim()).map(t => t.startsWith('.') ? '*' + t : t));
|
||||
if (!process.env.PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK && !proxyBypassRules.includes('<-loopback>')) proxyBypassRules.push('<-loopback>');
|
||||
if (proxyBypassRules.length > 0) chromeArguments.push(`--proxy-bypass-list=${proxyBypassRules.join(';')}`);
|
||||
}
|
||||
chromeArguments.push(...args);
|
||||
return chromeArguments;
|
||||
}
|
||||
readyState(options) {
|
||||
var _options$args;
|
||||
if (options.useWebSocket || (_options$args = options.args) !== null && _options$args !== void 0 && _options$args.some(a => a.startsWith('--remote-debugging-port'))) return new ChromiumReadyState();
|
||||
return undefined;
|
||||
}
|
||||
getExecutableName(options) {
|
||||
if (options.channel) return options.channel;
|
||||
return options.headless ? 'chromium-headless-shell' : 'chromium';
|
||||
}
|
||||
}
|
||||
exports.Chromium = Chromium;
|
||||
class ChromiumReadyState extends _browserType.BrowserReadyState {
|
||||
onBrowserOutput(message) {
|
||||
const match = message.match(/DevTools listening on (.*)/);
|
||||
if (match) this._wsEndpoint.resolve(match[1]);
|
||||
}
|
||||
}
|
||||
async function urlToWSEndpoint(progress, endpointURL, headers) {
|
||||
if (endpointURL.startsWith('ws')) return endpointURL;
|
||||
progress.log(`<ws preparing> retrieving websocket url from ${endpointURL}`);
|
||||
const httpURL = endpointURL.endsWith('/') ? `${endpointURL}json/version/` : `${endpointURL}/json/version/`;
|
||||
const json = await (0, _network.fetchData)({
|
||||
url: httpURL,
|
||||
headers
|
||||
}, async (_, resp) => new Error(`Unexpected status ${resp.statusCode} when connecting to ${httpURL}.\n` + `This does not look like a DevTools server, try connecting via ws://.`));
|
||||
return JSON.parse(json).webSocketDebuggerUrl;
|
||||
}
|
||||
async function seleniumErrorHandler(params, response) {
|
||||
const body = await streamToString(response);
|
||||
let message = body;
|
||||
try {
|
||||
const json = JSON.parse(body);
|
||||
message = json.value.localizedMessage || json.value.message;
|
||||
} catch (e) {}
|
||||
return new Error(`Error connecting to Selenium at ${params.url}: ${message}`);
|
||||
}
|
||||
function addProtocol(url) {
|
||||
if (!['ws://', 'wss://', 'http://', 'https://'].some(protocol => url.startsWith(protocol))) return 'http://' + url;
|
||||
return url;
|
||||
}
|
||||
function streamToString(stream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
stream.on('data', chunk => chunks.push(Buffer.from(chunk)));
|
||||
stream.on('error', reject);
|
||||
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
||||
});
|
||||
}
|
||||
function parseSeleniumRemoteParams(env, progress) {
|
||||
try {
|
||||
const parsed = JSON.parse(env.value);
|
||||
progress.log(`<selenium> using additional ${env.name} "${env.value}"`);
|
||||
return parsed;
|
||||
} catch (e) {
|
||||
progress.log(`<selenium> ignoring additional ${env.name} "${env.value}": ${e}`);
|
||||
}
|
||||
}
|
||||
69
node_modules/playwright-core/lib/server/chromium/chromiumSwitches.js
generated
vendored
Normal file
69
node_modules/playwright-core/lib/server/chromium/chromiumSwitches.js
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.chromiumSwitches = void 0;
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// No dependencies as it is used from the Electron loader.
|
||||
|
||||
const disabledFeatures = [
|
||||
// See https://github.com/microsoft/playwright/pull/10380
|
||||
'AcceptCHFrame',
|
||||
// See https://github.com/microsoft/playwright/pull/10679
|
||||
'AutoExpandDetailsElement',
|
||||
// See https://github.com/microsoft/playwright/issues/14047
|
||||
'AvoidUnnecessaryBeforeUnloadCheckSync',
|
||||
// See https://github.com/microsoft/playwright/pull/12992
|
||||
'CertificateTransparencyComponentUpdater',
|
||||
// This makes Page.frameScheduledNavigation arrive much later after a click,
|
||||
// making our navigation auto-wait after click not working.
|
||||
// Can be removed once we deperecate noWaitAfter.
|
||||
// See https://github.com/microsoft/playwright/pull/34372.
|
||||
'DeferRendererTasksAfterInput', 'DestroyProfileOnBrowserClose',
|
||||
// See https://github.com/microsoft/playwright/pull/13854
|
||||
'DialMediaRouteProvider',
|
||||
// Chromium is disabling manifest version 2. Allow testing it as long as Chromium can actually run it.
|
||||
// Disabled in https://chromium-review.googlesource.com/c/chromium/src/+/6265903.
|
||||
'ExtensionManifestV2Disabled', 'GlobalMediaControls',
|
||||
// See https://github.com/microsoft/playwright/pull/27605
|
||||
'HttpsUpgrades', 'ImprovedCookieControls', 'LazyFrameLoading',
|
||||
// Hides the Lens feature in the URL address bar. Its not working in unofficial builds.
|
||||
'LensOverlay',
|
||||
// See https://github.com/microsoft/playwright/pull/8162
|
||||
'MediaRouter',
|
||||
// See https://github.com/microsoft/playwright/issues/28023
|
||||
'PaintHolding',
|
||||
// See https://github.com/microsoft/playwright/issues/32230
|
||||
'ThirdPartyStoragePartitioning',
|
||||
// See https://github.com/microsoft/playwright/issues/16126
|
||||
'Translate'];
|
||||
const chromiumSwitches = exports.chromiumSwitches = ['--disable-field-trial-config',
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:testing/variations/README.md
|
||||
'--disable-background-networking', '--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows', '--disable-back-forward-cache',
|
||||
// Avoids surprises like main request not being intercepted during page.goBack().
|
||||
'--disable-breakpad', '--disable-client-side-phishing-detection', '--disable-component-extensions-with-background-pages', '--disable-component-update',
|
||||
// Avoids unneeded network activity after startup.
|
||||
'--no-default-browser-check', '--disable-default-apps', '--disable-dev-shm-usage', '--disable-extensions', '--disable-features=' + disabledFeatures.join(','), '--allow-pre-commit-input', '--disable-hang-monitor', '--disable-ipc-flooding-protection', '--disable-popup-blocking', '--disable-prompt-on-repost', '--disable-renderer-backgrounding', '--force-color-profile=srgb', '--metrics-recording-only', '--no-first-run', '--enable-automation', '--password-store=basic', '--use-mock-keychain',
|
||||
// See https://chromium-review.googlesource.com/c/chromium/src/+/2436773
|
||||
'--no-service-autorun', '--export-tagged-pdf',
|
||||
// https://chromium-review.googlesource.com/c/chromium/src/+/4853540
|
||||
'--disable-search-engine-choice-screen',
|
||||
// https://issues.chromium.org/41491762
|
||||
'--unsafely-disable-devtools-self-xss-warnings'];
|
||||
237
node_modules/playwright-core/lib/server/chromium/crAccessibility.js
generated
vendored
Normal file
237
node_modules/playwright-core/lib/server/chromium/crAccessibility.js
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.getAccessibilityTree = getAccessibilityTree;
|
||||
/**
|
||||
* Copyright 2018 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
async function getAccessibilityTree(client, needle) {
|
||||
const {
|
||||
nodes
|
||||
} = await client.send('Accessibility.getFullAXTree');
|
||||
const tree = CRAXNode.createTree(client, nodes);
|
||||
return {
|
||||
tree,
|
||||
needle: needle ? await tree._findElement(needle) : null
|
||||
};
|
||||
}
|
||||
class CRAXNode {
|
||||
constructor(client, payload) {
|
||||
this._payload = void 0;
|
||||
this._children = [];
|
||||
this._richlyEditable = false;
|
||||
this._editable = false;
|
||||
this._focusable = false;
|
||||
this._expanded = false;
|
||||
this._hidden = false;
|
||||
this._name = void 0;
|
||||
this._role = void 0;
|
||||
this._cachedHasFocusableChild = void 0;
|
||||
this._client = void 0;
|
||||
this._client = client;
|
||||
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 === 'expanded') this._expanded = 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' || role === 'StaticText';
|
||||
}
|
||||
_hasFocusableChild() {
|
||||
if (this._cachedHasFocusableChild === undefined) {
|
||||
this._cachedHasFocusableChild = false;
|
||||
for (const child of this._children) {
|
||||
if (child._focusable || child._hasFocusableChild()) {
|
||||
this._cachedHasFocusableChild = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._cachedHasFocusableChild;
|
||||
}
|
||||
children() {
|
||||
return this._children;
|
||||
}
|
||||
async _findElement(element) {
|
||||
const objectId = element._objectId;
|
||||
const {
|
||||
node: {
|
||||
backendNodeId
|
||||
}
|
||||
} = await this._client.send('DOM.describeNode', {
|
||||
objectId
|
||||
});
|
||||
const needle = this.find(node => node._payload.backendDOMNodeId === backendNodeId);
|
||||
return needle || null;
|
||||
}
|
||||
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._role !== 'WebArea' && this._role !== 'RootWebArea' && 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;
|
||||
}
|
||||
normalizedRole() {
|
||||
switch (this._role) {
|
||||
case 'RootWebArea':
|
||||
return 'WebArea';
|
||||
case 'StaticText':
|
||||
return 'text';
|
||||
default:
|
||||
return this._role;
|
||||
}
|
||||
}
|
||||
serialize() {
|
||||
const properties = new Map();
|
||||
for (const property of this._payload.properties || []) properties.set(property.name.toLowerCase(), property.value.value);
|
||||
if (this._payload.description) properties.set('description', this._payload.description.value);
|
||||
const node = {
|
||||
role: this.normalizedRole(),
|
||||
name: this._payload.name ? this._payload.name.value || '' : ''
|
||||
};
|
||||
const userStringProperties = ['description', 'keyshortcuts', 'roledescription', 'valuetext'];
|
||||
for (const userStringProperty of userStringProperties) {
|
||||
if (!properties.has(userStringProperty)) continue;
|
||||
node[userStringProperty] = properties.get(userStringProperty);
|
||||
}
|
||||
const booleanProperties = ['disabled', 'expanded', 'focused', 'modal', 'multiline', 'multiselectable', 'readonly', 'required', 'selected'];
|
||||
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' || this._role === 'RootWebArea')) continue;
|
||||
const value = properties.get(booleanProperty);
|
||||
if (!value) continue;
|
||||
node[booleanProperty] = value;
|
||||
}
|
||||
const numericalProperties = ['level', 'valuemax', 'valuemin'];
|
||||
for (const numericalProperty of numericalProperties) {
|
||||
if (!properties.has(numericalProperty)) continue;
|
||||
node[numericalProperty] = properties.get(numericalProperty);
|
||||
}
|
||||
const tokenProperties = ['autocomplete', 'haspopup', 'invalid', 'orientation'];
|
||||
for (const tokenProperty of tokenProperties) {
|
||||
const value = properties.get(tokenProperty);
|
||||
if (!value || value === 'false') continue;
|
||||
node[tokenProperty] = value;
|
||||
}
|
||||
const axNode = node;
|
||||
if (this._payload.value) {
|
||||
if (typeof this._payload.value.value === 'string') axNode.valueString = this._payload.value.value;
|
||||
if (typeof this._payload.value.value === 'number') axNode.valueNumber = this._payload.value.value;
|
||||
}
|
||||
if (properties.has('checked')) axNode.checked = properties.get('checked') === 'true' ? 'checked' : properties.get('checked') === 'false' ? 'unchecked' : 'mixed';
|
||||
if (properties.has('pressed')) axNode.pressed = properties.get('pressed') === 'true' ? 'pressed' : properties.get('pressed') === 'false' ? 'released' : 'mixed';
|
||||
return axNode;
|
||||
}
|
||||
static createTree(client, payloads) {
|
||||
const nodeById = new Map();
|
||||
for (const payload of payloads) nodeById.set(payload.nodeId, new CRAXNode(client, 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;
|
||||
}
|
||||
}
|
||||
517
node_modules/playwright-core/lib/server/chromium/crBrowser.js
generated
vendored
Normal file
517
node_modules/playwright-core/lib/server/chromium/crBrowser.js
generated
vendored
Normal file
@@ -0,0 +1,517 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.CRBrowserContext = exports.CRBrowser = void 0;
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _assert = require("../../utils/isomorphic/assert");
|
||||
var _crypto = require("../utils/crypto");
|
||||
var _artifact = require("../artifact");
|
||||
var _browser = require("../browser");
|
||||
var _browserContext = require("../browserContext");
|
||||
var _frames = require("../frames");
|
||||
var network = _interopRequireWildcard(require("../network"));
|
||||
var _page = require("../page");
|
||||
var _crConnection = require("./crConnection");
|
||||
var _crPage = require("./crPage");
|
||||
var _crProtocolHelper = require("./crProtocolHelper");
|
||||
var _crServiceWorker = require("./crServiceWorker");
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class CRBrowser extends _browser.Browser {
|
||||
static async connect(parent, transport, options, devtools) {
|
||||
// Make a copy in case we need to update `headful` property below.
|
||||
options = {
|
||||
...options
|
||||
};
|
||||
const connection = new _crConnection.CRConnection(transport, options.protocolLogger, options.browserLogsCollector);
|
||||
const browser = new CRBrowser(parent, connection, options);
|
||||
browser._devtools = devtools;
|
||||
if (browser.isClank()) browser._isCollocatedWithServer = false;
|
||||
const session = connection.rootSession;
|
||||
if (options.__testHookOnConnectToBrowser) await options.__testHookOnConnectToBrowser();
|
||||
const version = await session.send('Browser.getVersion');
|
||||
browser._version = version.product.substring(version.product.indexOf('/') + 1);
|
||||
browser._userAgent = version.userAgent;
|
||||
// We don't trust the option as it may lie in case of connectOverCDP where remote browser
|
||||
// may have been launched with different options.
|
||||
browser.options.headful = !version.userAgent.includes('Headless');
|
||||
if (!options.persistent) {
|
||||
await session.send('Target.setAutoAttach', {
|
||||
autoAttach: true,
|
||||
waitForDebuggerOnStart: true,
|
||||
flatten: true
|
||||
});
|
||||
return browser;
|
||||
}
|
||||
browser._defaultContext = new CRBrowserContext(browser, undefined, options.persistent);
|
||||
await Promise.all([session.send('Target.setAutoAttach', {
|
||||
autoAttach: true,
|
||||
waitForDebuggerOnStart: true,
|
||||
flatten: true
|
||||
}).then(async () => {
|
||||
// Target.setAutoAttach has a bug where it does not wait for new Targets being attached.
|
||||
// However making a dummy call afterwards fixes this.
|
||||
// This can be removed after https://chromium-review.googlesource.com/c/chromium/src/+/2885888 lands in stable.
|
||||
await session.send('Target.getTargetInfo');
|
||||
}), browser._defaultContext._initialize()]);
|
||||
await browser._waitForAllPagesToBeInitialized();
|
||||
return browser;
|
||||
}
|
||||
constructor(parent, connection, options) {
|
||||
super(parent, options);
|
||||
this._connection = void 0;
|
||||
this._session = void 0;
|
||||
this._clientRootSessionPromise = null;
|
||||
this._contexts = new Map();
|
||||
this._crPages = new Map();
|
||||
this._backgroundPages = new Map();
|
||||
this._serviceWorkers = new Map();
|
||||
this._devtools = void 0;
|
||||
this._version = '';
|
||||
this._tracingRecording = false;
|
||||
this._tracingClient = void 0;
|
||||
this._userAgent = '';
|
||||
this._connection = connection;
|
||||
this._session = this._connection.rootSession;
|
||||
this._connection.on(_crConnection.ConnectionEvents.Disconnected, () => this._didDisconnect());
|
||||
this._session.on('Target.attachedToTarget', this._onAttachedToTarget.bind(this));
|
||||
this._session.on('Target.detachedFromTarget', this._onDetachedFromTarget.bind(this));
|
||||
this._session.on('Browser.downloadWillBegin', this._onDownloadWillBegin.bind(this));
|
||||
this._session.on('Browser.downloadProgress', this._onDownloadProgress.bind(this));
|
||||
}
|
||||
async doCreateNewContext(options) {
|
||||
const proxy = options.proxyOverride || options.proxy;
|
||||
let proxyBypassList = undefined;
|
||||
if (proxy) {
|
||||
if (process.env.PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK) proxyBypassList = proxy.bypass;else proxyBypassList = '<-loopback>' + (proxy.bypass ? `,${proxy.bypass}` : '');
|
||||
}
|
||||
const {
|
||||
browserContextId
|
||||
} = await this._session.send('Target.createBrowserContext', {
|
||||
disposeOnDetach: true,
|
||||
proxyServer: proxy ? proxy.server : undefined,
|
||||
proxyBypassList
|
||||
});
|
||||
const context = new CRBrowserContext(this, browserContextId, options);
|
||||
await context._initialize();
|
||||
this._contexts.set(browserContextId, context);
|
||||
return context;
|
||||
}
|
||||
contexts() {
|
||||
return Array.from(this._contexts.values());
|
||||
}
|
||||
version() {
|
||||
return this._version;
|
||||
}
|
||||
userAgent() {
|
||||
return this._userAgent;
|
||||
}
|
||||
_platform() {
|
||||
if (this._userAgent.includes('Windows')) return 'win';
|
||||
if (this._userAgent.includes('Macintosh')) return 'mac';
|
||||
return 'linux';
|
||||
}
|
||||
isClank() {
|
||||
return this.options.name === 'clank';
|
||||
}
|
||||
async _waitForAllPagesToBeInitialized() {
|
||||
await Promise.all([...this._crPages.values()].map(crPage => crPage._page.waitForInitializedOrError()));
|
||||
}
|
||||
_onAttachedToTarget({
|
||||
targetInfo,
|
||||
sessionId,
|
||||
waitingForDebugger
|
||||
}) {
|
||||
if (targetInfo.type === 'browser') return;
|
||||
const session = this._session.createChildSession(sessionId);
|
||||
(0, _assert.assert)(targetInfo.browserContextId, 'targetInfo: ' + JSON.stringify(targetInfo, null, 2));
|
||||
let context = this._contexts.get(targetInfo.browserContextId) || null;
|
||||
if (!context) {
|
||||
// TODO: auto attach only to pages from our contexts.
|
||||
// assert(this._defaultContext);
|
||||
context = this._defaultContext;
|
||||
}
|
||||
if (targetInfo.type === 'other' && targetInfo.url.startsWith('devtools://devtools') && this._devtools) {
|
||||
this._devtools.install(session);
|
||||
return;
|
||||
}
|
||||
const treatOtherAsPage = targetInfo.type === 'other' && process.env.PW_CHROMIUM_ATTACH_TO_OTHER;
|
||||
if (!context || targetInfo.type === 'other' && !treatOtherAsPage) {
|
||||
session.detach().catch(() => {});
|
||||
return;
|
||||
}
|
||||
(0, _assert.assert)(!this._crPages.has(targetInfo.targetId), 'Duplicate target ' + targetInfo.targetId);
|
||||
(0, _assert.assert)(!this._backgroundPages.has(targetInfo.targetId), 'Duplicate target ' + targetInfo.targetId);
|
||||
(0, _assert.assert)(!this._serviceWorkers.has(targetInfo.targetId), 'Duplicate target ' + targetInfo.targetId);
|
||||
if (targetInfo.type === 'background_page') {
|
||||
const backgroundPage = new _crPage.CRPage(session, targetInfo.targetId, context, null, {
|
||||
hasUIWindow: false,
|
||||
isBackgroundPage: true
|
||||
});
|
||||
this._backgroundPages.set(targetInfo.targetId, backgroundPage);
|
||||
return;
|
||||
}
|
||||
if (targetInfo.type === 'page' || treatOtherAsPage) {
|
||||
const opener = targetInfo.openerId ? this._crPages.get(targetInfo.openerId) || null : null;
|
||||
const crPage = new _crPage.CRPage(session, targetInfo.targetId, context, opener, {
|
||||
hasUIWindow: targetInfo.type === 'page',
|
||||
isBackgroundPage: false
|
||||
});
|
||||
this._crPages.set(targetInfo.targetId, crPage);
|
||||
return;
|
||||
}
|
||||
if (targetInfo.type === 'service_worker') {
|
||||
const serviceWorker = new _crServiceWorker.CRServiceWorker(context, session, targetInfo.url);
|
||||
this._serviceWorkers.set(targetInfo.targetId, serviceWorker);
|
||||
context.emit(CRBrowserContext.CREvents.ServiceWorker, serviceWorker);
|
||||
return;
|
||||
}
|
||||
|
||||
// Detach from any targets we are not interested in, to avoid side-effects.
|
||||
//
|
||||
// One example of a side effect: upon shared worker restart, we receive
|
||||
// Inspector.targetReloadedAfterCrash and backend waits for Runtime.runIfWaitingForDebugger
|
||||
// from any attached client. If we do not resume, shared worker will stall.
|
||||
session.detach().catch(() => {});
|
||||
}
|
||||
_onDetachedFromTarget(payload) {
|
||||
const targetId = payload.targetId;
|
||||
const crPage = this._crPages.get(targetId);
|
||||
if (crPage) {
|
||||
this._crPages.delete(targetId);
|
||||
crPage.didClose();
|
||||
return;
|
||||
}
|
||||
const backgroundPage = this._backgroundPages.get(targetId);
|
||||
if (backgroundPage) {
|
||||
this._backgroundPages.delete(targetId);
|
||||
backgroundPage.didClose();
|
||||
return;
|
||||
}
|
||||
const serviceWorker = this._serviceWorkers.get(targetId);
|
||||
if (serviceWorker) {
|
||||
this._serviceWorkers.delete(targetId);
|
||||
serviceWorker.didClose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
_didDisconnect() {
|
||||
for (const crPage of this._crPages.values()) crPage.didClose();
|
||||
this._crPages.clear();
|
||||
for (const backgroundPage of this._backgroundPages.values()) backgroundPage.didClose();
|
||||
this._backgroundPages.clear();
|
||||
for (const serviceWorker of this._serviceWorkers.values()) serviceWorker.didClose();
|
||||
this._serviceWorkers.clear();
|
||||
this._didClose();
|
||||
}
|
||||
_findOwningPage(frameId) {
|
||||
for (const crPage of this._crPages.values()) {
|
||||
const frame = crPage._page._frameManager.frame(frameId);
|
||||
if (frame) return crPage;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
_onDownloadWillBegin(payload) {
|
||||
const page = this._findOwningPage(payload.frameId);
|
||||
if (!page) {
|
||||
// There might be no page when download originates from something unusual, like
|
||||
// a DevTools window or maybe an extension page.
|
||||
// See https://github.com/microsoft/playwright/issues/22551.
|
||||
return;
|
||||
}
|
||||
page.willBeginDownload();
|
||||
let originPage = page._page.initializedOrUndefined();
|
||||
// If it's a new window download, report it on the opener page.
|
||||
if (!originPage && page._opener) originPage = page._opener._page.initializedOrUndefined();
|
||||
if (!originPage) return;
|
||||
this._downloadCreated(originPage, payload.guid, payload.url, payload.suggestedFilename);
|
||||
}
|
||||
_onDownloadProgress(payload) {
|
||||
if (payload.state === 'completed') this._downloadFinished(payload.guid, '');
|
||||
if (payload.state === 'canceled') this._downloadFinished(payload.guid, this._closeReason || 'canceled');
|
||||
}
|
||||
async _closePage(crPage) {
|
||||
await this._session.send('Target.closeTarget', {
|
||||
targetId: crPage._targetId
|
||||
});
|
||||
}
|
||||
async newBrowserCDPSession() {
|
||||
return await this._connection.createBrowserSession();
|
||||
}
|
||||
async startTracing(page, options = {}) {
|
||||
(0, _assert.assert)(!this._tracingRecording, 'Cannot start recording trace while already recording trace.');
|
||||
this._tracingClient = page ? page._delegate._mainFrameSession._client : this._session;
|
||||
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 {
|
||||
screenshots = false,
|
||||
categories = defaultCategories
|
||||
} = options;
|
||||
if (screenshots) categories.push('disabled-by-default-devtools.screenshot');
|
||||
this._tracingRecording = true;
|
||||
await this._tracingClient.send('Tracing.start', {
|
||||
transferMode: 'ReturnAsStream',
|
||||
categories: categories.join(',')
|
||||
});
|
||||
}
|
||||
async stopTracing() {
|
||||
(0, _assert.assert)(this._tracingClient, 'Tracing was not started.');
|
||||
const [event] = await Promise.all([new Promise(f => this._tracingClient.once('Tracing.tracingComplete', f)), this._tracingClient.send('Tracing.end')]);
|
||||
const tracingPath = _path.default.join(this.options.artifactsDir, (0, _crypto.createGuid)() + '.crtrace');
|
||||
await (0, _crProtocolHelper.saveProtocolStream)(this._tracingClient, event.stream, tracingPath);
|
||||
this._tracingRecording = false;
|
||||
const artifact = new _artifact.Artifact(this, tracingPath);
|
||||
artifact.reportFinished();
|
||||
return artifact;
|
||||
}
|
||||
isConnected() {
|
||||
return !this._connection._closed;
|
||||
}
|
||||
async _clientRootSession() {
|
||||
if (!this._clientRootSessionPromise) this._clientRootSessionPromise = this._connection.createBrowserSession();
|
||||
return this._clientRootSessionPromise;
|
||||
}
|
||||
}
|
||||
exports.CRBrowser = CRBrowser;
|
||||
class CRBrowserContext extends _browserContext.BrowserContext {
|
||||
constructor(browser, browserContextId, options) {
|
||||
super(browser, options, browserContextId);
|
||||
this._authenticateProxyViaCredentials();
|
||||
}
|
||||
async _initialize() {
|
||||
(0, _assert.assert)(!Array.from(this._browser._crPages.values()).some(page => page._browserContext === this));
|
||||
const promises = [super._initialize()];
|
||||
if (this._browser.options.name !== 'clank' && this._options.acceptDownloads !== 'internal-browser-default') {
|
||||
promises.push(this._browser._session.send('Browser.setDownloadBehavior', {
|
||||
behavior: this._options.acceptDownloads === 'accept' ? 'allowAndName' : 'deny',
|
||||
browserContextId: this._browserContextId,
|
||||
downloadPath: this._browser.options.downloadsPath,
|
||||
eventsEnabled: true
|
||||
}));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
_crPages() {
|
||||
return [...this._browser._crPages.values()].filter(crPage => crPage._browserContext === this);
|
||||
}
|
||||
possiblyUninitializedPages() {
|
||||
return this._crPages().map(crPage => crPage._page);
|
||||
}
|
||||
async doCreateNewPage() {
|
||||
(0, _browserContext.assertBrowserContextIsNotOwned)(this);
|
||||
const oldKeys = this._browser.isClank() ? new Set(this._browser._crPages.keys()) : undefined;
|
||||
let {
|
||||
targetId
|
||||
} = await this._browser._session.send('Target.createTarget', {
|
||||
url: 'about:blank',
|
||||
browserContextId: this._browserContextId
|
||||
});
|
||||
if (oldKeys) {
|
||||
// Chrome for Android returns tab ids (1, 2, 3, 4, 5) instead of content target ids here, work around it via the
|
||||
// heuristic assuming that there is only one page created at a time.
|
||||
const newKeys = new Set(this._browser._crPages.keys());
|
||||
// Remove old keys.
|
||||
for (const key of oldKeys) newKeys.delete(key);
|
||||
// Remove potential concurrent popups.
|
||||
for (const key of newKeys) {
|
||||
const page = this._browser._crPages.get(key);
|
||||
if (page._opener) newKeys.delete(key);
|
||||
}
|
||||
(0, _assert.assert)(newKeys.size === 1);
|
||||
[targetId] = [...newKeys];
|
||||
}
|
||||
return this._browser._crPages.get(targetId)._page;
|
||||
}
|
||||
async doGetCookies(urls) {
|
||||
const {
|
||||
cookies
|
||||
} = await this._browser._session.send('Storage.getCookies', {
|
||||
browserContextId: this._browserContextId
|
||||
});
|
||||
return network.filterCookies(cookies.map(c => {
|
||||
const copy = {
|
||||
sameSite: 'Lax',
|
||||
...c
|
||||
};
|
||||
delete copy.size;
|
||||
delete copy.priority;
|
||||
delete copy.session;
|
||||
delete copy.sameParty;
|
||||
delete copy.sourceScheme;
|
||||
delete copy.sourcePort;
|
||||
return copy;
|
||||
}), urls);
|
||||
}
|
||||
async addCookies(cookies) {
|
||||
await this._browser._session.send('Storage.setCookies', {
|
||||
cookies: network.rewriteCookies(cookies),
|
||||
browserContextId: this._browserContextId
|
||||
});
|
||||
}
|
||||
async doClearCookies() {
|
||||
await this._browser._session.send('Storage.clearCookies', {
|
||||
browserContextId: this._browserContextId
|
||||
});
|
||||
}
|
||||
async doGrantPermissions(origin, permissions) {
|
||||
const webPermissionToProtocol = new Map([['geolocation', 'geolocation'], ['midi', 'midi'], ['notifications', 'notifications'], ['camera', 'videoCapture'], ['microphone', 'audioCapture'], ['background-sync', 'backgroundSync'], ['ambient-light-sensor', 'sensors'], ['accelerometer', 'sensors'], ['gyroscope', 'sensors'], ['magnetometer', 'sensors'], ['clipboard-read', 'clipboardReadWrite'], ['clipboard-write', 'clipboardSanitizedWrite'], ['payment-handler', 'paymentHandler'],
|
||||
// chrome-specific permissions we have.
|
||||
['midi-sysex', 'midiSysex'], ['storage-access', 'storageAccess']]);
|
||||
const filtered = permissions.map(permission => {
|
||||
const protocolPermission = webPermissionToProtocol.get(permission);
|
||||
if (!protocolPermission) throw new Error('Unknown permission: ' + permission);
|
||||
return protocolPermission;
|
||||
});
|
||||
await this._browser._session.send('Browser.grantPermissions', {
|
||||
origin: origin === '*' ? undefined : origin,
|
||||
browserContextId: this._browserContextId,
|
||||
permissions: filtered
|
||||
});
|
||||
}
|
||||
async doClearPermissions() {
|
||||
await this._browser._session.send('Browser.resetPermissions', {
|
||||
browserContextId: this._browserContextId
|
||||
});
|
||||
}
|
||||
async setGeolocation(geolocation) {
|
||||
(0, _browserContext.verifyGeolocation)(geolocation);
|
||||
this._options.geolocation = geolocation;
|
||||
for (const page of this.pages()) await page._delegate.updateGeolocation();
|
||||
}
|
||||
async setExtraHTTPHeaders(headers) {
|
||||
this._options.extraHTTPHeaders = headers;
|
||||
for (const page of this.pages()) await page._delegate.updateExtraHTTPHeaders();
|
||||
for (const sw of this.serviceWorkers()) await sw.updateExtraHTTPHeaders();
|
||||
}
|
||||
async setUserAgent(userAgent) {
|
||||
this._options.userAgent = userAgent;
|
||||
for (const page of this.pages()) await page._delegate.updateUserAgent();
|
||||
// TODO: service workers don't have Emulation domain?
|
||||
}
|
||||
async setOffline(offline) {
|
||||
this._options.offline = offline;
|
||||
for (const page of this.pages()) await page._delegate.updateOffline();
|
||||
for (const sw of this.serviceWorkers()) await sw.updateOffline();
|
||||
}
|
||||
async doSetHTTPCredentials(httpCredentials) {
|
||||
this._options.httpCredentials = httpCredentials;
|
||||
for (const page of this.pages()) await page._delegate.updateHttpCredentials();
|
||||
for (const sw of this.serviceWorkers()) await sw.updateHttpCredentials();
|
||||
}
|
||||
async doAddInitScript(initScript) {
|
||||
for (const page of this.pages()) await page._delegate.addInitScript(initScript);
|
||||
}
|
||||
async doRemoveNonInternalInitScripts() {
|
||||
for (const page of this.pages()) await page._delegate.removeNonInternalInitScripts();
|
||||
}
|
||||
async doUpdateRequestInterception() {
|
||||
for (const page of this.pages()) await page._delegate.updateRequestInterception();
|
||||
for (const sw of this.serviceWorkers()) await sw.updateRequestInterception();
|
||||
}
|
||||
async doClose(reason) {
|
||||
// Headful chrome cannot dispose browser context with opened 'beforeunload'
|
||||
// dialogs, so we should close all that are currently opened.
|
||||
// We also won't get new ones since `Target.disposeBrowserContext` does not trigger
|
||||
// beforeunload.
|
||||
const openedBeforeUnloadDialogs = [];
|
||||
for (const crPage of this._crPages()) {
|
||||
const dialogs = [...crPage._page._frameManager._openedDialogs].filter(dialog => dialog.type() === 'beforeunload');
|
||||
openedBeforeUnloadDialogs.push(...dialogs);
|
||||
}
|
||||
await Promise.all(openedBeforeUnloadDialogs.map(dialog => dialog.dismiss()));
|
||||
if (!this._browserContextId) {
|
||||
await this.stopVideoRecording();
|
||||
// Closing persistent context should close the browser.
|
||||
await this._browser.close({
|
||||
reason
|
||||
});
|
||||
return;
|
||||
}
|
||||
await this._browser._session.send('Target.disposeBrowserContext', {
|
||||
browserContextId: this._browserContextId
|
||||
});
|
||||
this._browser._contexts.delete(this._browserContextId);
|
||||
for (const [targetId, serviceWorker] of this._browser._serviceWorkers) {
|
||||
if (serviceWorker._browserContext !== this) continue;
|
||||
// When closing a browser context, service workers are shutdown
|
||||
// asynchronously and we get detached from them later.
|
||||
// To avoid the wrong order of notifications, we manually fire
|
||||
// "close" event here and forget about the service worker.
|
||||
serviceWorker.didClose();
|
||||
this._browser._serviceWorkers.delete(targetId);
|
||||
}
|
||||
}
|
||||
async stopVideoRecording() {
|
||||
await Promise.all(this._crPages().map(crPage => crPage._mainFrameSession._stopVideoRecording()));
|
||||
}
|
||||
onClosePersistent() {
|
||||
// When persistent context is closed, we do not necessary get Target.detachedFromTarget
|
||||
// for all the background pages.
|
||||
for (const [targetId, backgroundPage] of this._browser._backgroundPages.entries()) {
|
||||
if (backgroundPage._browserContext === this && backgroundPage._page.initializedOrUndefined()) {
|
||||
backgroundPage.didClose();
|
||||
this._browser._backgroundPages.delete(targetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
async clearCache() {
|
||||
for (const page of this._crPages()) await page._networkManager.clearCache();
|
||||
}
|
||||
async cancelDownload(guid) {
|
||||
// The upstream CDP method is implemented in a way that no explicit error would be given
|
||||
// regarding the requested `guid`, even if the download is in a state not suitable for
|
||||
// cancellation (finished, cancelled, etc.) or the guid is invalid at all.
|
||||
await this._browser._session.send('Browser.cancelDownload', {
|
||||
guid: guid,
|
||||
browserContextId: this._browserContextId
|
||||
});
|
||||
}
|
||||
backgroundPages() {
|
||||
const result = [];
|
||||
for (const backgroundPage of this._browser._backgroundPages.values()) {
|
||||
if (backgroundPage._browserContext === this && backgroundPage._page.initializedOrUndefined()) result.push(backgroundPage._page);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
serviceWorkers() {
|
||||
return Array.from(this._browser._serviceWorkers.values()).filter(serviceWorker => serviceWorker._browserContext === this);
|
||||
}
|
||||
async newCDPSession(page) {
|
||||
let targetId = null;
|
||||
if (page instanceof _page.Page) {
|
||||
targetId = page._delegate._targetId;
|
||||
} else if (page instanceof _frames.Frame) {
|
||||
const session = page._page._delegate._sessions.get(page._id);
|
||||
if (!session) throw new Error(`This frame does not have a separate CDP session, it is a part of the parent frame's session`);
|
||||
targetId = session._targetId;
|
||||
} else {
|
||||
throw new Error('page: expected Page or Frame');
|
||||
}
|
||||
const rootSession = await this._browser._clientRootSession();
|
||||
return rootSession.attachToTarget(targetId);
|
||||
}
|
||||
}
|
||||
exports.CRBrowserContext = CRBrowserContext;
|
||||
CRBrowserContext.CREvents = {
|
||||
BackgroundPage: 'backgroundpage',
|
||||
ServiceWorker: 'serviceworker'
|
||||
};
|
||||
228
node_modules/playwright-core/lib/server/chromium/crConnection.js
generated
vendored
Normal file
228
node_modules/playwright-core/lib/server/chromium/crConnection.js
generated
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.kBrowserCloseMessageId = exports.ConnectionEvents = exports.CRSession = exports.CRConnection = exports.CDPSession = void 0;
|
||||
var _events = require("events");
|
||||
var _utils = require("../../utils");
|
||||
var _debugLogger = require("../utils/debugLogger");
|
||||
var _helper = require("../helper");
|
||||
var _protocolError = require("../protocolError");
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const ConnectionEvents = exports.ConnectionEvents = {
|
||||
Disconnected: Symbol('ConnectionEvents.Disconnected')
|
||||
};
|
||||
|
||||
// CRPlaywright uses this special id to issue Browser.close command which we
|
||||
// should ignore.
|
||||
const kBrowserCloseMessageId = exports.kBrowserCloseMessageId = -9999;
|
||||
class CRConnection extends _events.EventEmitter {
|
||||
constructor(transport, protocolLogger, browserLogsCollector) {
|
||||
super();
|
||||
this._lastId = 0;
|
||||
this._transport = void 0;
|
||||
this._sessions = new Map();
|
||||
this._protocolLogger = void 0;
|
||||
this._browserLogsCollector = void 0;
|
||||
this._browserDisconnectedLogs = void 0;
|
||||
this.rootSession = void 0;
|
||||
this._closed = false;
|
||||
this.setMaxListeners(0);
|
||||
this._transport = transport;
|
||||
this._protocolLogger = protocolLogger;
|
||||
this._browserLogsCollector = browserLogsCollector;
|
||||
this.rootSession = new CRSession(this, null, '');
|
||||
this._sessions.set('', this.rootSession);
|
||||
this._transport.onmessage = this._onMessage.bind(this);
|
||||
// onclose should be set last, since it can be immediately called.
|
||||
this._transport.onclose = this._onClose.bind(this);
|
||||
}
|
||||
_rawSend(sessionId, method, params) {
|
||||
const id = ++this._lastId;
|
||||
const message = {
|
||||
id,
|
||||
method,
|
||||
params
|
||||
};
|
||||
if (sessionId) message.sessionId = sessionId;
|
||||
this._protocolLogger('send', message);
|
||||
this._transport.send(message);
|
||||
return id;
|
||||
}
|
||||
async _onMessage(message) {
|
||||
this._protocolLogger('receive', message);
|
||||
if (message.id === kBrowserCloseMessageId) return;
|
||||
const session = this._sessions.get(message.sessionId || '');
|
||||
if (session) session._onMessage(message);
|
||||
}
|
||||
_onClose(reason) {
|
||||
this._closed = true;
|
||||
this._transport.onmessage = undefined;
|
||||
this._transport.onclose = undefined;
|
||||
this._browserDisconnectedLogs = _helper.helper.formatBrowserLogs(this._browserLogsCollector.recentLogs(), reason);
|
||||
this.rootSession.dispose();
|
||||
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
|
||||
}
|
||||
close() {
|
||||
if (!this._closed) this._transport.close();
|
||||
}
|
||||
async createBrowserSession() {
|
||||
const {
|
||||
sessionId
|
||||
} = await this.rootSession.send('Target.attachToBrowserTarget');
|
||||
return new CDPSession(this.rootSession, sessionId);
|
||||
}
|
||||
}
|
||||
exports.CRConnection = CRConnection;
|
||||
class CRSession extends _events.EventEmitter {
|
||||
constructor(connection, parentSession, sessionId, eventListener) {
|
||||
super();
|
||||
this._connection = void 0;
|
||||
this._eventListener = void 0;
|
||||
this._callbacks = new Map();
|
||||
this._sessionId = void 0;
|
||||
this._parentSession = void 0;
|
||||
this._crashed = false;
|
||||
this._closed = false;
|
||||
this.on = void 0;
|
||||
this.addListener = void 0;
|
||||
this.off = void 0;
|
||||
this.removeListener = void 0;
|
||||
this.once = void 0;
|
||||
this.setMaxListeners(0);
|
||||
this._connection = connection;
|
||||
this._parentSession = parentSession;
|
||||
this._sessionId = sessionId;
|
||||
this._eventListener = eventListener;
|
||||
this.on = super.on;
|
||||
this.addListener = super.addListener;
|
||||
this.off = super.removeListener;
|
||||
this.removeListener = super.removeListener;
|
||||
this.once = super.once;
|
||||
}
|
||||
_markAsCrashed() {
|
||||
this._crashed = true;
|
||||
}
|
||||
createChildSession(sessionId, eventListener) {
|
||||
const session = new CRSession(this._connection, this, sessionId, eventListener);
|
||||
this._connection._sessions.set(sessionId, session);
|
||||
return session;
|
||||
}
|
||||
async send(method, params) {
|
||||
if (this._crashed || this._closed || this._connection._closed || this._connection._browserDisconnectedLogs) throw new _protocolError.ProtocolError(this._crashed ? 'crashed' : 'closed', undefined, this._connection._browserDisconnectedLogs);
|
||||
const id = this._connection._rawSend(this._sessionId, method, params);
|
||||
return new Promise((resolve, reject) => {
|
||||
this._callbacks.set(id, {
|
||||
resolve,
|
||||
reject,
|
||||
error: new _protocolError.ProtocolError('error', method)
|
||||
});
|
||||
});
|
||||
}
|
||||
_sendMayFail(method, params) {
|
||||
return this.send(method, params).catch(error => _debugLogger.debugLogger.log('error', error));
|
||||
}
|
||||
_onMessage(object) {
|
||||
var _object$error;
|
||||
if (object.id && this._callbacks.has(object.id)) {
|
||||
const callback = this._callbacks.get(object.id);
|
||||
this._callbacks.delete(object.id);
|
||||
if (object.error) {
|
||||
callback.error.setMessage(object.error.message);
|
||||
callback.reject(callback.error);
|
||||
} else {
|
||||
callback.resolve(object.result);
|
||||
}
|
||||
} else if (object.id && ((_object$error = object.error) === null || _object$error === void 0 ? void 0 : _object$error.code) === -32001) {
|
||||
// Message to a closed session, just ignore it.
|
||||
} else {
|
||||
var _object$error2;
|
||||
(0, _utils.assert)(!object.id, (object === null || object === void 0 || (_object$error2 = object.error) === null || _object$error2 === void 0 ? void 0 : _object$error2.message) || undefined);
|
||||
Promise.resolve().then(() => {
|
||||
if (this._eventListener) this._eventListener(object.method, object.params);
|
||||
this.emit(object.method, object.params);
|
||||
});
|
||||
}
|
||||
}
|
||||
async detach() {
|
||||
if (this._closed) throw new Error(`Session already detached. Most likely the page has been closed.`);
|
||||
if (!this._parentSession) throw new Error('Root session cannot be closed');
|
||||
// Ideally, detaching should resume any target, but there is a bug in the backend,
|
||||
// so we must Runtime.runIfWaitingForDebugger first.
|
||||
await this._sendMayFail('Runtime.runIfWaitingForDebugger');
|
||||
await this._parentSession.send('Target.detachFromTarget', {
|
||||
sessionId: this._sessionId
|
||||
});
|
||||
this.dispose();
|
||||
}
|
||||
dispose() {
|
||||
this._closed = true;
|
||||
this._connection._sessions.delete(this._sessionId);
|
||||
for (const callback of this._callbacks.values()) {
|
||||
callback.error.setMessage(`Internal server error, session closed.`);
|
||||
callback.error.type = this._crashed ? 'crashed' : 'closed';
|
||||
callback.error.logs = this._connection._browserDisconnectedLogs;
|
||||
callback.reject(callback.error);
|
||||
}
|
||||
this._callbacks.clear();
|
||||
}
|
||||
}
|
||||
exports.CRSession = CRSession;
|
||||
class CDPSession extends _events.EventEmitter {
|
||||
constructor(parentSession, sessionId) {
|
||||
super();
|
||||
this.guid = void 0;
|
||||
this._session = void 0;
|
||||
this._listeners = [];
|
||||
this.guid = `cdp-session@${sessionId}`;
|
||||
this._session = parentSession.createChildSession(sessionId, (method, params) => this.emit(CDPSession.Events.Event, {
|
||||
method,
|
||||
params
|
||||
}));
|
||||
this._listeners = [_utils.eventsHelper.addEventListener(parentSession, 'Target.detachedFromTarget', event => {
|
||||
if (event.sessionId === sessionId) this._onClose();
|
||||
})];
|
||||
}
|
||||
async send(method, params) {
|
||||
return await this._session.send(method, params);
|
||||
}
|
||||
async detach() {
|
||||
return await this._session.detach();
|
||||
}
|
||||
async attachToTarget(targetId) {
|
||||
const {
|
||||
sessionId
|
||||
} = await this.send('Target.attachToTarget', {
|
||||
targetId,
|
||||
flatten: true
|
||||
});
|
||||
return new CDPSession(this._session, sessionId);
|
||||
}
|
||||
_onClose() {
|
||||
_utils.eventsHelper.removeEventListeners(this._listeners);
|
||||
this._session.dispose();
|
||||
this.emit(CDPSession.Events.Closed);
|
||||
}
|
||||
}
|
||||
exports.CDPSession = CDPSession;
|
||||
CDPSession.Events = {
|
||||
Event: 'event',
|
||||
Closed: 'close'
|
||||
};
|
||||
246
node_modules/playwright-core/lib/server/chromium/crCoverage.js
generated
vendored
Normal file
246
node_modules/playwright-core/lib/server/chromium/crCoverage.js
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.CRCoverage = void 0;
|
||||
var _utils = require("../../utils");
|
||||
var _eventsHelper = require("../utils/eventsHelper");
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class CRCoverage {
|
||||
constructor(client) {
|
||||
this._jsCoverage = void 0;
|
||||
this._cssCoverage = void 0;
|
||||
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.CRCoverage = CRCoverage;
|
||||
class JSCoverage {
|
||||
constructor(client) {
|
||||
this._client = void 0;
|
||||
this._enabled = void 0;
|
||||
this._scriptIds = void 0;
|
||||
this._scriptSources = void 0;
|
||||
this._eventListeners = void 0;
|
||||
this._resetOnNavigation = void 0;
|
||||
this._reportAnonymousScripts = false;
|
||||
this._client = client;
|
||||
this._enabled = false;
|
||||
this._scriptIds = new Set();
|
||||
this._scriptSources = new Map();
|
||||
this._eventListeners = [];
|
||||
this._resetOnNavigation = false;
|
||||
}
|
||||
async start(options) {
|
||||
(0, _utils.assert)(!this._enabled, 'JSCoverage is already enabled');
|
||||
const {
|
||||
resetOnNavigation = true,
|
||||
reportAnonymousScripts = false
|
||||
} = options;
|
||||
this._resetOnNavigation = resetOnNavigation;
|
||||
this._reportAnonymousScripts = reportAnonymousScripts;
|
||||
this._enabled = true;
|
||||
this._scriptIds.clear();
|
||||
this._scriptSources.clear();
|
||||
this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(this._client, 'Debugger.scriptParsed', this._onScriptParsed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Debugger.paused', this._onDebuggerPaused.bind(this))];
|
||||
await Promise.all([this._client.send('Profiler.enable'), this._client.send('Profiler.startPreciseCoverage', {
|
||||
callCount: true,
|
||||
detailed: true
|
||||
}), this._client.send('Debugger.enable'), this._client.send('Debugger.setSkipAllPauses', {
|
||||
skip: true
|
||||
})]);
|
||||
}
|
||||
_onDebuggerPaused() {
|
||||
this._client.send('Debugger.resume');
|
||||
}
|
||||
_onExecutionContextsCleared() {
|
||||
if (!this._resetOnNavigation) return;
|
||||
this._scriptIds.clear();
|
||||
this._scriptSources.clear();
|
||||
}
|
||||
async _onScriptParsed(event) {
|
||||
this._scriptIds.add(event.scriptId);
|
||||
// Ignore other anonymous scripts unless the reportAnonymousScripts option is true.
|
||||
if (!event.url && !this._reportAnonymousScripts) return;
|
||||
// This might fail if the page has already navigated away.
|
||||
const response = await this._client._sendMayFail('Debugger.getScriptSource', {
|
||||
scriptId: event.scriptId
|
||||
});
|
||||
if (response) this._scriptSources.set(event.scriptId, response.scriptSource);
|
||||
}
|
||||
async stop() {
|
||||
(0, _utils.assert)(this._enabled, 'JSCoverage is not enabled');
|
||||
this._enabled = false;
|
||||
const [profileResponse] = await Promise.all([this._client.send('Profiler.takePreciseCoverage'), this._client.send('Profiler.stopPreciseCoverage'), this._client.send('Profiler.disable'), this._client.send('Debugger.disable')]);
|
||||
_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
|
||||
const coverage = {
|
||||
entries: []
|
||||
};
|
||||
for (const entry of profileResponse.result) {
|
||||
if (!this._scriptIds.has(entry.scriptId)) continue;
|
||||
if (!entry.url && !this._reportAnonymousScripts) continue;
|
||||
const source = this._scriptSources.get(entry.scriptId);
|
||||
if (source) coverage.entries.push({
|
||||
...entry,
|
||||
source
|
||||
});else coverage.entries.push(entry);
|
||||
}
|
||||
return coverage;
|
||||
}
|
||||
}
|
||||
class CSSCoverage {
|
||||
constructor(client) {
|
||||
this._client = void 0;
|
||||
this._enabled = void 0;
|
||||
this._stylesheetURLs = void 0;
|
||||
this._stylesheetSources = void 0;
|
||||
this._eventListeners = void 0;
|
||||
this._resetOnNavigation = void 0;
|
||||
this._client = client;
|
||||
this._enabled = false;
|
||||
this._stylesheetURLs = new Map();
|
||||
this._stylesheetSources = new Map();
|
||||
this._eventListeners = [];
|
||||
this._resetOnNavigation = false;
|
||||
}
|
||||
async start(options) {
|
||||
(0, _utils.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 = [_eventsHelper.eventsHelper.addEventListener(this._client, 'CSS.styleSheetAdded', this._onStyleSheet.bind(this)), _eventsHelper.eventsHelper.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;
|
||||
// This might fail if the page has already navigated away.
|
||||
const response = await this._client._sendMayFail('CSS.getStyleSheetText', {
|
||||
styleSheetId: header.styleSheetId
|
||||
});
|
||||
if (response) {
|
||||
this._stylesheetURLs.set(header.styleSheetId, header.sourceURL);
|
||||
this._stylesheetSources.set(header.styleSheetId, response.text);
|
||||
}
|
||||
}
|
||||
async stop() {
|
||||
(0, _utils.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')]);
|
||||
_eventsHelper.eventsHelper.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 = {
|
||||
entries: []
|
||||
};
|
||||
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.entries.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);
|
||||
}
|
||||
104
node_modules/playwright-core/lib/server/chromium/crDevTools.js
generated
vendored
Normal file
104
node_modules/playwright-core/lib/server/chromium/crDevTools.js
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.CRDevTools = void 0;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const kBindingName = '__pw_devtools__';
|
||||
|
||||
// This class intercepts preferences-related DevTools embedder methods
|
||||
// and stores preferences as a json file in the browser installation directory.
|
||||
class CRDevTools {
|
||||
constructor(preferencesPath) {
|
||||
this._preferencesPath = void 0;
|
||||
this._prefs = void 0;
|
||||
this._savePromise = void 0;
|
||||
this.__testHookOnBinding = void 0;
|
||||
this._preferencesPath = preferencesPath;
|
||||
this._savePromise = Promise.resolve();
|
||||
}
|
||||
install(session) {
|
||||
session.on('Runtime.bindingCalled', async event => {
|
||||
if (event.name !== kBindingName) return;
|
||||
const parsed = JSON.parse(event.payload);
|
||||
let result = undefined;
|
||||
if (this.__testHookOnBinding) this.__testHookOnBinding(parsed);
|
||||
if (parsed.method === 'getPreferences') {
|
||||
if (this._prefs === undefined) {
|
||||
try {
|
||||
const json = await _fs.default.promises.readFile(this._preferencesPath, 'utf8');
|
||||
this._prefs = JSON.parse(json);
|
||||
} catch (e) {
|
||||
this._prefs = {};
|
||||
}
|
||||
}
|
||||
result = this._prefs;
|
||||
} else if (parsed.method === 'setPreference') {
|
||||
this._prefs[parsed.params[0]] = parsed.params[1];
|
||||
this._save();
|
||||
} else if (parsed.method === 'removePreference') {
|
||||
delete this._prefs[parsed.params[0]];
|
||||
this._save();
|
||||
} else if (parsed.method === 'clearPreferences') {
|
||||
this._prefs = {};
|
||||
this._save();
|
||||
}
|
||||
session.send('Runtime.evaluate', {
|
||||
expression: `window.DevToolsAPI.embedderMessageAck(${parsed.id}, ${JSON.stringify(result)})`,
|
||||
contextId: event.executionContextId
|
||||
}).catch(e => null);
|
||||
});
|
||||
Promise.all([session.send('Runtime.enable'), session.send('Runtime.addBinding', {
|
||||
name: kBindingName
|
||||
}), session.send('Page.enable'), session.send('Page.addScriptToEvaluateOnNewDocument', {
|
||||
source: `
|
||||
(() => {
|
||||
const init = () => {
|
||||
// Lazy init happens when InspectorFrontendHost is initialized.
|
||||
// At this point DevToolsHost is ready to be used.
|
||||
const host = window.DevToolsHost;
|
||||
const old = host.sendMessageToEmbedder.bind(host);
|
||||
host.sendMessageToEmbedder = message => {
|
||||
if (['getPreferences', 'setPreference', 'removePreference', 'clearPreferences'].includes(JSON.parse(message).method))
|
||||
window.${kBindingName}(message);
|
||||
else
|
||||
old(message);
|
||||
};
|
||||
};
|
||||
let value;
|
||||
Object.defineProperty(window, 'InspectorFrontendHost', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() { return value; },
|
||||
set(v) { value = v; init(); },
|
||||
});
|
||||
})()
|
||||
`
|
||||
}), session.send('Runtime.runIfWaitingForDebugger')]).catch(e => null);
|
||||
}
|
||||
_save() {
|
||||
// Serialize saves to avoid corruption.
|
||||
this._savePromise = this._savePromise.then(async () => {
|
||||
await _fs.default.promises.writeFile(this._preferencesPath, JSON.stringify(this._prefs)).catch(e => null);
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.CRDevTools = CRDevTools;
|
||||
143
node_modules/playwright-core/lib/server/chromium/crDragDrop.js
generated
vendored
Normal file
143
node_modules/playwright-core/lib/server/chromium/crDragDrop.js
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.DragManager = void 0;
|
||||
var _crProtocolHelper = require("./crProtocolHelper");
|
||||
var _utils = require("../../utils");
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class DragManager {
|
||||
constructor(page) {
|
||||
this._crPage = void 0;
|
||||
this._dragState = null;
|
||||
this._lastPosition = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
this._crPage = page;
|
||||
}
|
||||
async cancelDrag() {
|
||||
if (!this._dragState) return false;
|
||||
await this._crPage._mainFrameSession._client.send('Input.dispatchDragEvent', {
|
||||
type: 'dragCancel',
|
||||
x: this._lastPosition.x,
|
||||
y: this._lastPosition.y,
|
||||
data: {
|
||||
items: [],
|
||||
dragOperationsMask: 0xFFFF
|
||||
}
|
||||
});
|
||||
this._dragState = null;
|
||||
return true;
|
||||
}
|
||||
async interceptDragCausedByMove(x, y, button, buttons, modifiers, moveCallback) {
|
||||
this._lastPosition = {
|
||||
x,
|
||||
y
|
||||
};
|
||||
if (this._dragState) {
|
||||
await this._crPage._mainFrameSession._client.send('Input.dispatchDragEvent', {
|
||||
type: 'dragOver',
|
||||
x,
|
||||
y,
|
||||
data: this._dragState,
|
||||
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers)
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (button !== 'left') return moveCallback();
|
||||
const client = this._crPage._mainFrameSession._client;
|
||||
let onDragIntercepted;
|
||||
const dragInterceptedPromise = new Promise(x => onDragIntercepted = x);
|
||||
function setupDragListeners() {
|
||||
let didStartDrag = Promise.resolve(false);
|
||||
let dragEvent = null;
|
||||
const dragListener = event => dragEvent = event;
|
||||
const mouseListener = () => {
|
||||
didStartDrag = new Promise(callback => {
|
||||
window.addEventListener('dragstart', dragListener, {
|
||||
once: true,
|
||||
capture: true
|
||||
});
|
||||
setTimeout(() => callback(dragEvent ? !dragEvent.defaultPrevented : false), 0);
|
||||
});
|
||||
};
|
||||
window.addEventListener('mousemove', mouseListener, {
|
||||
once: true,
|
||||
capture: true
|
||||
});
|
||||
window.__cleanupDrag = async () => {
|
||||
const val = await didStartDrag;
|
||||
window.removeEventListener('mousemove', mouseListener, {
|
||||
capture: true
|
||||
});
|
||||
window.removeEventListener('dragstart', dragListener, {
|
||||
capture: true
|
||||
});
|
||||
delete window.__cleanupDrag;
|
||||
return val;
|
||||
};
|
||||
}
|
||||
await this._crPage._page.safeNonStallingEvaluateInAllFrames(`(${setupDragListeners.toString()})()`, 'utility');
|
||||
client.on('Input.dragIntercepted', onDragIntercepted);
|
||||
try {
|
||||
await client.send('Input.setInterceptDrags', {
|
||||
enabled: true
|
||||
});
|
||||
} catch {
|
||||
// If Input.setInterceptDrags is not supported, just do a regular move.
|
||||
// This can be removed once we stop supporting old Electron.
|
||||
client.off('Input.dragIntercepted', onDragIntercepted);
|
||||
return moveCallback();
|
||||
}
|
||||
await moveCallback();
|
||||
const expectingDrag = (await Promise.all(this._crPage._page.frames().map(async frame => {
|
||||
return frame.nonStallingEvaluateInExistingContext('window.__cleanupDrag && window.__cleanupDrag()', 'utility').catch(() => false);
|
||||
}))).some(x => x);
|
||||
this._dragState = expectingDrag ? (await dragInterceptedPromise).data : null;
|
||||
client.off('Input.dragIntercepted', onDragIntercepted);
|
||||
await client.send('Input.setInterceptDrags', {
|
||||
enabled: false
|
||||
});
|
||||
if (this._dragState) {
|
||||
await this._crPage._mainFrameSession._client.send('Input.dispatchDragEvent', {
|
||||
type: 'dragEnter',
|
||||
x,
|
||||
y,
|
||||
data: this._dragState,
|
||||
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers)
|
||||
});
|
||||
}
|
||||
}
|
||||
isDragging() {
|
||||
return !!this._dragState;
|
||||
}
|
||||
async drop(x, y, modifiers) {
|
||||
(0, _utils.assert)(this._dragState, 'missing drag state');
|
||||
await this._crPage._mainFrameSession._client.send('Input.dispatchDragEvent', {
|
||||
type: 'drop',
|
||||
x,
|
||||
y,
|
||||
data: this._dragState,
|
||||
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers)
|
||||
});
|
||||
this._dragState = null;
|
||||
}
|
||||
}
|
||||
exports.DragManager = DragManager;
|
||||
140
node_modules/playwright-core/lib/server/chromium/crExecutionContext.js
generated
vendored
Normal file
140
node_modules/playwright-core/lib/server/chromium/crExecutionContext.js
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.CRExecutionContext = void 0;
|
||||
exports.createHandle = createHandle;
|
||||
var _assert = require("../../utils/isomorphic/assert");
|
||||
var _crProtocolHelper = require("./crProtocolHelper");
|
||||
var _stackTrace = require("../../utils/isomorphic/stackTrace");
|
||||
var _utilityScriptSerializers = require("../isomorphic/utilityScriptSerializers");
|
||||
var js = _interopRequireWildcard(require("../javascript"));
|
||||
var dom = _interopRequireWildcard(require("../dom"));
|
||||
var _protocolError = require("../protocolError");
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class CRExecutionContext {
|
||||
constructor(client, contextPayload) {
|
||||
this._client = void 0;
|
||||
this._contextId = void 0;
|
||||
this._client = client;
|
||||
this._contextId = contextPayload.id;
|
||||
}
|
||||
async rawEvaluateJSON(expression) {
|
||||
const {
|
||||
exceptionDetails,
|
||||
result: remoteObject
|
||||
} = await this._client.send('Runtime.evaluate', {
|
||||
expression,
|
||||
contextId: this._contextId,
|
||||
returnByValue: true
|
||||
}).catch(rewriteError);
|
||||
if (exceptionDetails) throw new js.JavaScriptErrorInEvaluate((0, _crProtocolHelper.getExceptionMessage)(exceptionDetails));
|
||||
return remoteObject.value;
|
||||
}
|
||||
async rawEvaluateHandle(context, expression) {
|
||||
const {
|
||||
exceptionDetails,
|
||||
result: remoteObject
|
||||
} = await this._client.send('Runtime.evaluate', {
|
||||
expression,
|
||||
contextId: this._contextId
|
||||
}).catch(rewriteError);
|
||||
if (exceptionDetails) throw new js.JavaScriptErrorInEvaluate((0, _crProtocolHelper.getExceptionMessage)(exceptionDetails));
|
||||
return createHandle(context, remoteObject);
|
||||
}
|
||||
async evaluateWithArguments(expression, returnByValue, utilityScript, values, handles) {
|
||||
const {
|
||||
exceptionDetails,
|
||||
result: remoteObject
|
||||
} = await this._client.send('Runtime.callFunctionOn', {
|
||||
functionDeclaration: expression,
|
||||
objectId: utilityScript._objectId,
|
||||
arguments: [{
|
||||
objectId: utilityScript._objectId
|
||||
}, ...values.map(value => ({
|
||||
value
|
||||
})), ...handles.map(handle => ({
|
||||
objectId: handle._objectId
|
||||
}))],
|
||||
returnByValue,
|
||||
awaitPromise: true,
|
||||
userGesture: true
|
||||
}).catch(rewriteError);
|
||||
if (exceptionDetails) throw new js.JavaScriptErrorInEvaluate((0, _crProtocolHelper.getExceptionMessage)(exceptionDetails));
|
||||
return returnByValue ? (0, _utilityScriptSerializers.parseEvaluationResultValue)(remoteObject.value) : createHandle(utilityScript._context, remoteObject);
|
||||
}
|
||||
async getProperties(object) {
|
||||
const response = await this._client.send('Runtime.getProperties', {
|
||||
objectId: object._objectId,
|
||||
ownProperties: true
|
||||
});
|
||||
const result = new Map();
|
||||
for (const property of response.result) {
|
||||
if (!property.enumerable || !property.value) continue;
|
||||
result.set(property.name, createHandle(object._context, property.value));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
async releaseHandle(handle) {
|
||||
if (!handle._objectId) return;
|
||||
await (0, _crProtocolHelper.releaseObject)(this._client, handle._objectId);
|
||||
}
|
||||
}
|
||||
exports.CRExecutionContext = CRExecutionContext;
|
||||
function rewriteError(error) {
|
||||
if (error.message.includes('Object reference chain is too long')) throw new Error('Cannot serialize result: object reference chain is too long.');
|
||||
if (error.message.includes('Object couldn\'t be returned by value')) return {
|
||||
result: {
|
||||
type: 'undefined'
|
||||
}
|
||||
};
|
||||
if (error instanceof TypeError && error.message.startsWith('Converting circular structure to JSON')) (0, _stackTrace.rewriteErrorMessage)(error, error.message + ' Are you passing a nested JSHandle?');
|
||||
if (!js.isJavaScriptErrorInEvaluate(error) && !(0, _protocolError.isSessionClosedError)(error)) throw new Error('Execution context was destroyed, most likely because of a navigation.');
|
||||
throw error;
|
||||
}
|
||||
function potentiallyUnserializableValue(remoteObject) {
|
||||
const value = remoteObject.value;
|
||||
const unserializableValue = remoteObject.unserializableValue;
|
||||
return unserializableValue ? js.parseUnserializableValue(unserializableValue) : value;
|
||||
}
|
||||
function renderPreview(object) {
|
||||
if (object.type === 'undefined') return 'undefined';
|
||||
if ('value' in object) return String(object.value);
|
||||
if (object.unserializableValue) return String(object.unserializableValue);
|
||||
if (object.description === 'Object' && object.preview) {
|
||||
const tokens = [];
|
||||
for (const {
|
||||
name,
|
||||
value
|
||||
} of object.preview.properties) tokens.push(`${name}: ${value}`);
|
||||
return `{${tokens.join(', ')}}`;
|
||||
}
|
||||
if (object.subtype === 'array' && object.preview) return js.sparseArrayToString(object.preview.properties);
|
||||
return object.description;
|
||||
}
|
||||
function createHandle(context, remoteObject) {
|
||||
if (remoteObject.subtype === 'node') {
|
||||
(0, _assert.assert)(context instanceof dom.FrameExecutionContext);
|
||||
return new dom.ElementHandle(context, remoteObject.objectId);
|
||||
}
|
||||
return new js.JSHandle(context, remoteObject.subtype || remoteObject.type, renderPreview(remoteObject), remoteObject.objectId, potentiallyUnserializableValue(remoteObject));
|
||||
}
|
||||
182
node_modules/playwright-core/lib/server/chromium/crInput.js
generated
vendored
Normal file
182
node_modules/playwright-core/lib/server/chromium/crInput.js
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.RawTouchscreenImpl = exports.RawMouseImpl = exports.RawKeyboardImpl = void 0;
|
||||
var _utils = require("../../utils");
|
||||
var input = _interopRequireWildcard(require("../input"));
|
||||
var _macEditingCommands = require("../macEditingCommands");
|
||||
var _crProtocolHelper = require("./crProtocolHelper");
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class RawKeyboardImpl {
|
||||
constructor(_client, _isMac, _dragManger) {
|
||||
this._client = _client;
|
||||
this._isMac = _isMac;
|
||||
this._dragManger = _dragManger;
|
||||
}
|
||||
_commandsForCode(code, modifiers) {
|
||||
if (!this._isMac) return [];
|
||||
const parts = [];
|
||||
for (const modifier of ['Shift', 'Control', 'Alt', 'Meta']) {
|
||||
if (modifiers.has(modifier)) parts.push(modifier);
|
||||
}
|
||||
parts.push(code);
|
||||
const shortcut = parts.join('+');
|
||||
let commands = _macEditingCommands.macEditingCommands[shortcut] || [];
|
||||
if ((0, _utils.isString)(commands)) commands = [commands];
|
||||
// Commands that insert text are not supported
|
||||
commands = commands.filter(x => !x.startsWith('insert'));
|
||||
// remove the trailing : to match the Chromium command names.
|
||||
return commands.map(c => c.substring(0, c.length - 1));
|
||||
}
|
||||
async keydown(modifiers, keyName, description, autoRepeat) {
|
||||
const {
|
||||
code,
|
||||
key,
|
||||
location,
|
||||
text
|
||||
} = description;
|
||||
if (code === 'Escape' && (await this._dragManger.cancelDrag())) return;
|
||||
const commands = this._commandsForCode(code, modifiers);
|
||||
await this._client.send('Input.dispatchKeyEvent', {
|
||||
type: text ? 'keyDown' : 'rawKeyDown',
|
||||
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
|
||||
windowsVirtualKeyCode: description.keyCodeWithoutLocation,
|
||||
code,
|
||||
commands,
|
||||
key,
|
||||
text,
|
||||
unmodifiedText: text,
|
||||
autoRepeat,
|
||||
location,
|
||||
isKeypad: location === input.keypadLocation
|
||||
});
|
||||
}
|
||||
async keyup(modifiers, keyName, description) {
|
||||
const {
|
||||
code,
|
||||
key,
|
||||
location
|
||||
} = description;
|
||||
await this._client.send('Input.dispatchKeyEvent', {
|
||||
type: 'keyUp',
|
||||
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
|
||||
key,
|
||||
windowsVirtualKeyCode: description.keyCodeWithoutLocation,
|
||||
code,
|
||||
location
|
||||
});
|
||||
}
|
||||
async sendText(text) {
|
||||
await this._client.send('Input.insertText', {
|
||||
text
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.RawKeyboardImpl = RawKeyboardImpl;
|
||||
class RawMouseImpl {
|
||||
constructor(page, client, dragManager) {
|
||||
this._client = void 0;
|
||||
this._page = void 0;
|
||||
this._dragManager = void 0;
|
||||
this._page = page;
|
||||
this._client = client;
|
||||
this._dragManager = dragManager;
|
||||
}
|
||||
async move(x, y, button, buttons, modifiers, forClick) {
|
||||
const actualMove = async () => {
|
||||
await this._client.send('Input.dispatchMouseEvent', {
|
||||
type: 'mouseMoved',
|
||||
button,
|
||||
buttons: (0, _crProtocolHelper.toButtonsMask)(buttons),
|
||||
x,
|
||||
y,
|
||||
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers)
|
||||
});
|
||||
};
|
||||
if (forClick) {
|
||||
// Avoid extra protocol calls related to drag and drop, because click relies on
|
||||
// move-down-up protocol commands being sent synchronously.
|
||||
return actualMove();
|
||||
}
|
||||
await this._dragManager.interceptDragCausedByMove(x, y, button, buttons, modifiers, actualMove);
|
||||
}
|
||||
async down(x, y, button, buttons, modifiers, clickCount) {
|
||||
if (this._dragManager.isDragging()) return;
|
||||
await this._client.send('Input.dispatchMouseEvent', {
|
||||
type: 'mousePressed',
|
||||
button,
|
||||
buttons: (0, _crProtocolHelper.toButtonsMask)(buttons),
|
||||
x,
|
||||
y,
|
||||
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
|
||||
clickCount
|
||||
});
|
||||
}
|
||||
async up(x, y, button, buttons, modifiers, clickCount) {
|
||||
if (this._dragManager.isDragging()) {
|
||||
await this._dragManager.drop(x, y, modifiers);
|
||||
return;
|
||||
}
|
||||
await this._client.send('Input.dispatchMouseEvent', {
|
||||
type: 'mouseReleased',
|
||||
button,
|
||||
buttons: (0, _crProtocolHelper.toButtonsMask)(buttons),
|
||||
x,
|
||||
y,
|
||||
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
|
||||
clickCount
|
||||
});
|
||||
}
|
||||
async wheel(x, y, buttons, modifiers, deltaX, deltaY) {
|
||||
await this._client.send('Input.dispatchMouseEvent', {
|
||||
type: 'mouseWheel',
|
||||
x,
|
||||
y,
|
||||
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
|
||||
deltaX,
|
||||
deltaY
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.RawMouseImpl = RawMouseImpl;
|
||||
class RawTouchscreenImpl {
|
||||
constructor(client) {
|
||||
this._client = void 0;
|
||||
this._client = client;
|
||||
}
|
||||
async tap(x, y, modifiers) {
|
||||
await Promise.all([this._client.send('Input.dispatchTouchEvent', {
|
||||
type: 'touchStart',
|
||||
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
|
||||
touchPoints: [{
|
||||
x,
|
||||
y
|
||||
}]
|
||||
}), this._client.send('Input.dispatchTouchEvent', {
|
||||
type: 'touchEnd',
|
||||
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
|
||||
touchPoints: []
|
||||
})]);
|
||||
}
|
||||
}
|
||||
exports.RawTouchscreenImpl = RawTouchscreenImpl;
|
||||
767
node_modules/playwright-core/lib/server/chromium/crNetworkManager.js
generated
vendored
Normal file
767
node_modules/playwright-core/lib/server/chromium/crNetworkManager.js
generated
vendored
Normal file
@@ -0,0 +1,767 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.CRNetworkManager = void 0;
|
||||
var _utils = require("../../utils");
|
||||
var _eventsHelper = require("../utils/eventsHelper");
|
||||
var _helper = require("../helper");
|
||||
var network = _interopRequireWildcard(require("../network"));
|
||||
var _protocolError = require("../protocolError");
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class CRNetworkManager {
|
||||
constructor(page, serviceWorker) {
|
||||
this._page = void 0;
|
||||
this._serviceWorker = void 0;
|
||||
this._requestIdToRequest = new Map();
|
||||
this._requestIdToRequestWillBeSentEvent = new Map();
|
||||
this._credentials = null;
|
||||
this._attemptedAuthentications = new Set();
|
||||
this._userRequestInterceptionEnabled = false;
|
||||
this._protocolRequestInterceptionEnabled = false;
|
||||
this._offline = false;
|
||||
this._extraHTTPHeaders = [];
|
||||
this._requestIdToRequestPausedEvent = new Map();
|
||||
this._responseExtraInfoTracker = new ResponseExtraInfoTracker();
|
||||
this._sessions = new Map();
|
||||
this._page = page;
|
||||
this._serviceWorker = serviceWorker;
|
||||
}
|
||||
async addSession(session, workerFrame, isMain) {
|
||||
const sessionInfo = {
|
||||
session,
|
||||
isMain,
|
||||
workerFrame,
|
||||
eventListeners: []
|
||||
};
|
||||
sessionInfo.eventListeners = [_eventsHelper.eventsHelper.addEventListener(session, 'Fetch.requestPaused', this._onRequestPaused.bind(this, sessionInfo)), _eventsHelper.eventsHelper.addEventListener(session, 'Fetch.authRequired', this._onAuthRequired.bind(this, sessionInfo)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this, sessionInfo)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.requestWillBeSentExtraInfo', this._onRequestWillBeSentExtraInfo.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.requestServedFromCache', this._onRequestServedFromCache.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.responseReceived', this._onResponseReceived.bind(this, sessionInfo)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.responseReceivedExtraInfo', this._onResponseReceivedExtraInfo.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.loadingFinished', this._onLoadingFinished.bind(this, sessionInfo)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.loadingFailed', this._onLoadingFailed.bind(this, sessionInfo))];
|
||||
if (this._page) {
|
||||
sessionInfo.eventListeners.push(...[_eventsHelper.eventsHelper.addEventListener(session, 'Network.webSocketCreated', e => this._page._frameManager.onWebSocketCreated(e.requestId, e.url)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page._frameManager.onWebSocketRequest(e.requestId)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.webSocketHandshakeResponseReceived', e => this._page._frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page._frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page._frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.webSocketClosed', e => this._page._frameManager.webSocketClosed(e.requestId)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.webSocketFrameError', e => this._page._frameManager.webSocketError(e.requestId, e.errorMessage))]);
|
||||
}
|
||||
this._sessions.set(session, sessionInfo);
|
||||
await Promise.all([session.send('Network.enable'), this._updateProtocolRequestInterceptionForSession(sessionInfo, true /* initial */), this._setOfflineForSession(sessionInfo, true /* initial */), this._setExtraHTTPHeadersForSession(sessionInfo, true /* initial */)]);
|
||||
}
|
||||
removeSession(session) {
|
||||
const info = this._sessions.get(session);
|
||||
if (info) _eventsHelper.eventsHelper.removeEventListeners(info.eventListeners);
|
||||
this._sessions.delete(session);
|
||||
}
|
||||
async _forEachSession(cb) {
|
||||
await Promise.all([...this._sessions.values()].map(info => {
|
||||
if (info.isMain) return cb(info);
|
||||
return cb(info).catch(e => {
|
||||
// Broadcasting a message to the closed target should be a noop.
|
||||
if ((0, _protocolError.isSessionClosedError)(e)) return;
|
||||
throw e;
|
||||
});
|
||||
}));
|
||||
}
|
||||
async authenticate(credentials) {
|
||||
this._credentials = credentials;
|
||||
await this._updateProtocolRequestInterception();
|
||||
}
|
||||
async setOffline(offline) {
|
||||
if (offline === this._offline) return;
|
||||
this._offline = offline;
|
||||
await this._forEachSession(info => this._setOfflineForSession(info));
|
||||
}
|
||||
async _setOfflineForSession(info, initial) {
|
||||
if (initial && !this._offline) return;
|
||||
// Workers are affected by the owner frame's Network.emulateNetworkConditions.
|
||||
if (info.workerFrame) return;
|
||||
await info.session.send('Network.emulateNetworkConditions', {
|
||||
offline: this._offline,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1
|
||||
});
|
||||
}
|
||||
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;
|
||||
await this._forEachSession(info => this._updateProtocolRequestInterceptionForSession(info));
|
||||
}
|
||||
async _updateProtocolRequestInterceptionForSession(info, initial) {
|
||||
const enabled = this._protocolRequestInterceptionEnabled;
|
||||
if (initial && !enabled) return;
|
||||
const cachePromise = info.session.send('Network.setCacheDisabled', {
|
||||
cacheDisabled: enabled
|
||||
});
|
||||
let fetchPromise = Promise.resolve(undefined);
|
||||
if (!info.workerFrame) {
|
||||
if (enabled) fetchPromise = info.session.send('Fetch.enable', {
|
||||
handleAuthRequests: true,
|
||||
patterns: [{
|
||||
urlPattern: '*',
|
||||
requestStage: 'Request'
|
||||
}]
|
||||
});else fetchPromise = info.session.send('Fetch.disable');
|
||||
}
|
||||
await Promise.all([cachePromise, fetchPromise]);
|
||||
}
|
||||
async setExtraHTTPHeaders(extraHTTPHeaders) {
|
||||
if (!this._extraHTTPHeaders.length && !extraHTTPHeaders.length) return;
|
||||
this._extraHTTPHeaders = extraHTTPHeaders;
|
||||
await this._forEachSession(info => this._setExtraHTTPHeadersForSession(info));
|
||||
}
|
||||
async _setExtraHTTPHeadersForSession(info, initial) {
|
||||
if (initial && !this._extraHTTPHeaders.length) return;
|
||||
await info.session.send('Network.setExtraHTTPHeaders', {
|
||||
headers: (0, _utils.headersArrayToObject)(this._extraHTTPHeaders, false /* lowerCase */)
|
||||
});
|
||||
}
|
||||
async clearCache() {
|
||||
await this._forEachSession(async info => {
|
||||
// Sending 'Network.setCacheDisabled' with 'cacheDisabled = true' will clear the MemoryCache.
|
||||
await info.session.send('Network.setCacheDisabled', {
|
||||
cacheDisabled: true
|
||||
});
|
||||
if (!this._protocolRequestInterceptionEnabled) await info.session.send('Network.setCacheDisabled', {
|
||||
cacheDisabled: false
|
||||
});
|
||||
if (!info.workerFrame) await info.session.send('Network.clearBrowserCache');
|
||||
});
|
||||
}
|
||||
_onRequestWillBeSent(sessionInfo, 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 requestPausedEvent = this._requestIdToRequestPausedEvent.get(requestId);
|
||||
if (requestPausedEvent) {
|
||||
this._onRequest(sessionInfo, event, requestPausedEvent.sessionInfo, requestPausedEvent.event);
|
||||
this._requestIdToRequestPausedEvent.delete(requestId);
|
||||
} else {
|
||||
this._requestIdToRequestWillBeSentEvent.set(event.requestId, {
|
||||
sessionInfo,
|
||||
event
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this._onRequest(sessionInfo, event, undefined, undefined);
|
||||
}
|
||||
}
|
||||
_onRequestServedFromCache(event) {
|
||||
this._responseExtraInfoTracker.requestServedFromCache(event);
|
||||
}
|
||||
_onRequestWillBeSentExtraInfo(event) {
|
||||
this._responseExtraInfoTracker.requestWillBeSentExtraInfo(event);
|
||||
}
|
||||
_onAuthRequired(sessionInfo, event) {
|
||||
let response = 'Default';
|
||||
const shouldProvideCredentials = this._shouldProvideCredentials(event.request.url);
|
||||
if (this._attemptedAuthentications.has(event.requestId)) {
|
||||
response = 'CancelAuth';
|
||||
} else if (shouldProvideCredentials) {
|
||||
response = 'ProvideCredentials';
|
||||
this._attemptedAuthentications.add(event.requestId);
|
||||
}
|
||||
const {
|
||||
username,
|
||||
password
|
||||
} = shouldProvideCredentials && this._credentials ? this._credentials : {
|
||||
username: undefined,
|
||||
password: undefined
|
||||
};
|
||||
sessionInfo.session._sendMayFail('Fetch.continueWithAuth', {
|
||||
requestId: event.requestId,
|
||||
authChallengeResponse: {
|
||||
response,
|
||||
username,
|
||||
password
|
||||
}
|
||||
});
|
||||
}
|
||||
_shouldProvideCredentials(url) {
|
||||
if (!this._credentials) return false;
|
||||
return !this._credentials.origin || new URL(url).origin.toLowerCase() === this._credentials.origin.toLowerCase();
|
||||
}
|
||||
_onRequestPaused(sessionInfo, event) {
|
||||
if (!event.networkId) {
|
||||
// Fetch without networkId means that request was not recognized by inspector, and
|
||||
// it will never receive Network.requestWillBeSent. Continue the request to not affect it.
|
||||
sessionInfo.session._sendMayFail('Fetch.continueRequest', {
|
||||
requestId: event.requestId
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (event.request.url.startsWith('data:')) return;
|
||||
const requestId = event.networkId;
|
||||
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId);
|
||||
if (requestWillBeSentEvent) {
|
||||
this._onRequest(requestWillBeSentEvent.sessionInfo, requestWillBeSentEvent.event, sessionInfo, event);
|
||||
this._requestIdToRequestWillBeSentEvent.delete(requestId);
|
||||
} else {
|
||||
var _existingRequest$_rou;
|
||||
const existingRequest = this._requestIdToRequest.get(requestId);
|
||||
const alreadyContinuedParams = existingRequest === null || existingRequest === void 0 || (_existingRequest$_rou = existingRequest._route) === null || _existingRequest$_rou === void 0 ? void 0 : _existingRequest$_rou._alreadyContinuedParams;
|
||||
if (alreadyContinuedParams && !event.redirectedRequestId) {
|
||||
// Sometimes Chromium network stack restarts the request internally.
|
||||
// For example, when no-cors request hits a "less public address space", it should be resent with cors.
|
||||
// There are some more examples here: https://source.chromium.org/chromium/chromium/src/+/main:services/network/url_loader.cc;l=1205-1234;drc=d5dd931e0ad3d9ffe74888ec62a3cc106efd7ea6
|
||||
// There are probably even more cases deep inside the network stack.
|
||||
//
|
||||
// Anyway, in this case, continue the request in the same way as before, and it should go through.
|
||||
//
|
||||
// Note: make sure not to prematurely continue the redirect, which shares the
|
||||
// `networkId` between the original request and the redirect.
|
||||
sessionInfo.session._sendMayFail('Fetch.continueRequest', {
|
||||
...alreadyContinuedParams,
|
||||
requestId: event.requestId
|
||||
});
|
||||
return;
|
||||
}
|
||||
this._requestIdToRequestPausedEvent.set(requestId, {
|
||||
sessionInfo,
|
||||
event
|
||||
});
|
||||
}
|
||||
}
|
||||
_onRequest(requestWillBeSentSessionInfo, requestWillBeSentEvent, requestPausedSessionInfo, requestPausedEvent) {
|
||||
var _this$_page, _this$_page2, _this$_page3;
|
||||
if (requestWillBeSentEvent.request.url.startsWith('data:')) return;
|
||||
let redirectedFrom = null;
|
||||
if (requestWillBeSentEvent.redirectResponse) {
|
||||
const request = this._requestIdToRequest.get(requestWillBeSentEvent.requestId);
|
||||
// If we connect late to the target, we could have missed the requestWillBeSent event.
|
||||
if (request) {
|
||||
this._handleRequestRedirect(request, requestWillBeSentEvent.redirectResponse, requestWillBeSentEvent.timestamp, requestWillBeSentEvent.redirectHasExtraInfo);
|
||||
redirectedFrom = request;
|
||||
}
|
||||
}
|
||||
let frame = requestWillBeSentEvent.frameId ? (_this$_page = this._page) === null || _this$_page === void 0 ? void 0 : _this$_page._frameManager.frame(requestWillBeSentEvent.frameId) : requestWillBeSentSessionInfo.workerFrame;
|
||||
// Requests from workers lack frameId, because we receive Network.requestWillBeSent
|
||||
// on the worker target. However, we receive Fetch.requestPaused on the page target,
|
||||
// and lack workerFrame there. Luckily, Fetch.requestPaused provides a frameId.
|
||||
if (!frame && this._page && requestPausedEvent && requestPausedEvent.frameId) frame = this._page._frameManager.frame(requestPausedEvent.frameId);
|
||||
|
||||
// Check if it's main resource request interception (targetId === main frame id).
|
||||
if (!frame && this._page && requestWillBeSentEvent.frameId === ((_this$_page2 = this._page) === null || _this$_page2 === void 0 ? void 0 : _this$_page2._delegate)._targetId) {
|
||||
// Main resource request for the page is being intercepted so the Frame is not created
|
||||
// yet. Precreate it here for the purposes of request interception. It will be updated
|
||||
// later as soon as the request continues and we receive frame tree from the page.
|
||||
frame = this._page._frameManager.frameAttached(requestWillBeSentEvent.frameId, null);
|
||||
}
|
||||
|
||||
// CORS options preflight request is generated by the network stack. If interception is enabled,
|
||||
// we accept all CORS options, assuming that this was intended when setting route.
|
||||
//
|
||||
// Note: it would be better to match the URL against interception patterns.
|
||||
const isInterceptedOptionsPreflight = !!requestPausedEvent && requestPausedEvent.request.method === 'OPTIONS' && requestWillBeSentEvent.initiator.type === 'preflight';
|
||||
if (isInterceptedOptionsPreflight && (this._page || this._serviceWorker).needsRequestInterception()) {
|
||||
const requestHeaders = requestPausedEvent.request.headers;
|
||||
const responseHeaders = [{
|
||||
name: 'Access-Control-Allow-Origin',
|
||||
value: requestHeaders['Origin'] || '*'
|
||||
}, {
|
||||
name: 'Access-Control-Allow-Methods',
|
||||
value: requestHeaders['Access-Control-Request-Method'] || 'GET, POST, OPTIONS, DELETE'
|
||||
}, {
|
||||
name: 'Access-Control-Allow-Credentials',
|
||||
value: 'true'
|
||||
}];
|
||||
if (requestHeaders['Access-Control-Request-Headers']) responseHeaders.push({
|
||||
name: 'Access-Control-Allow-Headers',
|
||||
value: requestHeaders['Access-Control-Request-Headers']
|
||||
});
|
||||
requestPausedSessionInfo.session._sendMayFail('Fetch.fulfillRequest', {
|
||||
requestId: requestPausedEvent.requestId,
|
||||
responseCode: 204,
|
||||
responsePhrase: network.statusText(204),
|
||||
responseHeaders,
|
||||
body: ''
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-service-worker requests MUST have a frame—if they don't, we pretend there was no request
|
||||
if (!frame && !this._serviceWorker) {
|
||||
if (requestPausedEvent) requestPausedSessionInfo.session._sendMayFail('Fetch.continueRequest', {
|
||||
requestId: requestPausedEvent.requestId
|
||||
});
|
||||
return;
|
||||
}
|
||||
let route = null;
|
||||
let headersOverride;
|
||||
if (requestPausedEvent) {
|
||||
// We do not support intercepting redirects.
|
||||
if (redirectedFrom || !this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) {
|
||||
var _redirectedFrom;
|
||||
// Chromium does not preserve header overrides between redirects, so we have to do it ourselves.
|
||||
headersOverride = (_redirectedFrom = redirectedFrom) === null || _redirectedFrom === void 0 || (_redirectedFrom = _redirectedFrom._originalRequestRoute) === null || _redirectedFrom === void 0 || (_redirectedFrom = _redirectedFrom._alreadyContinuedParams) === null || _redirectedFrom === void 0 ? void 0 : _redirectedFrom.headers;
|
||||
requestPausedSessionInfo.session._sendMayFail('Fetch.continueRequest', {
|
||||
requestId: requestPausedEvent.requestId,
|
||||
headers: headersOverride
|
||||
});
|
||||
} else {
|
||||
route = new RouteImpl(requestPausedSessionInfo.session, requestPausedEvent.requestId);
|
||||
}
|
||||
}
|
||||
const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document';
|
||||
const documentId = isNavigationRequest ? requestWillBeSentEvent.loaderId : undefined;
|
||||
const request = new InterceptableRequest({
|
||||
session: requestWillBeSentSessionInfo.session,
|
||||
context: (this._page || this._serviceWorker)._browserContext,
|
||||
frame: frame || null,
|
||||
serviceWorker: this._serviceWorker || null,
|
||||
documentId,
|
||||
route,
|
||||
requestWillBeSentEvent,
|
||||
requestPausedEvent,
|
||||
redirectedFrom,
|
||||
headersOverride: headersOverride || null
|
||||
});
|
||||
this._requestIdToRequest.set(requestWillBeSentEvent.requestId, request);
|
||||
if (route) {
|
||||
// We may not receive extra info when intercepting the request.
|
||||
// Use the headers from the Fetch.requestPausedPayload and release the allHeaders()
|
||||
// right away, so that client can call it from the route handler.
|
||||
request.request.setRawRequestHeaders((0, _utils.headersObjectToArray)(requestPausedEvent.request.headers, '\n'));
|
||||
}
|
||||
(((_this$_page3 = this._page) === null || _this$_page3 === void 0 ? void 0 : _this$_page3._frameManager) || this._serviceWorker).requestStarted(request.request, route || undefined);
|
||||
}
|
||||
_createResponse(request, responsePayload, hasExtraInfo) {
|
||||
var _responsePayload$secu, _responsePayload$secu2, _responsePayload$secu3, _responsePayload$secu4, _responsePayload$secu5;
|
||||
const getResponseBody = async () => {
|
||||
var _request$_route;
|
||||
const contentLengthHeader = Object.entries(responsePayload.headers).find(header => header[0].toLowerCase() === 'content-length');
|
||||
const expectedLength = contentLengthHeader ? +contentLengthHeader[1] : undefined;
|
||||
const session = request.session;
|
||||
const response = await session.send('Network.getResponseBody', {
|
||||
requestId: request._requestId
|
||||
});
|
||||
if (response.body || !expectedLength) return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
||||
|
||||
// Make sure no network requests sent while reading the body for fulfilled requests.
|
||||
if ((_request$_route = request._route) !== null && _request$_route !== void 0 && _request$_route._fulfilled) return Buffer.from('');
|
||||
|
||||
// For <link prefetch we are going to receive empty body with non-empty content-length expectation. Reach out for the actual content.
|
||||
const resource = await session.send('Network.loadNetworkResource', {
|
||||
url: request.request.url(),
|
||||
frameId: this._serviceWorker ? undefined : request.request.frame()._id,
|
||||
options: {
|
||||
disableCache: false,
|
||||
includeCredentials: true
|
||||
}
|
||||
});
|
||||
const chunks = [];
|
||||
while (resource.resource.stream) {
|
||||
const chunk = await session.send('IO.read', {
|
||||
handle: resource.resource.stream
|
||||
});
|
||||
chunks.push(Buffer.from(chunk.data, chunk.base64Encoded ? 'base64' : 'utf-8'));
|
||||
if (chunk.eof) {
|
||||
await session.send('IO.close', {
|
||||
handle: resource.resource.stream
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Buffer.concat(chunks);
|
||||
};
|
||||
const timingPayload = responsePayload.timing;
|
||||
let timing;
|
||||
if (timingPayload && !this._responseExtraInfoTracker.servedFromCache(request._requestId)) {
|
||||
timing = {
|
||||
startTime: (timingPayload.requestTime - request._timestamp + request._wallTime) * 1000,
|
||||
domainLookupStart: timingPayload.dnsStart,
|
||||
domainLookupEnd: timingPayload.dnsEnd,
|
||||
connectStart: timingPayload.connectStart,
|
||||
secureConnectionStart: timingPayload.sslStart,
|
||||
connectEnd: timingPayload.connectEnd,
|
||||
requestStart: timingPayload.sendStart,
|
||||
responseStart: timingPayload.receiveHeadersEnd
|
||||
};
|
||||
} else {
|
||||
timing = {
|
||||
startTime: request._wallTime * 1000,
|
||||
domainLookupStart: -1,
|
||||
domainLookupEnd: -1,
|
||||
connectStart: -1,
|
||||
secureConnectionStart: -1,
|
||||
connectEnd: -1,
|
||||
requestStart: -1,
|
||||
responseStart: -1
|
||||
};
|
||||
}
|
||||
const response = new network.Response(request.request, responsePayload.status, responsePayload.statusText, (0, _utils.headersObjectToArray)(responsePayload.headers), timing, getResponseBody, !!responsePayload.fromServiceWorker, responsePayload.protocol);
|
||||
if (responsePayload !== null && responsePayload !== void 0 && responsePayload.remoteIPAddress && typeof (responsePayload === null || responsePayload === void 0 ? void 0 : responsePayload.remotePort) === 'number') {
|
||||
response._serverAddrFinished({
|
||||
ipAddress: responsePayload.remoteIPAddress,
|
||||
port: responsePayload.remotePort
|
||||
});
|
||||
} else {
|
||||
response._serverAddrFinished();
|
||||
}
|
||||
response._securityDetailsFinished({
|
||||
protocol: responsePayload === null || responsePayload === void 0 || (_responsePayload$secu = responsePayload.securityDetails) === null || _responsePayload$secu === void 0 ? void 0 : _responsePayload$secu.protocol,
|
||||
subjectName: responsePayload === null || responsePayload === void 0 || (_responsePayload$secu2 = responsePayload.securityDetails) === null || _responsePayload$secu2 === void 0 ? void 0 : _responsePayload$secu2.subjectName,
|
||||
issuer: responsePayload === null || responsePayload === void 0 || (_responsePayload$secu3 = responsePayload.securityDetails) === null || _responsePayload$secu3 === void 0 ? void 0 : _responsePayload$secu3.issuer,
|
||||
validFrom: responsePayload === null || responsePayload === void 0 || (_responsePayload$secu4 = responsePayload.securityDetails) === null || _responsePayload$secu4 === void 0 ? void 0 : _responsePayload$secu4.validFrom,
|
||||
validTo: responsePayload === null || responsePayload === void 0 || (_responsePayload$secu5 = responsePayload.securityDetails) === null || _responsePayload$secu5 === void 0 ? void 0 : _responsePayload$secu5.validTo
|
||||
});
|
||||
this._responseExtraInfoTracker.processResponse(request._requestId, response, hasExtraInfo);
|
||||
return response;
|
||||
}
|
||||
_deleteRequest(request) {
|
||||
this._requestIdToRequest.delete(request._requestId);
|
||||
if (request._interceptionId) this._attemptedAuthentications.delete(request._interceptionId);
|
||||
}
|
||||
_handleRequestRedirect(request, responsePayload, timestamp, hasExtraInfo) {
|
||||
var _this$_page4, _this$_page5;
|
||||
const response = this._createResponse(request, responsePayload, hasExtraInfo);
|
||||
response.setTransferSize(null);
|
||||
response.setEncodedBodySize(null);
|
||||
response._requestFinished((timestamp - request._timestamp) * 1000);
|
||||
this._deleteRequest(request);
|
||||
(((_this$_page4 = this._page) === null || _this$_page4 === void 0 ? void 0 : _this$_page4._frameManager) || this._serviceWorker).requestReceivedResponse(response);
|
||||
(((_this$_page5 = this._page) === null || _this$_page5 === void 0 ? void 0 : _this$_page5._frameManager) || this._serviceWorker).reportRequestFinished(request.request, response);
|
||||
}
|
||||
_onResponseReceivedExtraInfo(event) {
|
||||
this._responseExtraInfoTracker.responseReceivedExtraInfo(event);
|
||||
}
|
||||
_onResponseReceived(sessionInfo, event) {
|
||||
var _this$_page6;
|
||||
let request = this._requestIdToRequest.get(event.requestId);
|
||||
// For frame-level Requests that are handled by a Service Worker's fetch handler, we'll never get a requestPaused event, so we need to
|
||||
// manually create the request. In an ideal world, crNetworkManager would be able to know this on Network.requestWillBeSent, but there
|
||||
// is not enough metadata there.
|
||||
if (!request && event.response.fromServiceWorker) {
|
||||
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(event.requestId);
|
||||
if (requestWillBeSentEvent) {
|
||||
this._requestIdToRequestWillBeSentEvent.delete(event.requestId);
|
||||
this._onRequest(sessionInfo, requestWillBeSentEvent.event, undefined, undefined);
|
||||
request = this._requestIdToRequest.get(event.requestId);
|
||||
}
|
||||
}
|
||||
// FileUpload sends a response without a matching request.
|
||||
if (!request) return;
|
||||
const response = this._createResponse(request, event.response, event.hasExtraInfo);
|
||||
(((_this$_page6 = this._page) === null || _this$_page6 === void 0 ? void 0 : _this$_page6._frameManager) || this._serviceWorker).requestReceivedResponse(response);
|
||||
}
|
||||
_onLoadingFinished(sessionInfo, event) {
|
||||
var _this$_page7;
|
||||
this._responseExtraInfoTracker.loadingFinished(event);
|
||||
const request = this._requestIdToRequest.get(event.requestId);
|
||||
// For certain requestIds we never receive requestWillBeSent event.
|
||||
// @see https://crbug.com/750469
|
||||
if (!request) return;
|
||||
this._maybeUpdateOOPIFMainRequest(sessionInfo, request);
|
||||
|
||||
// Under certain conditions we never get the Network.responseReceived
|
||||
// event from protocol. @see https://crbug.com/883475
|
||||
const response = request.request._existingResponse();
|
||||
if (response) {
|
||||
response.setTransferSize(event.encodedDataLength);
|
||||
response.responseHeadersSize().then(size => response.setEncodedBodySize(event.encodedDataLength - size));
|
||||
response._requestFinished(_helper.helper.secondsToRoundishMillis(event.timestamp - request._timestamp));
|
||||
}
|
||||
this._deleteRequest(request);
|
||||
(((_this$_page7 = this._page) === null || _this$_page7 === void 0 ? void 0 : _this$_page7._frameManager) || this._serviceWorker).reportRequestFinished(request.request, response);
|
||||
}
|
||||
_onLoadingFailed(sessionInfo, event) {
|
||||
var _this$_page8;
|
||||
this._responseExtraInfoTracker.loadingFailed(event);
|
||||
let request = this._requestIdToRequest.get(event.requestId);
|
||||
if (!request) {
|
||||
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(event.requestId);
|
||||
if (requestWillBeSentEvent) {
|
||||
// This is a case where request has failed before we had a chance to intercept it.
|
||||
// We stop waiting for Fetch.requestPaused (it might never come), and dispatch request event
|
||||
// right away, followed by requestfailed event.
|
||||
this._requestIdToRequestWillBeSentEvent.delete(event.requestId);
|
||||
this._onRequest(sessionInfo, requestWillBeSentEvent.event, undefined, undefined);
|
||||
request = this._requestIdToRequest.get(event.requestId);
|
||||
}
|
||||
}
|
||||
|
||||
// For certain requestIds we never receive requestWillBeSent event.
|
||||
// @see https://crbug.com/750469
|
||||
if (!request) return;
|
||||
this._maybeUpdateOOPIFMainRequest(sessionInfo, request);
|
||||
const response = request.request._existingResponse();
|
||||
if (response) {
|
||||
response.setTransferSize(null);
|
||||
response.setEncodedBodySize(null);
|
||||
response._requestFinished(_helper.helper.secondsToRoundishMillis(event.timestamp - request._timestamp));
|
||||
} else {
|
||||
// Loading failed before response has arrived - there will be no extra info events.
|
||||
request.request.setRawRequestHeaders(null);
|
||||
}
|
||||
this._deleteRequest(request);
|
||||
request.request._setFailureText(event.errorText || event.blockedReason || '');
|
||||
(((_this$_page8 = this._page) === null || _this$_page8 === void 0 ? void 0 : _this$_page8._frameManager) || this._serviceWorker).requestFailed(request.request, !!event.canceled);
|
||||
}
|
||||
_maybeUpdateOOPIFMainRequest(sessionInfo, request) {
|
||||
// OOPIF has a main request that starts in the parent session but finishes in the child session.
|
||||
// We check for the main request by matching loaderId and requestId, and if it now belongs to
|
||||
// a child session, migrate it there.
|
||||
if (request.session !== sessionInfo.session && !sessionInfo.isMain && request._documentId === request._requestId) request.session = sessionInfo.session;
|
||||
}
|
||||
}
|
||||
exports.CRNetworkManager = CRNetworkManager;
|
||||
class InterceptableRequest {
|
||||
constructor(options) {
|
||||
this.request = void 0;
|
||||
this._requestId = void 0;
|
||||
this._interceptionId = void 0;
|
||||
this._documentId = void 0;
|
||||
this._timestamp = void 0;
|
||||
this._wallTime = void 0;
|
||||
this._route = void 0;
|
||||
// Only first request in the chain can be intercepted, so this will
|
||||
// store the first and only Route in the chain (if any).
|
||||
this._originalRequestRoute = void 0;
|
||||
this.session = void 0;
|
||||
const {
|
||||
session,
|
||||
context,
|
||||
frame,
|
||||
documentId,
|
||||
route,
|
||||
requestWillBeSentEvent,
|
||||
requestPausedEvent,
|
||||
redirectedFrom,
|
||||
serviceWorker,
|
||||
headersOverride
|
||||
} = options;
|
||||
this.session = session;
|
||||
this._timestamp = requestWillBeSentEvent.timestamp;
|
||||
this._wallTime = requestWillBeSentEvent.wallTime;
|
||||
this._requestId = requestWillBeSentEvent.requestId;
|
||||
this._interceptionId = requestPausedEvent && requestPausedEvent.requestId;
|
||||
this._documentId = documentId;
|
||||
this._route = route;
|
||||
this._originalRequestRoute = route !== null && route !== void 0 ? route : redirectedFrom === null || redirectedFrom === void 0 ? void 0 : redirectedFrom._originalRequestRoute;
|
||||
const {
|
||||
headers,
|
||||
method,
|
||||
url,
|
||||
postDataEntries = null
|
||||
} = requestPausedEvent ? requestPausedEvent.request : requestWillBeSentEvent.request;
|
||||
const type = (requestWillBeSentEvent.type || '').toLowerCase();
|
||||
let postDataBuffer = null;
|
||||
const entries = postDataEntries === null || postDataEntries === void 0 ? void 0 : postDataEntries.filter(entry => entry.bytes);
|
||||
if (entries && entries.length) postDataBuffer = Buffer.concat(entries.map(entry => Buffer.from(entry.bytes, 'base64')));
|
||||
this.request = new network.Request(context, frame, serviceWorker, (redirectedFrom === null || redirectedFrom === void 0 ? void 0 : redirectedFrom.request) || null, documentId, url, type, method, postDataBuffer, headersOverride || (0, _utils.headersObjectToArray)(headers));
|
||||
}
|
||||
}
|
||||
class RouteImpl {
|
||||
constructor(session, interceptionId) {
|
||||
this._session = void 0;
|
||||
this._interceptionId = void 0;
|
||||
this._alreadyContinuedParams = void 0;
|
||||
this._fulfilled = false;
|
||||
this._session = session;
|
||||
this._interceptionId = interceptionId;
|
||||
}
|
||||
async continue(overrides) {
|
||||
this._alreadyContinuedParams = {
|
||||
requestId: this._interceptionId,
|
||||
url: overrides.url,
|
||||
headers: overrides.headers,
|
||||
method: overrides.method,
|
||||
postData: overrides.postData ? overrides.postData.toString('base64') : undefined
|
||||
};
|
||||
await catchDisallowedErrors(async () => {
|
||||
await this._session.send('Fetch.continueRequest', this._alreadyContinuedParams);
|
||||
});
|
||||
}
|
||||
async fulfill(response) {
|
||||
this._fulfilled = true;
|
||||
const body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64');
|
||||
const responseHeaders = splitSetCookieHeader(response.headers);
|
||||
await catchDisallowedErrors(async () => {
|
||||
await this._session.send('Fetch.fulfillRequest', {
|
||||
requestId: this._interceptionId,
|
||||
responseCode: response.status,
|
||||
responsePhrase: network.statusText(response.status),
|
||||
responseHeaders,
|
||||
body
|
||||
});
|
||||
});
|
||||
}
|
||||
async abort(errorCode = 'failed') {
|
||||
const errorReason = errorReasons[errorCode];
|
||||
(0, _utils.assert)(errorReason, 'Unknown error code: ' + errorCode);
|
||||
await catchDisallowedErrors(async () => {
|
||||
await this._session.send('Fetch.failRequest', {
|
||||
requestId: this._interceptionId,
|
||||
errorReason
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// In certain cases, protocol will return error if the request was already canceled
|
||||
// or the page was closed. We should tolerate these errors but propagate other.
|
||||
async function catchDisallowedErrors(callback) {
|
||||
try {
|
||||
return await callback();
|
||||
} catch (e) {
|
||||
if ((0, _protocolError.isProtocolError)(e) && e.message.includes('Invalid http status code or phrase')) throw e;
|
||||
}
|
||||
}
|
||||
function splitSetCookieHeader(headers) {
|
||||
const index = headers.findIndex(({
|
||||
name
|
||||
}) => name.toLowerCase() === 'set-cookie');
|
||||
if (index === -1) return headers;
|
||||
const header = headers[index];
|
||||
const values = header.value.split('\n');
|
||||
if (values.length === 1) return headers;
|
||||
const result = headers.slice();
|
||||
result.splice(index, 1, ...values.map(value => ({
|
||||
name: header.name,
|
||||
value
|
||||
})));
|
||||
return result;
|
||||
}
|
||||
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'
|
||||
};
|
||||
// This class aligns responses with response headers from extra info:
|
||||
// - Network.requestWillBeSent, Network.responseReceived, Network.loadingFinished/loadingFailed are
|
||||
// dispatched using one channel.
|
||||
// - Network.requestWillBeSentExtraInfo and Network.responseReceivedExtraInfo are dispatched on
|
||||
// another channel. Those channels are not associated, so events come in random order.
|
||||
//
|
||||
// This class will associate responses with the new headers. These extra info headers will become
|
||||
// available to client reliably upon requestfinished event only. It consumes CDP
|
||||
// signals on one end and processResponse(network.Response) signals on the other hands. It then makes
|
||||
// sure that responses have all the extra headers in place by the time request finishes.
|
||||
//
|
||||
// The shape of the instrumentation API is deliberately following the CDP, so that it
|
||||
// is clear what is called when and what this means to the tracker without extra
|
||||
// documentation.
|
||||
class ResponseExtraInfoTracker {
|
||||
constructor() {
|
||||
this._requests = new Map();
|
||||
}
|
||||
requestWillBeSentExtraInfo(event) {
|
||||
const info = this._getOrCreateEntry(event.requestId);
|
||||
info.requestWillBeSentExtraInfo.push(event);
|
||||
this._patchHeaders(info, info.requestWillBeSentExtraInfo.length - 1);
|
||||
this._checkFinished(info);
|
||||
}
|
||||
requestServedFromCache(event) {
|
||||
const info = this._getOrCreateEntry(event.requestId);
|
||||
info.servedFromCache = true;
|
||||
}
|
||||
servedFromCache(requestId) {
|
||||
const info = this._requests.get(requestId);
|
||||
return !!(info !== null && info !== void 0 && info.servedFromCache);
|
||||
}
|
||||
responseReceivedExtraInfo(event) {
|
||||
const info = this._getOrCreateEntry(event.requestId);
|
||||
info.responseReceivedExtraInfo.push(event);
|
||||
this._patchHeaders(info, info.responseReceivedExtraInfo.length - 1);
|
||||
this._checkFinished(info);
|
||||
}
|
||||
processResponse(requestId, response, hasExtraInfo) {
|
||||
var _info;
|
||||
let info = this._requests.get(requestId);
|
||||
// Cached responses have erroneous "hasExtraInfo" flag.
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1340398
|
||||
if (!hasExtraInfo || (_info = info) !== null && _info !== void 0 && _info.servedFromCache) {
|
||||
// Use "provisional" headers as "raw" ones.
|
||||
response.request().setRawRequestHeaders(null);
|
||||
response.setResponseHeadersSize(null);
|
||||
response.setRawResponseHeaders(null);
|
||||
return;
|
||||
}
|
||||
info = this._getOrCreateEntry(requestId);
|
||||
info.responses.push(response);
|
||||
this._patchHeaders(info, info.responses.length - 1);
|
||||
}
|
||||
loadingFinished(event) {
|
||||
const info = this._requests.get(event.requestId);
|
||||
if (!info) return;
|
||||
info.loadingFinished = event;
|
||||
this._checkFinished(info);
|
||||
}
|
||||
loadingFailed(event) {
|
||||
const info = this._requests.get(event.requestId);
|
||||
if (!info) return;
|
||||
info.loadingFailed = event;
|
||||
this._checkFinished(info);
|
||||
}
|
||||
_getOrCreateEntry(requestId) {
|
||||
let info = this._requests.get(requestId);
|
||||
if (!info) {
|
||||
info = {
|
||||
requestId: requestId,
|
||||
requestWillBeSentExtraInfo: [],
|
||||
responseReceivedExtraInfo: [],
|
||||
responses: []
|
||||
};
|
||||
this._requests.set(requestId, info);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
_patchHeaders(info, index) {
|
||||
const response = info.responses[index];
|
||||
const requestExtraInfo = info.requestWillBeSentExtraInfo[index];
|
||||
if (response && requestExtraInfo) {
|
||||
response.request().setRawRequestHeaders((0, _utils.headersObjectToArray)(requestExtraInfo.headers, '\n'));
|
||||
info.requestWillBeSentExtraInfo[index] = undefined;
|
||||
}
|
||||
const responseExtraInfo = info.responseReceivedExtraInfo[index];
|
||||
if (response && responseExtraInfo) {
|
||||
var _responseExtraInfo$he;
|
||||
response.setResponseHeadersSize(((_responseExtraInfo$he = responseExtraInfo.headersText) === null || _responseExtraInfo$he === void 0 ? void 0 : _responseExtraInfo$he.length) || 0);
|
||||
response.setRawResponseHeaders((0, _utils.headersObjectToArray)(responseExtraInfo.headers, '\n'));
|
||||
info.responseReceivedExtraInfo[index] = undefined;
|
||||
}
|
||||
}
|
||||
_checkFinished(info) {
|
||||
if (!info.loadingFinished && !info.loadingFailed) return;
|
||||
if (info.responses.length <= info.responseReceivedExtraInfo.length) {
|
||||
// We have extra info for each response.
|
||||
this._stopTracking(info.requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
// We are not done yet.
|
||||
}
|
||||
_stopTracking(requestId) {
|
||||
this._requests.delete(requestId);
|
||||
}
|
||||
}
|
||||
1119
node_modules/playwright-core/lib/server/chromium/crPage.js
generated
vendored
Normal file
1119
node_modules/playwright-core/lib/server/chromium/crPage.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
153
node_modules/playwright-core/lib/server/chromium/crPdf.js
generated
vendored
Normal file
153
node_modules/playwright-core/lib/server/chromium/crPdf.js
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.CRPDF = void 0;
|
||||
var _crProtocolHelper = require("./crProtocolHelper");
|
||||
var _utils = require("../../utils");
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const PagePaperFormats = {
|
||||
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
|
||||
}
|
||||
};
|
||||
const unitToPixels = {
|
||||
'px': 1,
|
||||
'in': 96,
|
||||
'cm': 37.8,
|
||||
'mm': 3.78
|
||||
};
|
||||
function convertPrintParameterToInches(text) {
|
||||
if (text === undefined) return undefined;
|
||||
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);
|
||||
(0, _utils.assert)(!isNaN(value), 'Failed to parse parameter value: ' + text);
|
||||
const pixels = value * unitToPixels[unit];
|
||||
return pixels / 96;
|
||||
}
|
||||
class CRPDF {
|
||||
constructor(client) {
|
||||
this._client = void 0;
|
||||
this._client = client;
|
||||
}
|
||||
async generate(options) {
|
||||
const {
|
||||
scale = 1,
|
||||
displayHeaderFooter = false,
|
||||
headerTemplate = '',
|
||||
footerTemplate = '',
|
||||
printBackground = false,
|
||||
landscape = false,
|
||||
pageRanges = '',
|
||||
preferCSSPageSize = false,
|
||||
margin = {},
|
||||
tagged = false,
|
||||
outline = false
|
||||
} = options;
|
||||
let paperWidth = 8.5;
|
||||
let paperHeight = 11;
|
||||
if (options.format) {
|
||||
const format = PagePaperFormats[options.format.toLowerCase()];
|
||||
(0, _utils.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 generateDocumentOutline = outline;
|
||||
const generateTaggedPDF = tagged;
|
||||
const result = await this._client.send('Page.printToPDF', {
|
||||
transferMode: 'ReturnAsStream',
|
||||
landscape,
|
||||
displayHeaderFooter,
|
||||
headerTemplate,
|
||||
footerTemplate,
|
||||
printBackground,
|
||||
scale,
|
||||
paperWidth,
|
||||
paperHeight,
|
||||
marginTop,
|
||||
marginBottom,
|
||||
marginLeft,
|
||||
marginRight,
|
||||
pageRanges,
|
||||
preferCSSPageSize,
|
||||
generateTaggedPDF,
|
||||
generateDocumentOutline
|
||||
});
|
||||
return await (0, _crProtocolHelper.readProtocolStream)(this._client, result.stream);
|
||||
}
|
||||
}
|
||||
exports.CRPDF = CRPDF;
|
||||
133
node_modules/playwright-core/lib/server/chromium/crProtocolHelper.js
generated
vendored
Normal file
133
node_modules/playwright-core/lib/server/chromium/crProtocolHelper.js
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.exceptionToError = exceptionToError;
|
||||
exports.getExceptionMessage = getExceptionMessage;
|
||||
exports.readProtocolStream = readProtocolStream;
|
||||
exports.releaseObject = releaseObject;
|
||||
exports.saveProtocolStream = saveProtocolStream;
|
||||
exports.toButtonsMask = toButtonsMask;
|
||||
exports.toConsoleMessageLocation = toConsoleMessageLocation;
|
||||
exports.toModifiersMask = toModifiersMask;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _stackTrace = require("../../utils/isomorphic/stackTrace");
|
||||
var _fileUtils = require("../utils/fileUtils");
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
function getExceptionMessage(exceptionDetails) {
|
||||
if (exceptionDetails.exception) return exceptionDetails.exception.description || String(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;
|
||||
}
|
||||
async function releaseObject(client, objectId) {
|
||||
await client.send('Runtime.releaseObject', {
|
||||
objectId
|
||||
}).catch(error => {});
|
||||
}
|
||||
async function saveProtocolStream(client, handle, path) {
|
||||
let eof = false;
|
||||
await (0, _fileUtils.mkdirIfNeeded)(path);
|
||||
const fd = await _fs.default.promises.open(path, 'w');
|
||||
while (!eof) {
|
||||
const response = await client.send('IO.read', {
|
||||
handle
|
||||
});
|
||||
eof = response.eof;
|
||||
const buf = Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
|
||||
await fd.write(buf);
|
||||
}
|
||||
await fd.close();
|
||||
await client.send('IO.close', {
|
||||
handle
|
||||
});
|
||||
}
|
||||
async function readProtocolStream(client, handle) {
|
||||
let eof = false;
|
||||
const chunks = [];
|
||||
while (!eof) {
|
||||
const response = await client.send('IO.read', {
|
||||
handle
|
||||
});
|
||||
eof = response.eof;
|
||||
const buf = Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
|
||||
chunks.push(buf);
|
||||
}
|
||||
await client.send('IO.close', {
|
||||
handle
|
||||
});
|
||||
return Buffer.concat(chunks);
|
||||
}
|
||||
function toConsoleMessageLocation(stackTrace) {
|
||||
return stackTrace && stackTrace.callFrames.length ? {
|
||||
url: stackTrace.callFrames[0].url,
|
||||
lineNumber: stackTrace.callFrames[0].lineNumber,
|
||||
columnNumber: stackTrace.callFrames[0].columnNumber
|
||||
} : {
|
||||
url: '',
|
||||
lineNumber: 0,
|
||||
columnNumber: 0
|
||||
};
|
||||
}
|
||||
function exceptionToError(exceptionDetails) {
|
||||
var _exceptionDetails$exc, _nameOverride$value;
|
||||
const messageWithStack = getExceptionMessage(exceptionDetails);
|
||||
const lines = messageWithStack.split('\n');
|
||||
const firstStackTraceLine = lines.findIndex(line => line.startsWith(' at'));
|
||||
let messageWithName = '';
|
||||
let stack = '';
|
||||
if (firstStackTraceLine === -1) {
|
||||
messageWithName = messageWithStack;
|
||||
} else {
|
||||
messageWithName = lines.slice(0, firstStackTraceLine).join('\n');
|
||||
stack = messageWithStack;
|
||||
}
|
||||
const {
|
||||
name,
|
||||
message
|
||||
} = (0, _stackTrace.splitErrorMessage)(messageWithName);
|
||||
const err = new Error(message);
|
||||
err.stack = stack;
|
||||
const nameOverride = (_exceptionDetails$exc = exceptionDetails.exception) === null || _exceptionDetails$exc === void 0 || (_exceptionDetails$exc = _exceptionDetails$exc.preview) === null || _exceptionDetails$exc === void 0 ? void 0 : _exceptionDetails$exc.properties.find(o => o.name === 'name');
|
||||
err.name = nameOverride ? (_nameOverride$value = nameOverride.value) !== null && _nameOverride$value !== void 0 ? _nameOverride$value : 'Error' : name;
|
||||
return err;
|
||||
}
|
||||
function toModifiersMask(modifiers) {
|
||||
let mask = 0;
|
||||
if (modifiers.has('Alt')) mask |= 1;
|
||||
if (modifiers.has('Control')) mask |= 2;
|
||||
if (modifiers.has('Meta')) mask |= 4;
|
||||
if (modifiers.has('Shift')) mask |= 8;
|
||||
return mask;
|
||||
}
|
||||
function toButtonsMask(buttons) {
|
||||
let mask = 0;
|
||||
if (buttons.has('left')) mask |= 1;
|
||||
if (buttons.has('right')) mask |= 2;
|
||||
if (buttons.has('middle')) mask |= 4;
|
||||
return mask;
|
||||
}
|
||||
112
node_modules/playwright-core/lib/server/chromium/crServiceWorker.js
generated
vendored
Normal file
112
node_modules/playwright-core/lib/server/chromium/crServiceWorker.js
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.CRServiceWorker = void 0;
|
||||
var _page = require("../page");
|
||||
var _crExecutionContext = require("./crExecutionContext");
|
||||
var _crNetworkManager = require("./crNetworkManager");
|
||||
var _browserContext = require("../browserContext");
|
||||
var network = _interopRequireWildcard(require("../network"));
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class CRServiceWorker extends _page.Worker {
|
||||
constructor(browserContext, session, url) {
|
||||
super(browserContext, url);
|
||||
this._browserContext = void 0;
|
||||
this._networkManager = void 0;
|
||||
this._session = void 0;
|
||||
this._session = session;
|
||||
this._browserContext = browserContext;
|
||||
if (!!process.env.PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS) this._networkManager = new _crNetworkManager.CRNetworkManager(null, this);
|
||||
session.once('Runtime.executionContextCreated', event => {
|
||||
this._createExecutionContext(new _crExecutionContext.CRExecutionContext(session, event.context));
|
||||
});
|
||||
if (this._networkManager && this._isNetworkInspectionEnabled()) {
|
||||
this.updateRequestInterception();
|
||||
this.updateExtraHTTPHeaders();
|
||||
this.updateHttpCredentials();
|
||||
this.updateOffline();
|
||||
this._networkManager.addSession(session, undefined, true /* isMain */).catch(() => {});
|
||||
}
|
||||
session.send('Runtime.enable', {}).catch(e => {});
|
||||
session.send('Runtime.runIfWaitingForDebugger').catch(e => {});
|
||||
session.on('Inspector.targetReloadedAfterCrash', () => {
|
||||
// Resume service worker after restart.
|
||||
session._sendMayFail('Runtime.runIfWaitingForDebugger', {});
|
||||
});
|
||||
}
|
||||
didClose() {
|
||||
var _this$_networkManager;
|
||||
(_this$_networkManager = this._networkManager) === null || _this$_networkManager === void 0 || _this$_networkManager.removeSession(this._session);
|
||||
this._session.dispose();
|
||||
super.didClose();
|
||||
}
|
||||
async updateOffline() {
|
||||
var _this$_networkManager2;
|
||||
if (!this._isNetworkInspectionEnabled()) return;
|
||||
await ((_this$_networkManager2 = this._networkManager) === null || _this$_networkManager2 === void 0 ? void 0 : _this$_networkManager2.setOffline(!!this._browserContext._options.offline).catch(() => {}));
|
||||
}
|
||||
async updateHttpCredentials() {
|
||||
var _this$_networkManager3;
|
||||
if (!this._isNetworkInspectionEnabled()) return;
|
||||
await ((_this$_networkManager3 = this._networkManager) === null || _this$_networkManager3 === void 0 ? void 0 : _this$_networkManager3.authenticate(this._browserContext._options.httpCredentials || null).catch(() => {}));
|
||||
}
|
||||
async updateExtraHTTPHeaders() {
|
||||
var _this$_networkManager4;
|
||||
if (!this._isNetworkInspectionEnabled()) return;
|
||||
await ((_this$_networkManager4 = this._networkManager) === null || _this$_networkManager4 === void 0 ? void 0 : _this$_networkManager4.setExtraHTTPHeaders(this._browserContext._options.extraHTTPHeaders || []).catch(() => {}));
|
||||
}
|
||||
async updateRequestInterception() {
|
||||
var _this$_networkManager5;
|
||||
if (!this._isNetworkInspectionEnabled()) return;
|
||||
await ((_this$_networkManager5 = this._networkManager) === null || _this$_networkManager5 === void 0 ? void 0 : _this$_networkManager5.setRequestInterception(this.needsRequestInterception()).catch(() => {}));
|
||||
}
|
||||
needsRequestInterception() {
|
||||
return this._isNetworkInspectionEnabled() && !!this._browserContext._requestInterceptor;
|
||||
}
|
||||
reportRequestFinished(request, response) {
|
||||
this._browserContext.emit(_browserContext.BrowserContext.Events.RequestFinished, {
|
||||
request,
|
||||
response
|
||||
});
|
||||
}
|
||||
requestFailed(request, _canceled) {
|
||||
this._browserContext.emit(_browserContext.BrowserContext.Events.RequestFailed, request);
|
||||
}
|
||||
requestReceivedResponse(response) {
|
||||
this._browserContext.emit(_browserContext.BrowserContext.Events.Response, response);
|
||||
}
|
||||
requestStarted(request, route) {
|
||||
this._browserContext.emit(_browserContext.BrowserContext.Events.Request, request);
|
||||
if (route) {
|
||||
var _this$_browserContext, _this$_browserContext2;
|
||||
const r = new network.Route(request, route);
|
||||
if ((_this$_browserContext = (_this$_browserContext2 = this._browserContext)._requestInterceptor) !== null && _this$_browserContext !== void 0 && _this$_browserContext.call(_this$_browserContext2, r, request)) return;
|
||||
r.continue({
|
||||
isFallback: true
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
_isNetworkInspectionEnabled() {
|
||||
return this._browserContext._options.serviceWorkers !== 'block';
|
||||
}
|
||||
}
|
||||
exports.CRServiceWorker = CRServiceWorker;
|
||||
145
node_modules/playwright-core/lib/server/chromium/defaultFontFamilies.js
generated
vendored
Normal file
145
node_modules/playwright-core/lib/server/chromium/defaultFontFamilies.js
generated
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.platformToFontFamilies = void 0;
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// DO NOT EDIT: this map is generated from Chromium source code by utils/generate_chromium_default_font_families.js
|
||||
const platformToFontFamilies = exports.platformToFontFamilies = {
|
||||
'linux': {
|
||||
'fontFamilies': {
|
||||
'standard': 'Times New Roman',
|
||||
'fixed': 'Monospace',
|
||||
'serif': 'Times New Roman',
|
||||
'sansSerif': 'Arial',
|
||||
'cursive': 'Comic Sans MS',
|
||||
'fantasy': 'Impact'
|
||||
}
|
||||
},
|
||||
'mac': {
|
||||
'fontFamilies': {
|
||||
'standard': 'Times',
|
||||
'fixed': 'Courier',
|
||||
'serif': 'Times',
|
||||
'sansSerif': 'Helvetica',
|
||||
'cursive': 'Apple Chancery',
|
||||
'fantasy': 'Papyrus'
|
||||
},
|
||||
'forScripts': [{
|
||||
'script': 'jpan',
|
||||
'fontFamilies': {
|
||||
'standard': 'Hiragino Kaku Gothic ProN',
|
||||
'fixed': 'Osaka-Mono',
|
||||
'serif': 'Hiragino Mincho ProN',
|
||||
'sansSerif': 'Hiragino Kaku Gothic ProN'
|
||||
}
|
||||
}, {
|
||||
'script': 'hang',
|
||||
'fontFamilies': {
|
||||
'standard': 'Apple SD Gothic Neo',
|
||||
'serif': 'AppleMyungjo',
|
||||
'sansSerif': 'Apple SD Gothic Neo'
|
||||
}
|
||||
}, {
|
||||
'script': 'hans',
|
||||
'fontFamilies': {
|
||||
'standard': ',PingFang SC,STHeiti',
|
||||
'serif': 'Songti SC',
|
||||
'sansSerif': ',PingFang SC,STHeiti',
|
||||
'cursive': 'Kaiti SC'
|
||||
}
|
||||
}, {
|
||||
'script': 'hant',
|
||||
'fontFamilies': {
|
||||
'standard': ',PingFang TC,Heiti TC',
|
||||
'serif': 'Songti TC',
|
||||
'sansSerif': ',PingFang TC,Heiti TC',
|
||||
'cursive': 'Kaiti TC'
|
||||
}
|
||||
}]
|
||||
},
|
||||
'win': {
|
||||
'fontFamilies': {
|
||||
'standard': 'Times New Roman',
|
||||
'fixed': 'Consolas',
|
||||
'serif': 'Times New Roman',
|
||||
'sansSerif': 'Arial',
|
||||
'cursive': 'Comic Sans MS',
|
||||
'fantasy': 'Impact'
|
||||
},
|
||||
'forScripts': [{
|
||||
'script': 'cyrl',
|
||||
'fontFamilies': {
|
||||
'standard': 'Times New Roman',
|
||||
'fixed': 'Courier New',
|
||||
'serif': 'Times New Roman',
|
||||
'sansSerif': 'Arial'
|
||||
}
|
||||
}, {
|
||||
'script': 'arab',
|
||||
'fontFamilies': {
|
||||
'fixed': 'Courier New',
|
||||
'sansSerif': 'Segoe UI'
|
||||
}
|
||||
}, {
|
||||
'script': 'grek',
|
||||
'fontFamilies': {
|
||||
'standard': 'Times New Roman',
|
||||
'fixed': 'Courier New',
|
||||
'serif': 'Times New Roman',
|
||||
'sansSerif': 'Arial'
|
||||
}
|
||||
}, {
|
||||
'script': 'jpan',
|
||||
'fontFamilies': {
|
||||
'standard': ',Meiryo,Yu Gothic',
|
||||
'fixed': 'MS Gothic',
|
||||
'serif': ',Yu Mincho,MS PMincho',
|
||||
'sansSerif': ',Meiryo,Yu Gothic'
|
||||
}
|
||||
}, {
|
||||
'script': 'hang',
|
||||
'fontFamilies': {
|
||||
'standard': 'Malgun Gothic',
|
||||
'fixed': 'Gulimche',
|
||||
'serif': 'Batang',
|
||||
'sansSerif': 'Malgun Gothic',
|
||||
'cursive': 'Gungsuh'
|
||||
}
|
||||
}, {
|
||||
'script': 'hans',
|
||||
'fontFamilies': {
|
||||
'standard': 'Microsoft YaHei',
|
||||
'fixed': 'NSimsun',
|
||||
'serif': 'Simsun',
|
||||
'sansSerif': 'Microsoft YaHei',
|
||||
'cursive': 'KaiTi'
|
||||
}
|
||||
}, {
|
||||
'script': 'hant',
|
||||
'fontFamilies': {
|
||||
'standard': 'Microsoft JhengHei',
|
||||
'fixed': 'MingLiU',
|
||||
'serif': 'PMingLiU',
|
||||
'sansSerif': 'Microsoft JhengHei',
|
||||
'cursive': 'DFKai-SB'
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
155
node_modules/playwright-core/lib/server/chromium/videoRecorder.js
generated
vendored
Normal file
155
node_modules/playwright-core/lib/server/chromium/videoRecorder.js
generated
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.VideoRecorder = void 0;
|
||||
var _utils = require("../../utils");
|
||||
var _instrumentation = require("../instrumentation");
|
||||
var _page = require("../page");
|
||||
var _processLauncher = require("../utils/processLauncher");
|
||||
var _progress = require("../progress");
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const fps = 25;
|
||||
class VideoRecorder {
|
||||
static async launch(page, ffmpegPath, options) {
|
||||
if (!options.outputFile.endsWith('.webm')) throw new Error('File must have .webm extension');
|
||||
const controller = new _progress.ProgressController((0, _instrumentation.serverSideCallMetadata)(), page);
|
||||
controller.setLogName('browser');
|
||||
return await controller.run(async progress => {
|
||||
const recorder = new VideoRecorder(page, ffmpegPath, progress);
|
||||
await recorder._launch(options);
|
||||
return recorder;
|
||||
});
|
||||
}
|
||||
constructor(page, ffmpegPath, progress) {
|
||||
this._process = null;
|
||||
this._gracefullyClose = null;
|
||||
this._lastWritePromise = Promise.resolve();
|
||||
this._lastFrameTimestamp = 0;
|
||||
this._lastFrameBuffer = null;
|
||||
this._lastWriteTimestamp = 0;
|
||||
this._progress = void 0;
|
||||
this._frameQueue = [];
|
||||
this._isStopped = false;
|
||||
this._ffmpegPath = void 0;
|
||||
this._progress = progress;
|
||||
this._ffmpegPath = ffmpegPath;
|
||||
page.on(_page.Page.Events.ScreencastFrame, frame => this.writeFrame(frame.buffer, frame.frameSwapWallTime / 1000));
|
||||
}
|
||||
async _launch(options) {
|
||||
// How to tune the codec:
|
||||
// 1. Read vp8 documentation to figure out the options.
|
||||
// https://www.webmproject.org/docs/encoder-parameters/
|
||||
// 2. Use the following command to map the options to ffmpeg arguments.
|
||||
// $ ./third_party/ffmpeg/ffmpeg-mac -h encoder=vp8
|
||||
// 3. A bit more about passing vp8 options to ffmpeg.
|
||||
// https://trac.ffmpeg.org/wiki/Encode/VP8
|
||||
// 4. Tuning for VP9:
|
||||
// https://developers.google.com/media/vp9/live-encoding
|
||||
//
|
||||
// How to stress-test video recording (runs 10 recorders in parallel to book all cpus available):
|
||||
// $ node ./utils/video_stress.js
|
||||
//
|
||||
// We use the following vp8 options:
|
||||
// "-qmin 0 -qmax 50" - quality variation from 0 to 50.
|
||||
// Suggested here: https://trac.ffmpeg.org/wiki/Encode/VP8
|
||||
// "-crf 8" - constant quality mode, 4-63, lower means better quality.
|
||||
// "-deadline realtime -speed 8" - do not use too much cpu to keep up with incoming frames.
|
||||
// "-b:v 1M" - video bitrate. Default value is too low for vp8
|
||||
// Suggested here: https://trac.ffmpeg.org/wiki/Encode/VP8
|
||||
// Note that we can switch to "-qmin 20 -qmax 50 -crf 30" for smaller video size but worse quality.
|
||||
//
|
||||
// We use "pad" and "crop" video filters (-vf option) to resize incoming frames
|
||||
// that might be of the different size to the desired video size.
|
||||
// https://ffmpeg.org/ffmpeg-filters.html#pad-1
|
||||
// https://ffmpeg.org/ffmpeg-filters.html#crop
|
||||
//
|
||||
// We use "image2pipe" mode to pipe frames and get a single video - https://trac.ffmpeg.org/wiki/Slideshow
|
||||
// "-f image2pipe -c:v mjpeg -i -" forces input to be read from standard input, and forces
|
||||
// mjpeg input image format.
|
||||
// "-avioflags direct" reduces general buffering.
|
||||
// "-fpsprobesize 0 -probesize 32 -analyzeduration 0" reduces initial buffering
|
||||
// while analyzing input fps and other stats.
|
||||
//
|
||||
// "-y" means overwrite output.
|
||||
// "-an" means no audio.
|
||||
// "-threads 1" means using one thread. This drastically reduces stalling when
|
||||
// cpu is overbooked. By default vp8 tries to use all available threads?
|
||||
|
||||
const w = options.width;
|
||||
const h = options.height;
|
||||
const args = `-loglevel error -f image2pipe -avioflags direct -fpsprobesize 0 -probesize 32 -analyzeduration 0 -c:v mjpeg -i pipe:0 -y -an -r ${fps} -c:v vp8 -qmin 0 -qmax 50 -crf 8 -deadline realtime -speed 8 -b:v 1M -threads 1 -vf pad=${w}:${h}:0:0:gray,crop=${w}:${h}:0:0`.split(' ');
|
||||
args.push(options.outputFile);
|
||||
const progress = this._progress;
|
||||
const {
|
||||
launchedProcess,
|
||||
gracefullyClose
|
||||
} = await (0, _processLauncher.launchProcess)({
|
||||
command: this._ffmpegPath,
|
||||
args,
|
||||
stdio: 'stdin',
|
||||
log: message => progress.log(message),
|
||||
tempDirectories: [],
|
||||
attemptToGracefullyClose: async () => {
|
||||
progress.log('Closing stdin...');
|
||||
launchedProcess.stdin.end();
|
||||
},
|
||||
onExit: (exitCode, signal) => {
|
||||
progress.log(`ffmpeg onkill exitCode=${exitCode} signal=${signal}`);
|
||||
}
|
||||
});
|
||||
launchedProcess.stdin.on('finish', () => {
|
||||
progress.log('ffmpeg finished input.');
|
||||
});
|
||||
launchedProcess.stdin.on('error', () => {
|
||||
progress.log('ffmpeg error.');
|
||||
});
|
||||
this._process = launchedProcess;
|
||||
this._gracefullyClose = gracefullyClose;
|
||||
}
|
||||
writeFrame(frame, timestamp) {
|
||||
(0, _utils.assert)(this._process);
|
||||
if (this._isStopped) return;
|
||||
if (this._lastFrameBuffer) {
|
||||
const durationSec = timestamp - this._lastFrameTimestamp;
|
||||
const repeatCount = Math.max(1, Math.round(fps * durationSec));
|
||||
for (let i = 0; i < repeatCount; ++i) this._frameQueue.push(this._lastFrameBuffer);
|
||||
this._lastWritePromise = this._lastWritePromise.then(() => this._sendFrames());
|
||||
}
|
||||
this._lastFrameBuffer = frame;
|
||||
this._lastFrameTimestamp = timestamp;
|
||||
this._lastWriteTimestamp = (0, _utils.monotonicTime)();
|
||||
}
|
||||
async _sendFrames() {
|
||||
while (this._frameQueue.length) await this._sendFrame(this._frameQueue.shift());
|
||||
}
|
||||
async _sendFrame(frame) {
|
||||
return new Promise(f => this._process.stdin.write(frame, f)).then(error => {
|
||||
if (error) this._progress.log(`ffmpeg failed to write: ${String(error)}`);
|
||||
});
|
||||
}
|
||||
async stop() {
|
||||
if (this._isStopped) return;
|
||||
this.writeFrame(Buffer.from([]), this._lastFrameTimestamp + ((0, _utils.monotonicTime)() - this._lastWriteTimestamp) / 1000);
|
||||
this._isStopped = true;
|
||||
await this._lastWritePromise;
|
||||
await this._gracefullyClose();
|
||||
}
|
||||
}
|
||||
exports.VideoRecorder = VideoRecorder;
|
||||
Reference in New Issue
Block a user