├── README.md ├── microtask.md ├── rendering-events.html ├── shell.css ├── shell.html └── test.html /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Event loop explainer 3 | 4 | ## What is the event loop? 5 | 6 | The event loop is the mastermind that orchestrates: 7 | 8 | * **what** JavaScript code gets executed 9 | 10 | * **when** does it run 11 | 12 | * when do layout and style get updated 13 | 14 | * render: when do DOM changes get rendered 15 | 16 | It is formally specified in whatwg's [HTML standard](https://html.spec.whatwg.org/multipage/webappapis.html#event-loops). 17 | 18 | The current specification is incomplete. The event loop behavior differs between browsers. Further work is being done to clarify the issues: 19 | 20 | * [Rendering Processing Model](https://docs.google.com/document/d/1Mw6qNw8UAEfW96CXaXRVYPPZjqQS3YdK7v57wFttAhs/edit?pref=2&pli=1#) 21 | 22 | * [Discussion on event order](https://github.com/w3c/pointerevents/issues/9) 23 | 24 | This document is a developer-oriented description of what event loop does. It tries to be readable, and might skip over edge cases for clarity. 25 | 26 | ## Event loop description 27 | 28 | This is what the spec says: 29 | 30 | eventLoop = { 31 | taskQueues: { 32 | events: [], // UI events from native GUI framework 33 | parser: [], // HTML parser 34 | callbacks: [], // setTimeout, requestIdleTask 35 | resources: [], // image loading 36 | domManipulation[] 37 | }, 38 | 39 | microtaskQueue: [ 40 | ], 41 | 42 | nextTask: function() { 43 | // Spec says: 44 | // "Select the oldest task on one of the event loop's task queues" 45 | // Which gives browser implementers lots of freedom 46 | // Queues can have different priorities, etc. 47 | for (let q of taskQueues) 48 | if (q.length > 0) 49 | return q.shift(); 50 | return null; 51 | }, 52 | 53 | executeMicrotasks: function() { 54 | if (scriptExecuting) 55 | return; 56 | let microtasks = this.microtaskQueue; 57 | this.microtaskQueue = []; 58 | for (let t of microtasks) 59 | t.execute(); 60 | }, 61 | 62 | needsRendering: function() { 63 | return vSyncTime() && (needsDomRerender() || hasEventLoopEventsToDispatch()); 64 | }, 65 | 66 | render: function() { 67 | dispatchPendingUIEvents(); 68 | resizeSteps(); 69 | scrollSteps(); 70 | mediaQuerySteps(); 71 | cssAnimationSteps(); 72 | fullscreenRenderingSteps(); 73 | 74 | animationFrameCallbackSteps(); 75 | 76 | 77 | while (resizeObserverSteps()) { 78 | updateStyle(); 79 | updateLayout(); 80 | } 81 | intersectionObserverObserves(); 82 | paint(); 83 | } 84 | } 85 | 86 | while(true) { 87 | task = eventLoop.nextTask(); 88 | if (task) { 89 | task.execute(); 90 | } 91 | eventLoop.executeMicrotasks(); 92 | if (eventLoop.needsRendering()) 93 | eventLoop.render(); 94 | } 95 | 96 | 97 | 98 | ## Close reading of the spec 99 | 100 | Now we understand the spec. 101 | 102 | Event loop does not say much about when events are dispatched: 103 | 104 | 1. Events on the same queue are dispatched in order. 105 | 106 | 2. Events can be dispatched directly, bypassing the event loop task queues. 107 | 108 | 3. Microtasks get executed immediately after a task. 109 | 110 | 4. Render part of the loop gets executed on vSync, and delivers events in the following order: 111 | 1. dispatch pending UI events 112 | 113 | 2. 'resize' event 114 | 115 | 3. 'scroll' event 116 | 117 | 4. mediaquery listeners 118 | 119 | 5. 'CSSAnimation' events 120 | 121 | 6. Observers 122 | 123 | 7. rAF 124 | 125 | ## Pending UI events 126 | 127 | There are two classes of UI events: 128 | 129 | 1. Discrete - those that aren't continuous (eg. mousedown, mouseup, 130 | touchstart, touchend) 131 | 132 | 2. Continuous - mousewheel, wheel, mousemove, pointermove, touchmove. 133 | 134 | Continuous events may be coalesced (updating positions, magnitude) with 135 | matching events (that haven't yet been dispatched) while being held in the 136 | UI event task queue. 137 | 138 | Ordering of discrete and continuous events must be preserved. Discrete events 139 | should be dispatched right as soon as possible when received from the 140 | hardware. Continuous events can be held and dispatched in the render part 141 | of the event loop. If a discrete event is received, all continuous events 142 | in the task queue should run immediately to prevent the discrete event 143 | from being delayed. 144 | 145 | ## What really happens 146 | 147 | We've built a [test page]( https://rawgit.com/atotic/event-loop/master/shell.html). The page simultaneously generates (listed in order): 148 | 149 | * 2 requestAnimationFrames 150 | 151 | * 2 setTimeout(0) 152 | 153 | * 2 chained promises 154 | 155 | * CSSAnimation 156 | 157 | * scroll event 158 | 159 | * resize event 160 | 161 | We compare when these callbacks get executed to what is expected by the 162 | spec. Here are the results from major browsers. Spoiler: they are all different. Pretty graphical analysis also available [here](https://cdn.rawgit.com/atotic/event-loop/caa3cfd4/rendering-events.html) 163 | 164 | ### Chrome 51 165 | 166 | 301.66 script start 167 | 302.91 script end 168 | 303.31 promise 0 169 | 303.86 promise 1 170 | 305.43 timeout 0 171 | 305.83 timeout 1 172 | 316.21 scroll 173 | 316.62 matchMedia 174 | 316.92 resize 175 | 317.29 animationstart 176 | 317.62 rAF 0 177 | 318 rAF 0 promise 178 | 318.31 rAF 1 179 | 17ms 180 | 181 | Chrome behavior almost matches the spec. Failure: 182 | 183 | * scroll event fires before resize event. It looks like Chrome puts scroll and resize on the same task queue. 184 | 185 | Chrome's conformance was expected, as spec was written with large 186 | input by the Chrome team :) 187 | 188 | ### Firefox 47 189 | 190 | 322 script start 191 | 323.47 animationstart 192 | 324.58 script end 193 | 326.2 scroll 194 | 327.96 matchMedia 195 | 330.03 resize 196 | 338.39 promise 0 197 | 339.13 promise 1 198 | 339.94 timeout 0 199 | 341.11 timeout 1 200 | 356.52 rAF 0 201 | 357.22 rAF 1 202 | 362.27 rAF 0 promise 203 | 40ms 204 | 205 | Firefox diverges from the spec. 206 | 207 | * scroll event fires before resize event. 208 | 209 | * Promises are not on a microtask queue, but a different task queue. Instead of executing immediately after `scriptEnd`, they execute after 210 | `resize`, and after `rAF 1` instead of `rAF 0`. 211 | 212 | * Timeout fires in the middle of the render part of the loop. According to spec, it should fire either before, or after, but not in the middle. 213 | 214 | * CSSAnimation event is delivered immediately, and not inside the 215 | `render` block. 216 | 217 | ### Safari 9.1.2 218 | 219 | 328.17 script start 220 | 329.32 script end 221 | 329.53 promise 0 222 | 329.96 scroll 223 | 330.24 timeout 0 224 | 330.38 timeout 1 225 | 330.5 promise 1 226 | 330.81 matchMedia 227 | 332.57 animationstart 228 | 332.88 resize 229 | 344.67 rAF 0 230 | 344.95 rAF 0 promise 231 | 345.09 rAF 1 232 | 17ms 233 | 234 | Safari diverges from the spec: 235 | 236 | * scroll event fires before resize event. 237 | 238 | * Chained Promise does not execute immediately, it happens after timeout. 239 | 240 | * timeout fires in the middle of `render` block, between `scroll` and `matchMedia`. 241 | 242 | ### Microsoft Edge XXXX 243 | 244 | 510.09 script start 245 | 512.08 script end 246 | 512.4 promise 0 247 | 512.69 promise 1 248 | 512.89 timeout 0 249 | 513.06 timeout 1 250 | 513.3 animationstart 251 | 526.24 scroll 252 | 528.1 rAF 0 253 | 528.44 rAF 0 promise 254 | 528.67 rAF 1 255 | 528.93 matchMedia 256 | 529.17 resize 257 | 19ms 258 | 259 | Microsoft diverges from the spec. It does well with Promises and timeout, 260 | but mostly ignores `render` event ordering from the spec, sprinking 261 | different events all over. 262 | 263 | ## Conclusion 264 | 265 | There are significant differences between browsers' implementation of the event loop. The spec is not helpful in determining what actually happens. 266 | 267 | It will be hard for developers to try to understand when is their 268 | arbitrary code going to get executed. If order of execution is important, 269 | be careful from the start: architect your ordered callbacks using only 270 | primitives whose behavior is well understood. 271 | 272 | Here are a few rules of thumb: 273 | 274 | * callbacks of the same type always execute in order requested. Callbacks of different types execute in unspecified order. Therefore, you should not mix callback types in your callback chain. 275 | 276 | * rAF always fire before rendering, no more than 60 times/sec. If your callbacks are manipulating DOM, use rAF, so that you do not disturb layout unless painting. 277 | 278 | * timeout(0) fires "in the next task loop". Use sparingly, only if you need high-frequency callbacks that are not tied to drawing. 279 | 280 | * Promises fire "sooner than timeout". 281 | 282 | ## Chrome implementation of the event loop 283 | 284 | ### Events 285 | 286 | DOM has 250 event types. See https://cs.chromium.org/chromium/src/out/Debug/gen/blink/core/EventTypeNames.h 287 | 288 | To find out how any particular event is handled, follow the code search links in code searh. 289 | 290 | Events are dispatched by following methods: 291 | 292 | #### 1. DOMWindowEventQueue 293 | 294 | Triggered by a timer 295 | 296 | Sample events: window.storage, window.hashchange, document.selectionchange 297 | 298 | #### 2. ScriptedAnimationController 299 | 300 | Triggered by BeginMainFrame function that's called by Frame. 301 | 302 | Also manages requestAnimationFrame requests 303 | 304 | sample events: Animation.finish, Animation.cancel, CSSAnimation.animationstart, CSSAnimation.animationiteration(CSSAnimation) 305 | 306 | #### 3. Custom dispatch 307 | 308 | Triggers vary: OS events, timers, document/element lifecycle events. 309 | 310 | Custom dispatch event do not pass through queues, they are fired 311 | directly. 312 | 313 | There are no globally applicable delivery guarantees for custom 314 | events. Specific events might have event-specific guarantees 315 | about ordering. 316 | 317 | #### 4. Microtask queue 318 | 319 | Triggered most often by EndOfTaskRunner.didProcessTask(). 320 | 321 | Tasks are run by TaskQueueManager. They are used internally by other dispatchers to schedule execution. 322 | 323 | Microtask queue executes whenever task completes. 324 | 325 | sample events: Image.onerror, Image.onload 326 | 327 | Microtasks also contain Promise callbacks 328 | 329 | #### 5. Main Thread Event Queue 330 | 331 | Implementation of the UI event task queue. Events are coalesced in this queue. 332 | This queue also handles requesting the vSync when continuous events have 333 | been placed in this task queue. 334 | 335 | 336 | ### [Timers](https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Timers) 337 | 338 | #### [requestIdleCallback](https://w3c.github.io/requestidlecallback/#the-requestidlecallback-methodsetTimeout) 339 | 340 | Triggered by internal timer when browser is idle. 341 | 342 | #### [requestAnimationFrame](https://html.spec.whatwg.org/multipage/webappapis.html#animation-frames) 343 | 344 | Triggered by ScriptedAnimationController, that also handles events. 345 | 346 | #### [Timers](https://html.spec.whatwg.org/multipage/webappapis.html#timers:dom-setinterval): setTimeout, setInterval 347 | 348 | Triggered by WebTaskRunner, which runs on TaskQueue primitive. 349 | 350 | ### Observers 351 | 352 | Observers watch for changes, and report all observations at once. 353 | 354 | There are two ways of watching for changes: 355 | 356 | 1. Push: trap changes when they happen, and push to observation queue. 357 | 358 | 2. Poll: poll for changes when it is time to broadcast. 359 | 360 | #### [MutationObserver](https://dom.spec.whatwg.org/#mutation-observers) 361 | 362 | Push-based. 363 | 364 | Observations broadcast is placed on microtask queue. 365 | 366 | #### [IntersectionObserver](http://rawgit.com/WICG/IntersectionObserver/master/index.html) 367 | 368 | Poll-based. 369 | 370 | Observations poll on layout, broadcast via 100ms timeout. 371 | 372 | ### Promises 373 | 374 | Completed promises run callbacks after completion. 375 | 376 | Callbacks are placed on the microtask queue. 377 | 378 | ## Multiple event loops and their interaction 379 | 380 | ## Examples 381 | -------------------------------------------------------------------------------- /microtask.md: -------------------------------------------------------------------------------- 1 | Promise spec: 2 | https://promisesaplus.com/#point-34 3 | 2.2.4: onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1]. 4 | This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called. 5 | ECMAScript promise spec: https://tc39.github.io/ecma262/#sec-promise-objects 6 | 7 | 8 | Promise 9 | 10 | https://html.spec.whatwg.org/#microtask-queue 11 | 12 | Each event loop has a microtask queue. 13 | 14 | 2 kinds of microtasks: solitary callback, compound microtasks. This spec only covers solitary. 15 | 16 | Microtask can be moved to a regular task queue if it spins the event loop. 17 | 18 | Algorithms: 19 | 20 | perform a microtask checkpoint: 21 | 22 | 23 | ``` 24 | let MicrotaskQueue = function(eventLoop) { 25 | this.eventLoop = eventLoop; 26 | } 27 | 28 | MicrotaskQueue.prototype = { 29 | 30 | microtasks: [], 31 | 32 | queueTask: task => { 33 | this.microtasks.push(task); 34 | }, 35 | 36 | getOldestTask: () => { 37 | return this.microtasks.shift(); 38 | }, 39 | 40 | performCheckpoint: () => { 41 | if (this.performing) 42 | return; 43 | this.performing = this.true; 44 | 45 | while (let task = this.getOldestTask()) { 46 | 47 | this.eventLoop.currentlyRunningTask = task; 48 | 49 | task.run(); 50 | 51 | this.eventLoop.currentlyRunningTask = null; 52 | } 53 | 54 | // For each environment settings object whose responsible event loop 55 | // is this event loop, notify about rejected promises on that 56 | // environment settings object. 57 | } 58 | 59 | } 60 | ``` 61 | 62 | When is MicrotaskQueue.performCheckpoint() called? 63 | 64 | 1) https://html.spec.whatwg.org/#clean-up-after-running-script 65 | If the JavaScript execution context stack is now empty, perform a microtask checkpoint. 66 | 2) https://html.spec.whatwg.org/#event-loop-processing-model 67 | Runs after task, before rendering 68 | Step ... execute task 69 | Step 6: Microtasks: Perform a microtask checkpoint. 70 | Step 7: Update the rendering 71 | 3) https://heycam.github.io/webidl/#es-invoking-callback-functions 72 | Invoking callback functions: 73 | Invoking callback performs "Clean up after running script with relevant settings" 74 | 75 | Microtasks are queued using: https://html.spec.whatwg.org/#enqueuejob(queuename,-job,-arguments):queue-a-microtask 76 | 77 | 8.1.3.7.1 EnqueueJob(queueName, job, arguments) 78 | 79 | 80 | ECMAScript promise spec: https://tc39.github.io/ecma262/#sec-promise-objects 81 | 82 | ECMAScript defines EnqueueJob method for executing promise callbacks. 83 | 84 | EnqueueJob is defined in https://html.spec.whatwg.org/#enqueuejob(queuename,-job,-arguments):queue-a-microtask 85 | 86 | 87 | Firefox: 88 | 89 | XPCJSRuntime::BeforeProcessTask 90 | Promise::PerformMicroTaskCheckpoint 91 | 92 | Bugs: 93 | - PerformMicroTaskCheckpoint called from XPCJSRuntime::BeforeProcessTask 94 | it should happen after task 95 | 96 | FF run & build 97 | ./mach build 98 | ./mach run 99 | 100 | FF microtask bug: 101 | https://bugzilla.mozilla.org/show_bug.cgi?id=1193394 102 | 103 | 1) Promise runs immediately after a callback 104 | rAF 105 | Promise.resolve().then( 106 | var p = new Promise( function(fulfill, reject) { 107 | fulfill(); 108 | // log("promise A fulfill"); 109 | }) 110 | promise.resolve() 111 | assert 112 | 2) Promise resolved inside a promise is run immediately 113 | 3) Inside event loop, promise runs after a task, and before rAF 114 | 4) What happens with alerts 115 | 116 | Firefox rAF 117 | schedule: 118 | nsGlobalWindow::RequestAnimationFrame 119 | mDoc->ScheduleFrameRequestCallback 120 | nsRefreshDriver::ScheduleFrameRequestCallbacks 121 | mFrameRequestCallbackDocs.AppendElement(aDocument); 122 | dispatch: 123 | nsRefreshDriver::Tick 124 | nsRefreshDriver::RunFrameRequestCallbacks 125 | 126 | Firefox promise dispatch 127 | dispatches from: 128 | Promise::PerformMicroTaskCheckpoint 129 | called from: 130 | nsGlobalWindow::RunTimeoutHandler 131 | XPCJSRuntime::BeforeProcessTask 132 | -------------------------------------------------------------------------------- /rendering-events.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | event-loop rendering events 5 | 56 | 57 |

