Skip to content

Commit

Permalink
Add an explicit algorithm for idle callback deadline
Browse files Browse the repository at this point in the history
The algorithm replaces the algorithm that is currently defined in prose here: https://w3c.github.io/requestidlecallback/#start-an-idle-period-algorithm.

It works like so:

* Timers can calculate their estimated next callback timestamps, based on their start time and document-inactive time so far.
* The deadline for idle tasks for an event loop is the earliest between:
  * The time until the next timer callback in this event loop is estimated to fire.
  * The time until the next render, estimated at 1000 ms / refresh rate after the start of the previous task that had a rendering opportunity, if there are pending animation frame callbacks (or if the user agent believes a render is coming).
  * 50ms

The deadline computation is passed as a set of algorithm steps to the requestIdleCallback() spec, so that it can always return the up-to-date deadline.

Helps with w3c/requestidlecallback#71.
  • Loading branch information
noamr committed Nov 15, 2021
1 parent 54bff73 commit bd63843
Showing 1 changed file with 135 additions and 21 deletions.
156 changes: 135 additions & 21 deletions source
Original file line number Diff line number Diff line change
Expand Up @@ -2186,6 +2186,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
<dfn data-x-href="https://infra.spec.whatwg.org/#string-position-variable">position variable</dfn></li>
<li><dfn data-x-href="https://infra.spec.whatwg.org/#skip-ascii-whitespace">skip ASCII whitespace</dfn></li>
<li>The <dfn data-x-href="https://infra.spec.whatwg.org/#ordered-map">ordered map</dfn> data structure and the associated definitions for
<dfn data-x="map key" data-x-href="https://infra.spec.whatwg.org/#map-key">key</dfn>,
<dfn data-x="map value" data-x-href="https://infra.spec.whatwg.org/#map-value">value</dfn>,
<dfn data-x="map empty" data-x-href="https://infra.spec.whatwg.org/#map-is-empty">empty</dfn>,
<dfn data-x="map entry" data-x-href="https://infra.spec.whatwg.org/#map-entry">entry</dfn>,
Expand All @@ -2195,6 +2196,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
<dfn data-x="map remove" data-x-href="https://infra.spec.whatwg.org/#map-remove">removing an entry</dfn>,
<dfn data-x="map clear" data-x-href="https://infra.spec.whatwg.org/#map-clear">clear</dfn>,
<dfn data-x="map get the keys" data-x-href="https://infra.spec.whatwg.org/#map-getting-the-keys">getting the keys</dfn>,
<dfn data-x="map get the values" data-x-href="https://infra.spec.whatwg.org/#map-getting-the-values">getting the values</dfn>,
<dfn data-x="map size" data-x-href="https://infra.spec.whatwg.org/#map-size">size</dfn>, and
<dfn data-x="map iterate" data-x-href="https://infra.spec.whatwg.org/#map-iterate">iterate</dfn></li>
<li>The <dfn data-x-href="https://infra.spec.whatwg.org/#list">list</dfn> data structure and the associated definitions for
Expand Down Expand Up @@ -88686,6 +88688,26 @@ new PaymentRequest(&hellip;); // Allowed to use
then:</p>

<ol>
<li>
<p>If <var>newDocument</var>'s <span>suspended timer handles</span> is not
<span data-x="list empty">empty</span>:</p>

<ol>
<li><p>Assert: <var>newDocument</var>'s <span>suspension time</span> is not zero.</p></li>

<li><p>Let <var>suspendDuration</var> be the <span>current high resolution time</span> minus
<var>newDocument</var>'s <span>suspension time</span>.</p></li>

<li><p>Let <var>activeTimers</var> be <var>newDocument</var>'s
<span>relevant global object</span>'s <span>map of active timers</span>.</p></li>

