From 662a021b39a64a5771b47c275ac0d4e0335f35b0 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Thu, 12 Dec 2019 21:48:03 +0000 Subject: [PATCH] Bug 1602489 - Basic eager evaluation support, r=nchevobbe. Differential Revision: https://phabricator.services.mozilla.com/D56393 --HG-- extra : moz-landing-system : lando --- browser/app/profile/firefox.js | 4 + devtools/client/themes/webconsole.css | 4 + devtools/client/webconsole/actions/input.js | 84 ++++++++++++++++--- devtools/client/webconsole/components/App.js | 11 ++- .../components/Input/EagerEvaluation.js | 66 +++++++++++++++ .../webconsole/components/Input/JSTerm.js | 14 ++++ .../webconsole/components/Input/moz.build | 1 + devtools/client/webconsole/constants.js | 3 + .../client/webconsole/reducers/history.js | 27 ++++++ .../client/webconsole/selectors/history.js | 6 ++ devtools/client/webconsole/store.js | 2 + .../webconsole/test/browser/browser.ini | 1 + .../browser_jsterm_eager_evaluation.js | 49 +++++++++++ .../client/webconsole/test/browser/head.js | 25 ++++++ .../webconsole/test/node/mocha-test-setup.js | 1 + devtools/server/actors/webconsole.js | 1 + .../actors/webconsole/eval-with-debugger.js | 72 +++++++++++++++- devtools/shared/fronts/object.js | 4 + devtools/shared/fronts/webconsole.js | 1 + devtools/shared/specs/webconsole.js | 1 + 20 files changed, 363 insertions(+), 14 deletions(-) create mode 100644 devtools/client/webconsole/components/Input/EagerEvaluation.js create mode 100644 devtools/client/webconsole/test/browser/browser_jsterm_eager_evaluation.js diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 674bd3544e58..7c7e08477fde 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -2157,6 +2157,10 @@ pref("devtools.webconsole.filter.netxhr", false); // Webconsole autocomplete preference pref("devtools.webconsole.input.autocomplete",true); +// Set to true to eagerly show the results of webconsole terminal evaluations +// when they don't have side effects. +pref("devtools.webconsole.input.eagerEvaluation", false); + // Browser console filters pref("devtools.browserconsole.filter.error", true); pref("devtools.browserconsole.filter.warn", true); diff --git a/devtools/client/themes/webconsole.css b/devtools/client/themes/webconsole.css index b89fa17f4c81..ef91720573e4 100644 --- a/devtools/client/themes/webconsole.css +++ b/devtools/client/themes/webconsole.css @@ -499,6 +499,10 @@ html #webconsole-notificationbox { fill: var(--theme-icon-checked-color); } +.eager-evaluation-result * { + color: var(--theme-comment) !important; +} + .webconsole-app .cm-auto-complete-shadow-text::after { content: attr(title); color: var(--theme-comment); diff --git a/devtools/client/webconsole/actions/input.js b/devtools/client/webconsole/actions/input.js index 117c5fc8c680..829b72b37bb7 100644 --- a/devtools/client/webconsole/actions/input.js +++ b/devtools/client/webconsole/actions/input.js @@ -5,7 +5,12 @@ "use strict"; const { Utils: WebConsoleUtils } = require("devtools/client/webconsole/utils"); -const { EVALUATE_EXPRESSION } = require("devtools/client/webconsole/constants"); +const { + EVALUATE_EXPRESSION, + SET_TERMINAL_INPUT, + SET_TERMINAL_EAGER_RESULT, +} = require("devtools/client/webconsole/constants"); +const { getAllPrefs } = require("devtools/client/webconsole/selectors/prefs"); loader.lazyServiceGetter( this, @@ -36,6 +41,21 @@ loader.lazyRequireGetter( ); const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers"; +async function getMappedExpression(hud, expression) { + let mapResult; + try { + mapResult = await hud.getMappedExpression(expression); + } catch (e) { + console.warn("Error when calling getMappedExpression", e); + } + + let mapped = null; + if (mapResult) { + ({ expression, mapped } = mapResult); + } + return { expression, mapped }; +} + function evaluateExpression(expression) { return async ({ dispatch, webConsoleUI, hud, client }) => { if (!expression) { @@ -61,16 +81,8 @@ function evaluateExpression(expression) { WebConsoleUtils.usageCount++; - let mappedExpressionRes; - try { - mappedExpressionRes = await hud.getMappedExpression(expression); - } catch (e) { - console.warn("Error when calling getMappedExpression", e); - } - - expression = mappedExpressionRes - ? mappedExpressionRes.expression - : expression; + let mapped; + ({ expression, mapped } = await getMappedExpression(hud, expression)); const { frameActor, webConsoleFront } = webConsoleUI.getFrameActor(); @@ -83,7 +95,7 @@ function evaluateExpression(expression) { frameActor, selectedNodeFront: webConsoleUI.getSelectedNodeFront(), webConsoleFront, - mapped: mappedExpressionRes ? mappedExpressionRes.mapped : null, + mapped, }) .then(onSettled, onSettled); @@ -195,8 +207,56 @@ function setInputValue(value) { }; } +function terminalInputChanged(expression) { + return async ({ dispatch, webConsoleUI, hud, client, getState }) => { + const prefs = getAllPrefs(getState()); + if (!prefs.eagerEvaluation) { + return; + } + + // The server does not support eager evaluation when replaying. + if (hud.currentTarget.isReplayEnabled()) { + return; + } + + const originalExpression = expression; + dispatch({ + type: SET_TERMINAL_INPUT, + expression, + }); + + let mapped; + ({ expression, mapped } = await getMappedExpression(hud, expression)); + + const { frameActor, webConsoleFront } = webConsoleUI.getFrameActor(); + + const response = await client.evaluateJSAsync(expression, { + frameActor, + selectedNodeFront: webConsoleUI.getSelectedNodeFront(), + webConsoleFront, + mapped, + eager: true, + }); + + const result = response.exception || response.result; + + // Don't show syntax errors or undefined results to the user. + if (result.isSyntaxError || result.type == "undefined") { + return; + } + + // eslint-disable-next-line consistent-return + return dispatch({ + type: SET_TERMINAL_EAGER_RESULT, + expression: originalExpression, + result, + }); + }; +} + module.exports = { evaluateExpression, focusInput, setInputValue, + terminalInputChanged, }; diff --git a/devtools/client/webconsole/components/App.js b/devtools/client/webconsole/components/App.js index 9bbf66aa0287..ca8835703871 100644 --- a/devtools/client/webconsole/components/App.js +++ b/devtools/client/webconsole/components/App.js @@ -39,6 +39,9 @@ const JSTerm = createFactory( const ConfirmDialog = createFactory( require("devtools/client/webconsole/components/Input/ConfirmDialog") ); +const EagerEvaluation = createFactory( + require("devtools/client/webconsole/components/Input/EagerEvaluation") +); // And lazy load the ones that may not be used. loader.lazyGetter(this, "SideBar", () => @@ -332,6 +335,10 @@ class App extends Component { }); } + renderEagerEvaluation() { + return EagerEvaluation(); + } + renderReverseSearch() { const { serviceContainer, reverseSearchInitialValue } = this.props; @@ -411,6 +418,7 @@ class App extends Component { const consoleOutput = this.renderConsoleOutput(); const notificationBox = this.renderNotificationBox(); const jsterm = this.renderJsTerm(); + const eager = this.renderEagerEvaluation(); const reverseSearch = this.renderReverseSearch(); const sidebar = this.renderSideBar(); const confirmDialog = this.renderConfirmDialog(); @@ -422,7 +430,8 @@ class App extends Component { { className: "flexible-output-input", key: "in-out-container" }, consoleOutput, notificationBox, - jsterm + jsterm, + eager ), editorMode ? GridElementWidthResizer({ diff --git a/devtools/client/webconsole/components/Input/EagerEvaluation.js b/devtools/client/webconsole/components/Input/EagerEvaluation.js new file mode 100644 index 000000000000..c48179e4d648 --- /dev/null +++ b/devtools/client/webconsole/components/Input/EagerEvaluation.js @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { Component } = require("devtools/client/shared/vendor/react"); +const dom = require("devtools/client/shared/vendor/react-dom-factories"); +const { connect } = require("devtools/client/shared/vendor/react-redux"); + +const { + getTerminalEagerResult, +} = require("devtools/client/webconsole/selectors/history"); + +loader.lazyGetter(this, "REPS", function() { + return require("devtools/client/shared/components/reps/reps").REPS; +}); +loader.lazyGetter(this, "MODE", function() { + return require("devtools/client/shared/components/reps/reps").MODE; +}); +loader.lazyRequireGetter( + this, + "PropTypes", + "devtools/client/shared/vendor/react-prop-types" +); + +/** + * Show the results of evaluating the current terminal text, if possible. + */ +class EagerEvaluation extends Component { + static get propTypes() { + return { + terminalEagerResult: PropTypes.any, + }; + } + + constructor(props) { + super(props); + } + + render() { + const { terminalEagerResult } = this.props; + if (terminalEagerResult !== null) { + return dom.span( + { + className: "devtools-monospace eager-evaluation-result", + }, + REPS.Rep({ + object: terminalEagerResult.getGrip + ? terminalEagerResult.getGrip() + : terminalEagerResult, + mode: MODE.LONG, + }) + ); + } + return null; + } +} + +function mapStateToProps(state) { + return { + terminalEagerResult: getTerminalEagerResult(state), + }; +} + +module.exports = connect(mapStateToProps)(EagerEvaluation); diff --git a/devtools/client/webconsole/components/Input/JSTerm.js b/devtools/client/webconsole/components/Input/JSTerm.js index 35ad8f49f4e3..3996c151e333 100644 --- a/devtools/client/webconsole/components/Input/JSTerm.js +++ b/devtools/client/webconsole/components/Input/JSTerm.js @@ -100,6 +100,8 @@ class JSTerm extends Component { editorToggle: PropTypes.func.isRequired, // Dismiss the editor onboarding UI. editorOnboardingDismiss: PropTypes.func.isRequired, + // Set the last JS input value. + terminalInputChanged: PropTypes.func.isRequired, // Is the input in editor mode. editorMode: PropTypes.bool, editorWidth: PropTypes.number, @@ -126,6 +128,14 @@ class JSTerm extends Component { // The delay should be small enough to be unnoticed by the user. this.autocompleteUpdate = debounce(this.props.autocompleteUpdate, 75, this); + // Updates to the terminal input which can trigger eager evaluations are + // similarly debounced. + this.terminalInputChanged = debounce( + this.props.terminalInputChanged, + 75, + this + ); + // Because the autocomplete has a slight delay (75ms), there can be time where the // codeMirror completion text is out-of-date, which might lead to issue when the user // accept the autocompletion while the update of the completion text is still pending. @@ -618,6 +628,7 @@ class JSTerm extends Component { */ _setValue(newValue = "") { this.lastInputValue = newValue; + this.terminalInputChanged(newValue); if (this.editor) { // In order to get the autocomplete popup to work properly, we need to set the @@ -767,6 +778,7 @@ class JSTerm extends Component { this.autocompleteUpdate(); } this.lastInputValue = value; + this.terminalInputChanged(value); } } @@ -1264,6 +1276,8 @@ function mapDispatchToProps(dispatch) { dispatch(actions.evaluateExpression(expression)), editorToggle: () => dispatch(actions.editorToggle()), editorOnboardingDismiss: () => dispatch(actions.editorOnboardingDismiss()), + terminalInputChanged: value => + dispatch(actions.terminalInputChanged(value)), }; } diff --git a/devtools/client/webconsole/components/Input/moz.build b/devtools/client/webconsole/components/Input/moz.build index 961fa90bc902..09ccd2c4dade 100644 --- a/devtools/client/webconsole/components/Input/moz.build +++ b/devtools/client/webconsole/components/Input/moz.build @@ -5,6 +5,7 @@ DevToolsModules( 'ConfirmDialog.js', + 'EagerEvaluation.js', 'EditorToolbar.js', 'JSTerm.js', 'ReverseSearchInput.css', diff --git a/devtools/client/webconsole/constants.js b/devtools/client/webconsole/constants.js index a9344badfbad..704b1085ee1c 100644 --- a/devtools/client/webconsole/constants.js +++ b/devtools/client/webconsole/constants.js @@ -15,6 +15,8 @@ const actionTypes = { EDITOR_TOGGLE: "EDITOR_TOGGLE", EDITOR_ONBOARDING_DISMISS: "EDITOR_ONBOARDING_DISMISS", EVALUATE_EXPRESSION: "EVALUATE_EXPRESSION", + SET_TERMINAL_INPUT: "SET_TERMINAL_INPUT", + SET_TERMINAL_EAGER_RESULT: "SET_TERMINAL_EAGER_RESULT", FILTER_TEXT_SET: "FILTER_TEXT_SET", FILTER_TOGGLE: "FILTER_TOGGLE", FILTERS_CLEAR: "FILTERS_CLEAR", @@ -84,6 +86,7 @@ const prefs = { // We use the same pref to enable the sidebar on webconsole and browser console. SIDEBAR_TOGGLE: "devtools.webconsole.sidebarToggle", AUTOCOMPLETE: "devtools.webconsole.input.autocomplete", + EAGER_EVALUATION: "devtools.webconsole.input.eagerEvaluation", GROUP_WARNINGS: "devtools.webconsole.groupWarningMessages", BROWSER_TOOLBOX_FISSION: "devtools.browsertoolbox.fission", }, diff --git a/devtools/client/webconsole/reducers/history.js b/devtools/client/webconsole/reducers/history.js index 8f2a96c9bab7..da48b5b03ccd 100644 --- a/devtools/client/webconsole/reducers/history.js +++ b/devtools/client/webconsole/reducers/history.js @@ -15,6 +15,8 @@ const { REVERSE_SEARCH_INPUT_CHANGE, REVERSE_SEARCH_BACK, REVERSE_SEARCH_NEXT, + SET_TERMINAL_INPUT, + SET_TERMINAL_EAGER_RESULT, } = require("devtools/client/webconsole/constants"); /** @@ -39,6 +41,9 @@ function getInitialState() { reverseSearchEnabled: false, currentReverseSearchResults: null, currentReverseSearchResultsPosition: null, + + terminalInput: null, + terminalEagerResult: null, }; } @@ -61,6 +66,10 @@ function history(state = getInitialState(), action, prefsState) { return reverseSearchBack(state); case REVERSE_SEARCH_NEXT: return reverseSearchNext(state); + case SET_TERMINAL_INPUT: + return setTerminalInput(state, action.expression); + case SET_TERMINAL_EAGER_RESULT: + return setTerminalEagerResult(state, action.expression, action.result); } return state; } @@ -217,4 +226,22 @@ function reverseSearchNext(state) { }; } +function setTerminalInput(state, expression) { + return { + ...state, + terminalInput: expression, + terminalEagerResult: null, + }; +} + +function setTerminalEagerResult(state, expression, result) { + if (state.terminalInput == expression) { + return { + ...state, + terminalEagerResult: result, + }; + } + return state; +} + exports.history = history; diff --git a/devtools/client/webconsole/selectors/history.js b/devtools/client/webconsole/selectors/history.js index f4b4fe51c74c..8aacc2160c66 100644 --- a/devtools/client/webconsole/selectors/history.js +++ b/devtools/client/webconsole/selectors/history.js @@ -81,6 +81,11 @@ function getReverseSearchTotalResults(state) { return currentReverseSearchResults.length; } +function getTerminalEagerResult(state) { + const { history } = state; + return history.terminalEagerResult; +} + module.exports = { getHistory, getHistoryEntries, @@ -88,4 +93,5 @@ module.exports = { getReverseSearchResult, getReverseSearchResultPosition, getReverseSearchTotalResults, + getTerminalEagerResult, }; diff --git a/devtools/client/webconsole/store.js b/devtools/client/webconsole/store.js index 7b16e93df3df..994d1d94d027 100644 --- a/devtools/client/webconsole/store.js +++ b/devtools/client/webconsole/store.js @@ -49,6 +49,7 @@ function configureStore(webConsoleUI, options = {}) { options.logLimit || Math.max(getIntPref("devtools.hud.loglimit"), 1); const sidebarToggle = getBoolPref(PREFS.FEATURES.SIDEBAR_TOGGLE); const autocomplete = getBoolPref(PREFS.FEATURES.AUTOCOMPLETE); + const eagerEvaluation = getBoolPref(PREFS.FEATURES.EAGER_EVALUATION); const groupWarnings = getBoolPref(PREFS.FEATURES.GROUP_WARNINGS); const historyCount = getIntPref(PREFS.UI.INPUT_HISTORY_COUNT); @@ -57,6 +58,7 @@ function configureStore(webConsoleUI, options = {}) { logLimit, sidebarToggle, autocomplete, + eagerEvaluation, historyCount, groupWarnings, }), diff --git a/devtools/client/webconsole/test/browser/browser.ini b/devtools/client/webconsole/test/browser/browser.ini index e5b23b11a459..ae733a57072d 100644 --- a/devtools/client/webconsole/test/browser/browser.ini +++ b/devtools/client/webconsole/test/browser/browser.ini @@ -251,6 +251,7 @@ skip-if = (os == "win" && processor == "aarch64") # disabled on aarch64 due to 1 [browser_jsterm_ctrl_key_nav.js] skip-if = os != 'mac' # The tested ctrl+key shortcuts are OSX only [browser_jsterm_document_no_xray.js] +[browser_jsterm_eager_evaluation.js] [browser_jsterm_editor.js] [browser_jsterm_editor_disabled_history_nav_with_keyboard.js] [browser_jsterm_editor_enter.js] diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_eager_evaluation.js b/devtools/client/webconsole/test/browser/browser_jsterm_eager_evaluation.js new file mode 100644 index 000000000000..f463b32ab8fb --- /dev/null +++ b/devtools/client/webconsole/test/browser/browser_jsterm_eager_evaluation.js @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const TEST_URI = `data:text/html;charset=utf-8, + +`; + +// Basic testing of eager evaluation functionality. Expressions which can be +// eagerly evaluated should show their results, and expressions with side +// effects should not perform those side effects. +add_task(async function() { + await pushPref("devtools.webconsole.input.eagerEvaluation", true); + const hud = await openNewTabAndConsole(TEST_URI); + + setInputValue(hud, "x + y"); + await waitForEagerEvaluationResult(hud, "7"); + + setInputValue(hud, "x + y + undefined"); + await waitForEagerEvaluationResult(hud, "NaN"); + + setInputValue(hud, "-x / 0"); + await waitForEagerEvaluationResult(hud, "-Infinity"); + + setInputValue(hud, "x = 10"); + await waitForNoEagerEvaluationResult(hud); + + setInputValue(hud, "x + 1"); + await waitForEagerEvaluationResult(hud, "4"); + + setInputValue(hud, "foo()"); + await waitForNoEagerEvaluationResult(hud); + + setInputValue(hud, "x + 2"); + await waitForEagerEvaluationResult(hud, "5"); + + setInputValue(hud, "x +"); + await waitForNoEagerEvaluationResult(hud); + + setInputValue(hud, "x + z"); + await waitForEagerEvaluationResult(hud, /ReferenceError/); +}); diff --git a/devtools/client/webconsole/test/browser/head.js b/devtools/client/webconsole/test/browser/head.js index a108eb4462c4..8e644a2f46b0 100644 --- a/devtools/client/webconsole/test/browser/head.js +++ b/devtools/client/webconsole/test/browser/head.js @@ -1154,6 +1154,31 @@ function isReverseSearchInputFocused(hud) { return document.activeElement == reverseSearchInput && documentIsFocused; } +async function waitForEagerEvaluationResult(hud, text) { + await waitUntil(() => { + const elem = hud.ui.outputNode.querySelector(".eager-evaluation-result"); + if (elem) { + if (text instanceof RegExp) { + return text.test(elem.innerText); + } + return elem.innerText == text; + } + return false; + }); + ok(true, `Got eager evaluation result ${text}`); +} + +// This just makes sure the eager evaluation result disappears. This will pass +// even for inputs which eventually have a result because nothing will be shown +// while the evaluation happens. Waiting here does make sure that a previous +// input was processed and sent down to the server for evaluating. +async function waitForNoEagerEvaluationResult(hud) { + await waitUntil(() => { + return !hud.ui.outputNode.querySelector(".eager-evaluation-result"); + }); + ok(true, `Eager evaluation result disappeared`); +} + /** * Selects a node in the inspector. * diff --git a/devtools/client/webconsole/test/node/mocha-test-setup.js b/devtools/client/webconsole/test/node/mocha-test-setup.js index 9af0821c3f8b..3969c36f0738 100644 --- a/devtools/client/webconsole/test/node/mocha-test-setup.js +++ b/devtools/client/webconsole/test/node/mocha-test-setup.js @@ -27,6 +27,7 @@ pref("devtools.webconsole.sidebarToggle", true); pref("devtools.webconsole.groupWarningMessages", false); pref("devtools.webconsole.input.editor", false); pref("devtools.webconsole.input.autocomplete", true); +pref("devtools.webconsole.input.eagerEvaluation", false); pref("devtools.browserconsole.contentMessages", true); pref("devtools.webconsole.input.editorWidth", 800); pref("devtools.webconsole.input.editorOnboarding", true); diff --git a/devtools/server/actors/webconsole.js b/devtools/server/actors/webconsole.js index 961c2430270c..ba42e57e09bf 100644 --- a/devtools/server/actors/webconsole.js +++ b/devtools/server/actors/webconsole.js @@ -1145,6 +1145,7 @@ const WebConsoleActor = ActorClassWithSpec(webconsoleSpec, { url: request.url, selectedNodeActor: request.selectedNodeActor, selectedObjectActor: request.selectedObjectActor, + eager: request.eager, }; const { mapped } = request; diff --git a/devtools/server/actors/webconsole/eval-with-debugger.js b/devtools/server/actors/webconsole/eval-with-debugger.js index 92ac3a4286e4..190f4128b8dc 100644 --- a/devtools/server/actors/webconsole/eval-with-debugger.js +++ b/devtools/server/actors/webconsole/eval-with-debugger.js @@ -105,10 +105,15 @@ function isObject(value) { exports.evalWithDebugger = function(string, options = {}, webConsole) { const evalString = getEvalInput(string); const { frame, dbg } = getFrameDbg(options, webConsole); + // early return for replay if (dbg.replaying) { + if (options.eager) { + throw new Error("Eager evaluations are not supported while replaying"); + } return evalReplay(frame, dbg, evalString); } + const { dbgWindow, bindSelf } = getDbgWindow(options, dbg, webConsole); const helpers = getHelpers(dbgWindow, options, webConsole); const { bindings, helperCache } = bindCommands( @@ -126,6 +131,11 @@ exports.evalWithDebugger = function(string, options = {}, webConsole) { updateConsoleInputEvaluation(dbg, dbgWindow, webConsole); + let sideEffectData = null; + if (options.eager) { + sideEffectData = preventSideEffects(dbg); + } + const result = getEvalResult( evalString, evalOptions, @@ -134,6 +144,10 @@ exports.evalWithDebugger = function(string, options = {}, webConsole) { dbgWindow ); + if (options.eager) { + allowSideEffects(dbg, sideEffectData); + } + const { helperResult } = helpers; // Clean up helpers helpers and bindings @@ -163,7 +177,7 @@ function getEvalResult(string, evalOptions, bindings, frame, dbgWindow) { // Attempt to initialize any declarations found in the evaluated string // since they may now be stuck in an "initializing" state due to the // error. Already-initialized bindings will be ignored. - if ("throw" in result) { + if (result && "throw" in result) { parseErrorOutput(dbgWindow, string); } return result; @@ -236,6 +250,62 @@ function parseErrorOutput(dbgWindow, string) { } } +function preventSideEffects(dbg) { + if (dbg.onEnterFrame || dbg.onNativeCall) { + throw new Error("Debugger has hook installed"); + } + + const data = { + executedScripts: new Set(), + debuggees: dbg.getDebuggees(), + + handler: { + hit: () => null, + }, + }; + + dbg.addAllGlobalsAsDebuggees(); + + dbg.onEnterFrame = frame => { + const script = frame.script; + + if (data.executedScripts.has(script)) { + return; + } + data.executedScripts.add(script); + + const offsets = script.getEffectfulOffsets(); + for (const offset of offsets) { + script.setBreakpoint(offset, data.handler); + } + }; + + dbg.onNativeCall = (callee, reason) => { + if (reason == "get") { + // Native getters are never considered effectful. + return undefined; + } + return null; + }; + + return data; +} + +function allowSideEffects(dbg, data) { + for (const script of data.executedScripts) { + script.clearBreakpoint(data.handler); + } + + for (const global of dbg.getDebuggees()) { + if (!data.debuggees.includes(global)) { + dbg.removeDebuggee(global); + } + } + + dbg.onEnterFrame = undefined; + dbg.onNativeCall = undefined; +} + function updateConsoleInputEvaluation(dbg, dbgWindow, webConsole) { // Adopt webConsole._lastConsoleInputEvaluation value in the new debugger, // to prevent "Debugger.Object belongs to a different Debugger" exceptions diff --git a/devtools/shared/fronts/object.js b/devtools/shared/fronts/object.js index 09d1b172383a..3bf685fbce5a 100644 --- a/devtools/shared/fronts/object.js +++ b/devtools/shared/fronts/object.js @@ -273,6 +273,10 @@ class ObjectFront extends FrontClassWithSpec(objectSpec) { return response; } + + get isSyntaxError() { + return this._grip.preview && this._grip.preview.name == "SyntaxError"; + } } /** diff --git a/devtools/shared/fronts/webconsole.js b/devtools/shared/fronts/webconsole.js index e54bb68618c0..bcc961170dd5 100644 --- a/devtools/shared/fronts/webconsole.js +++ b/devtools/shared/fronts/webconsole.js @@ -200,6 +200,7 @@ class WebConsoleFront extends FrontClassWithSpec(webconsoleSpec) { selectedNodeActor: opts.selectedNodeActor, selectedObjectActor: opts.selectedObjectActor, mapped: opts.mapped, + eager: opts.eager, }; const { resultID } = await super.evaluateJSAsync(options); diff --git a/devtools/shared/specs/webconsole.js b/devtools/shared/specs/webconsole.js index 5a170395d221..aead280d2dc7 100644 --- a/devtools/shared/specs/webconsole.js +++ b/devtools/shared/specs/webconsole.js @@ -153,6 +153,7 @@ const webconsoleSpecPrototype = { selectedNodeActor: Option(0, "string"), selectedObjectActor: Option(0, "string"), mapped: Option(0, "nullable:json"), + eager: Option(0, "nullable:boolean"), }, response: RetVal("console.evaluatejsasync"), },