Skip to content

Commit c897260

Browse files
authored
refactor[react-devtools-shared]: minor parsing improvements and modifications (#27661)
Had these stashed for some time, it includes: - Some refactoring to remove unnecessary `FlowFixMe`s and type castings via `any`. - Optimized version of parsing component names. We encode string names to utf8 and then pass it serialized from backend to frontend in a single array of numbers. Previously we would call `slice` to get the corresponding encoded string as a subarray and then parse each character. New implementation skips `slice` step and just receives `left` and `right` ranges for the string to parse. - Early `break` instead of `continue` when Store receives unexpected operation, like removing an element from the Store, which is not registered yet.
1 parent ce2bc58 commit c897260

File tree

6 files changed

+106
-65
lines changed

6 files changed

+106
-65
lines changed

packages/react-devtools-shared/src/backend/renderer.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,6 @@ export function getInternalReactConstants(version: string): {
439439
return 'Cache';
440440
case ClassComponent:
441441
case IncompleteClassComponent:
442-
return getDisplayName(resolvedType);
443442
case FunctionComponent:
444443
case IndeterminateComponent:
445444
return getDisplayName(resolvedType);

packages/react-devtools-shared/src/devtools/store.js

Lines changed: 80 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
setSavedComponentFilters,
2828
separateDisplayNameAndHOCs,
2929
shallowDiffers,
30-
utfDecodeString,
30+
utfDecodeStringWithRanges,
3131
} from '../utils';
3232
import {localStorageGetItem, localStorageSetItem} from '../storage';
3333
import {__DEBUG__} from '../constants';
@@ -450,7 +450,7 @@ export default class Store extends EventEmitter<{
450450
}
451451

452452
// This build of DevTools supports the legacy profiler.
453-
// This is a static flag, controled by the Store config.
453+
// This is a static flag, controlled by the Store config.
454454
get supportsProfiling(): boolean {
455455
return this._supportsProfiling;
456456
}
@@ -467,7 +467,7 @@ export default class Store extends EventEmitter<{
467467
}
468468

469469
// This build of DevTools supports the Timeline profiler.
470-
// This is a static flag, controled by the Store config.
470+
// This is a static flag, controlled by the Store config.
471471
get supportsTimeline(): boolean {
472472
return this._supportsTimeline;
473473
}
@@ -502,30 +502,58 @@ export default class Store extends EventEmitter<{
502502
}
503503

504504
// Find which root this element is in...
505-
let rootID;
506505
let root;
507506
let rootWeight = 0;
508507
for (let i = 0; i < this._roots.length; i++) {
509-
rootID = this._roots[i];
510-
root = ((this._idToElement.get(rootID): any): Element);
508+
const rootID = this._roots[i];
509+
root = this._idToElement.get(rootID);
510+
511+
if (root === undefined) {
512+
this._throwAndEmitError(
513+
Error(
514+
`Couldn't find root with id "${rootID}": no matching node was found in the Store.`,
515+
),
516+
);
517+
518+
return null;
519+
}
520+
511521
if (root.children.length === 0) {
512522
continue;
513-
} else if (rootWeight + root.weight > index) {
523+
}
524+
525+
if (rootWeight + root.weight > index) {
514526
break;
515527
} else {
516528
rootWeight += root.weight;
517529
}
518530
}
519531

532+
if (root === undefined) {
533+
return null;
534+
}
535+
520536
// Find the element in the tree using the weight of each node...
521537
// Skip over the root itself, because roots aren't visible in the Elements tree.
522-
let currentElement = ((root: any): Element);
538+
let currentElement: Element = root;
523539
let currentWeight = rootWeight - 1;
540+
524541
while (index !== currentWeight) {
525542
const numChildren = currentElement.children.length;
526543
for (let i = 0; i < numChildren; i++) {
527544
const childID = currentElement.children[i];
528-
const child = ((this._idToElement.get(childID): any): Element);
545+
const child = this._idToElement.get(childID);
546+
547+
if (child === undefined) {
548+
this._throwAndEmitError(
549+
Error(
550+
`Couldn't child element with id "${childID}": no matching node was found in the Store.`,
551+
),
552+
);
553+
554+
return null;
555+
}
556+
529557
const childWeight = child.isCollapsed ? 1 : child.weight;
530558

531559
if (index <= currentWeight + childWeight) {
@@ -538,7 +566,7 @@ export default class Store extends EventEmitter<{
538566
}
539567
}
540568

541-
return ((currentElement: any): Element) || null;
569+
return currentElement || null;
542570
}
543571

544572
getElementIDAtIndex(index: number): number | null {
@@ -560,32 +588,31 @@ export default class Store extends EventEmitter<{
560588
getElementsWithErrorsAndWarnings(): Array<{id: number, index: number}> {
561589
if (this._cachedErrorAndWarningTuples !== null) {
562590
return this._cachedErrorAndWarningTuples;
563-
} else {
564-
const errorAndWarningTuples: ErrorAndWarningTuples = [];
565-
566-
this._errorsAndWarnings.forEach((_, id) => {
567-
const index = this.getIndexOfElementID(id);
568-
if (index !== null) {
569-
let low = 0;
570-
let high = errorAndWarningTuples.length;
571-
while (low < high) {
572-
const mid = (low + high) >> 1;
573-
if (errorAndWarningTuples[mid].index > index) {
574-
high = mid;
575-
} else {
576-
low = mid + 1;
577-
}
578-
}
591+
}
579592

580-
errorAndWarningTuples.splice(low, 0, {id, index});
593+
const errorAndWarningTuples: ErrorAndWarningTuples = [];
594+
595+
this._errorsAndWarnings.forEach((_, id) => {
596+
const index = this.getIndexOfElementID(id);
597+
if (index !== null) {
598+
let low = 0;
599+
let high = errorAndWarningTuples.length;
600+
while (low < high) {
601+
const mid = (low + high) >> 1;
602+
if (errorAndWarningTuples[mid].index > index) {
603+
high = mid;
604+
} else {
605+
low = mid + 1;
606+
}
581607
}
582-
});
583608

584-
// Cache for later (at least until the tree changes again).
585-
this._cachedErrorAndWarningTuples = errorAndWarningTuples;
609+
errorAndWarningTuples.splice(low, 0, {id, index});
610+
}
611+
});
586612

587-
return errorAndWarningTuples;
588-
}
613+
// Cache for later (at least until the tree changes again).
614+
this._cachedErrorAndWarningTuples = errorAndWarningTuples;
615+
return errorAndWarningTuples;
589616
}
590617

591618
getErrorAndWarningCountForElementID(id: number): {
@@ -923,7 +950,11 @@ export default class Store extends EventEmitter<{
923950
const nextLength = operations[i];
924951
i++;
925952

926-
const nextString = utfDecodeString(operations.slice(i, i + nextLength));
953+
const nextString = utfDecodeStringWithRanges(
954+
operations,
955+
i,
956+
i + nextLength - 1,
957+
);
927958
stringTable.push(nextString);
928959
i += nextLength;
929960
}
@@ -1035,7 +1066,7 @@ export default class Store extends EventEmitter<{
10351066
),
10361067
);
10371068

1038-
continue;
1069+
break;
10391070
}
10401071

10411072
parentElement.children.push(id);
@@ -1088,7 +1119,7 @@ export default class Store extends EventEmitter<{
10881119
),
10891120
);
10901121

1091-
continue;
1122+
break;
10921123
}
10931124

10941125
i += 1;
@@ -1126,7 +1157,7 @@ export default class Store extends EventEmitter<{
11261157
),
11271158
);
11281159

