Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Editorial: modernize timers spec #7348

Merged
merged 6 commits into from
Nov 23, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 104 additions & 114 deletions source
Original file line number Diff line number Diff line change
Expand Up @@ -96441,35 +96441,26 @@ enum <dfn enum>DOMParserSupportedType</dfn> {
<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>

<p>The <dfn method for="WindowOrWorkerGlobalScope" data-x="dom-setTimeout"><code
id="dom-windowtimers-setTimeout">setTimeout(<var>handler</var>, <var>timeout</var>,
...<var>arguments</var>)</code></dfn> method must return the value returned by the <span>timer
initialization steps</span>, passing them the method's arguments, the object on which the method
for which the algorithm is running is implemented (a <code>Window</code> or
<code>WorkerGlobalScope</code> object) as the <var>method context</var>, and the <var>repeat</var>
flag set to false.</p>
...<var>arguments</var>)</code></dfn> method steps are to return the result of running the
<span>timer initialization steps</span> given <span>this</span>, <var>handler</var>,
<var>timeout</var>, <var>arguments</var>, and false.</p>

<p>The <dfn method for="WindowOrWorkerGlobalScope" data-x="dom-setInterval"><code
id="dom-windowtimers-setInterval">setInterval(<var>handler</var>, <var>timeout</var>,
...<var>arguments</var>)</code></dfn> method must return the value returned by the <span>timer
initialization steps</span>, passing them the method's arguments, the object on which the method
for which the algorithm is running is implemented (a <code>Window</code> or
<code>WorkerGlobalScope</code> object) as the <var>method context</var>, and the <var>repeat</var>
flag set to true.</p>
...<var>arguments</var>)</code></dfn> method steps are to return the result of running the
<span>timer initialization steps</span> given <span>this</span>, <var>handler</var>,
<var>timeout</var>, <var>arguments</var>, and true.</p>

