mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-07 04:26:32 +08:00
When filling the iframe that's showing the preview for html requests, we encode the html contents as a data-url and also set the sandbox property on the frame. This avoids remaking the request from within devtools. It's still possible for the iframe to spawn some requests. With this CL we prevent requests by setting the csp for the iframe. Bug: 833147 Change-Id: Id77321465c91fd68e5d6c6c3681e48b39cb32d14 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3557989 Reviewed-by: Danil Somsikov <dsv@chromium.org> Commit-Queue: Philip Pfaffe <pfaffe@chromium.org>
182 lines
5.6 KiB
TypeScript
182 lines
5.6 KiB
TypeScript
// Copyright 2022 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
/**
|
|
* @fileoverview Functions and state to tie error reporting and console output of
|
|
* the browser process and frontend pages together.
|
|
*/
|
|
|
|
/* eslint-disable no-console */
|
|
|
|
// use require here due to
|
|
// https://github.com/evanw/esbuild/issues/587#issuecomment-901397213
|
|
import puppeteer = require('puppeteer');
|
|
|
|
const ALLOWED_ASSERTION_FAILURES = [
|
|
// Failure during shutdown. crbug.com/1145969
|
|
'Session is unregistering, can\'t dispatch pending call to Debugger.setBlackboxPatterns',
|
|
// Failure during shutdown. crbug.com/1199322
|
|
'Session is unregistering, can\'t dispatch pending call to DOM.getDocument',
|
|
// Expected failures in assertion_test.ts
|
|
'expected failure 1',
|
|
'expected failure 2',
|
|
// A failing fetch isn't itself a real error.
|
|
// TODO(https://crbug.com/124534) Remove once those messages are not printed anymore.
|
|
'Failed to load resource: the server responded with a status of 404 (Not Found)',
|
|
// Every letter "typed" into the console can trigger a preview `Runtime.evaluate` call.
|
|
// There is no way for an e2e test to know whether all of them have resolved or if there are
|
|
// still pending calls. If the test finishes too early, the JS context is destroyed and pending
|
|
// evaluations will fail. We ignore these kinds of errors. Tests have to make sure themselves
|
|
// that all assertions and success criteria are met (e.g. autocompletions etc).
|
|
// See: https://crbug.com/1192052
|
|
'Request Runtime.evaluate failed. {"code":-32602,"message":"uniqueContextId not found"}',
|
|
'uniqueContextId not found',
|
|
];
|
|
|
|
const logLevels = {
|
|
log: 'I',
|
|
info: 'I',
|
|
warning: 'I',
|
|
error: 'E',
|
|
exception: 'E',
|
|
assert: 'E',
|
|
};
|
|
|
|
let stdout = '', stderr = '';
|
|
let unhandledRejectionSet = false;
|
|
|
|
export function setupBrowserProcessIO(browser: puppeteer.Browser): void {
|
|
const browserProcess = browser.process();
|
|
if (!browserProcess) {
|
|
throw new Error('browserProcess is unexpectedly not defined.');
|
|
}
|
|
|
|
if (browserProcess.stderr) {
|
|
browserProcess.stderr.setEncoding('utf8');
|
|
browserProcess.stderr.on('data', data => {
|
|
stderr += data;
|
|
});
|
|
}
|
|
|
|
if (browserProcess.stdout) {
|
|
browserProcess.stdout.setEncoding('utf8');
|
|
browserProcess.stdout.on('data', data => {
|
|
stdout += data;
|
|
});
|
|
}
|
|
|
|
if (!unhandledRejectionSet) {
|
|
browserProcess.on('unhandledRejection', error => {
|
|
throw new Error(`Unhandled rejection in Frontend: ${error}`);
|
|
});
|
|
unhandledRejectionSet = true;
|
|
}
|
|
}
|
|
|
|
export function installPageErrorHandlers(page: puppeteer.Page): void {
|
|
page.on('error', error => {
|
|
console.log('STDOUT:');
|
|
console.log(stdout);
|
|
console.log();
|
|
console.log('STDERR:');
|
|
console.log(stderr);
|
|
console.log();
|
|
throw new Error(`Error in Frontend: ${error}`);
|
|
});
|
|
|
|
page.on('pageerror', error => {
|
|
throw new Error(`Page error in Frontend: ${error}`);
|
|
});
|
|
|
|
page.on('console', async msg => {
|
|
const logLevel = logLevels[msg.type() as keyof typeof logLevels] as string;
|
|
if (logLevel) {
|
|
if (logLevel === 'E') {
|
|
let message = `${logLevel}> `;
|
|
if (msg.text() === 'JSHandle@error') {
|
|
const errorHandle: puppeteer.JSHandle<Error> = msg.args()[0];
|
|
message += await errorHandle.evaluate(error => {
|
|
return error.stack;
|
|
});
|
|
await errorHandle.dispose();
|
|
} else {
|
|
message += msg.text();
|
|
for (const frame of msg.stackTrace()) {
|
|
message += '\n' + formatStackFrame(frame);
|
|
}
|
|
}
|
|
if (isExpectedError(msg)) {
|
|
expectedErrors.push(message);
|
|
console.log('(expected) ' + message);
|
|
} else {
|
|
fatalErrors.push(message);
|
|
console.error(message);
|
|
}
|
|
} else {
|
|
console.log(`${logLevel}> ${formatStackFrame(msg.location())}: ${msg.text()}`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function isExpectedError(consoleMessage: puppeteer.ConsoleMessage) {
|
|
if (ALLOWED_ASSERTION_FAILURES.includes(consoleMessage.text())) {
|
|
return true;
|
|
}
|
|
for (const expectation of pendingErrorExpectations) {
|
|
if (expectation.check(consoleMessage)) {
|
|
pendingErrorExpectations.delete(expectation);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export class ErrorExpectation {
|
|
#caught: puppeteer.ConsoleMessage|undefined;
|
|
readonly #msg: string|RegExp;
|
|
constructor(msg: string|RegExp) {
|
|
this.#msg = msg;
|
|
}
|
|
|
|
get caught() {
|
|
return this.#caught;
|
|
}
|
|
|
|
check(consoleMessage: puppeteer.ConsoleMessage) {
|
|
const text = consoleMessage.text();
|
|
const match = (this.#msg instanceof RegExp) ? Boolean(text.match(this.#msg)) : text.includes(this.#msg);
|
|
if (match) {
|
|
this.#caught = consoleMessage;
|
|
}
|
|
return match;
|
|
}
|
|
}
|
|
|
|
export function expectError(msg: string|RegExp) {
|
|
const expectation = new ErrorExpectation(msg);
|
|
pendingErrorExpectations.add(expectation);
|
|
return expectation;
|
|
}
|
|
|
|
function formatStackFrame(stackFrame: puppeteer.ConsoleMessageLocation): string {
|
|
if (!stackFrame || !stackFrame.url) {
|
|
return '<unknown>';
|
|
}
|
|
const filename = stackFrame.url.replace(/^.*\//, '');
|
|
return `${filename}:${stackFrame.lineNumber}:${stackFrame.columnNumber}`;
|
|
}
|
|
|
|
export function dumpCollectedErrors(): void {
|
|
console.log('Expected errors: ' + expectedErrors.length);
|
|
console.log(' Fatal errors: ' + fatalErrors.length);
|
|
if (fatalErrors.length) {
|
|
throw new Error('Fatal errors logged:\n' + fatalErrors.join('\n'));
|
|
}
|
|
}
|
|
|
|
const pendingErrorExpectations = new Set<ErrorExpectation>();
|
|
export const fatalErrors: string[] = [];
|
|
export const expectedErrors: string[] = [];
|