A dive into event loop specification

58 | 59 |

Event loop 60 | specification 61 | has two main parts:

62 | 84 |

85 | 86 |

Existing browser conformance to the spec

87 |

Conformance was tested with a test page that triggers a number of events at once, and records the order in which 88 | they are executed. Fullscreen events were not tested.

89 |

Here are the results.

90 |
    91 |
  1. Microtasks are purple. 92 |
  2. Timeout task is used as an indicator for the first part of the event loop. 93 |
  3. rAF callback is used as an indicator to the end of the event loop. 94 |
95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
104 | 229 |

Predictabilty

230 |

The initial test suite was run in Sep 2016, and found many inconsistencies 231 | between implementations. I reran the tests in Oct 2018, and predictablity 232 | is much better now.

233 | 234 |

1. Event ordering

235 |

Every browser orders events a little differently, and none of them completely follow the spec.

236 |

Ordering of promises, mutations, and rAF are consistent among all browsers.

237 |

All browsers fire the rest of the events in the correct part of the event loop, 238 | but the event order differs.

239 |

either: Should we all fix our ordering to match the spec?

240 |

or: Should the spec be fixed instead?

241 |

What are the reasons for the current ordering in the spec?

242 | 243 |

Other issues

244 | 245 |

Specification maintenability

246 |

