);
}
+
+export default function SuspenseTimeline(): React$Node {
+ const store = useContext(StoreContext);
+ const {shells} = useContext(SuspenseTreeStateContext);
+
+ const defaultSelectedRootID = shells.find(rootID => {
+ const suspense = store.getSuspenseByID(rootID);
+ return (
+ store.supportsTogglingSuspense(rootID) &&
+ suspense !== null &&
+ suspense.children.length > 1
+ );
+ });
+ const [selectedRootID, setSelectedRootID] = useState(defaultSelectedRootID);
+
+ if (selectedRootID === undefined && defaultSelectedRootID !== undefined) {
+ setSelectedRootID(defaultSelectedRootID);
+ }
+
+ function handleChange(event: SyntheticEvent) {
+ const newRootID = +event.currentTarget.value;
+ // TODO: scrollIntoView both suspense rects and host instance.
+ setSelectedRootID(newRootID);
+ }
+
+ return (
+
+
+ {shells.length > 0 && (
+
+ )}
+
+ );
+}
diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js
index a16c93a4573e7..42f903ae09b6a 100644
--- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js
+++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js
@@ -81,7 +81,7 @@ function SuspenseTreeContextController({children}: Props): React.Node {
useEffect(() => {
const handleSuspenseTreeMutated = () => {
- transitionDispatch({
+ dispatch({
type: 'HANDLE_SUSPENSE_TREE_MUTATION',
});
};
@@ -91,7 +91,7 @@ function SuspenseTreeContextController({children}: Props): React.Node {
// At the moment, we can treat this as a mutation.
// We don't know which Elements were newly added/removed, but that should be okay in this case.
// It would only impact the search state, which is unlikely to exist yet at this point.
- transitionDispatch({
+ dispatch({
type: 'HANDLE_SUSPENSE_TREE_MUTATION',
});
}
diff --git a/packages/react-devtools-shared/src/hooks/SourceMapConsumer.js b/packages/react-devtools-shared/src/hooks/SourceMapConsumer.js
index d06038ad93e38..c9521431fce1b 100644
--- a/packages/react-devtools-shared/src/hooks/SourceMapConsumer.js
+++ b/packages/react-devtools-shared/src/hooks/SourceMapConsumer.js
@@ -136,9 +136,9 @@ function BasicSourceMapConsumer(sourceMapJSON: BasicSourceMap) {
}
type Section = {
- +generatedColumn: number,
- +generatedLine: number,
- +map: MixedSourceMap,
+ +offsetColumn0: number,
+ +offsetLine0: number,
+ +map: BasicSourceMap,
// Lazily parsed only when/as the section is needed.
sourceMapConsumer: SourceMapConsumerType | null,
@@ -160,12 +160,12 @@ function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) {
column: number,
...
} = section.offset;
- const offsetLine = offset.line;
- const offsetColumn = offset.column;
+ const offsetLine0 = offset.line;
+ const offsetColumn0 = offset.column;
if (
- offsetLine < lastOffset.line ||
- (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)
+ offsetLine0 < lastOffset.line ||
+ (offsetLine0 === lastOffset.line && offsetColumn0 < lastOffset.column)
) {
throw new Error('Section offsets must be ordered and non-overlapping.');
}
@@ -173,9 +173,8 @@ function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) {
lastOffset = offset;
return {
- // The offset fields are 0-based, but we use 1-based indices when encoding/decoding from VLQ.
- generatedLine: offsetLine + 1,
- generatedColumn: offsetColumn + 1,
+ offsetLine0,
+ offsetColumn0,
map: section.map,
sourceMapConsumer: null,
};
@@ -186,55 +185,29 @@ function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) {
lineNumber,
}: SearchPosition): ResultPosition {
// Error.prototype.stack columns are 1-based (like most IDEs) but ASTs are 0-based.
- const targetColumnNumber = columnNumber - 1;
-
- let section = null;
-
- let startIndex = 0;
- let stopIndex = sections.length - 1;
- let index = -1;
- while (startIndex <= stopIndex) {
- index = Math.floor((stopIndex + startIndex) / 2);
- section = sections[index];
-
- const currentLine = section.generatedLine;
- if (currentLine === lineNumber) {
- const currentColumn = section.generatedColumn;
- if (currentColumn === lineNumber) {
- break;
- } else {
- if (currentColumn > targetColumnNumber) {
- if (stopIndex - index > 0) {
- stopIndex = index;
- } else {
- index = stopIndex;
- break;
- }
- } else {
- if (index - startIndex > 0) {
- startIndex = index;
- } else {
- index = startIndex;
- break;
- }
- }
- }
+ const column0 = columnNumber - 1;
+ const line0 = lineNumber - 1;
+
+ // Sections must not overlap and must be sorted: https://tc39.es/source-map/#section-object
+ // Therefore the last section that has an offset less than or equal to the frame is the applicable one.
+ let left = 0;
+ let right = sections.length - 1;
+ let section: Section | null = null;
+
+ while (left <= right) {
+ // fast Math.floor
+ const middle = ~~((left + right) / 2);
+ const currentSection = sections[middle];
+
+ if (
+ currentSection.offsetLine0 < line0 ||
+ (currentSection.offsetLine0 === line0 &&
+ currentSection.offsetColumn0 <= column0)
+ ) {
+ section = currentSection;
+ left = middle + 1;
} else {
- if (currentLine > lineNumber) {
- if (stopIndex - index > 0) {
- stopIndex = index;
- } else {
- index = stopIndex;
- break;
- }
- } else {
- if (index - startIndex > 0) {
- startIndex = index;
- } else {
- index = startIndex;
- break;
- }
- }
+ right = middle - 1;
}
}
@@ -252,8 +225,9 @@ function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) {
}
return section.sourceMapConsumer.originalPositionFor({
- columnNumber,
- lineNumber,
+ // The mappings in a Source Map section are relative to the section offset.
+ columnNumber: columnNumber - section.offsetColumn0,
+ lineNumber: lineNumber - section.offsetLine0,
});
}
diff --git a/packages/react-devtools-shared/src/hooks/SourceMapTypes.js b/packages/react-devtools-shared/src/hooks/SourceMapTypes.js
index 450cd608aa61e..42389b56b0501 100644
--- a/packages/react-devtools-shared/src/hooks/SourceMapTypes.js
+++ b/packages/react-devtools-shared/src/hooks/SourceMapTypes.js
@@ -29,7 +29,7 @@ export type BasicSourceMap = {
};
export type IndexSourceMapSection = {
- map: IndexSourceMap | BasicSourceMap,
+ map: BasicSourceMap,
offset: {
line: number,
column: number,
diff --git a/packages/react-devtools-shared/src/utils.js b/packages/react-devtools-shared/src/utils.js
index 34c258ebe2b98..b404608e5573d 100644
--- a/packages/react-devtools-shared/src/utils.js
+++ b/packages/react-devtools-shared/src/utils.js
@@ -261,6 +261,7 @@ export function printOperationsArray(operations: Array
) {
i++; // supportsProfiling
i++; // supportsStrictMode
i++; // hasOwnerMetadata
+ i++; // supportsTogglingSuspense
} else {
const parentID = ((operations[i]: any): number);
i++;
diff --git a/packages/react-devtools-shell/index.html b/packages/react-devtools-shell/index.html
index ce381a7345325..87352e2f82f35 100644
--- a/packages/react-devtools-shell/index.html
+++ b/packages/react-devtools-shell/index.html
@@ -8,9 +8,9 @@