├── README.md ├── Explainer.md ├── Spec.html └── Overview.html /README.md: -------------------------------------------------------------------------------- 1 | animation-proxy 2 | =============== 3 | 4 | Accelerated Web Worker Animations 5 | 6 | This project contains (or will contain) documents explaining the animation proxy 7 | idea. With any luck, it will eventually contain a spec proposal and a polyfill. 8 | -------------------------------------------------------------------------------- /Explainer.md: -------------------------------------------------------------------------------- 1 | JavaScript-Driven Accelerated Animations 2 | =============== 3 | {vollick, jbroman, ajuma, rjkroege}@chromium.org 4 | 5 | Problem 6 | ------- 7 | JavaScript-driven animations are extremely flexible and powerful, but are subject to main thread jank (by jank, I mean unpredictable interruptions in the rate that animations are serviced on a thread due to other, unrelated work on that thread). And although JavaScript can run on a web worker, DOM access and CSS property animations are not permitted. Despite their susceptibility to main thread jank, main thread animations are widely used; they're the only way to create common effects such as parallax, position-sticky, image carousels, custom scroll animations, iphone-style contact lists, and physics-based animations. In a perfect world, the main thread would always be responsive enough to guarantee that a JavaScript animation callback would be serviced every frame. In reality, this is extremely hard to achieve, both for user agents and developers of large sites composed of disparate components. The result is a lot of janky pages. 8 | 9 | Why can't we update CSS properties from a worker thread? Updating CSS properties _could_ effect a style recalc or a layout and those operations must happen on the main thread. That said, there are certain 'layout-free' properties that can be modified without these side effects. These properties include transform, opacity and scroll offset. Clearly identifying these layout-free properties and allowing them to be animated from any thread would provide a simple and powerful way to achieve smooth animations. 10 | 11 | Goal 12 | ---- 13 | To allow authors to drive accelerated animations of CSS properties via JavaScript from a worker thread. We will focus on transform, opacity and scroll offset animations initially since virtually all user agents already support accelerated animation of these properties, either by accelerated CSS animations or threaded scrolling. 14 | 15 | High Level Proposal 16 | ------------------- 17 | Allow the creation of proxy objects that provide access to certain layout-free properties. These proxies could then be used (asynchronously) on web workers. 18 | 19 | A Tiny, But Surprisingly Expressive Kernel 20 | ------------------------------------------ 21 | What does this proxy buy us? A number of proposals that have been drafted to address some of the use cases mentioned in the problem statement could largely be implemented on top of this kernel. So, too, could some existing web APIs. Specifically, 22 | 23 | - Some bits of [Web Animations](http://dev.w3.org/fxtf/web-animations/) 24 | - [Touch-based Animation Scrubbing](https://docs.google.com/document/d/1vRUo_g1il-evZs975eNzGPOuJS7H5UBxs-iZmXHux48/edit) 25 | - [position:sticky](http://updates.html5rocks.com/2012/08/Stick-your-landings-position-sticky-lands-in-WebKit) 26 | - [Smooth Scrolling](http://dev.w3.org/csswg/cssom-view/), Sections 4, 5, 7, 12, and 13. 27 | - Accelerated CSS animations. ([I/O talk](http://www.youtube.com/watch?v=hAzhayTnhEI)) 28 | 29 | There have been many attempts at creating web APIs to make authoring smooth animations both possible and easy. It would be wonderful if the problem could be addressed with a small, easy-to-implement kernel. It would also let creative web developers devise new animation authoring libraries that are just as performant as their native counterparts. In addition to its simplicity, another benefit of this approach is that it ‘explains the web’ in the [Extensible Web Manifesto](http://extensiblewebmanifesto.org) sense. In particular, it explains accelerated animations. This brings us to... 30 | 31 | The Big Concern 32 | --------------- 33 | We want to explain the web, not the particular implementation details of a particular browser at a particular point in its history. Are we marrying ourselves to implementation details here? 34 | 35 | I think the answer is no. Virtually all user agents support (via CSS) accelerated opacity and transform animations, and they’re going to have to support them for the foreseeable future. Threaded scrolling is also increasingly common. By whatever means browsers are currently able to guarantee that things can slide around, scroll and fade in and out efficiently, they could potentially permit these effects to be driven by JavaScript on a web worker. It doesn’t, for example, tie us to the idea of a composited layer or a layer tree, concepts that may not be meaningful in all browser implementations. The animated elements might, say, be redrawn by the GPU each frame. But this doesn’t matter. These implementation details are orthogonal to the animation proxy concept. 36 | 37 | What About The Details? 38 | ----------------------- 39 | This explainer was kept intentionally high level. Our hope is to convince you of the merit of this problem and idea before diving into a debate about our particular solution. We, of course, have some thoughts about what the proxy API could look like, how you might create them, and how we could address timing issues, but those details will get worked out as we discuss this idea with you! 40 | 41 | ...well, maybe it wouldn't hurt to give a few examples using a hypothetical API (which will probably look a lot different than the final version). Before getting to the code, I want to emphasize that I do not expect that the average web developer will ever have to deal with animation proxies directly. It would be a simple matter to wrap the animation proxy code in a jQuery-like JavaScript library that manages the animation web worker thread and all proxies. I imagine that most developers would just use such a library. That said, here are a few snippets. 42 | 43 | ### parallax 44 | ```JavaScript 45 | // On the main thread. 46 | overflow_scroll_proxy = overflow_scroll_element.getAnimationProxy(); 47 | parallax_scroll_proxy = parallax_scroll_element.getAnimationProxy(); 48 | 49 | worker.postMessage({ 50 | 'scale': 0.9, 51 | 'overflow_proxy': overflow_scroll_proxy, 52 | 'parallax_proxy': parallax_scroll_proxy, 53 | }); 54 | 55 | // On the web worker. 56 | function on_message(msg) { 57 | parallax_scroll_scale = msg.scale; 58 | overflow_scroll_proxy = msg.overflow_proxy; 59 | parallax_scroll_proxy = msg.parallax_proxy; 60 | self.scheduleTick(tick); 61 | } 62 | 63 | function tick(timestamp) { 64 | overflow_scroll_proxy.getScrollOffset().then(do_parallax, error); 65 | } 66 | 67 | function do_parallax(offset) { 68 | offset.scale(parallax_scroll_scale); 69 | parallax_scroll_proxy.setScrollOffset(offset) 70 | .then(function(result) { self.scheduleTick(tick); }, 71 | error); 72 | } 73 | ``` 74 | 75 | ### position: sticky 76 | ```JavaScript 77 | // This is just the meat of the web worker side. Work would definitely need to be done on 78 | // the main thread to compute min_offset and max_offset and to pass that information to 79 | // the worker. 80 | function tick(timestamp) { 81 | overflow_scroll_proxy.getScrollOffset().then(do_sticky, error); 82 | } 83 | 84 | function do_sticky(offset) { 85 | var compensation = min_offset - Math.min(Math.max(offset.y, min_offset), max_offset); 86 | var transform = new CSSMatrix(); 87 | transform.Translate(0, compensation); 88 | sticky_element_proxy.setTransform(transform) 89 | .then(function(result) { self.scheduleTick(tick); }, 90 | error); 91 | } 92 | ``` 93 | -------------------------------------------------------------------------------- /Spec.html: -------------------------------------------------------------------------------- 1 |

Animation Proxy

2 |
  3 |   Status: ED
  4 |   ED: http://htmlpreview.github.io/?https://github.com/ianvollick/animation-proxy/blob/master/Overview.html
  5 |   Shortname: anim-proxy
  6 |   Level: 1
  7 |   Editor: Ian Vollick, Google, vollick@chromium.org
  8 |   Abstract: An animation proxy provides asynchronous access to certain layout-and-style-recalc-free properties. It may be used on a web worker thread.
  9 |   Ignored Terms: Promise, Element, DOMMatrix, WebWorker
 10 | 
11 |

Introduction

12 | JavaScript-driven animations are extremely flexible and powerful, but are subject to main thread jank (by jank, we mean unpredictable interruptions in the rate that animations are serviced on a thread due to other, unrelated work on that thread, and by main thread we refer to the thread on which JavaScript may access the DOM). And although JavaScript can run on a web worker, DOM access and CSS property animations are not permitted. Main thread animations are still widely used despite the fact that they are susceptible to jank; they are the only way to create common effects such as parallax, position-sticky, image carousels, custom scroll animations, iphone-style contact lists, and physics-based animations. In a perfect world, the main thread would always be responsive enough to guarantee that a JavaScript animation callback would be serviced every frame. In reality, this is extremely hard to achieve, both for user agents and developers of large sites composed of disparate components. The result is that many pages are unnecessarily janky. 13 | 14 | Access to the DOM from JavaScript is not thread safe, and although it is possible to parallelize certain operations such as layout, allowing layout-dependant properties to be modified from multiple threads leads to contention. There are, however, certain layout free properties that could be modified asynchronously from other threads without contention. A layout free CSS property, is any property that may be changed without incurring a layout or or a style recalc. These properties currently include transform, opacity and scroll offset. Virtually all user agents support these accelerated CSS properties (i.e., properties that may be animated on another thread) and increasingly support threaded scrolling. This is possible by making use of a browser component commonly called a compositor, which may modify these values without involving the main thread. Clearly identifying these layout free properties and allowing them to be animated from any thread would provide a simple and powerful way to achieve smooth animations, even in the face of unpredictable main thread work. 15 | 16 | This document proposes a new primitive, AnimationProxy, which permits a WebWorker to directly communicate with the compositor component without involving the main thread. 17 | 18 |

DOM Interfaces

19 | 20 |

AnimationProxy Creation

21 | 22 | AnimationProxy instances are obtained from DOM elements using the following interface. 23 | 24 |
 25 | interface AnimationProxyProvider {
 26 |     [RaisesException] AnimationProxy getAnimationProxy();
 27 | };
 28 | 
 29 | Element implements AnimationProxyProvider;
 30 | 
31 | 32 | Note: An AnimationProxy may be created for any element whose transform can be animated using a CSS transition (i.e., block level elements). If getAnimationProxy is called on an unsuitable element, it will throw an InvalidAccessError exception. 33 | 34 | Note: As with 3d transforms, the creation of an AnimationProxy will force the element to establish a stacking context. It will also act as the containing block for any fixed position descendants. See this section of the transform specification for more information on the effect on fixed position elements. 35 | 36 |
37 |

38 | An example: 39 | 40 |

 41 |         var spinner = document.getElementById('spinner');
 42 |         try {
 43 |             var spinnerProxy = spinner.getAnimationProxy();
 44 |             worker.postMessage({ 'effect': 'spin', 'proxy': spinnerProxy });
 45 |         } catch (exception) {
 46 |             console.log('Could not start spinner: ' + e.message);
 47 |         }
 48 |     
49 |
50 | 51 |

The AnimationProxy Interface

52 | 53 | The AnimationProxy is designed to permit asynchronous communication between web worker threads and the compositor component. Being an asynchronous API, it naturally deals in Promise objects. The Promise interface is defined in this spec. The error attribute of the Promise objects will be set to an enumerator of AnimationProxyErrors if the Promise is rejected. If the Promise's accept callback is invoked, the value attribute of the Promise object will be void for the set functions and for the get functions will be an instance of the type of the argument for the corresponding set function. For example, in the accept callback for getTransform the value will be a DOMMatrix. 54 | 55 |
 56 | interface AnimationProxy {
 57 |     Promise getTransform();
 58 |     Promise getOpacity();
 59 |     Promise getScrollOffset();
 60 | 
 61 |     Promise setTransform(DOMMatrix transform);
 62 |     Promise setOpacity(float opacity);
 63 |     Promise setScrollOffset(Point point);
 64 | };
 65 | 
 66 | enum AnimationProxyErrors {
 67 |     "proxiedElementWasRemoved"
 68 | };
 69 | 
70 | 71 | The get and set functions above return a Promise, but the work associated with the promise may be executed at any time, even before the promise is returned. This work is outlined here. 72 |
    73 |
  1. If there are too many operations in the current transaction (see AnimationFrameContext below), then the Promise will be rejected and its error will be "tooManyOperationsForAtomicTransaction". If this happens, none of the following steps will be performed. 74 |
  2. The get or set operation is sent to the compositor component. This operation may be sent along with other operations bundled in the same atomic transaction (see AnimationFrameContext below). 75 |
  3. The compositor will perform the operation, provided it is not a part of an atomic transaction with a deadline that has passed its deadline and the proxied element hasn't been removed. 76 |
  4. The Promise's accept or reject callback is invoked as appropriate, depending on the success of the preceding step. 77 |
78 | 79 | The DOMMatrix interface is defined here. 80 | 81 | Note: AnimationProxy is structured cloneable data and may be posted in messages between threads. It is permissible to use a proxy on multiple threads, but due to asynchrony, this may result in undefined behavior: there will be contention for the properties of the proxied element. 82 | 83 |

The WebWorkerAnimationTiming Interface

84 | 85 | AnimationProxy instances are intended for animation, and as such are most useful in the context of a requestAnimationFrame callback. Ideally, this callback will be executed each frame, though that may not always be possible for all user agents. Since these callbacks may be asynchronous with respect to the compositor, it will be convenient to provide some timing information to these callbacks and to allow for atomic transactions. So rather than having a web worker implement WindowAnimationTiming, we define the following, very similar interface. 86 | 87 |
 88 | [NoInterfaceObject]
 89 | interface WebWorkerAnimationTiming {
 90 |     long requestAnimationFrame(AnimationFrameCallback callback);
 91 |     void cancelAnimationFrame(long handle);
 92 | };
 93 | 
 94 | WebWorker implements WebWorkerAnimationTiming;
 95 | 
 96 | interface AnimationFrameContext {
 97 |     float progress();
 98 |     Promise beginAtomicTransaction(AnimationFrameCallback cb);
 99 |     Promise beginAtomicTransactionWithDeadline(AnimationFrameCallback cb);
100 | };
101 | 
102 | callback AnimationFrameCallback = void (AnimationFrameContext context);
103 | 
104 | enum AnimationFrameContextErrors {
105 |     "missedFrameDeadline",
106 |     "tooManyOperationsForAtomicTransaction"
107 | };
108 | 
109 | 110 | The AnimationFrameContext's progress method returns the fractional number of frames since the start of the frame in which the requestAnimationFrame callback was issued. For example, a value of 1.2 would indicate that we've passed one frame boundary since the callback was issued and we are 20% of the way through the next frame. A value greater than 1.0 indicates that beginAtomicTransactionWithDeadline will certainly fail as it has already missed the deadline for the current frame. 111 | 112 | Any modification to AnimationProxy instances that occur in an AnimationFrameCallback passed to beginAtomicTransaction are guaranteed to take effect in the same frame, although this may be a different frame than the frame in which requestAnimationFrame callback was issued. Changes made in a callback passed to beginAtomicTransactionWithDeadline will be undone if the callback is unable to complete in time. Modifications to other, non-AnimationProxy values will not be rolled back, however. If a rollback was required, the Promise's reject callback will be executed and its error attribute will be set to an enumerator of AnimationFrameContextErrors. For both beginAtomicTransaction and beginAtomicTransactionWithDeadline, the Promise's value in the accept callbacks will be an integer and will indicate the number of frames between the frame that the changes were applied and the frame in which the requestAnimationFrame callback was issued. 113 | 114 |
115 |

116 | An example: 117 | 118 |

119 |         // On the main thread.
120 |         overflow_scroll_proxy = overflow_scroll_element.getAnimationProxy();
121 |         parallax_scroll_proxy = parallax_scroll_element.getAnimationProxy();
122 | 
123 |         worker.postMessage({
124 |             'scale': 0.9,
125 |             'overflow_proxy': overflow_scroll_proxy,
126 |             'parallax_proxy': parallax_scroll_proxy,
127 |         });
128 | 
129 |         // On the web worker.
130 |         function on_message(msg) {
131 |             parallax_scroll_scale = msg.scale;
132 |             overflow_scroll_proxy = msg.overflow_proxy;
133 |             parallax_scroll_proxy = msg.parallax_proxy;
134 |             self.requestAnimationFrame(tick);
135 |         }
136 | 
137 |         function tick(animationFrameContext) {
138 |             overflow_scroll_proxy.getScrollOffset().then(do_parallax(animationFrameContext), error);
139 |         }
140 | 
141 |         function do_parallax(animationFrameContext) {
142 |             animationFrameContext.beginAtomicTransaction(function() {
143 |                 var offset = value.scale(parallax_scroll_scale);
144 |                 parallax_scroll_proxy.setScrollOffset(offset).then(scrolled, error);
145 |             }.then(function() { self.requestAnimationFrame(tick); }, error);
146 |         }
147 |     
148 |
149 | 150 | Note: It is permissible to interact with AnimationProxy instances outside the requestAnimationFrame callback, but there is no guarantee about the atomicity of these operations. For example, if a developer chooses to manually tick their animations using setTimout, then it is possible that some of the AnimationProxy modifications made in that animation callback will take effect in one frame and the rest in another. 151 | 152 | Note: It is the prerogative of the the user agent which thread the AnimationFrameCallback executes on. In particular, a user agent may choose to execute the callback on a thread in the compositor component to allow synchronous access to the compositor-owned, layout free properties. It may also choose to execute only a portion of the function on that thread to prevent unacceptable delays in the compositor component. 153 | 154 | Note: the Promise returned by beginAtomicTransaction or beginAtomicTransactionWithDeadline will not be resolved before the Promise's for the operations performed in that transaction. 155 | -------------------------------------------------------------------------------- /Overview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Animation Proxy 4 | 5 | 6 | 7 | 8 | 9 |
10 |

13 |

Animation Proxy

14 |

Editor’s Draft, 15 | 6 November 2013

16 |
This version:
http://htmlpreview.github.io/?https://github.com/ianvollick/animation-proxy/blob/master/Overview.html
Editor’s Draft:
http://htmlpreview.github.io/?https://github.com/ianvollick/animation-proxy/blob/master/Overview.html 17 |
Feedback:
18 |
www-style@w3.org 19 | with subject line 20 | “[anim-proxy] … message topic …”(archives)
Test Suite:
None Yet
Editors: 21 |
(Google)
22 |
23 | 24 |
25 |
26 | 27 |

Abstract

28 |

An animation proxy provides asynchronous access to certain layout-and-style-recalc-free properties. It may be used on a web worker thread. 29 | CSS is a language for describing the rendering of structured documents 30 | (such as HTML and XML) 31 | on screen, on paper, in speech, etc.

32 | 33 |

Status of this document

34 |

35 | This is a public copy of the editors’ draft. 36 | It is provided for discussion only and may change at any moment. 37 | Its publication here does not imply endorsement of its contents by W3C. 38 | Don’t cite this document other than as work in progress. 39 | 40 |

41 | The (archived) public mailing list 42 | www-style@w3.org 43 | (see instructions) 44 | is preferred for discussion of this specification. 45 | When sending e-mail, 46 | please put the text “anim-proxy” in the subject, 47 | preferably like this: 48 | “[anim-proxy] …summary of comment…” 49 | 50 |

51 | This document was produced by the CSS Working Group 52 | (part of the Style Activity). 53 | 54 |

55 | This document was produced by a group operating under 56 | the 5 February 2004 W3C Patent Policy. 57 | W3C maintains a public list of any patent disclosures 58 | made in connection with the deliverables of the group; 59 | that page also includes instructions for disclosing a patent. 60 | An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) 61 | must disclose the information in accordance with section 6 of the W3C Patent Policy.

62 |
63 | 64 |

Table of contents

65 |
77 | 78 |

1 Introduction

79 | JavaScript-driven animations are extremely flexible and powerful, but are subject to main thread jank (by jank, we mean unpredictable interruptions in the rate that animations are serviced on a thread due to other, unrelated work on that thread, and by main thread we refer to the thread on which JavaScript may access the DOM). And although JavaScript can run on a web worker, DOM access and CSS property animations are not permitted. Main thread animations are still widely used despite the fact that they are susceptible to jank; they are the only way to create common effects such as parallax, position-sticky, image carousels, custom scroll animations, iphone-style contact lists, and physics-based animations. In a perfect world, the main thread would always be responsive enough to guarantee that a JavaScript animation callback would be serviced every frame. In reality, this is extremely hard to achieve, both for user agents and developers of large sites composed of disparate components. The result is that many pages are unnecessarily janky. 80 | 81 |

Access to the DOM from JavaScript is not thread safe, and although it is possible to parallelize certain operations such as layout, allowing layout-dependant properties to be modified from multiple threads leads to contention. There are, however, certain layout free properties that could be modified asynchronously from other threads without contention. A layout free CSS property, is any property that may be changed without incurring a layout or or a style recalc. These properties currently include transform, opacity and scroll offset. Virtually all user agents support these accelerated CSS properties (i.e., properties that may be animated on another thread) and increasingly support threaded scrolling. This is possible by making use of a browser component commonly called a compositor, which may modify these values without involving the main thread. Clearly identifying these layout free properties and allowing them to be animated from any thread would provide a simple and powerful way to achieve smooth animations, even in the face of unpredictable main thread work. 82 | 83 |

This document proposes a new primitive, AnimationProxy, which permits a WebWorker to directly communicate with the compositor component without involving the main thread. 84 | 85 |

2 DOM Interfaces

86 | 87 |

2.1 AnimationProxy Creation

88 | 89 |

AnimationProxy instances are obtained from DOM elements using the following interface. 90 | 91 |

interface AnimationProxyProvider {
 92 |     [RaisesException] AnimationProxy getAnimationProxy();
 93 | };
 94 | 
 95 | Element implements AnimationProxyProvider;
 96 | 
97 |

Note: An AnimationProxy may be created for any element whose transform can be animated using a CSS transition (i.e., block level elements). If getAnimationProxy is called on an unsuitable element, it will throw an InvalidAccessError exception. 98 | 99 |

Note: As with 3d transforms, the creation of an AnimationProxy will force the element to establish a stacking context. It will also act as the containing block for any fixed position descendants. See this section of the transform specification for more information on the effect on fixed position elements. 100 | 101 |

102 |

103 | An example: 104 | 105 |

    var spinner = document.getElementById('spinner');
106 |     try {
107 |         var spinnerProxy = spinner.getAnimationProxy();
108 |         worker.postMessage({ 'effect': 'spin', 'proxy': spinnerProxy });
109 |     } catch (exception) {
110 |         console.log('Could not start spinner: ' + e.message);
111 |     }
112 | 
113 | 114 |

2.2 The AnimationProxy Interface

115 | 116 |

The AnimationProxy is designed to permit asynchronous communication between web worker threads and the compositor component. Being an asynchronous API, it naturally deals in Promise objects. The Promise interface is defined in this spec. The error attribute of the Promise objects will be set to an enumerator of AnimationProxyErrors if the Promise is rejected. If the Promise’s accept callback is invoked, the value attribute of the Promise object will be void for the set functions and for the get functions will be an instance of the type of the argument for the corresponding set function. For example, in the accept callback for getTransform the value will be a DOMMatrix. 117 | 118 |

interface AnimationProxy {
119 |     Promise getTransform();
120 |     Promise getOpacity();
121 |     Promise getScrollOffset();
122 | 
123 |     Promise setTransform(DOMMatrix transform);
124 |     Promise setOpacity(float opacity);
125 |     Promise setScrollOffset(Point point);
126 | };
127 | 
128 | enum AnimationProxyErrors {
129 |     "proxiedElementWasRemoved"
130 | };
131 | 
132 |

The get and set functions above return a Promise, but the work associated with the promise may be executed at any time, even before the promise is returned. This work is outlined here. 133 |

    134 |
  1. If there are too many operations in the current transaction (see AnimationFrameContext below), then the Promise will be rejected and its error will be "tooManyOperationsForAtomicTransaction". If this happens, none of the following steps will be performed. 135 |
  2. The get or set operation is sent to the compositor component. This operation may be sent along with other operations bundled in the same atomic transaction (see AnimationFrameContext below). 136 |
  3. The compositor will perform the operation, provided it is not a part of an atomic transaction with a deadline that has passed its deadline and the proxied element hasn’t been removed. 137 |
  4. The Promise’s accept or reject callback is invoked as appropriate, depending on the success of the preceding step. 138 |
139 | 140 |

The DOMMatrix interface is defined here. 141 | 142 |

Note: AnimationProxy is structured cloneable data and may be posted in messages between threads. It is permissible to use a proxy on multiple threads, but due to asynchrony, this may result in undefined behavior: there will be contention for the properties of the proxied element. 143 | 144 |

2.3 The WebWorkerAnimationTiming Interface

145 | 146 |

AnimationProxy instances are intended for animation, and as such are most useful in the context of a requestAnimationFrame callback. Ideally, this callback will be executed each frame, though that may not always be possible for all user agents. Since these callbacks may be asynchronous with respect to the compositor, it will be convenient to provide some timing information to these callbacks and to allow for atomic transactions. So rather than having a web worker implement WindowAnimationTiming, we define the following, very similar interface. 147 | 148 |

[NoInterfaceObject]
149 | interface WebWorkerAnimationTiming {
150 |     long requestAnimationFrame(AnimationFrameCallback callback);
151 |     void cancelAnimationFrame(long handle);
152 | };
153 | 
154 | WebWorker implements WebWorkerAnimationTiming;
155 | 
156 | interface AnimationFrameContext {
157 |     float progress();
158 |     Promise beginAtomicTransaction(AnimationFrameCallback cb);
159 |     Promise beginAtomicTransactionWithDeadline(AnimationFrameCallback cb);
160 | };
161 | 
162 | callback AnimationFrameCallback = void (AnimationFrameContext context);
163 | 
164 | enum AnimationFrameContextErrors {
165 |     "missedFrameDeadline",
166 |     "tooManyOperationsForAtomicTransaction"
167 | };
168 | 
169 |

The AnimationFrameContext’s progress method returns the fractional number of frames since the start of the frame in which the requestAnimationFrame callback was issued. For example, a value of 1.2 would indicate that we’ve passed one frame boundary since the callback was issued and we are 20% of the way through the next frame. A value greater than 1.0 indicates that beginAtomicTransactionWithDeadline will certainly fail as it has already missed the deadline for the current frame. 170 | 171 |

Any modification to AnimationProxy instances that occur in an AnimationFrameCallback passed to beginAtomicTransaction are guaranteed to take effect in the same frame, although this may be a different frame than the frame in which requestAnimationFrame callback was issued. Changes made in a callback passed to beginAtomicTransactionWithDeadline will be undone if the callback is unable to complete in time. Modifications to other, non-AnimationProxy values will not be rolled back, however. If a rollback was required, the Promise’s reject callback will be executed and its error attribute will be set to an enumerator of AnimationFrameContextErrors. For both beginAtomicTransaction and beginAtomicTransactionWithDeadline, the Promise’s value in the accept callbacks will be an integer and will indicate the number of frames between the frame that the changes were applied and the frame in which the requestAnimationFrame callback was issued. 172 | 173 |

174 |

175 | An example: 176 | 177 |

    // On the main thread.
178 |     overflow_scroll_proxy = overflow_scroll_element.getAnimationProxy();
179 |     parallax_scroll_proxy = parallax_scroll_element.getAnimationProxy();
180 | 
181 |     worker.postMessage({
182 |         'scale': 0.9,
183 |         'overflow_proxy': overflow_scroll_proxy,
184 |         'parallax_proxy': parallax_scroll_proxy,
185 |     });
186 | 
187 |     // On the web worker.
188 |     function on_message(msg) {
189 |         parallax_scroll_scale = msg.scale;
190 |         overflow_scroll_proxy = msg.overflow_proxy;
191 |         parallax_scroll_proxy = msg.parallax_proxy;
192 |         self.requestAnimationFrame(tick);
193 |     }
194 | 
195 |     function tick(animationFrameContext) {
196 |         overflow_scroll_proxy.getScrollOffset().then(do_parallax(animationFrameContext), error);
197 |     }
198 | 
199 |     function do_parallax(animationFrameContext) {
200 |         animationFrameContext.beginAtomicTransaction(function() {
201 |             var offset = value.scale(parallax_scroll_scale);
202 |             parallax_scroll_proxy.setScrollOffset(offset).then(scrolled, error);
203 |         }.then(function() { self.requestAnimationFrame(tick); }, error);
204 |     }
205 | 
206 | 207 |

Note: It is permissible to interact with AnimationProxy instances outside the requestAnimationFrame callback, but there is no guarantee about the atomicity of these operations. For example, if a developer chooses to manually tick their animations using setTimout, then it is possible that some of the AnimationProxy modifications made in that animation callback will take effect in one frame and the rest in another. 208 | 209 |

Note: It is the prerogative of the the user agent which thread the AnimationFrameCallback executes on. In particular, a user agent may choose to execute the callback on a thread in the compositor component to allow synchronous access to the compositor-owned, layout free properties. It may also choose to execute only a portion of the function on that thread to prevent unacceptable delays in the compositor component. 210 | 211 |

Note: the Promise returned by beginAtomicTransaction or beginAtomicTransactionWithDeadline will not be resolved before the Promise’s for the operations performed in that transaction. 212 | 213 |

214 | Conformance

215 | 216 |

217 | Document conventions

218 | 219 |

Conformance requirements are expressed with a combination of 220 | descriptive assertions and RFC 2119 terminology. The key words "MUST", 221 | "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", 222 | "RECOMMENDED", "MAY", and "OPTIONAL" in the normative parts of this 223 | document are to be interpreted as described in RFC 2119. 224 | However, for readability, these words do not appear in all uppercase 225 | letters in this specification. 226 | 227 |

All of the text of this specification is normative except sections 228 | explicitly marked as non-normative, examples, and notes. [RFC2119]

229 | 230 |

Examples in this specification are introduced with the words "for example" 231 | or are set apart from the normative text with class="example", 232 | like this: 233 | 234 |

235 |

This is an example of an informative example.

236 |
237 | 238 |

Informative notes begin with the word "Note" and are set apart from the 239 | normative text with class="note", like this: 240 | 241 |

Note, this is an informative note.

242 | 243 |

244 | Conformance classes

245 | 246 |

Conformance to this specification 247 | is defined for three conformance classes: 248 |

249 |
style sheet 250 |
A CSS 251 | style sheet. 252 |
renderer 253 |
A UA 254 | that interprets the semantics of a style sheet and renders 255 | documents that use them. 256 |
authoring tool 257 |
A UA 258 | that writes a style sheet. 259 |
260 | 261 |

A style sheet is conformant to this specification 262 | if all of its statements that use syntax defined in this module are valid 263 | according to the generic CSS grammar and the individual grammars of each 264 | feature defined in this module. 265 | 266 |

A renderer is conformant to this specification 267 | if, in addition to interpreting the style sheet as defined by the 268 | appropriate specifications, it supports all the features defined 269 | by this specification by parsing them correctly 270 | and rendering the document accordingly. However, the inability of a 271 | UA to correctly render a document due to limitations of the device 272 | does not make the UA non-conformant. (For example, a UA is not 273 | required to render color on a monochrome monitor.) 274 | 275 |

An authoring tool is conformant to this specification 276 | if it writes style sheets that are syntactically correct according to the 277 | generic CSS grammar and the individual grammars of each feature in 278 | this module, and meet all other conformance requirements of style sheets 279 | as described in this module. 280 | 281 |

282 | Partial implementations

283 | 284 |

So that authors can exploit the forward-compatible parsing rules to 285 | assign fallback values, CSS renderers must 286 | treat as invalid (and ignore 287 | as appropriate) any at-rules, properties, property values, keywords, 288 | and other syntactic constructs for which they have no usable level of 289 | support. In particular, user agents must not selectively 290 | ignore unsupported component values and honor supported values in a single 291 | multi-value property declaration: if any value is considered invalid 292 | (as unsupported values must be), CSS requires that the entire declaration 293 | be ignored.

294 | 295 |

296 | Experimental implementations

297 | 298 |

To avoid clashes with future CSS features, the CSS2.1 specification 299 | reserves a prefixed 300 | syntax for proprietary and experimental extensions to CSS. 301 | 302 |

Prior to a specification reaching the Candidate Recommendation stage 303 | in the W3C process, all implementations of a CSS feature are considered 304 | experimental. The CSS Working Group recommends that implementations 305 | use a vendor-prefixed syntax for such features, including those in 306 | W3C Working Drafts. This avoids incompatibilities with future changes 307 | in the draft. 308 |

309 | 310 |

311 | Non-experimental implementations

312 | 313 |

Once a specification reaches the Candidate Recommendation stage, 314 | non-experimental implementations are possible, and implementors should 315 | release an unprefixed implementation of any CR-level feature they 316 | can demonstrate to be correctly implemented according to spec. 317 | 318 |

To establish and maintain the interoperability of CSS across 319 | implementations, the CSS Working Group requests that non-experimental 320 | CSS renderers submit an implementation report (and, if necessary, the 321 | testcases used for that implementation report) to the W3C before 322 | releasing an unprefixed implementation of any CSS features. Testcases 323 | submitted to W3C are subject to review and correction by the CSS 324 | Working Group. 325 | 326 |

Further information on submitting testcases and implementation reports 327 | can be found from on the CSS Working Group’s website at 328 | http://www.w3.org/Style/CSS/Test/. 329 | Questions should be directed to the 330 | public-css-testsuite@w3.org 331 | mailing list. 332 | 333 |

334 | References

335 | 336 |

337 | Normative References

338 |
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. URL: http://www.ietf.org/rfc/rfc2119.txt
339 | 340 |

341 | Informative References

342 |
343 | 344 |

345 | Index

346 |
381 | 382 |

383 | Property index

384 |

No properties defined.

385 | 386 | 387 | --------------------------------------------------------------------------------