Skip to content

Commit

Permalink
Add requestAnimationFrame() in workers
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
fserb authored and domenic committed Aug 27, 2018
1 parent 825ede5 commit 3d9b41d
Showing 1 changed file with 150 additions and 58 deletions.
208 changes: 150 additions & 58 deletions source
Expand Up @@ -2540,8 +2540,10 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
<dfn data-x="map value" data-x-href="https://infra.spec.whatwg.org/#map-value">value</dfn>,
<dfn data-x="map entry" data-x-href="https://infra.spec.whatwg.org/#map-entry">entry</dfn>,
<dfn data-x="map exists" data-x-href="https://infra.spec.whatwg.org/#map-exists">exists</dfn>,
<dfn data-x="map get" data-x-href="https://infra.spec.whatwg.org/#map-get">getting the value of an entry</dfn>, and
<dfn data-x="map set" data-x-href="https://infra.spec.whatwg.org/#map-set">setting the value of an entry</dfn></li>
<dfn data-x="map get" data-x-href="https://infra.spec.whatwg.org/#map-get">getting the value of an entry</dfn>,
<dfn data-x="map set" data-x-href="https://infra.spec.whatwg.org/#map-set">setting the value of an entry</dfn>,
<dfn data-x="map remove" data-x-href="https://infra.spec.whatwg.org/#map-remove">removing an entry</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
<dfn data-x="list append" data-x-href="https://infra.spec.whatwg.org/#list-append">append</dfn>,
<dfn data-x="list replace" data-x-href="https://infra.spec.whatwg.org/#list-remove">replace</dfn>,
Expand Down Expand Up @@ -26206,9 +26208,9 @@ img.decode().then(() => {
<div class="example">
<p>Because the <code data-x="dom-img-decode">decode()</code> method attempts to ensure that the
decoded image data is available for at least one frame, it can be combined with the <code
data-x="dom-window-requestAnimationFrame">requestAnimationFrame()</code> API. This means it can
be used with coding styles or frameworks that ensure that all DOM modifications are batched
together as <span data-x="list of animation frame callbacks">animation frame
data-x="dom-AnimationFrameProvider-requestAnimationFrame">requestAnimationFrame()</code> API.
This means it can be used with coding styles or frameworks that ensure that all DOM modifications
are batched together as <span data-x="map of animation frame callbacks">animation frame
callbacks</span>:</p>

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

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

unsigned long <span data-x="dom-window-requestAnimationFrame">requestAnimationFrame</span>(<span>FrameRequestCallback</span> callback);
void <span data-x="dom-window-cancelAnimationFrame">cancelAnimationFrame</span>(unsigned long handle);

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 = []);
};
<span>Window</span> includes <span>GlobalEventHandlers</span>;
<span>Window</span> includes <span>WindowEventHandlers</span>;

callback <dfn>FrameRequestCallback</dfn> = void (<span>DOMHighResTimeStamp</span> time);</code></pre>
<span>Window</span> includes <span>WindowEventHandlers</span>;</code></pre>