1129-
continue;
1160+
break;
11301161
}
11311162

11321163
const index = parentElement.children.indexOf(id);
@@ -1172,7 +1203,17 @@ export default class Store extends EventEmitter<{
11721203
}
11731204
};
11741205

1175-
const root = ((this._idToElement.get(id): any): Element);
1206+
const root = this._idToElement.get(id);
1207+
if (root === undefined) {
1208+
this._throwAndEmitError(
1209+
Error(
1210+
`Cannot remove root "${id}": no matching node was found in the Store.`,
1211+
),
1212+
);
1213+
1214+
break;
1215+
}
1216+
11761217
recursivelyDeleteElements(id);
11771218

11781219
this._rootIDToCapabilities.delete(id);
@@ -1194,7 +1235,7 @@ export default class Store extends EventEmitter<{
11941235
),
11951236
);
11961237

1197-
continue;
1238+
break;
11981239
}
11991240

12001241
const children = element.children;
@@ -1279,7 +1320,7 @@ export default class Store extends EventEmitter<{
12791320

12801321
this._revision++;
12811322

1282-
// Any time the tree changes (e.g. elements added, removed, or reordered) cached inidices may be invalid.
1323+
// Any time the tree changes (e.g. elements added, removed, or reordered) cached indices may be invalid.
12831324
this._cachedErrorAndWarningTuples = null;
12841325

12851326
if (haveErrorsOrWarningsChanged) {

packages/react-devtools-shared/src/devtools/views/Components/Element.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export default function Element({data, index, style}: Props): React.Node {
122122
isStrictModeNonCompliant,
123123
key,
124124
type,
125-
} = ((element: any): ElementType);
125+
} = element;
126126

127127
// Only show strict mode non-compliance badges for top level elements.
128128
// Showing an inline badge for every element in the tree would be noisy.
@@ -173,17 +173,16 @@ export default function Element({data, index, style}: Props): React.Node {
173173
"
174174
</Fragment>
175175
)}
176+
176177
{hocDisplayNames !== null && hocDisplayNames.length > 0 ? (
177178
<Badge
178179
className={styles.Badge}
179180
hocDisplayNames={hocDisplayNames}
180181
type={type}>
181-
<DisplayName
182-
displayName={hocDisplayNames[0]}
183-
id={((id: any): number)}
184-
/>
182+
<DisplayName displayName={hocDisplayNames[0]} id={id} />
185183
</Badge>
186184
) : null}
185+
187186
{showInlineWarningsAndErrors && errorCount > 0 && (
188187
<Icon
189188
type="error"

packages/react-devtools-shared/src/devtools/views/Components/HocBadges.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,11 @@ export default function HocBadges({element}: Props): React.Node {
2525

2626
return (
2727
<div className={styles.HocBadges}>
28-
{hocDisplayNames !== null &&
29-
hocDisplayNames.map(hocDisplayName => (
30-
<div key={hocDisplayName} className={styles.Badge}>
31-
{hocDisplayName}
32-
</div>
33-
))}
28+
{hocDisplayNames.map(hocDisplayName => (
29+
<div key={hocDisplayName} className={styles.Badge}>
30+
{hocDisplayName}
31+
</div>
32+
))}
3433
</div>
3534
);
3635
}

packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
1818
TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,
1919
} from 'react-devtools-shared/src/constants';
20-
import {utfDecodeString} from 'react-devtools-shared/src/utils';
20+
import {utfDecodeStringWithRanges} from 'react-devtools-shared/src/utils';
2121
import {ElementTypeRoot} from 'react-devtools-shared/src/frontend/types';
2222
import ProfilerStore from 'react-devtools-shared/src/devtools/ProfilerStore';
2323

@@ -170,8 +170,10 @@ function updateTree(
170170
const stringTableEnd = i + stringTableSize;
171171
while (i < stringTableEnd) {
172172
const nextLength = operations[i++];
173-
const nextString = utfDecodeString(
174-
(operations.slice(i, i + nextLength): any),
173+
const nextString = utfDecodeStringWithRanges(
174+
operations,
175+
i,
176+
i + nextLength - 1,
175177
);
176178
stringTable.push(nextString);
177179
i += nextLength;

packages/react-devtools-shared/src/utils.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export function getWrappedDisplayName(
116116
wrapperName: string,
117117
fallbackName?: string,
118118
): string {
119-
const displayName = (outerType: any).displayName;
119+
const displayName = (outerType: any)?.displayName;
120120
return (
121121
displayName || `${wrapperName}(${getDisplayName(innerType, fallbackName)})`
122122
);
@@ -152,15 +152,14 @@ export function getUID(): number {
152152
return ++uidCounter;
153153
}
154154

155-
export function utfDecodeString(array: Array<number>): string {
156-
// Avoid spreading the array (e.g. String.fromCodePoint(...array))
157-
// Functions arguments are first placed on the stack before the function is called
158-
// which throws a RangeError for large arrays.
159-
// See github.com/facebook/react/issues/22293
155+
export function utfDecodeStringWithRanges(
156+
array: Array<number>,
157+
left: number,
158+
right: number,
159+
): string {
160160
let string = '';
161-
for (let i = 0; i < array.length; i++) {
162-
const char = array[i];
163-
string += String.fromCodePoint(char);
161+
for (let i = left; i <= right; i++) {
162+
string += String.fromCodePoint(array[i]);
164163
}
165164
return string;
166165
}
@@ -216,8 +215,10 @@ export function printOperationsArray(operations: Array<number>) {
216215
const stringTableEnd = i + stringTableSize;
217216
while (i < stringTableEnd) {
218217
const nextLength = operations[i++];
219-
const nextString = utfDecodeString(
220-
(operations.slice(i, i + nextLength): any),
218+
const nextString = utfDecodeStringWithRanges(
219+
operations,
220+
i,
221+
i + nextLength - 1,
221222
);
222223
stringTable.push(nextString);
223224
i += nextLength;

0 commit comments

Comments
 (0)