<p>The <dfn method for="WindowOrWorkerGlobalScope" data-x="dom-clearTimeout"><code
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
<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>
id="dom-windowtimers-clearInterval">clearInterval(<var>handle</var>)</code></dfn> method steps
are to <span data-x="map remove">remove</span> <span>this</span>'s <span>map of active
timers</span>[<var>handle</var>].</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 map, either method
Expand All @@ -96478,61 +96469,62 @@ enum <dfn enum>DOMParserSupportedType</dfn> {

<hr>

<p>The <!--en-GB--><dfn id="timer-initialisation-steps">timer initialization steps</dfn>, which
are invoked with some method arguments, a <var>method context</var>, a <var>repeat</var> flag
which can be true or false, and optionally (and only if the <var>repeat</var> flag is true) a
<var>previous handle</var>, are as follows:</p>
<p>The <!--en-GB--><dfn id="timer-initialisation-steps">timer initialization steps</dfn>, given a
<code>WindowOrWorkerGlobalScope</code> <var>global</var>, a string or <code
data-x="idl-Function">Function</code> <var>handle</var>, a number <var>timeout</var>, a list
domenic marked this conversation as resolved.
Show resolved Hide resolved
<var>arguments</var>, a boolean <var>repeat</var>, and optionally (and only if <var>repeat</var>
is true) a number <var>previousHandle</var>, are:</p>

<ol>
<li><p>Let <var>method context proxy</var> be <var>method context</var> if that
is a <code>WorkerGlobalScope</code> object, or else the <code>WindowProxy</code> that corresponds
to <var>method context</var>.</p></li>
<li><p>Let <var>thisArg</var> be <var>global</var> if that is a <code>WorkerGlobalScope</code>
object; otherwise let <var>thisArg</var> be the <code>WindowProxy</code> that corresponds to
<var>global</var>.</p></li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this instead be something like global's relevant Realm.[[GlobalEnv]].[[GlobalThisValue]]? (Elsewhere we use relevant Realm.[[GlobalEnv]]'s EnvironmentRecord's [[GlobalThisValue]] but JS itself does the shortcut there.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly, but I'd kind of like to not spread that very-opaque pattern too far. Instead we should define a wrapper term that we can use. I'll file a separate issue for that.


<li><p>If <var>previous handle</var> was provided, let <var>handle</var> be <var>previous
handle</var>; otherwise, let <var>handle</var> be an <span>implementation-defined</span> integer
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>previousHandle</var> was given, let <var>handle</var> be
<var>previousHandle</var>; otherwise, let <var>handle</var> be an
<span>implementation-defined</span> integer that is greater than zero that will identify the
timeout to be set by this call in <var>global</var>'s <span>map of active
timers</span>.</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>
<var>calleeRealm</var> be <var>global</var>'s <span data-x="concept-relevant-realm">relevant
Realm</span>.</p></li>

<li><p>Let <var>initiating script</var> be the <span>active script</span>.</p></li>

<li><p>Assert: <var>initiating script</var> is not null, since this algorithm is always called
from some script.</p></li>

<li>
<p>Let <var>task</var> be a <span data-x="concept-task">task</span> that runs the
following substeps:</p>
<p>Let <var>task</var> be a <span data-x="concept-task">task</span> that runs the following
substeps:</p>

<ol>
<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>
<var>global</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>

<dl class="switch">
<dt>If the first method argument is a <code data-x="idl-Function">Function</code></dt>
<dd><p><span data-x="es-invoking-callback-functions">Invoke</span> the <code
data-x="idl-Function">Function</code>. Use the third and subsequent method arguments (if any)
as the arguments for invoking the <code data-x="idl-Function">Function</code>. Use
<var>method context proxy</var> as the <span data-x="dfn-callback-this-value">callback this
value</span>. If this throws an exception, catch it, and <span>report the
exception</span>.</p></dd>
<dt>If <var>handler</var> is a <code data-x="idl-Function">Function</code></dt>
<dd><p><span data-x="es-invoking-callback-functions">Invoke</span> <var>handler</var> given
<var>arguments</var> with the <span data-x="dfn-callback-this-value">callback this
value</span> set to <var>thisArg</var>. If this throws an exception, catch it, and
<span>report the exception</span>.</p></dd>

<dt>Otherwise</dt>
<dd>
<ol>
<li><p>Assert: <var>handler</var> is a string.</p></li>

<li><p>Perform <span
data-x="the-hostensurecancompilestrings-implementation">HostEnsureCanCompileStrings</span>(<var>callerRealm</var>,
<var>calleeRealm</var>). If this throws an exception, catch it, <span>report the
exception</span>, and abort these steps.</p></li>

<li><p>Let <var>script source</var> be the first method argument.</p></li>

<li><p>Let <var>settings object</var> be <var>method context</var>'s <span>environment
settings object</span>.</p></li>
<li><p>Let <var>settings object</var> be <var>global</var>'s <span>relevant settings
object</span>.</p></li>

<li><p>Let <var>base URL</var> be <var>initiating script</var>'s <span
data-x="concept-script-base-url">base URL</span>.</p></li>
Expand All @@ -96559,12 +96551,12 @@ enum <dfn enum>DOMParserSupportedType</dfn> {
<p class="note">The effect of these options ensures that the string compilation done by
<code data-x="dom-setTimeout">setTimeout()</code> and <code
data-x="dom-setInterval">setInterval()</code> behaves equivalently to that done by
<code>eval()</code>. That is, <span>module script</span> fetches via <code>import()</code>
will behave the same in both contexts.</p>
<code>eval()</code>. That is, <span>module script</span> fetches via
<code>import()</code> will behave the same in both contexts.</p>
</li>

<li><p>Let <var>script</var> be the result of <span>creating a classic script</span> given
<var>script source</var>, <var>settings object</var>, <var>base URL</var>, and <var>fetch
<var>handler</var>, <var>settings object</var>, <var>base URL</var>, and <var>fetch
options</var>.</p></li>

<li><p><span data-x="run a classic script">Run the classic script</span>
Expand All @@ -96574,105 +96566,104 @@ enum <dfn enum>DOMParserSupportedType</dfn> {
</dl>
</li>

<li><p>If the <var>repeat</var> flag is true, then call <span>timer initialization
steps</span> again, passing them the same method arguments, the same <var>method
context</var>, with the <var>repeat</var> flag still set to true, and with the <var>previous handle</var> set to <var>handler</var>.</p></li>
<li><p>If <var>repeat</var> is true, then perform the <span>timer initialization
steps</span> again, given <var>global</var>, <var>handle</var>, <var>arguments</var>, true, and
domenic marked this conversation as resolved.
Show resolved Hide resolved
<var>handle</var>.</p></li>
</ol>
</li>

<li><p>Let <var>timeout</var> be the second method argument.</p></li>

<li>
<p>If the currently running <span data-x="concept-task">task</span> is a task that was created
by this algorithm, then let <var>nesting level</var> be the <span
data-x="concept-task">task</span>'s <span>timer nesting level</span>. Otherwise, let
<var>nesting level</var> be zero.</p>
<p>If the <span>surrounding agent</span>'s <span data-x="concept-agent-event-loop">event
loop</span>'s <span>currently running task</span> is a task that was created by this algorithm,
then let <var>nesting level</var> be the <span data-x="concept-task">task</span>'s <span>timer
nesting level</span>. Otherwise, let <var>nesting level</var> be zero.</p>

<p class="note">The task's <span>timer nesting level</span> is used both for nested calls to
<code data-x="dom-setTimeout">setTimeout()</code>, and for the repeating timers created by <code
data-x="dom-setInterval">setInterval()</code>. (Or, indeed, for any combination of the two.) In
other words, it represents nested invocations of this algorithm, not of a particular method.</p>
<code data-x="dom-setTimeout">setTimeout()</code>, and for the repeating timers created by
domenic marked this conversation as resolved.
Show resolved Hide resolved
<code data-x="dom-setInterval">setInterval()</code>. (Or, indeed, for any combination of the
two.) In other words, it represents nested invocations of this algorithm, not of a particular
method.</p>
</li>

<li><p>If <var>timeout</var> is less than 0, then set <var>timeout</var> to 0.</p></li>

<li><p>If <var>nesting level</var> is greater than 5, and <var>timeout</var> is less than 4, then
set <var>timeout</var> to 4.</p></li>
<li><p>If <var>nesting level</var> is greater than 5, and <var>timeout</var> is less than 4,
then set <var>timeout</var> to 4.</p></li>

<li><p>Increment <var>nesting level</var> by one.</p></li>

<li><p>Let <var>task</var>'s <dfn>timer nesting level</dfn> be <var>nesting
level</var>.</p></li>
<li><p>Set <var>task</var>'s <dfn>timer nesting level</dfn> to <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>
<li><p><span data-x="map set">Set</span> <var>global</var>'s
<span>map of active timers</span>[<var>handle</var>] to <var>startTime</var> plus
<var>timeout</var>.</p></li>

<li>
<p>If <var>method context</var> is a <code>Window</code> object, wait until the
<code>Document</code> associated with <var>method context</var> has been <span>fully
active</span> for a further <var>timeout</var> milliseconds (not necessarily
consecutively).</p>

<p>Otherwise, <var>method context</var> is a <code>WorkerGlobalScope</code> object;
wait until <var>timeout</var> milliseconds have passed with the worker not suspended
(not necessarily consecutively).</p>
</li>

<li>
<p>Wait until any invocations of this algorithm that had the same <var>method
context</var>, that started before this one, and whose <var>timeout</var> is equal to
or less than this one's, have completed.</p>
<p>Run the following steps <span>in parallel</span>:</p>

<p class="note">Argument conversion as defined by Web IDL (for example, invoking <code
data-x="">toString()</code> methods on objects passed as the first argument) happens in the
algorithms defined in Web IDL, before this algorithm is invoked.</p>
<ol>
<li>
<p>If <var>global</var> is a <code>Window</code> object, wait until <var>global</var>'s <span
data-x="concept-document-window">associated <code>Document</code></span> has been <span>fully
active</span> for a further <var>timeout</var> milliseconds (not necessarily
consecutively).</p>

<p>Otherwise, <var>global</var> is a <code>WorkerGlobalScope</code> object; wait
until <var>timeout</var> milliseconds have passed with the worker not suspended (not
necessarily consecutively).</p>
</li>

<div class="example">
<li><p>Wait until any invocations of this algorithm that had the same <var>global</var>, that
started before this one, and whose <var>timeout</var> is equal to or less than this one's,
have completed.</p></li>

<p>So for example, the following rather silly code will result in the log containing "<code
data-x="">ONE&nbsp;TWO&nbsp;</code>":</p>
<li>
<p>Optionally, wait a further <span>implementation-defined</span> length of time.</p>

<pre><code class="js">var log = '';
function logger(s) { log += s + ' '; }
<p class="note">This is intended to allow user agents to pad timeouts as needed to optimize
the power usage of the device. For example, some processors have a low-power mode where the
granularity of timers is reduced; on such platforms, user agents can slow timers down to fit
this schedule instead of requiring the processor to use the more accurate mode with its
associated higher power usage.</p>
</li>

setTimeout({ toString: function () {
setTimeout("logger('ONE')", 100);
return "logger('TWO')";
} }, 100);</code></pre>
<li>
<p><span>Queue a global task</span> on the <dfn>timer task source</dfn> given
<var>global</var> to run <var>task</var>.</p>

</div>
<p class="note">Once the task has been processed, if <var>repeat</var> is false, it is safe
to remove the entry for <var>handle</var> from the <span>map of active timers</span> (there
is no way for the entry's existence to be detected past this point, so it does not
technically matter one way or the other).</p>
</li>
</ol>
</li>

<li>
<p>Optionally, wait a further <span>implementation-defined</span> length of time.</p>
<li><p>Return <var>handle</var>.</p></li>
</ol>

<p class="note">This is intended to allow user agents to pad timeouts as needed to optimize the
power usage of the device. For example, some processors have a low-power mode where the
granularity of timers is reduced; on such platforms, user agents can slow timers down to fit
this schedule instead of requiring the processor to use the more accurate mode with its
associated higher power usage.</p>
</li>
<p class="note">Argument conversion as defined by Web IDL (for example, invoking <code
data-x="">toString()</code> methods on objects passed as the first argument) happens in the
algorithms defined in Web IDL, before this algorithm is invoked.</p>

<li>
<p><span>Queue a global task</span> on the <dfn>timer task source</dfn> given <var>method
context</var> to run <var>task</var>.</p>
<div class="example">
<p>So for example, the following rather silly code will result in the log containing "<code
data-x="">ONE&nbsp;TWO&nbsp;</code>":</p>

<p class="note">Once the task has been processed, if the <var>repeat</var> flag is
false, it is safe to remove the entry for <var>handle</var> from the <span>list of
active timers</span> (there is no way for the entry's existence to be detected past this point,
so it does not technically matter one way or the other).</p>
</li>
</ol>
<pre><code class="js">var log = '';
function logger(s) { log += s + ' '; }

setTimeout({ toString: function () {
setTimeout("logger('ONE')", 100);
return "logger('TWO')";
} }, 100);</code></pre>
</div>

</div>

<div class="example">

<p>To run tasks of several milliseconds back to back without any delay, while still yielding back
to the browser to avoid starving the user interface (and to avoid the browser killing the script
for hogging the CPU), simply queue the next timer before performing work:</p>
Expand All @@ -96697,7 +96688,6 @@ function scheduleWork() {
}

scheduleWork(); // queues a task to do lots of work</code></pre>

</div>


Expand Down