@atlaskit/editor-plugin-interactivity
Version:
Interactivity plugin for @atlaskit/editor-core
109 lines (102 loc) • 4.55 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import { BoundedMap } from '../collections/bounded-map';
import { eventKey, interactionEventKind } from './interaction-events';
/** Optional so a `PerformanceEntry` is assignable without a cast: the DOM typings lack the field. */
/** Entries of one interaction arrive within it, so older than the last few hundred is not needed. */
const MAX_TRACKED = 256;
/**
* Makes interactions out of what the observer reports, for one session. Entries sharing a non-zero
* `interactionId` — the browser's own definition of an interaction, the one INP filters on — are one
* interaction whose latency is the maximum `duration` among them. They arrive incrementally, so a
* latency can grow after it was first reported, and callers move what they counted rather than
* counting the interaction twice.
*
* The editor's events answer what an entry cannot: which interactions were the editor's, and how
* many there were, including the ones below the Event Timing reporting threshold.
*/
export class InteractionTracker {
/**
* Both arguments keep the interactions of the previous session out of this one.
*
* @param startsAfterInteractionId the highest id the previous tracker saw. Without it, an entry
* still arriving for that session's interaction would look new here and be counted twice.
* @param startedAt when the session opened. No id can keep out an interaction started before the
* first session of a page: Event Timing produces the entry after the event was presented, so the
* click on Edit that mounted the editor reaches an observer which subscribed while its handler
* ran. The page count the session started from has already counted it.
*/
constructor(startsAfterInteractionId = 0, startedAt = 0) {
_defineProperty(this, "interactions", new BoundedMap(MAX_TRACKED));
_defineProperty(this, "groupByEvent", new BoundedMap(MAX_TRACKED));
_defineProperty(this, "highestInteractionId", 0);
this.startsAfterInteractionId = startsAfterInteractionId;
this.startedAt = startedAt;
}
get lastInteractionId() {
return Math.max(this.highestInteractionId, this.startsAfterInteractionId);
}
/**
* Merges an entry into the interaction it belongs to.
*
* @returns what that changed, or nothing when the entry belongs to no interaction of this
* session or left the latency as it was.
*/
merge(entry) {
if (!Number.isFinite(entry.duration) || entry.duration < 0) {
return undefined;
}
const {
interactionId
} = entry;
if (!interactionId) {
return undefined;
}
// Neither check covers the other: an entry is dated by its own event rather than by the
// interaction, so the `keyup` of a press that rotated the session is dated after the
// rotation, and the click that mounted the editor has no id to recognise it by.
if (interactionId <= this.startsAfterInteractionId || entry.startTime < this.startedAt) {
return undefined;
}
this.highestInteractionId = Math.max(this.highestInteractionId, interactionId);
const tracked = this.interactions.get(interactionId);
if (tracked === undefined) {
// Taken once: an entry that only makes the interaction slower has to move its count
// within the group it was counted in, not into another one.
const group = this.groupByEvent.get(eventKey(entry.name, entry.startTime));
const interaction = {
group,
latencyMs: entry.duration
};
this.interactions.set(interactionId, interaction);
return {
type: 'new',
interactionId,
latencyMs: interaction.latencyMs,
group
};
}
// Not the slowest entry of the interaction, so its latency stands.
if (entry.duration <= tracked.latencyMs) {
return undefined;
}
const previousLatencyMs = tracked.latencyMs;
tracked.latencyMs = entry.duration;
return {
type: 'remeasured',
interactionId,
previousLatencyMs,
latencyMs: tracked.latencyMs,
group: tracked.group
};
}
recordEditorEvent(event) {
const kind = interactionEventKind(event.type);
if (!kind) {
return undefined;
}
// Every event of the interaction, because any of them can be the one Event Timing reports
// as the slowest: for a pointer press that is usually the click.
this.groupByEvent.set(eventKey(event.type, event.timeStamp), kind.group);
return kind.counts ? kind.group : undefined;
}
}