summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/actions/preview.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/debugger/src/actions/preview.js')
-rw-r--r--devtools/client/debugger/src/actions/preview.js231
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,
+ },
+ });
+ };
+}