Every specification that needs a slot in the paint cycle must add another line to event loop spec. 247 |

248 |

This is fragile. Currently, "run CSS animations and send events", and 249 | "run the fullscreen rendering steps" references are missing links. External 250 | spec has changed, but event loop was not updated.

251 |

We can rewrite the specification. Instead of it being a list of steps, it can become an ordered task queue.

252 |

Should we rewrite existing paint spec as an ordered task queue?

253 |

If we do, there are few design questions

254 |

Task registration is tricky. External specs will have to specify task registration.

255 |

Sort order: tasks still need to be sorted. This sort order must be centralized, global?

256 | 257 |

Throttling other events

258 |

Other events might benefit from throttling. ex: mousemove, pointermove, xhr progress.

259 |

Existing spec does not define any mechanism for throttled events. Touch events 260 | are currently throttled ad-hoc (Edge, Safari, Chrome for Android?).

261 |

Should we do it, and how we do event throttling has been 262 | discussed, but it is not 263 | settled. The main issue is what to do about dropped events. Leading proposal is 264 | to keep dropped events available as getCoalescedEvents 265 |

Throttling is already being implemented. Are there things we can agree on we could spec?

266 |

267 | 268 |

Event suppression

269 |

We should be aware that spec allows suppression of rendering steps for hidden windows.

