summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/selectors/sources.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/debugger/src/selectors/sources.js')
-rw-r--r--devtools/client/debugger/src/selectors/sources.js333
1 files changed, 333 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/selectors/sources.js b/devtools/client/debugger/src/selectors/sources.js
new file mode 100644
index 0000000000..5c74178867
--- /dev/null
+++ b/devtools/client/debugger/src/selectors/sources.js
@@ -0,0 +1,333 @@
+/* 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 { createSelector } from "reselect";
+import { shallowEqual } from "../utils/shallow-equal";
+
+import {
+ getPrettySourceURL,
+ isGenerated,
+ isPretty,
+ isJavaScript,
+} from "../utils/source";
+
+import { findPosition } from "../utils/breakpoint/breakpointPositions";
+import { isFulfilled } from "../utils/async-value";
+
+import { originalToGeneratedId } from "devtools/client/shared/source-map-loader/index";
+import { prefs } from "../utils/prefs";
+
+import {
+ hasSourceActor,
+ getSourceActor,
+ getBreakableLinesForSourceActors,
+} from "./source-actors";
+import { getSourceTextContent } from "./sources-content";
+
+export function hasSource(state, id) {
+ return state.sources.sources.has(id);
+}
+
+export function getSource(state, id) {
+ return state.sources.sources.get(id);
+}
+
+export function getSourceFromId(state, id) {
+ const source = getSource(state, id);
+ if (!source) {
+ console.warn(`source ${id} does not exist`);
+ }
+ return source;
+}
+
+export function getLocationSource(state, location) {
+ return getSource(state, location.sourceId);
+}
+
+export function getSourceByActorId(state, actorId) {
+ if (!hasSourceActor(state, actorId)) {
+ return null;
+ }
+
+ return getSource(state, getSourceActor(state, actorId).source);
+}
+
+function getSourcesByURL(state, url) {
+ const urls = getUrls(state);
+ if (!url || !urls[url]) {
+ return [];
+ }
+ return urls[url].map(id => getSource(state, id));
+}
+
+export function getSourceByURL(state, url) {
+ const foundSources = getSourcesByURL(state, url);
+ return foundSources ? foundSources[0] : null;
+}
+
+// This is used by tabs selectors
+export function getSpecificSourceByURL(state, url, isOriginal) {
+ const foundSources = getSourcesByURL(state, url);
+ if (foundSources) {
+ return foundSources.find(source => source.isOriginal == isOriginal);
+ }
+ return null;
+}
+
+function getOriginalSourceByURL(state, url) {
+ return getSpecificSourceByURL(state, url, true);
+}
+
+export function getGeneratedSourceByURL(state, url) {
+ return getSpecificSourceByURL(state, url, false);
+}
+
+export function getGeneratedSource(state, source) {
+ if (!source) {
+ return null;
+ }
+
+ if (isGenerated(source)) {
+ return source;
+ }
+
+ return getSourceFromId(state, originalToGeneratedId(source.id));
+}
+
+export function getPendingSelectedLocation(state) {
+ return state.sources.pendingSelectedLocation;
+}
+
+export function getPrettySource(state, id) {
+ if (!id) {
+ return null;
+ }
+
+ const source = getSource(state, id);
+ if (!source) {
+ return null;
+ }
+
+ return getOriginalSourceByURL(state, getPrettySourceURL(source.url));
+}
+
+export function hasPrettySource(state, id) {
+ return !!getPrettySource(state, id);
+}
+
+// This is only used externaly by tabs and breakpointSources selectors
+export function getSourcesMap(state) {
+ return state.sources.sources;
+}
+
+function getUrls(state) {
+ return state.sources.urls;
+}
+
+export const getSourceList = createSelector(
+ getSourcesMap,
+ sourcesMap => {
+ return [...sourcesMap.values()];
+ },
+ { equalityCheck: shallowEqual, resultEqualityCheck: shallowEqual }
+);
+
+// This is only used by tests
+export function getSourceCount(state) {
+ return getSourcesMap(state).size;
+}
+
+export function getSelectedLocation(state) {
+ return state.sources.selectedLocation;
+}
+
+export const getSelectedSource = createSelector(
+ getSelectedLocation,
+ getSourcesMap,
+ (selectedLocation, sourcesMap) => {
+ if (!selectedLocation) {
+ return undefined;
+ }
+
+ return sourcesMap.get(selectedLocation.sourceId);
+ }
+);
+
+// This is used by tests and pause reducers
+export function getSelectedSourceId(state) {
+ const source = getSelectedSource(state);
+ return source?.id;
+}
+
+/**
+ * Gets the first source actor for the source and/or thread
+ * provided.
+ *
+ * @param {Object} state
+ * @param {String} sourceId
+ * The source used
+ * @param {String} [threadId]
+ * The thread to check, this is optional.
+ * @param {Object} sourceActor
+ *
+ */
+export function getFirstSourceActorForGeneratedSource(
+ state,
+ sourceId,
+ threadId
+) {
+ let source = getSource(state, sourceId);
+ if (source.isOriginal) {
+ source = getSource(state, originalToGeneratedId(source.id));
+ }
+ let actorsInfo = state.sources.actors[source.id];
+ if (!actorsInfo || !actorsInfo.length) {
+ return null;
+ }
+ if (threadId) {
+ actorsInfo = actorsInfo.filter(actorInfo => actorInfo.thread == threadId);
+ }
+ return actorsInfo.length ? getSourceActor(state, actorsInfo[0].id) : null;
+}
+
+/**
+ * Get the source actor of the source
+ *
+ * @param {Object} state
+ * @param {String} id
+ * The source id
+ * @return {Array<Object>}
+ * List of source actors
+ */
+export function getSourceActorsForSource(state, id) {
+ const actorsInfo = state.sources.actors[id];
+ if (!actorsInfo) {
+ return [];
+ }
+
+ return actorsInfo
+ .map(actorInfo => getSourceActor(state, actorInfo.id))
+ .filter(actor => !!actor);
+}
+
+export function isSourceWithMap(state, id) {
+ return getSourceActorsForSource(state, id).some(
+ sourceActor => sourceActor.sourceMapURL
+ );
+}
+
+export function canPrettyPrintSource(state, location) {
+ const { sourceId } = location;
+ const source = getSource(state, sourceId);
+ if (
+ !source ||
+ isPretty(source) ||
+ source.isOriginal ||
+ (prefs.clientSourceMapsEnabled && isSourceWithMap(state, sourceId))
+ ) {
+ return false;
+ }
+
+ const content = getSourceTextContent(state, location);
+ const sourceContent = content && isFulfilled(content) ? content.value : null;
+
+ if (!sourceContent || !isJavaScript(source, sourceContent)) {
+ return false;
+ }
+
+ return true;
+}
+
+export function getPrettyPrintMessage(state, location) {
+ const source = getSource(state, location.sourceId);
+ if (!source) {
+ return L10N.getStr("sourceTabs.prettyPrint");
+ }
+
+ if (isPretty(source)) {
+ return L10N.getStr("sourceFooter.prettyPrint.isPrettyPrintedMessage");
+ }
+
+ if (source.isOriginal) {
+ return L10N.getStr("sourceFooter.prettyPrint.isOriginalMessage");
+ }
+
+ if (prefs.clientSourceMapsEnabled && isSourceWithMap(state, source.id)) {
+ return L10N.getStr("sourceFooter.prettyPrint.hasSourceMapMessage");
+ }
+
+ const content = getSourceTextContent(state, location);
+
+ const sourceContent = content && isFulfilled(content) ? content.value : null;
+ if (!sourceContent) {
+ return L10N.getStr("sourceFooter.prettyPrint.noContentMessage");
+ }
+
+ if (!isJavaScript(source, sourceContent)) {
+ return L10N.getStr("sourceFooter.prettyPrint.isNotJavascriptMessage");
+ }
+
+ return L10N.getStr("sourceTabs.prettyPrint");
+}
+
+// Used by visibleColumnBreakpoints selectors
+export function getBreakpointPositions(state) {
+ return state.sources.breakpointPositions;
+}
+
+export function getBreakpointPositionsForSource(state, sourceId) {
+ const positions = getBreakpointPositions(state);
+ return positions?.[sourceId];
+}
+
+// This is only used by one test
+export function hasBreakpointPositions(state, sourceId) {
+ return !!getBreakpointPositionsForSource(state, sourceId);
+}
+
+export function getBreakpointPositionsForLine(state, sourceId, line) {
+ const positions = getBreakpointPositionsForSource(state, sourceId);
+ return positions?.[line];
+}
+
+export function getBreakpointPositionsForLocation(state, location) {
+ const { sourceId } = location;
+ const positions = getBreakpointPositionsForSource(state, sourceId);
+ return findPosition(positions, location);
+}
+
+export function getBreakableLines(state, sourceId) {
+ if (!sourceId) {
+ return null;
+ }
+ const source = getSource(state, sourceId);
+ if (!source) {
+ return null;
+ }
+
+ if (source.isOriginal) {
+ return state.sources.breakableLines[sourceId];
+ }
+
+ const sourceActorsInfo = state.sources.actors[sourceId];
+ if (!sourceActorsInfo?.length) {
+ return null;
+ }
+
+ // We pull generated file breakable lines directly from the source actors
+ // so that breakable lines can be added as new source actors on HTML loads.
+ return getBreakableLinesForSourceActors(
+ state,
+ sourceActorsInfo.map(actorInfo => actorInfo.id),
+ source.isHTML
+ );
+}
+
+export const getSelectedBreakableLines = createSelector(
+ state => {
+ const sourceId = getSelectedSourceId(state);
+ return sourceId && getBreakableLines(state, sourceId);
+ },
+ breakableLines => new Set(breakableLines || [])
+);