fix: disambiguate repeated-target event timing
This commit is contained in:
@@ -30,6 +30,7 @@ export interface ScriptedActionSample {
|
|||||||
id: string;
|
id: string;
|
||||||
target: string;
|
target: string;
|
||||||
dispatchTime: number;
|
dispatchTime: number;
|
||||||
|
captureEndTime: number | null;
|
||||||
acknowledgementCount: number;
|
acknowledgementCount: number;
|
||||||
mutationTime: number | null;
|
mutationTime: number | null;
|
||||||
doubleRafTime: number | null;
|
doubleRafTime: number | null;
|
||||||
@@ -111,6 +112,7 @@ export const evaluateEventToNextPaint = (
|
|||||||
entry.target === action.target
|
entry.target === action.target
|
||||||
&& entry.startTime >= action.dispatchTime
|
&& entry.startTime >= action.dispatchTime
|
||||||
&& entry.startTime <= action.dispatchTime + 500
|
&& entry.startTime <= action.dispatchTime + 500
|
||||||
|
&& (action.captureEndTime === null || entry.startTime < action.captureEndTime)
|
||||||
));
|
));
|
||||||
if (matching.length === 0) return 16;
|
if (matching.length === 0) return 16;
|
||||||
const groups = new Map<number, number>();
|
const groups = new Map<number, number>();
|
||||||
@@ -270,7 +272,9 @@ export const installAuthorizedPerformanceInstrumentation = async (): Promise<voi
|
|||||||
for (const entry of list.getEntries()) {
|
for (const entry of list.getEntries()) {
|
||||||
const timed = entry as PerformanceEntry & { interactionId?: number; target?: Node | null };
|
const timed = entry as PerformanceEntry & { interactionId?: number; target?: Node | null };
|
||||||
for (const record of actionRecords.values()) {
|
for (const record of actionRecords.values()) {
|
||||||
if (timed.target === record.element) {
|
if (timed.target === record.element
|
||||||
|
&& timed.startTime >= record.dispatchTime
|
||||||
|
&& (record.captureEndTime === null || timed.startTime < record.captureEndTime)) {
|
||||||
(record.eventEntries as EventTimingSample[]).push({
|
(record.eventEntries as EventTimingSample[]).push({
|
||||||
target: record.id,
|
target: record.id,
|
||||||
startTime: timed.startTime,
|
startTime: timed.startTime,
|
||||||
@@ -360,10 +364,15 @@ export const installAuthorizedPerformanceInstrumentation = async (): Promise<voi
|
|||||||
if (!exactActionIds.has(id) || actionRecords.has(id)) throw new Error('The performance action is invalid or duplicated.');
|
if (!exactActionIds.has(id) || actionRecords.has(id)) throw new Error('The performance action is invalid or duplicated.');
|
||||||
const element = document.querySelector(selector);
|
const element = document.querySelector(selector);
|
||||||
if (!(element instanceof Element)) throw new Error('The performance action target is unavailable.');
|
if (!(element instanceof Element)) throw new Error('The performance action target is unavailable.');
|
||||||
|
const dispatchTime = performance.now();
|
||||||
|
for (const record of actionRecords.values()) {
|
||||||
|
if (record.element === element && record.captureEndTime === null) record.captureEndTime = dispatchTime;
|
||||||
|
}
|
||||||
actionRecords.set(id, {
|
actionRecords.set(id, {
|
||||||
id,
|
id,
|
||||||
target: id,
|
target: id,
|
||||||
dispatchTime: performance.now(),
|
dispatchTime,
|
||||||
|
captureEndTime: null,
|
||||||
acknowledgementCount: 0,
|
acknowledgementCount: 0,
|
||||||
mutationTime: null,
|
mutationTime: null,
|
||||||
doubleRafTime: null,
|
doubleRafTime: null,
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ describe('performance metric math', () => {
|
|||||||
id,
|
id,
|
||||||
target: `target-${id}`,
|
target: `target-${id}`,
|
||||||
dispatchTime: index * 1_000,
|
dispatchTime: index * 1_000,
|
||||||
|
captureEndTime: null,
|
||||||
acknowledgementCount: 1,
|
acknowledgementCount: 1,
|
||||||
mutationTime: index * 1_000 + 20,
|
mutationTime: index * 1_000 + 20,
|
||||||
doubleRafTime: index * 1_000 + 40,
|
doubleRafTime: index * 1_000 + 40,
|
||||||
@@ -74,6 +75,52 @@ describe('performance metric math', () => {
|
|||||||
expect(() => evaluateEventToNextPaint([{ ...actions[0]!, acknowledgementCount: 2 }, ...actions.slice(1)])).toThrow(/acknowledgement/u);
|
expect(() => evaluateEventToNextPaint([{ ...actions[0]!, acknowledgementCount: 2 }, ...actions.slice(1)])).toThrow(/acknowledgement/u);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('excludes a later compressed interaction when sequential actions share one DOM target', () => {
|
||||||
|
const ids = ['start', 'volume', 'mute', 'unmute', 'pause', 'resume', 'toolkit', 'contact'] as const;
|
||||||
|
const actions = ids.map((id, index): ScriptedActionSample => ({
|
||||||
|
id,
|
||||||
|
target: id,
|
||||||
|
dispatchTime: index === 4 ? 400 : index === 5 ? 450 : index * 1_000,
|
||||||
|
captureEndTime: index === 4 ? 450 : null,
|
||||||
|
acknowledgementCount: 1,
|
||||||
|
mutationTime: (index === 4 ? 400 : index === 5 ? 450 : index * 1_000) + 5,
|
||||||
|
doubleRafTime: (index === 4 ? 400 : index === 5 ? 450 : index * 1_000) + 10,
|
||||||
|
eventTimingSupported: true,
|
||||||
|
eventEntries: id === 'pause'
|
||||||
|
? [
|
||||||
|
{ target: 'pause', startTime: 410, duration: 30, interactionId: 41 },
|
||||||
|
{ target: 'pause', startTime: 460, duration: 40, interactionId: 42 },
|
||||||
|
]
|
||||||
|
: id === 'resume'
|
||||||
|
? [{ target: 'resume', startTime: 460, duration: 40, interactionId: 42 }]
|
||||||
|
: [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(evaluateEventToNextPaint(actions).values.slice(4, 6)).toEqual([30, 40]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('still rejects genuinely ambiguous interaction groups within one capture window', () => {
|
||||||
|
const ids = ['start', 'volume', 'mute', 'unmute', 'pause', 'resume', 'toolkit', 'contact'] as const;
|
||||||
|
const actions = ids.map((id, index): ScriptedActionSample => ({
|
||||||
|
id,
|
||||||
|
target: id,
|
||||||
|
dispatchTime: index * 1_000,
|
||||||
|
captureEndTime: null,
|
||||||
|
acknowledgementCount: 1,
|
||||||
|
mutationTime: index * 1_000 + 5,
|
||||||
|
doubleRafTime: index * 1_000 + 10,
|
||||||
|
eventTimingSupported: true,
|
||||||
|
eventEntries: id === 'pause'
|
||||||
|
? [
|
||||||
|
{ target: 'pause', startTime: 4_010, duration: 30, interactionId: 41 },
|
||||||
|
{ target: 'pause', startTime: 4_020, duration: 40, interactionId: 42 },
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(() => evaluateEventToNextPaint(actions)).toThrow(/ambiguous/u);
|
||||||
|
});
|
||||||
|
|
||||||
it('evaluates scene continuity at interval boundaries and callback coverage', () => {
|
it('evaluates scene continuity at interval boundaries and callback coverage', () => {
|
||||||
const result = evaluateSceneContinuity(
|
const result = evaluateSceneContinuity(
|
||||||
[{ start: 0, end: 100 }],
|
[{ start: 0, end: 100 }],
|
||||||
|
|||||||
Reference in New Issue
Block a user