Skip to content

Commit fb707e3

Browse files
committed
Prevent area selections from going beyond the start and end of the trace bounds
1 parent 46432bb commit fb707e3

5 files changed

Lines changed: 126 additions & 9 deletions

File tree

ui/src/base/bigint_math.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,14 @@ export class BigintMath {
113113
static abs(n: bigint) {
114114
return n < 0n ? -1n * n : n;
115115
}
116+
117+
static clamp(n: bigint, min: bigint, max: bigint): bigint {
118+
if (n < min) {
119+
return min;
120+
} else if (n > max) {
121+
return max;
122+
} else {
123+
return n;
124+
}
125+
}
116126
}

ui/src/base/bigint_math_unittest.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,4 +255,32 @@ describe('BigIntMath', () => {
255255
expect(result).toEqual(0n);
256256
});
257257
});
258+
259+
describe('clamp', () => {
260+
it('returns value when within range', () => {
261+
expect(BIM.clamp(5n, 0n, 10n)).toEqual(5n);
262+
});
263+
264+
it('returns min when value is below range', () => {
265+
expect(BIM.clamp(-5n, 0n, 10n)).toEqual(0n);
266+
});
267+
268+
it('returns max when value is above range', () => {
269+
expect(BIM.clamp(15n, 0n, 10n)).toEqual(10n);
270+
});
271+
272+
it('returns value when equal to min', () => {
273+
expect(BIM.clamp(0n, 0n, 10n)).toEqual(0n);
274+
});
275+
276+
it('returns value when equal to max', () => {
277+
expect(BIM.clamp(10n, 0n, 10n)).toEqual(10n);
278+
});
279+
280+
it('works with negative ranges', () => {
281+
expect(BIM.clamp(-15n, -10n, -5n)).toEqual(-10n);
282+
expect(BIM.clamp(-7n, -10n, -5n)).toEqual(-7n);
283+
expect(BIM.clamp(0n, -10n, -5n)).toEqual(-5n);
284+
});
285+
});
258286
});

ui/src/base/time.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ export class Time {
176176
return Time.fromRaw(BigintMath.quant(a, b));
177177
}
178178

179+
static clamp(t: time, min: time, max: time): time {
180+
return Time.fromRaw(BigintMath.clamp(t, min, max));
181+
}
182+
179183
static formatSeconds(time: time): string {
180184
return Time.toSeconds(time).toString() + ' s';
181185
}
@@ -440,6 +444,13 @@ export class TimeSpan {
440444
Time.add(this.end, padding),
441445
);
442446
}
447+
448+
clamp(min: time, max: time): TimeSpan {
449+
return new TimeSpan(
450+
Time.clamp(this.start, min, max),
451+
Time.clamp(this.end, min, max),
452+
);
453+
}
443454
}
444455

