Skip to content

Commit

Permalink
Refactored code so that isInternalModule function could be unit tested.
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Vaughn committed Oct 20, 2021
1 parent a6f6461 commit c3439e9
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ import type {
ViewRefs,
} from '../view-base';

import {
CHROME_WEBSTORE_EXTENSION_ID,
INTERNAL_EXTENSION_ID,
LOCAL_EXTENSION_ID,
} from 'react-devtools-shared/src/constants';
import {
BackgroundColorView,
Surface,
Expand All @@ -36,6 +31,7 @@ import {
rectIntersectsRect,
verticallyStackedLayout,
} from '../view-base';
import {isInternalModule} from './utils/moduleFilters';
import {
durationToWidth,
positioningScaleFactor,
Expand Down Expand Up @@ -75,56 +71,6 @@ function hoverColorForStackFrame(stackFrame: FlamechartStackFrame): string {
return hslaColorToString(color);
}

function isInternalModule(
internalModuleSourceToRanges: InternalModuleSourceToRanges,
flamechartStackFrame: FlamechartStackFrame,
): boolean {
const {locationColumn, locationLine, scriptUrl} = flamechartStackFrame;

if (scriptUrl == null || locationColumn == null || locationLine == null) {
return true;
}

// Internal modules are only registered if DevTools was running when the profile was captured,
// but DevTools should also hide its own frames to avoid over-emphasizing them.
if (
// Handle webpack-internal:// sources
scriptUrl.includes('/react-devtools') ||
scriptUrl.includes('/react_devtools') ||
// Filter out known extension IDs
scriptUrl.includes(CHROME_WEBSTORE_EXTENSION_ID) ||
scriptUrl.includes(INTERNAL_EXTENSION_ID) ||
scriptUrl.includes(LOCAL_EXTENSION_ID)

// Unfortunately this won't get everything, like relatively loaded chunks or Web Worker files.
) {
return true;
}

// Filter out React internal packages.
const ranges = internalModuleSourceToRanges.get(scriptUrl);
if (ranges != null) {
for (let i = 0; i < ranges.length; i++) {
const [startStackFrame, stopStackFrame] = ranges[i];

const isAfterStart =
locationLine > startStackFrame.lineNumber ||
(locationLine === startStackFrame.lineNumber &&
locationColumn >= startStackFrame.columnNumber);
const isBeforeStop =
locationLine < stopStackFrame.lineNumber ||
(locationLine === stopStackFrame.lineNumber &&
locationColumn <= stopStackFrame.columnNumber);

if (isAfterStart && isBeforeStop) {
return true;
}
}
}

return false;
}

class FlamechartStackLayerView extends View {
/** Layer to display */
_stackLayer: FlamechartStackLayer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

export const outerErrorA = new Error();

export const moduleStartError = new Error();
export const innerError = new Error();
export const moduleStopError = new Error();

export const outerErrorB = new Error();
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

export const moduleAStartError = new Error();
export const innerErrorA = new Error();
export const moduleAStopError = new Error();

export const outerError = new Error();

export const moduleBStartError = new Error();
export const innerErrorB = new Error();
export const moduleBStopError = new Error();
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import {isInternalModule} from '../moduleFilters';

describe('isInternalModule', () => {
let map;

function createFlamechartStackFrame(scriptUrl, locationLine, locationColumn) {
return {
name: 'test',
timestamp: 0,
duration: 1,
scriptUrl,
locationLine,
locationColumn,
};
}

function createStackFrame(fileName, lineNumber, columnNumber) {
return {
columnNumber: columnNumber,
lineNumber: lineNumber,
fileName: fileName,
functionName: 'test',
source: ` at test (${fileName}:${lineNumber}:${columnNumber})`,
};
}

beforeEach(() => {
map = new Map();
map.set('foo', [
[createStackFrame('foo', 10, 0), createStackFrame('foo', 15, 100)],
]);
map.set('bar', [
[createStackFrame('bar', 10, 0), createStackFrame('bar', 15, 100)],
[createStackFrame('bar', 20, 0), createStackFrame('bar', 25, 100)],
]);
});

it('should properly identify stack frames within the provided module ranges', () => {
expect(
isInternalModule(map, createFlamechartStackFrame('foo', 10, 0)),
).toBe(true);
expect(
isInternalModule(map, createFlamechartStackFrame('foo', 12, 35)),
).toBe(true);
expect(
isInternalModule(map, createFlamechartStackFrame('foo', 15, 100)),
).toBe(true);
expect(
isInternalModule(map, createFlamechartStackFrame('bar', 12, 0)),
).toBe(true);
expect(
isInternalModule(map, createFlamechartStackFrame('bar', 22, 125)),
).toBe(true);
});

it('should properly identify stack frames outside of the provided module ranges', () => {
expect(isInternalModule(map, createFlamechartStackFrame('foo', 9, 0))).toBe(
false,
);
expect(
isInternalModule(map, createFlamechartStackFrame('foo', 15, 101)),
).toBe(false);
expect(
isInternalModule(map, createFlamechartStackFrame('bar', 17, 0)),
).toBe(false);
expect(
isInternalModule(map, createFlamechartStackFrame('baz', 12, 0)),
).toBe(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type {
FlamechartStackFrame,
InternalModuleSourceToRanges,
} from '../../types';

import {
CHROME_WEBSTORE_EXTENSION_ID,
INTERNAL_EXTENSION_ID,
LOCAL_EXTENSION_ID,
} from 'react-devtools-shared/src/constants';

export function isInternalModule(
internalModuleSourceToRanges: InternalModuleSourceToRanges,
flamechartStackFrame: FlamechartStackFrame,
): boolean {
const {locationColumn, locationLine, scriptUrl} = flamechartStackFrame;

if (scriptUrl == null || locationColumn == null || locationLine == null) {
// This could indicate a browser-internal API like performance.mark().
return false;
}

// Internal modules are only registered if DevTools was running when the profile was captured,
// but DevTools should also hide its own frames to avoid over-emphasizing them.
if (
// Handle webpack-internal:// sources
scriptUrl.includes('/react-devtools') ||
scriptUrl.includes('/react_devtools') ||
// Filter out known extension IDs
scriptUrl.includes(CHROME_WEBSTORE_EXTENSION_ID) ||
scriptUrl.includes(INTERNAL_EXTENSION_ID) ||
scriptUrl.includes(LOCAL_EXTENSION_ID)
// Unfortunately this won't get everything, like relatively loaded chunks or Web Worker files.
) {
return true;
}

// Filter out React internal packages.
const ranges = internalModuleSourceToRanges.get(scriptUrl);
if (ranges != null) {
for (let i = 0; i < ranges.length; i++) {
const [startStackFrame, stopStackFrame] = ranges[i];

const isAfterStart =
locationLine > startStackFrame.lineNumber ||
(locationLine === startStackFrame.lineNumber &&
locationColumn >= startStackFrame.columnNumber);
const isBeforeStop =
locationLine < stopStackFrame.lineNumber ||
(locationLine === stopStackFrame.lineNumber &&
locationColumn <= stopStackFrame.columnNumber);

if (isAfterStart && isBeforeStop) {
return true;
}
}
}

return false;
}
2 changes: 1 addition & 1 deletion packages/react-devtools-shared/src/backend/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ export type DevToolsHook = {
) => void,

// Scheduling Profiler internal module filtering
getInternalModuleRanges: () => Array<[Error, Error]>,
getInternalModuleRanges: () => Array<[string, string]>,
registerInternalModuleStart: (moduleStartError: Error) => void,
registerInternalModuleStop: (moduleStopError: Error) => void,

Expand Down
34 changes: 25 additions & 9 deletions packages/react-devtools-shared/src/hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -490,21 +490,37 @@ export function installHook(target: any): DevToolsHook | null {
}
}

const openModuleRangesStack: Array<Error> = [];
const moduleRanges: Array<[Error, Error]> = [];
type StackFrameString = string;

function getInternalModuleRanges(): Array<[Error, Error]> {
const openModuleRangesStack: Array<StackFrameString> = [];
const moduleRanges: Array<[StackFrameString, StackFrameString]> = [];

function getTopStackFrameString(error: Error): StackFrameString | null {
const frames = error.stack.split('\n');
const frame = frames.length > 1 ? frames[1] : null;
return frame;
}

function getInternalModuleRanges(): Array<
[StackFrameString, StackFrameString],
> {
return moduleRanges;
}

function registerInternalModuleStart(moduleStartError: Error) {
openModuleRangesStack.push(moduleStartError);
function registerInternalModuleStart(error: Error) {
const startStackFrame = getTopStackFrameString(error);
if (startStackFrame !== null) {
openModuleRangesStack.push(startStackFrame);
}
}

function registerInternalModuleStop(moduleStopError: Error) {
const moduleStartError = openModuleRangesStack.pop();
if (moduleStartError) {
moduleRanges.push([moduleStartError, moduleStopError]);
function registerInternalModuleStop(error: Error) {
if (openModuleRangesStack.length > 0) {
const startStackFrame = openModuleRangesStack.pop();
const stopStackFrame = getTopStackFrameString(error);
if (stopStackFrame !== null) {
moduleRanges.push([startStackFrame, stopStackFrame]);
}
}
}

Expand Down
13 changes: 3 additions & 10 deletions packages/react-reconciler/src/SchedulingProfiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,10 @@ function markInternalModuleRanges() {
) {
const ranges = __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges();
for (let i = 0; i < ranges.length; i++) {
const [startError, stopError] = ranges[i];
const [startStackFrame, stopStackFrame] = ranges[i];

// Don't embed Error stack parsing logic into the reconciler.
// Just serialize the top stack frame and let the profiler parse it.
const startFrames = startError.stack.split('\n');
const startFrame = startFrames.length > 1 ? startFrames[1] : '';
const stopFrames = stopError.stack.split('\n');
const stopFrame = stopFrames.length > 1 ? stopFrames[1] : '';

markAndClear(`--react-internal-module-start-${startFrame}`);
markAndClear(`--react-internal-module-stop-${stopFrame}`);
markAndClear(`--react-internal-module-start-${startStackFrame}`);
markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
}
}
}
Expand Down

0 comments on commit c3439e9

Please sign in to comment.