Skip to content

Commit 3d9b41d

Browse files
fserbdomenic
authored andcommitted
Add requestAnimationFrame() in workers
Fixes #3587. Based on the proposal at https://github.com/junov/OffscreenCanvasAnimation/blob/2e0546417d4f45d194270a67a1cdf303f2e0ef88/OffscreenCanvasAnimation.md. This introduces a new AnimationFrameProvider mixin interface to contain requestAnimationFrame() and cancelAnimationFrame(). It's included in Window and DedicatedWorkerGlobalScope. In addition to generalizing them to work on both objects, the two methods were rewritten editorially to be based on Infra primitives. This change also introduces the concept of updating the rendering of worker event loops.
1 parent 825ede5 commit 3d9b41d

File tree

1 file changed

+150
-58
lines changed

1 file changed

+150
-58
lines changed

source

Lines changed: 150 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,8 +2540,10 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
25402540
<dfn data-x="map value" data-x-href="https://infra.spec.whatwg.org/#map-value">value</dfn>,
25412541
<dfn data-x="map entry" data-x-href="https://infra.spec.whatwg.org/#map-entry">entry</dfn>,
25422542
<dfn data-x="map exists" data-x-href="https://infra.spec.whatwg.org/#map-exists">exists</dfn>,
2543-
<dfn data-x="map get" data-x-href="https://infra.spec.whatwg.org/#map-get">getting the value of an entry</dfn>, and
2544-
<dfn data-x="map set" data-x-href="https://infra.spec.whatwg.org/#map-set">setting the value of an entry</dfn></li>
2543+
<dfn data-x="map get" data-x-href="https://infra.spec.whatwg.org/#map-get">getting the value of an entry</dfn>,
2544+
<dfn data-x="map set" data-x-href="https://infra.spec.whatwg.org/#map-set">setting the value of an entry</dfn>,
2545+
<dfn data-x="map remove" data-x-href="https://infra.spec.whatwg.org/#map-remove">removing an entry</dfn>, and
2546+
<dfn data-x="map iterate" data-x-href="https://infra.spec.whatwg.org/#map-iterate">iterate</dfn></li>
25452547
<li>The <dfn data-x-href="https://infra.spec.whatwg.org/#list">list</dfn> data structure and the associated definitions for
25462548
<dfn data-x="list append" data-x-href="https://infra.spec.whatwg.org/#list-append">append</dfn>,
25472549
<dfn data-x="list replace" data-x-href="https://infra.spec.whatwg.org/#list-remove">replace</dfn>,
@@ -26206,9 +26208,9 @@ img.decode().then(() => {
2620626208
<div class="example">
2620726209
<p>Because the <code data-x="dom-img-decode">decode()</code> method attempts to ensure that the
2620826210
decoded image data is available for at least one frame, it can be combined with the <code
26209-
data-x="dom-window-requestAnimationFrame">requestAnimationFrame()</code> API. This means it can
26210-
be used with coding styles or frameworks that ensure that all DOM modifications are batched
26211-
together as <span data-x="list of animation frame callbacks">animation frame
26211+
data-x="dom-AnimationFrameProvider-requestAnimationFrame">requestAnimationFrame()</code> API.
26212+
This means it can be used with coding styles or frameworks that ensure that all DOM modifications
26213+
are batched together as <span data-x="map of animation frame callbacks">animation frame
2621226214
callbacks</span>:</p>
2621326215

2621426216
<pre><code class="js" data-x="">const container = document.querySelector("#container");
@@ -65456,7 +65458,8 @@ interface <dfn>OffscreenCanvasRenderingContext2D</dfn> {
6545665458
<p>Copies the rendering context's <span data-x="offscreencontext2d-bitmap">bitmap</span> to
6545765459
the bitmap of the <span data-x="offscreencanvas-placeholder">placeholder <code>canvas</code>
6545865460
element</span> of the <span>associated <code>OffscreenCanvas</code> object</span>. The copy
65459-
operation is asynchronous.</p>
65461+
operation is synchronous. Calling this method is not needed for the transfer, since it happens
65462+
automatically during the <span>event loop</span> execution.</p>
6546065463
</dd>
6546165464

6546265465
<dt><var>offscreenCanvas</var> = <var>offscreenCanvasRenderingContext2D</var> . <code subdfn
@@ -78159,15 +78162,10 @@ interface <dfn data-export="" data-dfn-type="interface">Window</dfn> : <span>Eve
7815978162
DOMString? <span data-x="dom-prompt">prompt</span>(optional DOMString message = "", optional DOMString default = "");
7816078163
void <span data-x="dom-print">print</span>();
7816178164

78162-
unsigned long <span data-x="dom-window-requestAnimationFrame">requestAnimationFrame</span>(<span>FrameRequestCallback</span> callback);
78163-
void <span data-x="dom-window-cancelAnimationFrame">cancelAnimationFrame</span>(unsigned long handle);
78164-
7816578165
void <span data-x="dom-window-postMessage">postMessage</span>(any message, USVString targetOrigin, optional sequence&lt;<span data-x="idl-object">object</span>&gt; transfer = []);
7816678166
};
7816778167
<span>Window</span> includes <span>GlobalEventHandlers</span>;
78168-
<span>Window</span> includes <span>WindowEventHandlers</span>;
78169-
78170-
callback <dfn>FrameRequestCallback</dfn> = void (<span>DOMHighResTimeStamp</span> time);</code></pre>
78168+
<span>Window</span> includes <span>WindowEventHandlers</span>;</code></pre>
7817178169

7817278170
<!-- for more features to add here, look here:
7817378171
http://msdn.microsoft.com/workshop/author/dhtml/reference/objects/obj_window.asp
@@ -89119,7 +89117,7 @@ dictionary <dfn>PromiseRejectionEventInit</dfn> : <span>EventInit</span> {
8911989117
<li><p><i>Unnecessary rendering</i>: If there are <span data-x="browsing context">browsing
8912089118
contexts</span> <var>browsingContexts</var> for which the user agent believes updating the
8912189119
rendering would have no visible effect and which possess no <code>Document</code> objects with
89122-
a non-empty <span>list of animation frame callbacks</span>, then remove from <var>docs</var>
89120+
a non-empty <span>map of animation frame callbacks</span>, then remove from <var>docs</var>
8912389121
all <code>Document</code> objects whose <span data-x="concept-document-bc">browsing
8912489122
context</span> is in <var>browsingContexts</var>. Invoke the <span>mark paint timing</span>
8912589123
algorithm for each <code>Document</code> object removed.</p></li>
@@ -89186,13 +89184,40 @@ dictionary <dfn>PromiseRejectionEventInit</dfn> : <span>EventInit</span> {
8918689184
</ol>
8918789185
</li>
8918889186

89189-
<li><p>If this is a <a href="#workers">worker</a> <span>event loop</span> (i.e. one running for a
89190-
<code>WorkerGlobalScope</code>), but there are no <span data-x="concept-task">tasks</span> in the
89191-
<span>event loop</span>'s <span data-x="task queue">task queues</span> and the
89192-
<code>WorkerGlobalScope</code> object's <span
89193-
data-x="dom-WorkerGlobalScope-closing">closing</span> flag is true, then destroy the <span>event
89194-
loop</span>, aborting these steps, resuming the <span>run a worker</span> steps described in the
89195-
<a href="#workers">Web workers</a> section below.</p></li>
89187+
<li>
89188+
<p>If this is a <a href="#workers">worker</a> <span>event loop</span> (i.e., one running for
89189+
a <code>WorkerGlobalScope</code>):</p>
89190+
89191+
<ol>
89192+
<li>
89193+
<p>If this is a <span data-x="concept-AnimationFrameProvider-supported">supported</span>
89194+
<code>DedicatedWorkerGlobalScope</code> and the user agent believes that it would benefit from
89195+
having its rendering updated at this time, then:</p>
89196+
89197+
<ol>
89198+
<li><p>Let <var>now</var> be the <span>current high resolution time</span>. <ref
89199+
spec=HRT></p></li>
89200+
89201+
<li><p><span>Run the animation frame callbacks</span> for that
89202+
<code>DedicatedWorkerGlobalScope</code>, passing in <var>now</var> as the
89203+
timestamp.</p></li>
89204+
89205+
<li><p>Update the rendering of that dedicated worker to reflect the current state.</p></li>
89206+
</ol>
89207+
89208+
<p class="note">Similar to the notes for <span data-x="update the rendering">updating the
89209+
rendering</span> in a <span>browsing context</span> <span>event loop</span>, a user agent can
89210+
determine the rate of rendering in the dedicated worker.</p>
89211+
</li>
89212+
89213+
<li><p>If there are no <span data-x="concept-task">tasks</span> in the <span>event
89214+
loop</span>'s <span data-x="task queue">task queues</span> and the
89215+
<code>WorkerGlobalScope</code> object's <span
89216+
data-x="dom-WorkerGlobalScope-closing">closing</span> flag is true, then destroy the
89217+
<span>event loop</span>, aborting these steps, resuming the <span>run a worker</span> steps
89218+
described in the <a href="#workers">Web workers</a> section below.</p></li>
89219+
</ol>
89220+
</li>
8919689221
</ol>
8919789222

8919889223
<hr>
@@ -91586,10 +91611,10 @@ scheduleWork(); // queues a task to do lots of work</code></pre>
9158691611
<p>Authors ought to be aware that scheduling a lot of microtasks has the same performance
9158791612
downsides as running a lot of synchronous code. Both will prevent the browser from doing its own
9158891613
work, such as rendering or scrolling. In many cases, <code
91589-
data-x="dom-window-requestAnimationFrame">requestAnimationFrame()</code> or
91614+
data-x="dom-AnimationFrameProvider-requestAnimationFrame">requestAnimationFrame()</code> or
9159091615
<code>requestIdleCallback()</code> is a better choice. In particular, if the goal is to run code
9159191616
before the next rendering cycle, that is the purpose of <code
91592-
data-x="dom-window-requestAnimationFrame">requestAnimationFrame()</code>.</p>
91617+
data-x="dom-AnimationFrameProvider-requestAnimationFrame">requestAnimationFrame()</code>.</p>
9159391618

9159491619
<p>As can be seen from the following examples, the best way of thinking about <code
9159591620
data-x="dom-queueMicrotask">queueMicrotask()</code> is as a mechanism for rearranging synchronous
@@ -93686,67 +93711,134 @@ loadMySprites().then(runDemo);</code></pre>
9368693711

9368793712
<h3>Animation frames</h3>
9368893713

93689-
<p>Each <code>Document</code> has a <dfn>list of animation frame callbacks</dfn>, which must be
93690-
initially empty, and an <dfn>animation frame callback identifier</dfn>, which is a number which
93691-
must initially be zero.</p>
93714+
<p>Some objects include the <code>AnimationFrameProvider</code> interface mixin.</p>
9369293715

93693-
<p>When the <dfn><code
93694-
data-x="dom-window-requestAnimationFrame">requestAnimationFrame()</code></dfn> method is called,
93695-
the user agent must run the following steps:</p>
93716+
<pre class="idl">callback <dfn>FrameRequestCallback</dfn> = void (<span>DOMHighResTimeStamp</span> time);
9369693717

93697-
<ol>
93718+
interface mixin <dfn>AnimationFrameProvider</dfn> {
93719+
unsigned long <span data-x="dom-AnimationFrameProvider-requestAnimationFrame">requestAnimationFrame</span>(<span>FrameRequestCallback</span> callback);
93720+
void <span data-x="AnimationFrameProvider-cancelAnimationFrame">cancelAnimationFrame</span>(unsigned long handle);
93721+
};
93722+
<span>Window</span> includes <span>AnimationFrameProvider</span>;
93723+
<span>DedicatedWorkerGlobalScope</span> includes <span>AnimationFrameProvider</span>;</pre>
9369893724

93699-
<li><p>Let <var>document</var> be this <code>Window</code> object's <span
93700-
data-x="concept-document-window">associated <code>Document</code></span>.</p></li>
93725+
<p>Each <code>AnimationFrameProvider</code> object also has a <dfn
93726+
data-x="concept-AnimationFrameProvider-target-object">target object</dfn> that stores the
93727+
provider's internal state. It is defined as follows:</p>
9370193728

93702-
<li><p>Increment <var>document</var>'s <span>animation frame callback identifier</span> by
93703-
one.</p></li>
93729+
<dl>
93730+
<dt>If the <code>AnimationFrameProvider</code> is a <code>Window</code></dt>
93731+
<dd>The <code>Window</code>'s <span data-x="concept-document-window">associated
93732+
<code>Document</code></span></dd>
9370493733

93705-
<li><p>Append the method's argument to <var>document</var>'s <span>list of animation frame
93706-
callbacks</span>, associated with <var>document</var>'s <span>animation frame callback
93707-
identifier</span>'s current value.</p></li>
93734+
<dt>If the <code>AnimationFrameProvider</code> is a <code>DedicatedWorkerGlobalScope</code></dt>
93735+
<dd>The <code>DedicatedWorkerGlobalScope</code></dd>
93736+
</dl>
9370893737

93709-
<li><p>Return <var>document</var>'s <span>animation frame callback identifier</span>'s current
93710-
value.</p></li>
93738+
<p>Each <span data-x="concept-AnimationFrameProvider-target-object">target object</span> has a
93739+
<dfn id="list-of-animation-frame-callbacks">map of animation frame callbacks</dfn>, which is an
93740+
<span>ordered map</span> that must be initially empty, and an <dfn>animation frame callback
93741+
identifier</dfn>, which is a number that must initially be zero.</p>
9371193742

93712-
</ol>
93743+
<p>An <code>AnimationFrameProvider</code> <var>provider</var> is considered <dfn
93744+
data-x="concept-AnimationFrameProvider-supported">supported</dfn> if any of the following
93745+
hold:</p>
9371393746

93714-
<p>When the <dfn><code
93715-
data-x="dom-window-cancelAnimationFrame">cancelAnimationFrame()</code></dfn> method is called,
93716-
the user agent must run the following steps:</p>
93747+
<ul class="brief">
93748+
<li><var>provider</var> is a <code>Window</code>.</li>
93749+
93750+
<li><var>provider</var>'s <span>owner set</span> <span data-x="list contains">contains</span> a
93751+
<code>Document</code> object.</li>
93752+
93753+
<li>Any of the <code>DedicatedWorkerGlobalScope</code> objects in <var>provider</var>'s
93754+
<span>owner set</span> are <span
93755+
data-x="concept-AnimationFrameProvider-supported">supported</span>.</li>
93756+
</ul>
93757+
93758+
<hr>
93759+
93760+
<p>The <dfn><code
93761+
data-x="dom-AnimationFrameProvider-requestAnimationFrame">requestAnimationFrame(<var>callback</var>)</code></dfn>
93762+
method must run the following steps:</p>
9371793763

9371893764
<ol>
93765+
<li><p>If this <code>AnimationFrameProvider</code> is not <span
93766+
data-x="concept-AnimationFrameProvider-supported">supported</span>, then throw a
93767+
<span>"<code>NotSupportedError</code>"</span> <code>DOMException</code>.</p></li>
9371993768

93720-
<li><p>Let <var>document</var> be this <code>Window</code> object's <span
93721-
data-x="concept-document-window">associated <code>Document</code></span>.</p></li>
93769+
<li><p>Let <var>target</var> be this <code>AnimationFrameProvider</code>'s <span
93770+
data-x="concept-AnimationFrameProvider-target-object">target object</span>.</p></li>
93771+
93772+
<li><p>Increment <var>target</var>'s <span>animation frame callback identifier</span> by
93773+
one, and let <var>handle</var> be the result.</p></li>
9372293774

93723-
<li><p>Find the entry in <var>document</var>'s <span>list of animation frame callbacks</span>
93724-
that is associated with the value given by the method's argument.</p></li>
93775+
<li><p>Let <var>callbacks</var> be <var>target</var>'s <span>map of animation frame
93776+
callbacks</span>.</p></li>
9372593777

93726-
<li><p>If there is such an entry, remove it from <var>document</var>'s <span>list of animation
93727-
frame callbacks</span>.</p></li>
93778+
<li><p><span data-x="map set">Set</span> <var>callbacks</var>[<var>handle</var>] to
93779+
<var>callback</var>.</p></li>
9372893780

93781+
<li><p>Return <var>handle</var>.</p></li>
9372993782
</ol>
9373093783

93731-
<p>When the user agent is to <dfn>run the animation frame callbacks</dfn> for a
93732-
<code>Document</code> <var>doc</var> with a timestamp <var>now</var>, it must run the following
93733-
steps:</p>
93784+
<p>The <dfn><code
93785+
data-x="AnimationFrameProvider-cancelAnimationFrame">cancelAnimationFrame(<var>handle</var>)</code></dfn>
93786+
method must run the following steps:</p>
9373493787

9373593788
<ol>
93789+
<li><p>If this <code>AnimationFrameProvider</code> is not <span
93790+
data-x="concept-AnimationFrameProvider-supported">supported</span>, then throw a
93791+
<span>"<code>NotSupportedError</code>"</span> <code>DOMException</code>.</p></li>
9373693792

93737-
<li><p>Let <var>callbacks</var> be a list of the entries in <var>doc</var>'s <span>list of
93738-
animation frame callbacks</span>, in the order in which they were added to the list.</p></li>
93793+
<li><p>Let <var>callbacks</var> be this <code>AnimationFrameProvider</code>'s <span
93794+
data-x="concept-AnimationFrameProvider-target-object">target object</span>'s <span>map of
93795+
animation frame callbacks</span>.</p></li>
9373993796

93740-
<li><p>Set <var>doc</var>'s <span>list of animation frame callbacks</span> to the empty
93741-
list.</p></li>
93797+
<li><p><span data-x="map remove">Remove</span> <var>callbacks</var>[<var>handle</var>].</p></li>
93798+
</ol>
93799+
93800+
<p>To <dfn>run the animation frame callbacks</dfn> for a <span
93801+
data-x="concept-AnimationFrameProvider-target-object">target object</span> <var>target</var> with
93802+
a timestamp <var>now</var>:</p>
9374293803

93743-
<li><p>For each entry in <var>callbacks</var>, in order: <span data-x="es-invoking-callback-functions">invoke the callback</span>, passing
93744-
<var>now</var> as the only argument, and if an exception is thrown, <span>report the
93745-
exception</span>. <ref spec=WEBIDL></p></li>
93804+
<ol>
93805+
<li><p>Let <var>callbacks</var> be a clone of <var>target</var>'s <span>map of animation frame
93806+
callbacks</span>.</p></li>
9374693807

93808+
<li><p>Set <var>target</var>'s <span>map of animation frame callbacks</span> to a new empty
93809+
<span>ordered map</span>.</p></li>
93810+
93811+
<li><p><span data-x="map iterate">For each</span> <var>handle</var> → <var>callback</var> of
93812+
<var>callbacks</var>, <span data-x="es-invoking-callback-functions">invoke</span>
93813+
<var>callback</var>, passing <var>now</var> as the only argument, and if an exception is thrown,
93814+
<span>report the exception</span>.</p></li>
9374793815
</ol>
9374893816

93817+
<div class="example">
93818+
<p>Inside workers, <code
93819+
data-x="dom-AnimationFrameProvider-requestAnimationFrame">requestAnimationFrame()</code> can be
93820+
used together with an <code>OffscreenCanvas</code> transferred from a <code>canvas</code>
93821+
element. First, in the document, transfer control to the worker:</p>
93822+
93823+
<pre><code class="js" data-x="">const offscreenCanvas = document.getElementById("c").transferControlToOffscreen();
93824+
worker.postMessage(offscreenCanvas, [offscreenCanvas]);</code></pre>
93825+
93826+
<p>Then, in the worker, the following code will draw a rectangle moving from left to right:</p>
93827+
93828+
<pre><code class="js" data-x="">let ctx, pos = 0;
93829+
function draw(dt) {
93830+
ctx.clearRect(0, 0, 100, 100);
93831+
ctx.fillRect(pos, 0, 10, 10);
93832+
pos += 10 * dt;
93833+
requestAnimationFrame(draw);
93834+
}
9374993835

93836+
self.onmessage = function(ev) {
93837+
const transferredCanvas = ev.data;
93838+
ctx = transferredCanvas.getContext("2d");
93839+
draw();
93840+
};</code></pre>
93841+
</div>
9375093842

9375193843
<h2 split-filename="comms" id="comms">Communication</h2>
9375293844

0 commit comments

Comments
 (0)