diff --git a/src/actions/profile-view.js b/src/actions/profile-view.js index 15ae284bfa..72dc4a556f 100644 --- a/src/actions/profile-view.js +++ b/src/actions/profile-view.js @@ -654,6 +654,10 @@ export function updatePreviewSelection( } export function commitRange(start: number, end: number): Action { + if (end === start) { + // Ensure that the duration of the range is non-zero. + end = end + 0.0001; + } return { type: 'COMMIT_RANGE', start, diff --git a/src/components/shared/MarkerTooltipContents.js b/src/components/shared/MarkerTooltipContents.js index b8c58700c8..393e8d7103 100644 --- a/src/components/shared/MarkerTooltipContents.js +++ b/src/components/shared/MarkerTooltipContents.js @@ -59,7 +59,7 @@ function _markerDetailNullable( value: T | void | null, fn: T => string = String ): React.Node { - if (value === undefined || value === null) { + if (value === undefined || value === null || fn(value).length === 0) { return null; } return _markerDetail(key, label, value, fn); @@ -607,7 +607,7 @@ function getMarkerDetails( case 'Invalidation': { return (
- {_markerDetail('url', 'URL', data.url)} + {_markerDetailNullable('url', 'URL', data.url)} {_markerDetail('line', 'Line', data.line)}
); diff --git a/src/components/shared/Reorderable.js b/src/components/shared/Reorderable.js index 7b6cfb1c3b..2c26168d5e 100644 --- a/src/components/shared/Reorderable.js +++ b/src/components/shared/Reorderable.js @@ -179,7 +179,6 @@ class Reorderable extends React.PureComponent { ]; this.setState({ - phase: 'MANIPULATING', manipulatingIndex: elementIndex, manipulationDelta: 0, destinationIndex: elementIndex, @@ -188,6 +187,10 @@ class Reorderable extends React.PureComponent { }); const mouseMoveListener = (event: EventWithPageProperties) => { + if (this.state.phase === 'RESTING') { + // Only start manipulating on the mouse move. + this.setState({ phase: 'MANIPULATING' }); + } const delta = clamp( event[xy.pageXY] - mouseDownPos, -spaceBefore, @@ -199,14 +202,19 @@ class Reorderable extends React.PureComponent { }); }; const mouseUpListener = (event: EventWithPageProperties) => { + window.removeEventListener('mousemove', mouseMoveListener, true); + window.removeEventListener('mouseup', mouseUpListener, true); + if (this.state.phase === 'RESTING') { + // A mousemove never transitioned to the MANIPULATING state, so + // exit out now. + return; + } mouseMoveListener(event); const destinationIndex = this.state.destinationIndex; this.setState({ phase: 'FINISHING', finalOffset: offsets[destinationIndex], }); - window.removeEventListener('mousemove', mouseMoveListener, true); - window.removeEventListener('mouseup', mouseUpListener, true); setTimeout(() => { this.setState({ phase: 'RESTING', diff --git a/src/profile-logic/call-tree.js b/src/profile-logic/call-tree.js index 55045653ef..1dee92c4ca 100644 --- a/src/profile-logic/call-tree.js +++ b/src/profile-logic/call-tree.js @@ -195,7 +195,6 @@ export class CallTree { const resourceType = this._resourceTable.type[resourceIndex]; const isJS = this._funcTable.isJS[funcIndex]; const libName = this._getOriginAnnotation(funcIndex); - const precision = this._isIntegerInterval ? 0 : 1; let icon = null; if (resourceType === resourceTypes.webhost) { @@ -211,7 +210,7 @@ export class CallTree { displayData = { totalTime: formatNumber(totalTime), selfTime: selfTime === 0 ? '—' : formatNumber(selfTime), - totalTimePercent: `${(100 * totalTimeRelative).toFixed(precision)}%`, + totalTimePercent: `${(100 * totalTimeRelative).toFixed(1)}%`, name: funcName, lib: libName, // Dim platform pseudo-stacks. diff --git a/src/profile-logic/committed-ranges.js b/src/profile-logic/committed-ranges.js index d5c546a99d..213bebdf04 100644 --- a/src/profile-logic/committed-ranges.js +++ b/src/profile-logic/committed-ranges.js @@ -26,7 +26,15 @@ export function parseCommittedRanges( if (!m) { return { start: 0, end: 1000 }; } - return { start: Number(m[1]) * 1000, end: Number(m[2]) * 1000 }; + + const m1 = Number(m[1]); + let m2 = Number(m[2]); + + if (m2 === m1) { + // Ensure that the duration of the range is non-zero. + m2 = m2 + 0.0001; + } + return { start: m1 * 1000, end: m2 * 1000 }; }); } diff --git a/src/profile-logic/gecko-profile-versioning.js b/src/profile-logic/gecko-profile-versioning.js index 40ce69ad4a..1b5d91f6a3 100644 --- a/src/profile-logic/gecko-profile-versioning.js +++ b/src/profile-logic/gecko-profile-versioning.js @@ -17,7 +17,7 @@ import { } from './convert-markers'; import { UniqueStringArray } from '../utils/unique-string-array'; -export const CURRENT_VERSION = 11; // The current version of the Gecko profile format. +export const CURRENT_VERSION = 12; // The current version of the Gecko profile format. // Gecko profiles before version 1 did not have a profile.meta.version field. // Treat those as version zero. @@ -430,5 +430,30 @@ const _upgraders = { } convertToVersionElevenRecursive(profile); }, + [12]: profile => { + // This version will add column numbers to the JS functions and scripts. + // There is also a new property in the frameTable called "column" which + // swaps positions with the "category" property. The new value for + // "category" in the frameTable schema will be 5. + const oldSchemaCategoryIndex = 4; + const newSchemaCategoryIndex = 5; + function convertToVersionTwelveRecursive(p) { + for (const thread of p.threads) { + const schemaIndexCategory = thread.frameTable.schema.category; + for (const frame of thread.frameTable.data) { + if (frame.hasOwnProperty(schemaIndexCategory)) { + frame[newSchemaCategoryIndex] = frame[oldSchemaCategoryIndex]; + frame[oldSchemaCategoryIndex] = null; + } + } + thread.frameTable.schema.category = newSchemaCategoryIndex; + thread.frameTable.schema.column = oldSchemaCategoryIndex; + } + for (const subprocessProfile of p.processes) { + convertToVersionTwelveRecursive(subprocessProfile); + } + } + convertToVersionTwelveRecursive(profile); + }, }; /* eslint-enable no-useless-computed-key */ diff --git a/src/profile-logic/process-profile.js b/src/profile-logic/process-profile.js index a682e1ad71..d3f360b3a5 100644 --- a/src/profile-logic/process-profile.js +++ b/src/profile-logic/process-profile.js @@ -500,6 +500,7 @@ function _processFrameTable( func: frameFuncs, implementation: geckoFrameStruct.implementation, line: geckoFrameStruct.line, + column: geckoFrameStruct.column, optimizations: geckoFrameStruct.optimizations, length: geckoFrameStruct.length, }; diff --git a/src/profile-logic/processed-profile-versioning.js b/src/profile-logic/processed-profile-versioning.js index ca818ec9c5..78ff3462af 100644 --- a/src/profile-logic/processed-profile-versioning.js +++ b/src/profile-logic/processed-profile-versioning.js @@ -22,7 +22,7 @@ import { import { UniqueStringArray } from '../utils/unique-string-array'; import { timeCode } from '../utils/time-code'; -export const CURRENT_VERSION = 14; // The current version of the "processed" profile format. +export const CURRENT_VERSION = 15; // The current version of the "processed" profile format. // Processed profiles before version 1 did not have a profile.meta.preprocessedProfileVersion // field. Treat those as version zero. @@ -637,5 +637,14 @@ const _upgraders = { } } }, + [15]: profile => { + // Profiles now have a column property in the frameTable + for (const thread of profile.threads) { + thread.frameTable.column = new Array(thread.frameTable.length); + for (let i = 0; i < thread.frameTable.length; i++) { + thread.frameTable.column[i] = null; + } + } + }, }; /* eslint-enable no-useless-computed-key */ diff --git a/src/profile-logic/profile-data.js b/src/profile-logic/profile-data.js index e13203b535..95113c669f 100644 --- a/src/profile-logic/profile-data.js +++ b/src/profile-logic/profile-data.js @@ -617,6 +617,7 @@ export function collapsePlatformStackFrames(thread: Thread): Thread { implementation: frameTable.implementation.slice(), optimizations: frameTable.optimizations.slice(), line: frameTable.line.slice(), + column: frameTable.column.slice(), category: frameTable.category.slice(), func: frameTable.func.slice(), address: frameTable.address.slice(), @@ -692,6 +693,7 @@ export function collapsePlatformStackFrames(thread: Thread): Thread { newFrameTable.implementation.push(null); newFrameTable.optimizations.push(null); newFrameTable.line.push(null); + newFrameTable.column.push(null); newFrameTable.category.push(null); newFrameTable.func.push(newFuncIndex); newFrameTable.address.push(-1); diff --git a/src/profile-logic/transforms.js b/src/profile-logic/transforms.js index 6393e749f3..40118b2934 100644 --- a/src/profile-logic/transforms.js +++ b/src/profile-logic/transforms.js @@ -760,6 +760,7 @@ export function collapseResource( func: frameTable.func.slice(), implementation: frameTable.implementation.slice(), line: frameTable.line.slice(), + column: frameTable.column.slice(), optimizations: frameTable.optimizations.slice(), length: frameTable.length, }; @@ -834,6 +835,7 @@ export function collapseResource( newFrameTable.category.push(frameTable.category[frameIndex]); newFrameTable.func.push(collapsedFuncIndex); newFrameTable.line.push(frameTable.line[frameIndex]); + newFrameTable.column.push(frameTable.column[frameIndex]); newFrameTable.implementation.push( frameTable.implementation[frameIndex] ); diff --git a/src/test/components/__snapshots__/ProfileCallTreeView.test.js.snap b/src/test/components/__snapshots__/ProfileCallTreeView.test.js.snap index 6044343713..037aa083aa 100644 --- a/src/test/components/__snapshots__/ProfileCallTreeView.test.js.snap +++ b/src/test/components/__snapshots__/ProfileCallTreeView.test.js.snap @@ -568,9 +568,9 @@ exports[`calltree/ProfileCallTreeView computes a width for a call tree of a real - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 67% + 66.7% - 67% + 66.7% - 67% + 66.7% - 33% + 33.3% - 33% + 33.3% - 33% + 33.3% - 33% + 33.3% - 100% + 100.0% - 100% + 100.0% - 67% + 66.7% - 33% + 33.3% - 33% + 33.3% - 33% + 33.3% - 33% + 33.3% - 100% + 100.0% - 100% + 100.0% - 67% + 66.7% - 33% + 33.3% - 33% + 33.3% - 33% + 33.3% - 33% + 33.3% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 50% + 50.0% - 50% + 50.0% - 50% + 50.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 50% + 50.0% - 50% + 50.0% - 50% + 50.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 100% + 100.0% - 50% + 50.0% - 50% + 50.0% - 50% + 50.0% , optimizations: Array, line: Array, + column: Array, category: Array, length: number, }; diff --git a/src/types/profile.js b/src/types/profile.js index a3f74d8eab..faa9f6a790 100644 --- a/src/types/profile.js +++ b/src/types/profile.js @@ -128,6 +128,7 @@ export type FrameTable = { func: IndexIntoFuncTable[], implementation: (IndexIntoStringTable | null)[], line: (number | null)[], + column: (number | null)[], optimizations: ({} | null)[], length: number, };