diff options
Diffstat (limited to 'devtools/client/debugger/src/actions/preview.js')
-rw-r--r-- | devtools/client/debugger/src/actions/preview.js | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/actions/preview.js b/devtools/client/debugger/src/actions/preview.js new file mode 100644 index 0000000000..b07ae69d1f --- /dev/null +++ b/devtools/client/debugger/src/actions/preview.js @@ -0,0 +1,231 @@ +/* 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/>. */ + +// @flow + +import { isConsole } from "../utils/preview"; +import { findBestMatchExpression } from "../utils/ast"; +import { getGrip, getFront } from "../utils/evaluation-result"; +import { getExpressionFromCoords } from "../utils/editor/get-expression"; +import { isOriginal } from "../utils/source"; +import { isNodeTest } from "../utils/environment"; + +import { + getPreview, + isLineInScope, + isSelectedFrameVisible, + getSelectedSource, + getSelectedFrame, + getSymbols, + getCurrentThread, + getPreviewCount, + getSelectedException, +} from "../selectors"; + +import { getMappedExpression } from "./expressions"; + +import type { Action, ThunkArgs } from "./types"; +import type { Position, Context } from "../types"; +import type { AstLocation } from "../workers/parser"; + +function findExpressionMatch(state, codeMirror: any, tokenPos: Object) { + const source = getSelectedSource(state); + if (!source) { + return; + } + + const symbols = getSymbols(state, source); + + let match; + if (!symbols || symbols.loading) { + match = getExpressionFromCoords(codeMirror, tokenPos); + } else { + match = findBestMatchExpression(symbols, tokenPos); + } + return match; +} + +export function updatePreview( + cx: Context, + target: HTMLElement, + tokenPos: Object, + codeMirror: any +) { + return ({ dispatch, getState, client, sourceMaps }: ThunkArgs) => { + const cursorPos = target.getBoundingClientRect(); + + if ( + !isSelectedFrameVisible(getState()) || + !isLineInScope(getState(), tokenPos.line) + ) { + return; + } + + const match = findExpressionMatch(getState(), codeMirror, tokenPos); + if (!match) { + return; + } + + const { expression, location } = match; + + if (isConsole(expression)) { + return; + } + + dispatch(setPreview(cx, expression, location, tokenPos, cursorPos, target)); + }; +} + +export function setPreview( + cx: Context, + expression: string, + location: AstLocation, + tokenPos: Position, + cursorPos: ClientRect, + target: HTMLElement +) { + return async ({ dispatch, getState, client, sourceMaps }: ThunkArgs) => { + dispatch({ type: "START_PREVIEW" }); + const previewCount = getPreviewCount(getState()); + if (getPreview(getState())) { + dispatch(clearPreview(cx)); + } + + const source = getSelectedSource(getState()); + if (!source) { + return; + } + + const thread = getCurrentThread(getState()); + const selectedFrame = getSelectedFrame(getState(), thread); + + if (location && isOriginal(source)) { + const mapResult = await dispatch(getMappedExpression(expression)); + if (mapResult) { + expression = mapResult.expression; + } + } + + if (!selectedFrame) { + return; + } + + const { result } = await client.evaluateInFrame(expression, { + frameId: selectedFrame.id, + thread, + }); + + const resultGrip = getGrip(result); + + // Error case occurs for a token that follows an errored evaluation + // https://github.com/firefox-devtools/debugger/pull/8056 + // Accommodating for null allows us to show preview for falsy values + // line "", false, null, Nan, and more + if (resultGrip === null) { + return; + } + + // Handle cases where the result is invisible to the debugger + // and not possible to preview. Bug 1548256 + if ( + resultGrip && + resultGrip.class && + typeof resultGrip.class === "string" && + resultGrip.class.includes("InvisibleToDebugger") + ) { + return; + } + + const root = { + name: expression, + path: expression, + contents: { + value: resultGrip, + front: getFront(result), + }, + }; + const properties = await client.loadObjectProperties(root); + + // The first time a popup is rendered, the mouse should be hovered + // on the token. If it happens to be hovered on whitespace, it should + // not render anything + if (!target.matches(":hover") && !isNodeTest()) { + return; + } + + // Don't finish dispatching if another setPreview was started + if (previewCount != getPreviewCount(getState())) { + return; + } + + dispatch({ + type: "SET_PREVIEW", + cx, + value: { + expression, + resultGrip, + properties, + root, + location, + tokenPos, + cursorPos, + target, + }, + }); + }; +} + +export function clearPreview(cx: Context) { + return ({ dispatch, getState, client }: ThunkArgs) => { + const currentSelection = getPreview(getState()); + if (!currentSelection) { + return; + } + + return dispatch( + ({ + type: "CLEAR_PREVIEW", + cx, + }: Action) + ); + }; +} + +export function setExceptionPreview( + cx: Context, + target: HTMLElement, + tokenPos: Object, + codeMirror: any +) { + return async ({ dispatch, getState }: ThunkArgs) => { + const cursorPos = target.getBoundingClientRect(); + + const match = findExpressionMatch(getState(), codeMirror, tokenPos); + if (!match) { + return; + } + + const tokenColumnStart = match.location.start.column + 1; + const exception = getSelectedException( + getState(), + tokenPos.line, + tokenColumnStart + ); + if (!exception) { + return; + } + + dispatch({ + type: "SET_PREVIEW", + cx, + value: { + exception, + location: match.location, + tokenPos, + cursorPos, + target, + }, + }); + }; +} |