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 79f6f34
Show file tree
Hide file tree
Showing 8 changed files with 245 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,112 @@
/**
* 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 ErrorStackParser from 'error-stack-parser';
import {isInternalModule} from '../moduleFilters';

import * as moduleOne from './__modules__/module-one';
import * as moduleTwo from './__modules__/module-two';

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

function parseAndGetStackFrame(error, index = 0) {
const stackFrame = ErrorStackParser.parse(error)[index];

// Strip out the portion of the file path that includes "react-devtools-scheduling-profiler"
// or every frame will be flagged as internal.
stackFrame.fileName = stackFrame.fileName.replace(
/.+__tests__/,
'../__tests__',
);

return stackFrame;
}

function stackFrameToFlamechartStackFrame(stackFrame) {
return {
name: 'test',
timestamp: 0,
duration: 1,
scriptUrl: stackFrame.fileName,
locationLine: stackFrame.lineNumber,
locationColumn: stackFrame.columnNumber,
};
}

beforeEach(() => {
moduleSourceToRanges = new Map();

let startErrorStackFrame = parseAndGetStackFrame(
moduleOne.moduleStartError,
);
let stopErrorStackFrame = parseAndGetStackFrame(moduleOne.moduleStopError);
moduleSourceToRanges.set(startErrorStackFrame.fileName, [
[startErrorStackFrame, stopErrorStackFrame],
]);

const moduleTwoRanges = [];

startErrorStackFrame = parseAndGetStackFrame(moduleTwo.moduleAStartError);
stopErrorStackFrame = parseAndGetStackFrame(moduleTwo.moduleAStopError);
moduleTwoRanges.push([startErrorStackFrame, stopErrorStackFrame]);

startErrorStackFrame = parseAndGetStackFrame(moduleTwo.moduleBStartError);
stopErrorStackFrame = parseAndGetStackFrame(moduleTwo.moduleBStopError);
moduleTwoRanges.push([startErrorStackFrame, stopErrorStackFrame]);

moduleSourceToRanges.set(startErrorStackFrame.fileName, moduleTwoRanges);
});

it('should properly identify stack frames within the provided module ranges', () => {
let flamechartStackFrame = stackFrameToFlamechartStackFrame(
parseAndGetStackFrame(moduleOne.innerError),
);
expect(isInternalModule(moduleSourceToRanges, flamechartStackFrame)).toBe(
true,
);

flamechartStackFrame = stackFrameToFlamechartStackFrame(
parseAndGetStackFrame(moduleTwo.innerErrorA),
);
expect(isInternalModule(moduleSourceToRanges, flamechartStackFrame)).toBe(
true,
);

flamechartStackFrame = stackFrameToFlamechartStackFrame(
parseAndGetStackFrame(moduleTwo.innerErrorB),
);
expect(isInternalModule(moduleSourceToRanges, flamechartStackFrame)).toBe(
true,
);
});

it('should properly identify stack frames outside of the provided module ranges', () => {
let flamechartStackFrame = stackFrameToFlamechartStackFrame(
parseAndGetStackFrame(moduleOne.outerErrorA),
);
expect(isInternalModule(moduleSourceToRanges, flamechartStackFrame)).toBe(
false,
);

flamechartStackFrame = stackFrameToFlamechartStackFrame(
parseAndGetStackFrame(moduleOne.outerErrorB),
);
expect(isInternalModule(moduleSourceToRanges, flamechartStackFrame)).toBe(
false,
);

flamechartStackFrame = stackFrameToFlamechartStackFrame(
parseAndGetStackFrame(moduleTwo.outerError),
);
expect(isInternalModule(moduleSourceToRanges, flamechartStackFrame)).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 79f6f34

Please sign in to comment.