270 |

This might cause trouble if we move non-suppresable events into rendering slot.

271 | 272 |

Related work

273 |

Rendering Processing Model: 274 | Rendering pipeline is intertwined with the event loop. Also discusses ordering 275 | of style/layout updates.

276 | 277 |

Task Scheduling for w3ctag: w3ctag

278 | 279 |

rAF aligned input events in chrome

280 |

Existing spec

281 |
282 | 
283 | 
284 | 
285 | 
286 | Existing spec:
287 | ### What does existing spec do?
288 | 
289 | 5) Run the resize steps
290 | 
291 | 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.
292 | 
293 | 6) Run the scroll steps
294 | For each item target in doc’s pending scroll event targets, in the order they were added to the list, run these substeps:
295 | 
296 | If target is a Document, fire an event named scroll that bubbles at target.
297 | Otherwise, fire an event named scroll at target.
298 | Empty doc’s pending scroll event targets.
299 | Fires an event, first target gets notified first
300 | 
301 | 7) Evaluate media queries and report changes
302 | 
303 | For each MediaQueryList object target that has doc as its document, in the order they were created, oldest first, run these substeps:
304 | 
305 | 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.
306 | 
307 | 8) run CSS animations and send events
308 | 
309 | Not specified.
310 | 
311 | 9)  run the fullscreen rendering steps
312 | 
313 | Not specified.
314 | 
315 | 10) run the animation frame callbacks
316 | 
317 | 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.
318 | 
319 | 11) run the update intersection observations steps
320 | 
321 | Let observer list be a list of all IntersectionObservers whose root is in the DOM tree of document.
322 | For each observer in observer list:
323 | 
324 | 12) Paint
325 | 
326 | ## Predictablility OKRs
327 | Concrete standards proposal and tests for event loop behavior
328 | https://github.com/atotic/event-loop
329 | Specify that some events fire just before rAF
330 | 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
331 | 
332 | ##Existing event loop spec##
333 |   