<!-- for more features to add here, look here:
http://msdn.microsoft.com/workshop/author/dhtml/reference/objects/obj_window.asp
Expand Down Expand Up @@ -89119,7 +89117,7 @@ dictionary <dfn>PromiseRejectionEventInit</dfn> : <span>EventInit</span> {
<li><p><i>Unnecessary rendering</i>: If there are <span data-x="browsing context">browsing
contexts</span> <var>browsingContexts</var> for which the user agent believes updating the
rendering would have no visible effect and which possess no <code>Document</code> objects with
a non-empty <span>list of animation frame callbacks</span>, then remove from <var>docs</var>
a non-empty <span>map of animation frame callbacks</span>, then remove from <var>docs</var>
all <code>Document</code> objects whose <span data-x="concept-document-bc">browsing
context</span> is in <var>browsingContexts</var>. Invoke the <span>mark paint timing</span>
algorithm for each <code>Document</code> object removed.</p></li>
Expand Down Expand Up @@ -89186,13 +89184,40 @@ dictionary <dfn>PromiseRejectionEventInit</dfn> : <span>EventInit</span> {
</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>If this is a <a href="#workers">worker</a> <span>event loop</span> (i.e., one running for
a <code>WorkerGlobalScope</code>):</p>

<ol>
<li>
<p>If this is a <span data-x="concept-AnimationFrameProvider-supported">supported</span>
<code>DedicatedWorkerGlobalScope</code> and the user agent believes that it would benefit from
having its rendering updated at this time, then:</p>

<ol>
<li><p>Let <var>now</var> be the <span>current high resolution time</span>. <ref
spec=HRT></p></li>

<li><p><span>Run the animation frame callbacks</span> for that
<code>DedicatedWorkerGlobalScope</code>, passing in <var>now</var> as the
timestamp.</p></li>

<li><p>Update the rendering of that dedicated worker to reflect the current state.</p></li>
</ol>

<p class="note">Similar to the notes for <span data-x="update the rendering">updating the
rendering</span> in a <span>browsing context</span> <span>event loop</span>, a user agent can
determine the rate of rendering in the dedicated worker.</p>
</li>

<li><p>If 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>
</ol>
</li>
</ol>

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

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

<h3>Animation frames</h3>

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

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

<ol>
interface mixin <dfn>AnimationFrameProvider</dfn> {
unsigned long <span data-x="dom-AnimationFrameProvider-requestAnimationFrame">requestAnimationFrame</span>(<span>FrameRequestCallback</span> callback);
void <span data-x="AnimationFrameProvider-cancelAnimationFrame">cancelAnimationFrame</span>(unsigned long handle);
};
<span>Window</span> includes <span>AnimationFrameProvider</span>;
<span>DedicatedWorkerGlobalScope</span> includes <span>AnimationFrameProvider</span>;</pre>

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

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

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

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

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

<p>When the <dfn><code
data-x="dom-window-cancelAnimationFrame">cancelAnimationFrame()</code></dfn> method is called,
the user agent must run the following steps:</p>
<ul class="brief">
<li><var>provider</var> is a <code>Window</code>.</li>

<li><var>provider</var>'s <span>owner set</span> <span data-x="list contains">contains</span> a
<code>Document</code> object.</li>

<li>Any of the <code>DedicatedWorkerGlobalScope</code> objects in <var>provider</var>'s
<span>owner set</span> are <span
data-x="concept-AnimationFrameProvider-supported">supported</span>.</li>
</ul>

<hr>

<p>The <dfn><code
data-x="dom-AnimationFrameProvider-requestAnimationFrame">requestAnimationFrame(<var>callback</var>)</code></dfn>
method must run the following steps:</p>

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

<li><p>Let <var>document</var> be this <code>Window</code> object's <span
data-x="concept-document-window">associated <code>Document</code></span>.</p></li>
<li><p>Let <var>target</var> be this <code>AnimationFrameProvider</code>'s <span
data-x="concept-AnimationFrameProvider-target-object">target object</span>.</p></li>

<li><p>Increment <var>target</var>'s <span>animation frame callback identifier</span> by
one, and let <var>handle</var> be the result.</p></li>

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

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

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

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

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

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

<li><p>Set <var>doc</var>'s <span>list of animation frame callbacks</span> to the empty
list.</p></li>
<li><p><span data-x="map remove">Remove</span> <var>callbacks</var>[<var>handle</var>].</p></li>
</ol>

<p>To <dfn>run the animation frame callbacks</dfn> for a <span
data-x="concept-AnimationFrameProvider-target-object">target object</span> <var>target</var> with
a timestamp <var>now</var>:</p>

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

<li><p>Set <var>target</var>'s <span>map of animation frame callbacks</span> to a new empty
<span>ordered map</span>.</p></li>

<li><p><span data-x="map iterate">For each</span> <var>handle</var> → <var>callback</var> of
<var>callbacks</var>, <span data-x="es-invoking-callback-functions">invoke</span>
<var>callback</var>, passing <var>now</var> as the only argument, and if an exception is thrown,
<span>report the exception</span>.</p></li>
</ol>

<div class="example">
<p>Inside workers, <code
data-x="dom-AnimationFrameProvider-requestAnimationFrame">requestAnimationFrame()</code> can be
used together with an <code>OffscreenCanvas</code> transferred from a <code>canvas</code>
element. First, in the document, transfer control to the worker:</p>

<pre><code class="js" data-x="">const offscreenCanvas = document.getElementById("c").transferControlToOffscreen();
worker.postMessage(offscreenCanvas, [offscreenCanvas]);</code></pre>

<p>Then, in the worker, the following code will draw a rectangle moving from left to right:</p>

<pre><code class="js" data-x="">let ctx, pos = 0;
function draw(dt) {
ctx.clearRect(0, 0, 100, 100);
ctx.fillRect(pos, 0, 10, 10);
pos += 10 * dt;
requestAnimationFrame(draw);
}

self.onmessage = function(ev) {
const transferredCanvas = ev.data;
ctx = transferredCanvas.getContext("2d");
draw();
};</code></pre>
</div>

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

Expand Down

0 comments on commit 3d9b41d

Please sign in to comment.