-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy pathrendering-events.html
494 lines (415 loc) · 18.9 KB
/
rendering-events.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
<!doctype html>
<head>
<meta charset="UTF-8">
<title>event-loop rendering events</title>
<style>
.question {
font-style: italic;
}
table {
font-family: sans-serif;
}
.scriptstart {
background-color: #DDD;
}
.scriptend {
background-color: #AAA;
}
.promisea {
background-color: rgba(0,0,255, 0.1);
}
.mutate {
background-color: rgba(0,0,255, 0.2);
}
.promiseb {
background-color: rgba(0,0,255, 0.3);
}
.timeouta {
background-color: rgba(255,255,0,0.1);
}
.timeoutb {
background-color: rgba(255,255,0,0.2);
}
.resize {
background-color: rgba(0,255,0,0.1);
}
.scroll {
background-color: rgba(0,255,0,0.2);
}
.matchmedia {
background-color: rgba(0,255,0,0.3);
}
.animationstart {
background-color: rgba(0,255,0,0.4);
}
.rafa {
background-color: rgba(255,255,0,0.3);
}
.rafapromise {
color: rgba(0,0,255, 0.7);
background-color: rgba(255,255,0,0.4);
}
.rafb {
background-color: rgba(255,255,0,0.5);
}
</style>
</head>
<h2>A dive into event loop specification</h2>
<p>Event loop
<a href="https://html.spec.whatwg.org/multipage/webappapis.html#event-loops">specification</a>
has two main parts:</p>
<ul>
<li>first part deals with task dispatch:
<ul>
<li>pick a task,
<li>execute it
<li>perform a microtask checkpoint
</ul>
<li>second part specifies what happens if rendering update is needed:
<ul>
<li>execute many pre-rendering steps:
<ol>
<li>resize
<li>scroll
<li>media query
<li>CSS animations
<li>fullscreen
<li>animation frame callbacks
<li>intersection observer
</ol>
<li>then paint
</ul>
</ul>
<p>
<h2>Existing browser conformance to the spec</h2>
<p>Conformance was tested with a <a href="shell.html">test page</a> that triggers a number of events at once, and records the order in which
they are executed. Fullscreen events were not tested.</p>
<p>Here are the results.</p>
<ol>
<li>Microtasks are <span class="promisea">purple</span>.
<li><span class="timeouta">Timeout</span> task is used as an indicator for the first part of the event loop.
<li><span class="rafa">rAF</span> callback is used as an indicator to the end of the event loop.
</ol>
<table id="testTable">
<thead>
<tr>
</tr>
</thead>
<tbody>
</tbody>
</table>
<script>
(function() {
var classMap = [
{ p: /script start/, c: "scriptstart"},
{ p: /script end/, c: "scriptend"},
{ p: /promise A/, c: "promisea"},
{ p: /mutate/, c: "mutate"},
{ p: /promise B/, c: "promiseb"},
{ p: /timeout A/, c: "timeouta"},
{ p: /timeout B/, c: "timeoutb"},
{ p: /scroll/, c: "scroll"},
{ p: /matchMedia/, c: "matchmedia"},
{ p: /resize/, c: "resize"},
{ p: /animationstart/, c: "animationstart"},
{ p: /rAF A$/, c: "rafa"},
{ p: /rAF A promise$/, c: "rafapromise"},
{ p: /rAF B$/, c: "rafb"},
];
function getClassName(str) {
for (var m of classMap) {
if (str.match(m.p))
return m.c;
}
console.error("baad");
return "";
}
function fillResults(browser, results) {
var table = document.querySelector("#testTable");
var title = document.createElement('td');
title.innerText = browser;
table.querySelector('thead').querySelector('tr').appendChild(title);
var tbody = table.querySelector('tbody');
for (var i=0; i<results.length; ++i) {
var td = document.createElement('td');
td.classList.add(getClassName(results[i]));
td.innerText = results[i];
if (i >= tbody.children.length)
tbody.appendChild(document.createElement('tr'));
tbody.children[i].appendChild(td);
}
};
fillResults("Standard", [
"script start",
"script end",
"promise A",
"mutate",
"promise B",
"timeout A",
"timeout B",
"resize",
"scroll",
"matchMedia",
"animationstart",
"rAF A",
"rAF A promise",
"rAF B"
]);
fillResults("Chrome 70", [
"0.1 script start",
"1.4 script end",
"1.6 promise A",
"1.8 mutate",
"2.1 promise B",
"4 timeout A",
"4.2 timeout B",
"14 scroll",
"14.2 matchMedia",
"14.4 resize",
"14.6 animationstart",
"14.7 rAF A",
"15 rAF A promise",
"15.1 rAF B",
]);
fillResults("Safari 11", [
"0 script start",
"2 script end",
"2 promise A",
"3 mutate",
"3 promise B",
"3 timeout A",
"3 timeout B",
"3 scroll",
"4 matchMedia",
"5 animationstart",
"6 resize",
"15 rAF A",
"15 rAF A promise",
"15 rAF B",
]);
fillResults("Edge 42.1713", [
"0 script start",
"2.6 script end",
"2.7 promise A",
"2.8 mutate",
"3 promise B",
"6.8 animationstart",
"8.3 matchMedia",
"8.3 resize",
"11.2 timeout A",
"11.2 timeout B",
"15.2 scroll",
"15.5 rAF A",
"15.6 rAF A promise",
"15.6 rAF B"
]);
fillResults("Firefox 60.2", [
"0 script start",
"5 script end",
"5 promise A",
"6 mutate",
"6 promise B",
"7 matchMedia",
"10 timeout A",
"10 timeout B",
"15 resize",
"16 scroll",
"17 animationstart",
"17 rAF A",
"17 rAF A promise",
"17 rAF B"
]);
})();
</script>
<h2>Predictabilty</h2>
<p>The initial test suite was run in Sep 2016, and found many inconsistencies
between implementations. I reran the tests in Oct 2018, and predictablity
is much better now.</p>
<h3>1. Event ordering</h3>
<p>Every browser orders events a little differently, and none of them completely follow the spec.</p>
<p>Ordering of promises, mutations, and rAF are consistent among all browsers.</p>
<p>All browsers fire the rest of the events in the correct part of the event loop,
but the event order differs.</p>
<p class="question">either: Should we all fix our ordering to match the spec?</p>
<p class="question">or: Should the spec be fixed instead?</p>
<p class="question">What are the reasons for the current ordering in the spec?</p>
<h2>Other issues</h2>
<h3>Specification maintenability</h3>
<p>Every specification that needs a slot in the paint cycle must add another line to event loop spec.
</p>
<p>This is fragile. Currently, "run CSS animations and send events", and
"run the fullscreen rendering steps" references are missing links. External
spec has changed, but event loop was not updated.</p>
<p>We can rewrite the specification. Instead of it being a list of steps, it can become an ordered task queue.</p>
<p class="question">Should we rewrite existing paint spec as an ordered task queue?</p>
<p>If we do, there are few design questions</p>
<p class="question">Task registration is tricky. External specs will have to specify task registration.</p>
<p class="question">Sort order: tasks still need to be sorted. This sort order must be centralized, global?</p>
<h3>Throttling other events</h3>
<p>Other events might benefit from throttling. ex: mousemove, pointermove, xhr progress.</p>
<p>Existing spec does not define any mechanism for throttled events. Touch events
are currently throttled ad-hoc (Edge, Safari, Chrome for Android?).</p>
<p>Should we do it, and how we do event throttling has been
<a href="https://github.com/w3c/pointerevents/issues/9">discussed</a>, but it is not
settled. The main issue is what to do about dropped events. Leading proposal is
to keep dropped events available as <a href="https://github.com/w3c/pointerevents/issues/22">getCoalescedEvents</a>
<p class="question">Throttling is already being implemented. Are there things we can agree on we could spec?</p>
</p>
<h3>Event suppression</h3>
<p>We should be aware that spec allows suppression of rendering steps for hidden windows.</p>
<p>This might cause trouble if we move non-suppresable events into rendering slot.</p>
<h2>Related work</h2>
<p><a href="http://bit.ly/rendering-pipeline">Rendering Processing Model</a>:
Rendering pipeline is intertwined with the event loop. Also discusses ordering
of style/layout updates.</p>
<p>Task Scheduling for w3ctag: <a href="https://github.com/w3ctag/spec-reviews/issues/72">w3ctag</a></p>
<p><a href="https://docs.google.com/document/d/1aM9AqyYuceRHOmsJZXFKcgi_D4jvFAymYvI36OEwIpI/edit">rAF aligned input events</a> in <a https://www.chromestatus.com/features/4892570664566784">chrome</a></p>
<h2>Existing spec</h2>
<pre>
Existing spec:
### What does existing spec do?
5) Run the resize steps
If doc’s viewport has had its width or height changed (e.g. as a result of the user resizing the browser window, or changing the page zoom scale factor, or an iframe element’s dimensions are changed) since the last time these steps were run, fire an event named resize at the Window object associated with doc.
6) Run the scroll steps
For each item target in doc’s pending scroll event targets, in the order they were added to the list, run these substeps:
If target is a Document, fire an event named scroll that bubbles at target.
Otherwise, fire an event named scroll at target.
Empty doc’s pending scroll event targets.
Fires an event, first target gets notified first
7) Evaluate media queries and report changes
For each MediaQueryList object target that has doc as its document, in the order they were created, oldest first, run these substeps:
If target’s matches state has changed since the last time these steps were run, dispatch a new event to target using the MediaQueryList interface, with its type attribute initialized to change, its isTrusted attribute initialized to true, its media attribute initialized to target’s media, and its matches attribute initialized to target’s matches state.
8) run CSS animations and send events
Not specified.
9) run the fullscreen rendering steps
Not specified.
10) run the animation frame callbacks
For each entry in callbacks, in order: invoke the callback, passing now as the only argument, and if an exception is thrown, report the exception.
11) run the update intersection observations steps
Let observer list be a list of all IntersectionObservers whose root is in the DOM tree of document.
For each observer in observer list:
12) Paint
## Predictablility OKRs
Concrete standards proposal and tests for event loop behavior
https://github.com/atotic/event-loop
Specify that some events fire just before rAF
Engage other vendors in discussion about which events should be defined to be coupled to rAF, and to what extent their order should be defined Write interop tests for rAF-coupled events
##Existing event loop spec##
<p>An <span>event loop</span> must continually run through the following steps for as long as it
exists:</p>
<ol>
<!-- lots of places in the spec refer to "step 1" -->
<li id="step1">
<p>Select the oldest <span data-x="concept-task">task</span> on one of the <span>event
loop</span>'s <span data-x="task queue">task queues</span>, if any, ignoring, in the case of a
<span>browsing context</span> <span>event loop</span>, tasks whose associated
<code>Document</code>s are not <span>fully active</span>. The user agent may pick any <span>task
queue</span>. If there is no task to select, then jump to the <i>microtasks</i> step below.</p>
</li>
<!-- note: reference to 'previous step' below -->
<li><p>Set the <span>event loop</span>'s <span>currently running task</span> to the <span
data-x="concept-task">task</span> selected in the previous step.</p></li>
<li><p><i>Run</i>: Run the selected <span data-x="concept-task">task</span>.</p></li>
<li><p>Set the <span>event loop</span>'s <span>currently running task</span> back to
null.</p></li>
<li><p>Remove the task that was run in the <i>run</i> step above from its <span>task
queue</span>.</p></li>
<li><p><i>Microtasks</i>: <span>Perform a microtask checkpoint</span>.</p></li>
<li>
<p><i>Update the rendering</i>: If this <span>event loop</span> is a <span>browsing
context</span> <span>event loop</span> (as opposed to a <a href="#workers">worker</a>
<span>event loop</span>), then run the following substeps.</p>
<ol>
<li><p>Let <var>now</var> be the value that would be returned by the <code>Performance</code>
object's <code data-x="dom-Performance-now">now()</code> method. <ref spec=HRT></p>
<li>
<p>Let <var>docs</var> be the list of <code>Document</code> objects associated with the
<span>event loop</span> in question, sorted arbitrarily except that the following conditions
must be met:</p>
<ul>
<li><p>Any <code>Document</code> <var>B</var> that is <span>nested through</span> a
<code>Document</code> <var>A</var> must be listed after <var>A</var> in the list.</p></li>
<li><p>If there are two documents <var>A</var> and <var>B</var> whose <span
data-x="concept-document-bc">browsing contexts</span> are both <span data-x="nested browsing
context">nested browsing contexts</span> and their <span data-x="browsing context
container">browsing context containers</span> are both elements in the same
<code>Document</code> <var>C</var>, then the order of <var>A</var> and <var>B</var> in the
list must match the relative <span>tree order</span> of their respective <span
data-x="browsing context container">browsing context containers</span> in
<var>C</var>.</p></li>
</ul>
<p>In the steps below that iterate over <var>docs</var>, each <code>Document</code> must be
processed in the order it is found in the list.</p>
</li>
<li>
<p>If there are <span data-x="top-level browsing context">top-level browsing contexts</span>
<var>B</var> that the user agent believes would not benefit from having their rendering
updated at this time, then remove from <var>docs</var> all <code>Document</code> objects whose
<span data-x="concept-document-bc">browsing context</span>'s <span>top-level browsing
context</span> is in <var>B</var>.</p>
<div class="note">
<p>Whether a <span>top-level browsing context</span> would benefit from having its rendering
updated depends on various factors, such as the update frequency. For example, if the browser
is attempting to achieve a 60Hz refresh rate, then these steps are only necessary every 60th
of a second (about 16.7ms). If the browser finds that a <span>top-level browsing
context</span> is not able to sustain this rate, it might drop to a more sustainable 30Hz for
that set of <code>Document</code>s, rather than occasionally dropping frames. (This
specification does not mandate any particular model for when to update the rendering.)
Similarly, if a <span>top-level browsing context</span> is in the background, the user agent
might decide to drop that page to a much slower 4Hz, or even less.</p>
<p>Another example of why a browser might skip updating the rendering is to ensure certain
<span data-x="concept-task">tasks</span> are executed immediately after each other, with only
<span data-x="perform a microtask checkpoint">microtask checkpoints</span> interleaved (and
without, e.g., <span data-x="run the animation frame callbacks">animation frame
callbacks</span> interleaved). For example, a user agent might wish to coalesce timer
callbacks together, with no intermediate rendering updates.</p>
</div>
</li>
<li>
<p>If there are a <span data-x="nested browsing context">nested browsing contexts</span>
<var>B</var> that the user agent believes would not benefit from having their rendering
updated at this time, then remove from <var>docs</var> all <code>Document</code> objects whose
<span data-x="concept-document-bc">browsing context</span> is in <var>B</var>.</p>
<p class="note">As with <span data-x="top-level browsing context">top-level browsing
contexts</span>, a variety of factors can influence whether it is profitable for a browser to
update the rendering of <span data-x="nested browsing context">nested browsing
contexts</span>. For example, a user agent might wish to spend less resources rendering
third-party content, especially if it is not currently visible to the user or if resources are
constrained. In such cases, the browser could decide to update the rendering for such content
infrequently or never.</p>
</li>
<li><p>For each <span>fully active</span> <code>Document</code> in <var>docs</var>, <span>run the resize
steps</span> for that <code>Document</code>, passing in <var>now</var> as the timestamp. <ref
spec="CSSOMVIEW"/></p></li>
<li><p>For each <span>fully active</span> <code>Document</code> in <var>docs</var>, <span>run the scroll
steps</span> for that <code>Document</code>, passing in <var>now</var> as the timestamp. <ref
spec="CSSOMVIEW"/></p></li>
<li><p>For each <span>fully active</span> <code>Document</code> in <var>docs</var>, <span>evaluate media queries
and report changes</span> for that <code>Document</code>, passing in <var>now</var> as the timestamp. <ref
spec="CSSOMVIEW"/></p></li>
<li><p>For each <span>fully active</span> <code>Document</code> in <var>docs</var>, <dfn>run CSS animations and send
events</dfn> for that <code>Document</code>, passing in <var>now</var> as the timestamp. <ref
spec="CSSANIMATIONS"/></p></li>
<li><p>For each <span>fully active</span> <code>Document</code> in <var>docs</var>, <dfn>run the fullscreen rendering
steps</dfn> for that <code>Document</code>, passing in <var>now</var> as the timestamp. <ref
spec="FULLSCREEN"/></p></li>
<li><p>For each <span>fully active</span> <code>Document</code> in <var>docs</var>, <span>run the animation frame
callbacks</span> for that <code>Document</code>, passing in <var>now</var> as the
timestamp.</p></li>
<li><p>For each <span>fully active</span> <code>Document</code> in <var>docs</var>, <span>run the update
intersection observations steps</span> for that <code>Document</code>, passing in <var>now</var> as the
timestamp. <ref spec="INTERSECTIONOBSERVER"/></p></li>
<li><p>For each <span>fully active</span> <code>Document</code> in <var>docs</var>, update the
rendering or user interface of that <code>Document</code> and its <span
data-x="concept-document-bc">browsing context</span> to reflect the current state.</p></li>
</ol>
</li>
<li><p>If this is a <a href="#workers">worker</a> <span>event loop</span> (i.e. one running for a
<code>WorkerGlobalScope</code>), but there are no <span data-x="concept-task">tasks</span> in the
<span>event loop</span>'s <span data-x="task queue">task queues</span> and the
<code>WorkerGlobalScope</code> object's <span
data-x="dom-WorkerGlobalScope-closing">closing</span> flag is true, then destroy the <span>event
loop</span>, aborting these steps, resuming the <span>run a worker</span> steps described in the
<a href="#workers">Web workers</a> section below.</p></li>
<li><p>Return to the <a href="#step1">first step</a> of the <span>event loop</span>.</p></li>
</pre>