An event loop must continually run through the following steps for as long as it 334 | exists:

335 | 336 |
    337 | 338 | 339 | 340 |
  1. 341 | 342 |

    Select the oldest task on one of the event 343 | loop's task queues, if any, ignoring, in the case of a 344 | browsing context event loop, tasks whose associated 345 | Documents are not fully active. The user agent may pick any task 346 | queue. If there is no task to select, then jump to the microtasks step below.

    347 | 348 |
  2. 349 | 350 | 351 | 352 |
  3. Set the event loop's currently running task to the task selected in the previous step.

  4. 354 | 355 |
  5. Run: Run the selected task.

  6. 356 | 357 |
  7. Set the event loop's currently running task back to 358 | null.

  8. 359 | 360 |
  9. Remove the task that was run in the run step above from its task 361 | queue.

  10. 362 | 363 |
  11. Microtasks: Perform a microtask checkpoint.

  12. 364 | 365 |
  13. 366 | 367 |

    Update the rendering: If this event loop is a browsing 368 | context event loop (as opposed to a worker 369 | event loop), then run the following substeps.

    370 | 371 |
      372 | 373 |
    1. Let now be the value that would be returned by the Performance 374 | object's now() method.

      375 | 376 |
    2. 377 | 378 |

      Let docs be the list of Document objects associated with the 379 | event loop in question, sorted arbitrarily except that the following conditions 380 | must be met:

      381 | 382 |
        383 | 384 |
      • Any Document B that is nested through a 385 | Document A must be listed after A in the list.

      • 386 | 387 |
      • If there are two documents A and B whose browsing contexts are both nested browsing contexts and their browsing context containers are both elements in the same 391 | Document C, then the order of A and B in the 392 | list must match the relative tree order of their respective browsing context containers in 394 | C.

      • 395 | 396 |
      397 | 398 |

      In the steps below that iterate over docs, each Document must be 399 | processed in the order it is found in the list.

      400 | 401 |
    3. 402 | 403 |
    4. 404 | 405 |

      If there are top-level browsing contexts 406 | B that the user agent believes would not benefit from having their rendering 407 | updated at this time, then remove from docs all Document objects whose 408 | browsing context's top-level browsing 409 | context is in B.

      410 | 411 |
      412 |

      Whether a top-level browsing context would benefit from having its rendering 413 | updated depends on various factors, such as the update frequency. For example, if the browser 414 | is attempting to achieve a 60Hz refresh rate, then these steps are only necessary every 60th 415 | of a second (about 16.7ms). If the browser finds that a top-level browsing 416 | context is not able to sustain this rate, it might drop to a more sustainable 30Hz for 417 | that set of Documents, rather than occasionally dropping frames. (This 418 | specification does not mandate any particular model for when to update the rendering.) 419 | Similarly, if a top-level browsing context is in the background, the user agent 420 | might decide to drop that page to a much slower 4Hz, or even less.

      421 | 422 |

      Another example of why a browser might skip updating the rendering is to ensure certain 423 | tasks are executed immediately after each other, with only 424 | microtask checkpoints interleaved (and 425 | without, e.g., animation frame 426 | callbacks interleaved). For example, a user agent might wish to coalesce timer 427 | callbacks together, with no intermediate rendering updates.

      428 |
      429 | 430 |
    5. 431 | 432 |
    6. 433 | 434 |

      If there are a nested browsing contexts 435 | B that the user agent believes would not benefit from having their rendering 436 | updated at this time, then remove from docs all Document objects whose 437 | browsing context is in B.

      438 | 439 |

      As with top-level browsing 440 | contexts, a variety of factors can influence whether it is profitable for a browser to 441 | update the rendering of nested browsing 442 | contexts. For example, a user agent might wish to spend less resources rendering 443 | third-party content, especially if it is not currently visible to the user or if resources are 444 | constrained. In such cases, the browser could decide to update the rendering for such content 445 | infrequently or never.

      446 | 447 |
    7. 448 | 449 |
    8. For each fully active Document in docs, run the resize 450 | steps for that Document, passing in now as the timestamp.

    9. 452 | 453 |
    10. For each fully active Document in docs, run the scroll 454 | steps for that Document, passing in now as the timestamp.

    11. 456 | 457 |
    12. For each fully active Document in docs, evaluate media queries 458 | and report changes for that Document, passing in now as the timestamp.

    13. 460 | 461 |
    14. For each fully active Document in docs, run CSS animations and send 462 | events for that Document, passing in now as the timestamp.

    15. 464 | 465 |
    16. For each fully active Document in docs, run the fullscreen rendering 466 | steps for that Document, passing in now as the timestamp.

    17. 468 | 469 |
    18. For each fully active Document in docs, run the animation frame 470 | callbacks for that Document, passing in now as the 471 | timestamp.

    19. 472 | 473 |
    20. For each fully active Document in docs, run the update 474 | intersection observations steps for that Document, passing in now as the 475 | timestamp.

    21. 476 | 477 |
    22. For each fully active Document in docs, update the 478 | rendering or user interface of that Document and its browsing context to reflect the current state.

    23. 480 | 481 |
    482 | 483 |
  14. 484 | 485 |
  15. If this is a worker event loop (i.e. one running for a 486 | WorkerGlobalScope), but there are no tasks in the 487 | event loop's task queues and the 488 | WorkerGlobalScope object's closing flag is true, then destroy the event 490 | loop, aborting these steps, resuming the run a worker steps described in the 491 | Web workers section below.

  16. 492 | 493 |
  17. Return to the first step of the event loop.

  18. 494 |
