Files
react-native-devtools-frontend/front_end/object_ui/JavaScriptREPL.js
T
Erik Luo 66bcf105f5 DevTools: iterate on console pin interaction UX
- Console prompt is cleared when text is pinned
- Pins persist across refresh
- Newly created pin runs with side effects until text differs
  or new expression is committed (on blur)
- Ctrl-Enter moves focus from Pin to Console prompt
- Added command menu option to create new pin
- Pressing Tab indents, moves focus iff pin is empty
  (same logic as Console prompt)
- Pins producing errors show error message on red background
- Pin text is trimmed upon blur/commit

Bug: 849875
Change-Id: I641abb6b5e19a84c17b1a6cd3e50ca7b459f2015
Reviewed-on: https://chromium-review.googlesource.com/1141096
Commit-Queue: Erik Luo <luoe@chromium.org>
Reviewed-by: Andrey Lushnikov <lushnikov@chromium.org>
Reviewed-by: Joel Einbinder <einbinder@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#586108}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 811a4303b8b973d61567d61372ec8e1d692037c0
2018-08-25 02:15:37 +00:00

106 lines
3.6 KiB
JavaScript

// Copyright 2018 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.
ObjectUI.JavaScriptREPL = class {
/**
* @param {string} code
* @return {string}
*/
static wrapObjectLiteral(code) {
// Only parenthesize what appears to be an object literal.
if (!(/^\s*\{/.test(code) && /\}\s*$/.test(code)))
return code;
const parse = (async () => 0).constructor;
try {
// Check if the code can be interpreted as an expression.
parse('return ' + code + ';');
// No syntax error! Does it work parenthesized?
const wrappedCode = '(' + code + ')';
parse(wrappedCode);
return wrappedCode;
} catch (e) {
return code;
}
}
/**
* @param {string} text
* @return {!Promise<!{text: string, preprocessed: boolean}>}
*/
static async preprocessExpression(text) {
text = ObjectUI.JavaScriptREPL.wrapObjectLiteral(text);
let preprocessed = false;
if (text.indexOf('await') !== -1) {
const preprocessedText = await Formatter.formatterWorkerPool().preprocessTopLevelAwaitExpressions(text);
preprocessed = !!preprocessedText;
text = preprocessedText || text;
}
return {text, preprocessed};
}
/**
* @param {string} text
* @param {boolean} throwOnSideEffect
* @param {number=} timeout
* @param {boolean=} allowErrors
* @return {!Promise<!{preview: !DocumentFragment, result: ?SDK.RuntimeModel.EvaluationResult}>}
*/
static async evaluateAndBuildPreview(text, throwOnSideEffect, timeout, allowErrors) {
const executionContext = UI.context.flavor(SDK.ExecutionContext);
const isTextLong = text.length > ObjectUI.JavaScriptREPL._MaxLengthForEvaluation;
if (!text || !executionContext || (throwOnSideEffect && isTextLong))
return {preview: createDocumentFragment(), result: null};
const wrappedResult = await ObjectUI.JavaScriptREPL.preprocessExpression(text);
const options = {
expression: wrappedResult.text,
generatePreview: true,
includeCommandLineAPI: true,
throwOnSideEffect: throwOnSideEffect,
timeout: timeout
};
const result = await executionContext.evaluate(
options, false /* userGesture */, wrappedResult.preprocessed /* awaitPromise */);
const preview = ObjectUI.JavaScriptREPL._buildEvaluationPreview(result, allowErrors);
return {preview, result};
}
/**
* @param {!SDK.RuntimeModel.EvaluationResult} result
* @param {boolean=} allowErrors
* @return {!DocumentFragment}
*/
static _buildEvaluationPreview(result, allowErrors) {
const fragment = createDocumentFragment();
if (result.error)
return fragment;
if (result.exceptionDetails && result.exceptionDetails.exception && result.exceptionDetails.exception.description) {
const exception = result.exceptionDetails.exception.description;
if (exception.startsWith('TypeError: ') || allowErrors)
fragment.createChild('span').textContent = result.exceptionDetails.text + ' ' + exception;
return fragment;
}
const formatter = new ObjectUI.RemoteObjectPreviewFormatter();
const {preview, type, subtype, description} = result.object;
if (preview && type === 'object' && subtype !== 'node') {
formatter.appendObjectPreview(fragment, preview, false /* isEntry */);
} else {
const nonObjectPreview = formatter.renderPropertyPreview(type, subtype, description.trimEnd(400));
fragment.appendChild(nonObjectPreview);
}
return fragment;
}
};
/**
* @const
* @type {number}
*/
ObjectUI.JavaScriptREPL._MaxLengthForEvaluation = 2000;