| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as SDK from '../../../../core/sdk/sdk.js'; |
| import * as Formatter from '../../../../models/formatter/formatter.js'; |
| import * as SourceMapScopes from '../../../../models/source_map_scopes/source_map_scopes.js'; |
| import * as Acorn from '../../../../third_party/acorn/acorn.js'; |
| import * as UI from '../../legacy.js'; |
| |
| import {RemoteObjectPreviewFormatter} from './RemoteObjectPreviewFormatter.js'; |
| |
| export class JavaScriptREPL { |
| static wrapObjectLiteral(code: string): string { |
| // Only parenthesize what appears to be an object literal. |
| const result = /^\s*\{\s*(.*)\s*\}[\s;]*$/.exec(code); |
| if (result === null) { |
| return code; |
| } |
| const [, body] = result; |
| let level = 0; |
| for (const c of body) { |
| if (c === '{') { |
| level++; |
| } else if (c === '}' && --level < 0) { |
| return code; |
| } |
| } |
| |
| const parse = (expression: string): void => void Acorn.parse( |
| expression, |
| {ecmaVersion: 2022, allowAwaitOutsideFunction: true, ranges: false, allowReturnOutsideFunction: true}); |
| try { |
| // Check if the body can be interpreted as an expression. |
| parse('return {' + body + '};'); |
| |
| // No syntax error! Does it work parenthesized? |
| const wrappedCode = '({' + body + '})'; |
| parse(wrappedCode); |
| |
| return wrappedCode; |
| } catch { |
| return code; |
| } |
| } |
| |
| static async evaluate( |
| text: string, executionContext: SDK.RuntimeModel.ExecutionContext, throwOnSideEffect: boolean, replMode: boolean, |
| timeout?: number, objectGroup?: string, awaitPromise = false, |
| silent = false): Promise<SDK.RuntimeModel.EvaluationResult|null> { |
| const isTextLong = text.length > maxLengthForEvaluation; |
| if (!text || (throwOnSideEffect && isTextLong)) { |
| return null; |
| } |
| |
| let expression = text; |
| const callFrame = executionContext.debuggerModel.selectedCallFrame(); |
| if (callFrame?.script.isJavaScript()) { |
| const nameMap = await SourceMapScopes.NamesResolver.allVariablesInCallFrame(callFrame); |
| try { |
| expression = |
| await Formatter.FormatterWorkerPool.formatterWorkerPool().javaScriptSubstitute(expression, nameMap); |
| } catch { |
| } |
| } |
| |
| expression = JavaScriptREPL.wrapObjectLiteral(expression); |
| const options = { |
| expression, |
| generatePreview: true, |
| includeCommandLineAPI: true, |
| throwOnSideEffect, |
| timeout, |
| objectGroup, |
| disableBreaks: true, |
| replMode, |
| silent, |
| }; |
| return await executionContext.evaluate(options, false /* userGesture */, awaitPromise); |
| } |
| |
| static async evaluateAndBuildPreview( |
| text: string, throwOnSideEffect: boolean, replMode: boolean, timeout?: number, allowErrors?: boolean, |
| objectGroup?: string, awaitPromise = false, silent = false): Promise<{ |
| preview: DocumentFragment, |
| result: SDK.RuntimeModel.EvaluationResult|null, |
| }> { |
| const executionContext = UI.Context.Context.instance().flavor(SDK.RuntimeModel.ExecutionContext); |
| if (!executionContext) { |
| return {preview: document.createDocumentFragment(), result: null}; |
| } |
| |
| const result = await JavaScriptREPL.evaluate( |
| text, executionContext, throwOnSideEffect, replMode, timeout, objectGroup, awaitPromise, silent); |
| if (!result) { |
| return {preview: document.createDocumentFragment(), result: null}; |
| } |
| |
| const formatter = new RemoteObjectPreviewFormatter(); |
| const preview = formatter.renderEvaluationResultPreviewFragment(result, allowErrors); |
| return {preview, result}; |
| } |
| } |
| |
| const maxLengthForEvaluation = 2000; |