Event Delegation to Worker and Worklet
majidvp@, nzolghadr@
Outline
A Simplified Anatomy of Animation Frame
Event Handling
Dispatch rAF aligned input events and run handlers
Animation Frame
Run user rAF callbacks
Style
Compute new style based on scripted changes and other animations
Layout | Paint
Position all element, and paint them appropriately
Raster | Compositing
Raster painted content and composite (Parallelize and use GPU as much as possible)
Input
Pixels
Mostly Single Thread
Event Handling
Dispatch input events and run event handlers
Animation Frame
Run user rAF callbacks
Style
Compute new style based on scripted changes and other animations
Layout | Paint
Position all element, and paint them appropriately
Raster | Compositing
Raster painted content and composite (Parallelize and use GPU as much as possible)
Input
Pixels
Worker
New Outputs in Worker and Worklets
Event Handling
Dispatch input events and run event handlers
Animation Frame
Run user rAF callbacks
Style
Compute new style based on scripted changes and other animations
Layout | Paint
Position all element, and paint them appropriately
Raster | Compositing
Raster painted content and composite (Parallelize and use GPU as much as possible)
Input
Pixels
Worker + Offscreen Canvas
Animation Worklet
Audio Worklet
Bottleneck
Handling Input in Worker/Worklet?
Event Handling
Dispatch input events and run event handlers
Animation Frame
Run user rAF callbacks
Style
Compute new style based on scripted changes and other animations
Layout | Paint
Position all element, and paint them appropriately
Raster | Compositing
Raster painted content and composite (Parallelize and use GPU as much as possible)
Input
Pixels
Worker + Offscreen Canvas
Animation Worklet
Audio Worklet
Input
Use Cases
Demo - Main-thread forwarding event using postMessage
Proposal - Passive Event Handling in Worker
Proposed API
var t1 =
document.getElementById("target");
var worker = new Worker("worker.js");
worker.addEventTarget(t1);
self.addEventListener("eventtargetadded",(event) => {
// target is t1
event.target.addEventListener(
"pointermove",
(e) => {
// Handle event e
}, {capture: true}
});
Main
Worker
interface mixin EventDelegate {
void addEventTarget(EventTarget target, EventDelegationOptions? option);
void removeEventTarget(EventTarget target);
};
// This dictionary allows us to add options in the future if needed.
dictionary EventDelegationOptions {
any context; // To accommodate for any additional context data from the other thread.
};
interface Worker includes EventDelegate;
interface WorkletAnimation includes EventDelegate;
interface AudioWorkletNode includes EventDelegate;
// The opaque proxy representing an event target in worker.
interface DelegatedEventTarget : EventTarget {};
Scrubbing DOM from Events
Hit-Testing Off Main
Double Handling Problem
Polyfillablity
Easy to polyfill using postMessage.
What’s Next?
Thanks!
Why participate in event propagation?
Alternative Forking Model - fork at root and bypass event propagation altogether
Appendix - Example OffScreen Canvas Drawing - Main
// This canvas could be embedded within an iframe if only the root window events are allowed to be delegated to the worker.�<canvas id="canvas"></canvas>��<script>� var worker = new Worker("worker.js");� var canvas = document.getElementById("canvas")�� var handler = canvas.transferControlToOffscreen();� worker.postMessage({canvas: handler}, [handler]);� worker.addEventTarget(canvas);�</script>
Appendix - Example OffScreen Canvas Drawing - Worker
var context;��addEventListener("message", (msg) => {� if (msg.data.canvas)� context = msg.data.canvas.getContext("2d");�});��addEventListener("eventtargetadded", ({target}) => {� target.addEventListener("pointermove", onPointerMove);�});
addEventListener("eventtargetremoved", ({target}) => {� target.removeEventListener("pointermove", onPointerMove);�});
function onPointerMove(event){� // Use event.clientX/Y or offsetX/Y to draw things on the context.� context.beginPath();� context.arc(event.offsetX, event.offsetY, 5, 0, 2.0* Math.PI, false);� context.closePath();� context.fill();� context.commit();�}