/* 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 . */
import { createSelector } from "devtools/client/shared/vendor/reselect";
import { getPrettySourceURL, 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 { UNDEFINED_LOCATION, NO_LOCATION } from "../reducers/sources";
import {
hasSourceActor,
getSourceActor,
getBreakableLinesForSourceActors,
isSourceActorWithSourceMap,
} from "./source-actors";
import { getSourceTextContent } from "./sources-content";
export function hasSource(state, id) {
return state.sources.mutableSources.has(id);
}
export function getSource(state, id) {
return state.sources.mutableSources.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 getSourceByActorId(state, actorId) {
if (!hasSourceActor(state, actorId)) {
return null;
}
return getSource(state, getSourceActor(state, actorId).source);
}
function getSourcesByURL(state, url) {
return state.sources.mutableSourcesPerUrl.get(url) || [];
}
export function getSourceByURL(state, url) {
const foundSources = getSourcesByURL(state, url);
return foundSources[0];
}
// This is used by tabs selectors
export function getSpecificSourceByURL(state, url, isOriginal) {
const foundSources = getSourcesByURL(state, url);
return foundSources.find(source => source.isOriginal == isOriginal);
}
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 (!source.isOriginal) {
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));
}
// This is only used by Project Search and tests.
export function getSourceList(state) {
return [...state.sources.mutableSources.values()];
}
// This is only used by tests and create.js
export function getSourceCount(state) {
return state.sources.mutableSources.size;
}
export function getSelectedLocation(state) {
return state.sources.selectedLocation;
}
/**
* Return the "mapped" location for the currently selected location:
* - When selecting a location in an original source, returns
* the related location in the bundle source.
*
* - When selecting a location in a bundle source, returns
* the related location in the original source. This may return undefined
* while we are still computing this information. (we need to query the asynchronous SourceMap service)
*
* - Otherwise, when selecting a location in a source unrelated to source map
* or a pretty printed source, returns null.
*/
export function getSelectedMappedSource(state) {
const selectedLocation = getSelectedLocation(state);
if (!selectedLocation) {
return null;
}
// Don't map pretty printed to its related compressed source
if (selectedLocation.source.isPrettyPrinted) {
return null;
}
// If we are on a bundle with a functional source-map,
// the `selectLocation` action should compute the `selectedOriginalLocation` field.
if (
!selectedLocation.source.isOriginal &&
isSourceActorWithSourceMap(state, selectedLocation.sourceActor.id)
) {
const { selectedOriginalLocation } = state.sources;
// Return undefined if we are still loading the source map.
// `selectedOriginalLocation` will be set to undefined instead of null
if (
selectedOriginalLocation &&
selectedOriginalLocation != UNDEFINED_LOCATION &&
selectedOriginalLocation != NO_LOCATION
) {
return selectedOriginalLocation.source;
}
return null;
}
const mappedSource = getGeneratedSource(state, selectedLocation.source);
// getGeneratedSource will return the exact same source object on sources
// that don't map to any original source. In this case, return null
// as that's most likely a regular source, not using source maps.
if (mappedSource == selectedLocation.source) {
return null;
}
return mappedSource || null;
}
export const getSelectedSource = createSelector(
getSelectedLocation,
selectedLocation => {
if (!selectedLocation) {
return undefined;
}
return selectedLocation.source;
}
);
// This is used by tests and pause reducers
export function getSelectedSourceId(state) {
const source = getSelectedSource(state);
return source?.id;
}
export function getShouldSelectOriginalLocation(state) {
return state.sources.shouldSelectOriginalLocation;
}
export function getShouldHighlightSelectedLocation(state) {
return state.sources.shouldHighlightSelectedLocation;
}
/**
* 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);
// The source may have been removed if we are being called by async code
if (!source) {
return null;
}
if (source.isOriginal) {
source = getSource(state, originalToGeneratedId(source.id));
}
const actors = getSourceActorsForSource(state, source.id);
if (threadId) {
return actors.find(actorInfo => actorInfo.thread == threadId) || null;
}
return actors[0] || null;
}
/**
* Get the source actor of the source
*
* @param {Object} state
* @param {String} id
* The source id
* @return {Array