<li><p>For each <var>handle</var> in <var>newDocument</var>'s <span>suspended timer
handles</span>, if <var>activeTimers</var>[<var>handle</var>] <span data-x="map
exists">exists</span>, then increase <var>activeTimers</var>[<var>handle</var>] by
<var>suspendDuration</var>.</p></li>
</ol>
</li>

<li><p>Remove any <span data-x="concept-task">tasks</span> queued by the <span>history traversal
task source</span> that are associated with any <code>Document</code> objects in the
<span>top-level browsing context</span>'s <span>document family</span>.</p></li> <!-- so the
Expand Down Expand Up @@ -89122,6 +89144,12 @@ dictionary <dfn dictionary>PageTransitionEventInit</dfn> : <span>EventInit</span
data-x="event-pagehide">pagehide</code> events in a row without an intervening <code
data-x="event-pageshow">pageshow</code>, or vice versa).</p>

<p>A <code>Document</code> has a <code>DOMHighResTimeStamp</code> <dfn>suspension time</dfn>,
initially 0.</p>

<p>A <code>Document</code> has a <span>list</span> of <dfn>suspended timer handles</dfn>,
initially empty.</p>

<p><span data-x="event loop">Event loops</span> have a <dfn>termination nesting level</dfn>
counter, which must initially be 0.</p>

Expand Down Expand Up @@ -89254,6 +89282,13 @@ dictionary <dfn dictionary>PageTransitionEventInit</dfn> : <span>EventInit</span
<li><p>Decrease the <span>event loop</span>'s <span>termination nesting level</span> by
one.</p></li>

<li><p>Set <var>document</var>'s <span>suspension time</span> to the
<span>current high resolution time</span>.</p></li>

<li><p>Set <var>document</var>'s <span>suspended timer handles</span> to the result of
<span data-x="map get the keys">getting the keys</span> for the
<span>map of active timers</span>.</p></li>

<li><p>Run any <span>unloading document cleanup steps</span> for <var>document</var> that are
defined by this specification and <span>other applicable specifications</span>.</p></li>

Expand Down Expand Up @@ -89311,7 +89346,8 @@ dictionary <dfn dictionary>PageTransitionEventInit</dfn> : <span>EventInit</span
data-x="concept-EventSource-forcibly-close">forcibly close</span>
<var>eventSource</var>.</p></li>

<li><p>Empty <var>window</var>'s <span>list of active timers</span>.</p></li>
<li><p><span data-x="map clear">Clear</span> <var>window</var>'s
<span>map of active timers</span>.</p></li>
</ol>
</li>
</ol>
Expand Down Expand Up @@ -93525,6 +93561,16 @@ import "https://example.com/foo/../module2.mjs";</code></pre>
which is initially false. It is used to prevent reentrant invocation of the <span>perform a
microtask checkpoint</span> algorithm.</p>

<p>Each <span>window event loop</span> has a <code>DOMHighResTimeStamp</code>
<dfn>last render opportunity time</dfn>, initially set to zero.</p>

<p>Each <span>window event loop</span> has a <code>DOMHighResTimeStamp</code>
<dfn>last idle period start time</dfn>, initially set to zero.</p>

<p>To get the <dfn>same-loop windows</dfn> for a <span>window event loop</span> <var>loop</var>,
return all <code>Window</code> objects whose <span>relevant agent</span>'s
<span data-x="concept-agent-event-loop">event loop</span> is <var>loop</var>.</p>


<h5>Queuing tasks</h5>

Expand Down Expand Up @@ -93781,7 +93827,8 @@ import "https://example.com/foo/../module2.mjs";</code></pre>
</li>

<li><p>If <var>docs</var> is not empty, then set <var>hasARenderingOpportunity</var> to
true.</p></li>
true and set this <span>event loop</span>'s <span>last render opportunity time</span> to
<var>taskStartTime</var>.</p></li>