495 | -------------------------------------------------------------------------------- /shell.css: -------------------------------------------------------------------------------- 1 | button { 2 | font-size: xx-large; 3 | 4 | } 5 | .result { 6 | margin-top: 8px; 7 | padding: 8px; 8 | color: green; 9 | border-radius: 5px; 10 | border: 1px solid green; 11 | font-size: initial; 12 | } 13 | .result.error { 14 | color: red; 15 | background-color: rgba(255,0,0,0.2); 16 | border: 1px solid red; 17 | } 18 | .result.warn { 19 | color: #424218; 20 | background-color: rgba(255,255, 0, 0.2); 21 | border: 1px solid yellow; 22 | } 23 | iframe { 24 | margin-top: 16px; 25 | display:block; 26 | } 27 | 28 | .animate { 29 | animation-duration: 0.2s; 30 | animation-name: slidein; 31 | } 32 | @keyframes slidein { 33 | from { 34 | margin-left: 100%; 35 | width: 100%; 36 | } 37 | to { 38 | margin-left: 0%; 39 | width: 100%; 40 | } 41 | } 42 | #slider { 43 | background-color: blue; 44 | width: 50px; 45 | height: 1px; 46 | } 47 | -------------------------------------------------------------------------------- /shell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | event-loop test 7 | 8 | 9 | 10 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | event-loop test 7 | 8 | 9 | 10 |