summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/actions/sources/loadSourceText.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/debugger/src/actions/sources/loadSourceText.js')
-rw-r--r--devtools/client/debugger/src/actions/sources/loadSourceText.js256
1 files changed, 256 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/actions/sources/loadSourceText.js b/devtools/client/debugger/src/actions/sources/loadSourceText.js
new file mode 100644
index 0000000000..8210b07a97
--- /dev/null
+++ b/devtools/client/debugger/src/actions/sources/loadSourceText.js
@@ -0,0 +1,256 @@
+/* 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/>. */
+
+import { PROMISE } from "../utils/middleware/promise";
+import {
+ getSourceTextContent,
+ getSettledSourceTextContent,
+ getGeneratedSource,
+ getSourcesEpoch,
+ getBreakpointsForSource,
+ getSourceActorsForSource,
+ getFirstSourceActorForGeneratedSource,
+} from "../../selectors";
+import { addBreakpoint } from "../breakpoints";
+
+import { prettyPrintSource } from "./prettyPrint";
+import { isFulfilled, fulfilled } from "../../utils/async-value";
+
+import { isPretty } from "../../utils/source";
+import { createLocation } from "../../utils/location";
+import { memoizeableAction } from "../../utils/memoizableAction";
+
+async function loadGeneratedSource(sourceActor, { client }) {
+ // If no source actor can be found then the text for the
+ // source cannot be loaded.
+ if (!sourceActor) {
+ throw new Error("Source actor is null or not defined");
+ }
+
+ let response;
+ try {
+ response = await client.sourceContents(sourceActor);
+ } catch (e) {
+ throw new Error(`sourceContents failed: ${e}`);
+ }
+
+ return {
+ text: response.source,
+ contentType: response.contentType || "text/javascript",
+ };
+}
+
+async function loadOriginalSource(
+ source,
+ { getState, client, sourceMapLoader, prettyPrintWorker }
+) {
+ if (isPretty(source)) {
+ const generatedSource = getGeneratedSource(getState(), source);
+ if (!generatedSource) {
+ throw new Error("Unable to find minified original.");
+ }
+
+ const content = getSettledSourceTextContent(
+ getState(),
+ createLocation({
+ source: generatedSource,
+ })
+ );
+
+ return prettyPrintSource(
+ sourceMapLoader,
+ prettyPrintWorker,
+ generatedSource,
+ content,
+ getSourceActorsForSource(getState(), generatedSource.id)
+ );
+ }
+
+ const result = await sourceMapLoader.getOriginalSourceText(source.id);
+ if (!result) {
+ // The way we currently try to load and select a pending
+ // selected location, it is possible that we will try to fetch the
+ // original source text right after the source map has been cleared
+ // after a navigation event.
+ throw new Error("Original source text unavailable");
+ }
+ return result;
+}
+
+async function loadGeneratedSourceTextPromise(cx, sourceActor, thunkArgs) {
+ const { dispatch, getState } = thunkArgs;
+ const epoch = getSourcesEpoch(getState());
+
+ await dispatch({
+ type: "LOAD_GENERATED_SOURCE_TEXT",
+ sourceActorId: sourceActor.actor,
+ epoch,
+ [PROMISE]: loadGeneratedSource(sourceActor, thunkArgs),
+ });
+
+ await onSourceTextContentAvailable(
+ cx,
+ sourceActor.sourceObject,
+ sourceActor,
+ thunkArgs
+ );
+}
+
+async function loadOriginalSourceTextPromise(cx, source, thunkArgs) {
+ const { dispatch, getState } = thunkArgs;
+ const epoch = getSourcesEpoch(getState());
+ await dispatch({
+ type: "LOAD_ORIGINAL_SOURCE_TEXT",
+ sourceId: source.id,
+ epoch,
+ [PROMISE]: loadOriginalSource(source, thunkArgs),
+ });
+
+ await onSourceTextContentAvailable(cx, source, null, thunkArgs);
+}
+
+/**
+ * Function called everytime a new original or generated source gets its text content
+ * fetched from the server and registered in the reducer.
+ *
+ * @param {Object} cx
+ * @param {Object} source
+ * @param {Object} sourceActor (optional)
+ * If this is a generated source, we expect a precise source actor.
+ * @param {Object} thunkArgs
+ */
+async function onSourceTextContentAvailable(
+ cx,
+ source,
+ sourceActor,
+ { dispatch, getState, parserWorker }
+) {
+ const location = createLocation({
+ source,
+ sourceActor,
+ });
+ const content = getSettledSourceTextContent(getState(), location);
+ if (!content) {
+ return;
+ }
+
+ if (parserWorker.isLocationSupported(location)) {
+ parserWorker.setSource(
+ source.id,
+ isFulfilled(content)
+ ? content.value
+ : { type: "text", value: "", contentType: undefined }
+ );
+ }
+
+ // Update the text in any breakpoints for this source by re-adding them.
+ const breakpoints = getBreakpointsForSource(getState(), source.id);
+ for (const breakpoint of breakpoints) {
+ await dispatch(
+ addBreakpoint(
+ cx,
+ breakpoint.location,
+ breakpoint.options,
+ breakpoint.disabled
+ )
+ );
+ }
+}
+
+/**
+ * Loads the source text for the generated source based of the source actor
+ * @param {Object} sourceActor
+ * There can be more than one source actor per source
+ * so the source actor needs to be specified. This is
+ * required for generated sources but will be null for
+ * original/pretty printed sources.
+ */
+export const loadGeneratedSourceText = memoizeableAction(
+ "loadGeneratedSourceText",
+ {
+ getValue: ({ sourceActor }, { getState }) => {
+ if (!sourceActor) {
+ return null;
+ }
+
+ const sourceTextContent = getSourceTextContent(
+ getState(),
+ createLocation({
+ source: sourceActor.sourceObject,
+ sourceActor,
+ })
+ );
+
+ if (!sourceTextContent || sourceTextContent.state === "pending") {
+ return sourceTextContent;
+ }
+
+ // This currently swallows source-load-failure since we return fulfilled
+ // here when content.state === "rejected". In an ideal world we should
+ // propagate that error upward.
+ return fulfilled(sourceTextContent);
+ },
+ createKey: ({ sourceActor }, { getState }) => {
+ const epoch = getSourcesEpoch(getState());
+ return `${epoch}:${sourceActor.actor}`;
+ },
+ action: ({ cx, sourceActor }, thunkArgs) =>
+ loadGeneratedSourceTextPromise(cx, sourceActor, thunkArgs),
+ }
+);
+
+/**
+ * Loads the source text for an original source and source actor
+ * @param {Object} source
+ * The original source to load the source text
+ */
+export const loadOriginalSourceText = memoizeableAction(
+ "loadOriginalSourceText",
+ {
+ getValue: ({ source }, { getState }) => {
+ if (!source) {
+ return null;
+ }
+
+ const sourceTextContent = getSourceTextContent(
+ getState(),
+ createLocation({
+ source,
+ })
+ );
+ if (!sourceTextContent || sourceTextContent.state === "pending") {
+ return sourceTextContent;
+ }
+
+ // This currently swallows source-load-failure since we return fulfilled
+ // here when content.state === "rejected". In an ideal world we should
+ // propagate that error upward.
+ return fulfilled(sourceTextContent);
+ },
+ createKey: ({ source }, { getState }) => {
+ const epoch = getSourcesEpoch(getState());
+ return `${epoch}:${source.id}`;
+ },
+ action: ({ cx, source }, thunkArgs) =>
+ loadOriginalSourceTextPromise(cx, source, thunkArgs),
+ }
+);
+
+export function loadSourceText(cx, source, sourceActor) {
+ return async ({ dispatch, getState }) => {
+ if (!source) {
+ return null;
+ }
+ if (source.isOriginal) {
+ return dispatch(loadOriginalSourceText({ cx, source }));
+ }
+ if (!sourceActor) {
+ sourceActor = getFirstSourceActorForGeneratedSource(
+ getState(),
+ source.id
+ );
+ }
+ return dispatch(loadGeneratedSourceText({ cx, sourceActor }));
+ };
+}