<li>
<p><i>Unnecessary rendering</i>: Remove from <var>docs</var> all <code>Document</code> objects
Expand Down Expand Up @@ -93907,10 +93954,70 @@ import "https://example.com/foo/../module2.mjs";</code></pre>
<li><var>hasARenderingOpportunity</var> is false</li>
</ul>

<p>then for each <code>Window</code> object whose <span>relevant agent</span>'s
<span data-x="concept-agent-event-loop">event loop</span> is this event loop, run the
<span>start an idle period algorithm</span>, passing the <code>Window</code>. <ref
spec="REQUESTIDLECALLBACK"></p>
<p>then:

<ol>
<li>
<p>Let <var>computeDeadline</var> be the following steps:</p>

<ol>
<li>
<p>Let <var>deadline</var> be this <span>event loop</span>'s
<span>last idle period start time</span> plus 50.</p>

<p class="note">The cap of 50ms in the future is to ensure responsiveness to new user input
within the threshold of human perception.</p>
</li>

<li><p>Let <var>hasPendingRenders</var> be false.</p></li>

<li>
<p>For each <var>windowInSameLoop</var> of the <span>same-loop windows</span>
for this <span>event loop</span>:</p>

<ol>
<li><p>If <var>windowInSameLoop</var>'s <span>map of animation frame callbacks</span> is
not <span data-x="map empty">empty</span>, or if the user agent believes that the
<var>windowInSameLoop</var> might have pending rendering updates, set
<var>hasPendingRenders</var> to true.</p></li>

<li><p>Let <var>timerCallbackEstimates</var> be the result of
<span data-x="map get the values">getting the values</span> of
<var>windowInSameLoop</var>'s <span>map of active timers</span>.</p></li>

<li><p>For each <var>timeoutDeadline</var> of <var>timerCallbackEstimates</var>, if
<var>timeoutDeadline</var> is less than <var>deadline</var>, set
<var>deadline</var> to <var>timeoutDeadline</var>.</p></li>
</ol>
</li>

<li>
<p>If <var>hasPendingRenders</var> is true, then:</p>

<ol>
<li>
<p>Let <var>nextRenderDeadline</var> be this <span>event loop</span>'s
<span>last render opportunity time</span> plus (1000 divided by the current refresh
rate).</p>

<p>The refresh rate can be hardware- or implementation-specific. For a refresh rate of
60Hz, the <var>nextRenderDeadline</var> would be about 16.67ms after the
<span>last render opportunity time</span>.</p>
</li>

<li><p>If <var>nextRenderDeadline</var> is less than
<var>deadline</var>, then return <var>nextRenderDeadline</var>.</p></li>
</ol>
</li>

<li><p>Return <var>deadline</var>.</p></li>
</ol>
</li>

<li><p>For each <var>win</var> of the <span>same-loop windows</span> for
this <span>event loop</span>, perform the <span>start an idle period algorithm</span> for
<var>win</var> with <var>computeDeadline</var>. <ref spec=REQUESTIDLECALLBACK></p></li>
</ol>
</li>