445456
/**

ui/src/base/time_unittest.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,51 @@ describe('TimeSpan', () => {
156156
const x = mkSpan(10n, 20n);
157157
expect(x.pad(5n)).toEqual(mkSpan(5n, 25n));
158158
});
159+
160+
describe('clamp', () => {
161+
it('returns same span when fully within bounds', () => {
162+
expect(mkSpan(10n, 20n).clamp(t(0n), t(30n))).toEqual(mkSpan(10n, 20n));
163+
});
164+
165+
it('clamps start when below lower bound', () => {
166+
expect(mkSpan(0n, 20n).clamp(t(5n), t(30n))).toEqual(mkSpan(5n, 20n));
167+
});
168+
169+
it('clamps end when above upper bound', () => {
170+
expect(mkSpan(10n, 40n).clamp(t(0n), t(30n))).toEqual(mkSpan(10n, 30n));
171+
});
172+
173+
it('clamps both ends when span exceeds bounds', () => {
174+
expect(mkSpan(0n, 40n).clamp(t(5n), t(30n))).toEqual(mkSpan(5n, 30n));
175+
});
176+
177+
it('produces zero-length span when entirely before lower bound', () => {
178+
expect(mkSpan(0n, 5n).clamp(t(10n), t(20n))).toEqual(mkSpan(10n, 10n));
179+
});
180+
181+
it('produces zero-length span when entirely after upper bound', () => {
182+
expect(mkSpan(25n, 30n).clamp(t(10n), t(20n))).toEqual(mkSpan(20n, 20n));
183+
});
184+
});
185+
});
186+
187+
describe('Time.clamp', () => {
188+
it('returns value when within range', () => {
189+
expect(Time.clamp(t(5n), t(0n), t(10n))).toBe(t(5n));
190+
});
191+
192+
it('returns min when below range', () => {
193+
expect(Time.clamp(t(-1n), t(0n), t(10n))).toBe(t(0n));
194+
});
195+
196+
it('returns max when above range', () => {
197+
expect(Time.clamp(t(15n), t(0n), t(10n))).toBe(t(10n));
198+
});
199+
200+
it('returns value when equal to min or max', () => {
201+
expect(Time.clamp(t(0n), t(0n), t(10n))).toBe(t(0n));
202+
expect(Time.clamp(t(10n), t(0n), t(10n))).toBe(t(10n));
203+
});
159204
});
160205

161206
test('formatTimezone', () => {

ui/src/core_plugins/dev.perfetto.Timeline/track_tree_view.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {
3737
import {HighPrecisionTime} from '../../base/high_precision_time';
3838
import {HighPrecisionTimeSpan} from '../../base/high_precision_time_span';
3939
import {assertExists} from '../../base/assert';
40-
import {Time} from '../../base/time';
40+
import {Time, TimeSpan} from '../../base/time';
4141
import {TimeScale} from '../../base/time_scale';
4242
import {
4343
DragEvent,
@@ -624,7 +624,11 @@ export class TrackTreeView implements m.ClassComponent<TrackTreeViewAttrs> {
624624
renderedTracks,
625625
);
626626

627-
this.handleDrag.currentTime = currentTime;
627+
this.handleDrag.currentTime = currentTime.clamp(
628+
trace.traceInfo.start,
629+
trace.traceInfo.end,
630+
);
631+
628632
trace.timeline.selectedSpan = this.handleDrag
629633
.timeSpan()
630634
.toTimeSpan();
@@ -640,10 +644,15 @@ export class TrackTreeView implements m.ClassComponent<TrackTreeViewAttrs> {
640644
renderedTracks,
641645
);
642646

647+
const newAreaSelection = new TimeSpan(
648+
Time.min(newStartTime.toTime('ceil'), areaSelection.end),
649+
Time.max(newStartTime.toTime('ceil'), areaSelection.end),
650+
).clamp(trace.traceInfo.start, trace.traceInfo.end);
651+
643652
trace.selection.selectArea({
644653
...areaSelection,
645-
end: Time.max(newStartTime.toTime('ceil'), areaSelection.end),
646-
start: Time.min(newStartTime.toTime('ceil'), areaSelection.end),
654+
start: newAreaSelection.start,
655+
end: newAreaSelection.end,
647656
});
648657
trace.timeline.selectedSpan = undefined;
649658
this.handleDrag = undefined;
@@ -678,7 +687,10 @@ export class TrackTreeView implements m.ClassComponent<TrackTreeViewAttrs> {
678687
renderedTracks,
679688
);
680689

681-
this.handleDrag.currentTime = currentTime;
690+
this.handleDrag.currentTime = currentTime.clamp(
691+
trace.traceInfo.start,
692+
trace.traceInfo.end,
693+
);
682694
trace.timeline.selectedSpan = this.handleDrag
683695
.timeSpan()
684696
.toTimeSpan();
@@ -694,10 +706,14 @@ export class TrackTreeView implements m.ClassComponent<TrackTreeViewAttrs> {
694706
renderedTracks,
695707
);
696708

709+
const newAreaSelection = new TimeSpan(
710+
Time.min(newEndTime.toTime('ceil'), areaSelection.start),
711+
Time.max(newEndTime.toTime('ceil'), areaSelection.start),
712+
).clamp(trace.traceInfo.start, trace.traceInfo.end);
697713
trace.selection.selectArea({
698714
...areaSelection,
699-
end: Time.max(newEndTime.toTime('ceil'), areaSelection.start),
700-
start: Time.min(newEndTime.toTime('ceil'), areaSelection.start),
715+
end: newAreaSelection.end,
716+
start: newAreaSelection.start,
701717
});
702718
trace.timeline.selectedSpan = undefined;
703719
this.handleDrag = undefined;
@@ -737,7 +753,10 @@ export class TrackTreeView implements m.ClassComponent<TrackTreeViewAttrs> {
737753
this.areaDrag.currentY = e.dragCurrent.y;
738754

739755
this.trace.raf.scheduleCanvasRedraw();
740-
trace.timeline.selectedSpan = this.areaDrag.timeSpan().toTimeSpan();
756+
trace.timeline.selectedSpan = this.areaDrag
757+
.timeSpan()
758+
.toTimeSpan()
759+
.clamp(trace.traceInfo.start, trace.traceInfo.end);
741760
},
742761
onDragEnd: (e) => {
743762
if (!this.areaDrag) {
@@ -768,7 +787,11 @@ export class TrackTreeView implements m.ClassComponent<TrackTreeViewAttrs> {
768787
.map((t) => t.uri)
769788
.filter((uri) => uri !== undefined);
770789

771-
const timeSpan = this.areaDrag.timeSpan().toTimeSpan();
790+
const timeSpan = this.areaDrag
791+
.timeSpan()
792+
.toTimeSpan()
793+
.clamp(trace.traceInfo.start, trace.traceInfo.end);
794+
772795
trace.selection.selectArea({
773796
start: timeSpan.start,
774797
end: timeSpan.end,

0 commit comments

Comments
 (0)