Skip to content

Commit

Permalink
Replace old submodule with a inline dir
Browse files Browse the repository at this point in the history
  • Loading branch information
mmocny committed Dec 3, 2023
1 parent 5e0ab97 commit 48e870c
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 1 deletion.
1 change: 0 additions & 1 deletion sandbox/event-based-event-timing
Submodule event-based-event-timing deleted from 0c5e16
10 changes: 10 additions & 0 deletions sandbox/event-based-event-timing/eventNames.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default [
'keydown',
'keyup',
'keypress',
'compositionstart',
'compositionupdate',
'compositionend',
'beforeinput',
'input',
];
19 changes: 19 additions & 0 deletions sandbox/event-based-event-timing/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>

<html lang="en">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
<link rel="stylesheet" href="./style.css">
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>

<body>
<input type="text" placeholder="test me" />

<script async type="module" src="./index.js"></script>
</body>

</html>
49 changes: 49 additions & 0 deletions sandbox/event-based-event-timing/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import eventNames from './eventNames.js';
import reportEventTiming from './stateMachine.js';

function block(ms) {
const target = performance.now() + ms;
while (performance.now() < target);
}

function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

function raf() {
return new Promise(resolve => requestAnimationFrame(resolve));
}

async function after_next_paint() {
await raf();
await Promise.race(delay(0), raf()); // TODO: AbortController
}

eventNames.forEach(eventName => {
// Add this as early as possible and use `capture` in order to measure events which stop propagation.
document.addEventListener(eventName, async function report_capture(event) {
// console.log('Event:', eventName, event.keyCode, event.key, event.code, performance.now() - event.timeStamp);
reportEventTiming(event);
}, { capture: true });

// Best effort measure of main thread duration.
document.addEventListener(eventName, async function report_bubbles(event) {
await raf();
performance.measure(eventName, { start: event.timeStamp, end: performance.now() });
}, { capture: false });
});


eventNames.forEach(eventName => {
Array.from({ length: 1 }).forEach(() => {

document.addEventListener(eventName, async function add_delay_capture() {
block(10);
}, { capture: true });

document.addEventListener(eventName, async function add_delay_bubbles() {
block(10);
}, { capture: false });

});
});
139 changes: 139 additions & 0 deletions sandbox/event-based-event-timing/stateMachine.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
const allInteractions = [];

let nextInteractionId = 0;
let composition_started = false;
const activeKeyboardPotentialInteractions = new Map();

class PotentialInteraction {
constructor(event) {
this.interactionId = 0;
this.events = [event];
}

addEvent(event) {
this.events.push(event);
// TODO: measure event duration on main thread?
}

convertToInteraction() {
this.interactionId = ++nextInteractionId;
}

complete() {
if (this.interactionId === 0) {
// console.log('Non Interaction', this.events);
} else {
// console.log('Interaction', this.interactionId, this.events);
allInteractions.push(this);
}
}
}

function createInteractiveEvent(event) {
const potentialInteraction = new PotentialInteraction(event);
return potentialInteraction;
}

function createNonInteractiveEvent(event) {
const potentialInteraction = new PotentialInteraction(event);
potentialInteraction.complete();
return potentialInteraction;
}

export default function reportEventTiming(event) {
const type = event.type;
const keyCode = event.keyCode;
const key = event.key;
const code = event.code;

switch (type) {
case 'keydown': {
if (composition_started) {
const current = createNonInteractiveEvent(event);
return;
}

if (activeKeyboardPotentialInteractions.has(keyCode)) {
const previous = activeKeyboardPotentialInteractions.get(keyCode);
if (keyCode != 229) {
previous.convertToInteraction();
}
previous.complete();
activeKeyboardPotentialInteractions.delete(keyCode);
}

const current = createInteractiveEvent(event);
activeKeyboardPotentialInteractions.set(event.keyCode, current);

break;

} case 'keyup': {
if (composition_started || !activeKeyboardPotentialInteractions.has(keyCode)) {
const current = createNonInteractiveEvent(event);
return;
}

const previous = activeKeyboardPotentialInteractions.get(keyCode);
previous.addEvent(event);
previous.convertToInteraction();
previous.complete();
activeKeyboardPotentialInteractions.delete(keyCode);

break;

} case 'compositionstart': {
composition_started = true;
for (let [keyCode, potentialInteraction] of activeKeyboardPotentialInteractions.entries()) {
// potentialInteraction.convertToInteraction();
potentialInteraction.complete();
activeKeyboardPotentialInteractions.delete(keyCode);
};

break;

} case 'compositionend': {
composition_started = false;

break;

} case 'input': {
if (!composition_started) {
const current = createNonInteractiveEvent(event);
return;
}

const current = createInteractiveEvent(event);
current.convertToInteraction();
current.complete();

break;

} default: {
break;
}
}

// TODO: "Maybe Flush" Keyboard entries
}


function findInteractionWithEventTimestamp(timestamp) {
return allInteractions.find(interaction => interaction.events.some(event => event.timeStamp === timestamp));
}

let firstObservedInteractionID = Number.POSITIVE_INFINITY;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.interactionId) {
firstObservedInteractionID = Math.min(entry.interactionId, firstObservedInteractionID);
}

const interaction = findInteractionWithEventTimestamp(entry.startTime);
if (!interaction) return;

const interactionNumber = entry.interactionId ? (entry.interactionId - firstObservedInteractionID) / 7 + 1 : 0;
console.groupCollapsed('ET.interaction:', interactionNumber, 'Event.interaction:', interaction.interactionId, 'duration:', entry.duration);
console.log(entry, interaction);
console.groupEnd();
}
}).observe({ type: 'event', buffered: true, durationThreshold: 0 });
Empty file.

0 comments on commit 48e870c

Please sign in to comment.