forked from mirrors/gecko-dev
This used to be a node package. Let's make it become regular commonjs devtools modules. This will make it trivial to migrate this to ES Modules. Also possibly make this code become the unique layer in m-c on top of the source-map package. We no longer use webpack to build the two bundles (index.js and worker.js), instead, we are using the toolkit worker loader (require.js) in order to load all this code without any build step. As this is no longer a node package, I removed node-specific modules (assertRoot/wasmAsset) and simplify the definition of wasm file URIs as they are now fixed. Also moving the debugger to load internal "devtools/client/shared/source-map/source-map.js" module in jest as running the Web Worker instantiated by source-map/index.js is too complex. Differential Revision: https://phabricator.services.mozilla.com/D159115
331 lines
8.2 KiB
JavaScript
331 lines
8.2 KiB
JavaScript
/* 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
|
|
) {
|
|
const source = getSource(state, sourceId);
|
|
if (source.isOriginal) {
|
|
return null;
|
|
}
|
|
let actorsInfo = state.sources.actors[sourceId];
|
|
if (!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, id) {
|
|
const source = getSource(state, id);
|
|
if (
|
|
!source ||
|
|
isPretty(source) ||
|
|
source.isOriginal ||
|
|
(prefs.clientSourceMapsEnabled && isSourceWithMap(state, id))
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
const content = getSourceTextContent(state, id);
|
|
const sourceContent = content && isFulfilled(content) ? content.value : null;
|
|
|
|
if (!sourceContent || !isJavaScript(source, sourceContent)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
export function getPrettyPrintMessage(state, source) {
|
|
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, source.id);
|
|
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 || [])
|
|
);
|