<li>
Expand Down Expand Up @@ -96326,9 +96433,15 @@ enum <dfn enum>DOMParserSupportedType</dfn> {
<div w-nodev>

<p>Objects that implement the <code>WindowOrWorkerGlobalScope</code> mixin have a <dfn
export>list of active timers</dfn>. Each entry in this lists is identified by a number, which must
be unique within the list for the lifetime of the object that implements the
<code>WindowOrWorkerGlobalScope</code> mixin.</p>
export>map of active timers</dfn>, which is a <span>map</span>, initially empty. Each
<span data-x="map key">key</span> in this map is identified by a number, which must be unique
within the list for the lifetime of the object that implements the
<code>WindowOrWorkerGlobalScope</code> mixin, and each <span data-x="map value">value</span> is a
<code>DOMHighResTimeStamp</code>, representing the expiry time for that timer.</p>

<p>To get the <dfn export>list of active timers</dfn> for <code>WindowOrWorkerGlobalScope</code>
<var>global</var>, return the result of <span data-x="map get the keys">getting the keys</span>
for <var>global</var>'s <span>map of active timers</span>.</p>

<hr>

Expand All @@ -96352,14 +96465,12 @@ enum <dfn enum>DOMParserSupportedType</dfn> {
id="dom-windowtimers-clearTimeout">clearTimeout(<var>handle</var>)</code></dfn> and <dfn method
for="WindowOrWorkerGlobalScope" data-x="dom-clearInterval"><code
id="dom-windowtimers-clearInterval">clearInterval(<var>handle</var>)</code></dfn> methods must
clear the entry identified as <var>handle</var> from the <span>list of active timers</span> of the
<code>WindowOrWorkerGlobalScope</code> object on which the method was invoked, if any, where
<var>handle</var> is the argument passed to the method. (If <var>handle</var> does not identify an
entry in the <span>list of active timers</span> of the <code>WindowOrWorkerGlobalScope</code>
object on which the method was invoked, the method does nothing.)</p>
<span data-x="map remove">remove</span> <span>map of active timers</span>[<var>handle</var>] of
the <code>WindowOrWorkerGlobalScope</code> object on which the method was invoked, if any, where
<var>handle</var> is the argument passed to the method.</p>

<p class="note">Because <code data-x="dom-clearTimeout">clearTimeout()</code> and <code
data-x="dom-clearInterval">clearInterval()</code> clear entries from the same list, either method
data-x="dom-clearInterval">clearInterval()</code> clear entries from the same map, either method
can be used to clear timers created by <code data-x="dom-setTimeout">setTimeout()</code> or <code
data-x="dom-setInterval">setInterval()</code>.</p>

Expand All @@ -96380,9 +96491,6 @@ enum <dfn enum>DOMParserSupportedType</dfn> {
that is greater than zero that will identify the timeout to be set by this call in the <span>list
of active timers</span>.</p></li>

<li><p>If <var>previous handle</var> was not provided, add an entry to the <span>list of
active timers</span> for <var>handle</var>.</p></li>

<li><p>Let <var>callerRealm</var> be the <span>current Realm Record</span>, and
<var>calleeRealm</var> be <var>method context</var>'s <span>JavaScript realm</span>.</p></li>

Expand All @@ -96396,8 +96504,8 @@ enum <dfn enum>DOMParserSupportedType</dfn> {
following substeps:</p>

<ol>
<li><p>If the entry for <var>handle</var> in the <span>list of active timers</span> has been
cleared, then abort these steps.</p></li>
<li><p>If <var>handle</var> does not <span data-x="map exists">exist</span> in
<var>method context</var>'s <span>map of active timers</span>, then abort these steps.</p></li>

<li>
<p>Run the appropriate set of steps from the following list:</p>
Expand Down Expand Up @@ -96494,6 +96602,11 @@ enum <dfn enum>DOMParserSupportedType</dfn> {
<li><p>Let <var>task</var>'s <dfn>timer nesting level</dfn> be <var>nesting
level</var>.</p></li>

<li><p>Let <var>startTime</var> be the <span>current high resolution time</span>.</p></li>

<li><p><span data-x="map set">Set</span> <span>map of active timers</span>[<var>handle</var>] to
<var>startTime</var> plus <var>timeout</var>.</p></li>

<li><p>Return <var>handle</var>, and then continue running this algorithm
<span>in parallel</span>.</p></li>

Expand Down Expand Up @@ -102601,7 +102714,8 @@ interface <dfn interface>SharedWorkerGlobalScope</dfn> : <span>WorkerGlobalScope
</li>

<li>
<p>Empty the <var>worker global scope</var>'s <span>list of active timers</span>.</p>
<p><span data-x="map clear">Clear</span> the <var>worker global scope</var>'s
<span>map of active timers</span>.</p>
</li>

<li>
Expand Down

0 comments on commit bd63843

Please sign in to comment.