diff --git a/box-tree-api/Overview.bs b/box-tree-api/Overview.bs index 4097e3bd..2b71cd5b 100644 --- a/box-tree-api/Overview.bs +++ b/box-tree-api/Overview.bs @@ -72,6 +72,7 @@ Boxes are not explicitly exposed by this API.

API

+[Exposed=Window]
 interface DeadFragmentInformation {
 	readonly attribute Node node;
 	readonly attribute double width;
diff --git a/css-animationworklet/Makefile b/css-animation-worklet-1/Makefile
similarity index 84%
rename from css-animationworklet/Makefile
rename to css-animation-worklet-1/Makefile
index 04d7e27d..63fe58ab 100644
--- a/css-animationworklet/Makefile
+++ b/css-animation-worklet-1/Makefile
@@ -4,9 +4,9 @@
 #
 # Use "make REMOTE=1" to use remote bikeshed
 
-SOURCEFILE=index.bs
-OUTPUTFILE=index.html
-PREPROCESSOR=bikeshed.py
+SOURCEFILE=Overview.bs
+OUTPUTFILE=Overview.html
+PREPROCESSOR=bikeshed
 REMOTE_PREPROCESSOR_URL=https://api.csswg.org/bikeshed/
 
 all: $(OUTPUTFILE)
diff --git a/css-animation-worklet-1/Overview.bs b/css-animation-worklet-1/Overview.bs
new file mode 100644
index 00000000..4f883932
--- /dev/null
+++ b/css-animation-worklet-1/Overview.bs
@@ -0,0 +1,1286 @@
+
+Title:  CSS Animation Worklet API
+Status: ED
+Group: houdini
+ED: https://drafts.css-houdini.org/css-animation-worklet-1/
+TR: https://www.w3.org/TR/css-animation-worklet-1/
+Shortname: css-animation-worklet
+Level: 1
+Abstract:
+Editor: Majid Valipour, majidvp@google.com, w3cid 81464
+Editor: Robert Flack, flackr@chromium.org, w3cid 98451
+Editor: Stephen McGruer, smcgruer@chromium.org, w3cid 96463
+Ignored Terms: AnimationWorklet
+
+ + + +
+urlPrefix: https://heycam.github.io/webidl/; type: dfn;
+    text: NotSupportedError
+    urlPrefix: #dfn-;
+        text: callback this value
+        text: exception
+        text: throw
+        url: throw; text: thrown
+    urlPrefix: #;
+        url: Function; text: Function
+        url: VoidFunction; text: VoidFunction
+    url: invoke-a-callback-function; text: Invoke
+    url: construct-a-callback-function; text: constructing
+    url: es-type-mapping; text: converting
+urlPrefix: https://html.spec.whatwg.org/#; type: dfn;
+    url: run-the-animation-frame-callbacks; text: running the animation frame callbacks
+urlPrefix: http://w3c.github.io/html/infrastructure.html#; type: dfn;
+    text: structuredserialize
+    text: structureddeserialize
+urlPrefix: https://www.w3.org/TR/css3-transitions/#; type: dfn;
+    text: animatable properties
+urlPrefix: https://drafts.csswg.org/web-animations#; type: dfn;
+    url: the-documents-default-timeline; text: default document timeline
+    url: concept-animation; text: animation
+    text: effect value
+    text: effect stack
+    text: target property
+    text: timeline
+    text: animation effect
+    text: current time
+    text: local time
+    text: inherited time
+    text: ready
+    text: play state
+    text: playback rate
+    text: set the target effect of an animation
+    text: set the timeline of an animation
+    text: finished
+    text: idle
+    text: paused
+    text: pending
+    text: running
+    text: composite operation
+    text: animation class
+    text: replace state
+    text: active
+    text: persisted
+    text: removed
+    text: start delay
+    text: end delay
+    text: fill mode
+    text: iteration start
+    text: iteration count
+    text: iteration duration
+    text: playback direction
+    text: timing function
+    text: set the start time
+    text: set the current time
+    text: update the timing properties of an animation effect
+
+
+
+urlPrefix: https://w3c.github.io/web-animations/level-2/#;
+    type: dfn;
+        text: group effect
+        text: child effect
+urlPrefix: https://tc39.github.io/ecma262/#sec-; type: dfn;
+    text: IsCallable
+    text: IsConstructor
+    text: HasProperty
+    url: ecmascript-data-types-and-values; text: Type
+    url: map-objects; text:map object
+    url: get-o-p; text: Get
+    url: set-o-p-v-throw; text: Set
+    url: samevalue; text: SameValue
+    urlPrefix: native-error-types-used-in-this-standard-
+        text: TypeError
+urlPrefix: https://www.w3.org/TR/hr-time-2/#dom-; type: dfn
+    text: DOMHighResTimeStamp
+urlPrefix: https://wicg.github.io/scroll-animations/#; type: interface
+    url: scrolltimeline; text: ScrollTimeline
+    url: dictdef-scrolltimelineoptions; text: ScrollTimelineOptions
+    url: dom-scrolltimeline-scrollsource; text: scrollSource
+urlPrefix: https://wicg.github.io/scroll-animations/#; type: dfn
+    url: current-time-algorithm; text: current time of the ScrollTimeline;
+
+ +
+{
+    "explainer": {
+        "href": "https://github.com/w3c/css-houdini-drafts/blob/master/css-animation-worklet-1/README.md",
+        "title": "Animation Worklet Explainer",
+        "deliveredBy": [
+            "https://github.com/w3c/css-houdini-drafts"
+        ]
+    },
+    "principles": {
+        "href": "https://github.com/w3c/css-houdini-drafts/blob/master/css-animation-worklet-1/principles.md",
+        "title": "Animation Worklet Design Principles and Goals",
+        "deliveredBy": [
+            "https://github.com/w3c/css-houdini-drafts"
+        ]
+    }
+}
+
+ +Introduction {#intro} +===================== +This section is not normative. + +This document introduces a new primitive that provides extensibility in web animations and enables +high performance interactive procedural animations on the web. For details on the rationale and +motivation see both [[explainer]] and [[principles]]. + +The [=Animation Worklet=] API provides a method to create scripted animations that control a set +of [=animation effects=]. The API is designed to make it possible for user agents to run such +animations in their own dedicated thread to provide a degree of performance isolation from main +thread. + +Relationship to the Web Animations API {#relationship-to-web-animations} +------------------------------------------------------------------------ +This section is not normative. + +Animations running inside an [=Animation Worklet=] execution context expose the {{Animation}} +interface from the Web Animations specification on the main javascript execution context. This means +they can be controlled and inspected from main thread using the same Web Animation APIs. + + +Animation Worklet {#animation-worklet-desc} +=========================================== +Animation Worklet is a {{Worklet}} responsible for all classes related to custom +animations. The worklet can be accessed via {{animationWorklet}} attribute. + + +[Exposed=Window] +partial namespace CSS { + [SameObject] readonly attribute Worklet animationWorklet; +}; + + +The {{animationWorklet}}'s [=worklet global scope type=] is {{AnimationWorkletGlobalScope}}. + +{{AnimationWorkletGlobalScope}} represents the global execution context of {{animationWorklet}}. + + +[ Global=(Worklet,AnimationWorklet), Exposed=AnimationWorklet ] +interface AnimationWorkletGlobalScope : WorkletGlobalScope { + undefined registerAnimator(DOMString name, AnimatorInstanceConstructor animatorCtor); +}; + +callback AnimatorInstanceConstructor = any (any options, optional any state); + + + +Animator {#animator-desc} +========================= + +An Animator represents a custom animation that is running inside +{{AnimationWorkletGlobalScope}}. Each Animator is associated with an [=animation=] instance (of +type {{WorkletAnimation}}) in the document and determines how that animation progresses its +[=animation effect=]. The animate function contains the logic responsible for +translating the animation current time into appropriate progress of the animation effect. An +animator can only be instantiated by construction of a {{WorkletAnimation}} in the document. + + +Two animators types are supported: [=Stateless Animator=] and [=Stateful Animator=] each +providing a different state management strategy. + + +Stateless Animator {#stateless-animator-desc} +--------------------------------------------- + +A Stateless Animator is a type of animator does not depend on any local state either +stored on the instance or global scope. Effectively, the animate function of an +[=Stateless Animator=] can be treated as a pure function with the expectation that given the same +input, it produces the same output. + + +
+ This is how an stateless animator class should look. +
+        class FooAnimator {
+            constructor(options) {
+                // Called when a new animator is instantiated.
+            }
+            animate(currentTime, effect) {
+                // Animation frame logic goes here.
+            }
+        }
+    
+
+ +Note: The statelessness allows animation worklet to perform optimization such as producing multiple +animation frames in parallel, sharing a single animator instance for multiple animations, and +performing very cheap teardown and setup. Using [=Stateless Animator=] is highly recommended to +enable such optimizations. + +Stateful Animator {#stateful-animator-desc} +------------------------------------------- + +A Stateful Animator is a type of animator that can have local state and animation worklet +guarantees that it maintains this state as long as the stateful animator fulfills the contract +required by its interface and as described following. + + +[=Animation worklet=] maintains a set of {{WorkletGlobalScope}}s which may exist across different +threads or processes. Animation worklet may temporarily terminate a global scope (e.g., to preserve +resources) or move a running [=animator instance=] across different global scopes (e.g., if its +effect is mutable only in a certain thread). Animation worklet guarantees that a stateful animator +instance's state is maintained even if the instance is respawned in a different global scope. + +The basic mechanism for maintaining the state is that the animation worklet snapshots the local +state that is exposed via the [=state function=] and then reifies it so that it can be passed into +the constructor when the animator instance is respawned at a later time in a potentially different +global scope. The [=migrate an animator instance=] algorithm specifies this process in details. + +A user-defined stateful animator is expected to fulfill the required contract which is that its +state function returns an object representing its state that can be serialized using structured +serialized algorithm and that it can also recreate its state given that same object passed to its +constructor. + +
+ This is how a stateful animator class should look. +
+        class BarAnimator {
+            constructor(options, state) {
+              // Called when a new animator is instantiated (either first time or after being respawned).
+              this.currentVelocity  = state ? state.velocity : 0;
+            }
+            animate(currentTime, effect) {
+                // Animation frame logic goes here and can rely on this.currentVelocity.
+                this.currentVelocity += 0.1;
+            }
+            state() {
+              // The returned object should be serializable using structured clonable algorithm.
+              return {
+                velocity: this.currentVelocity;
+              }
+            }
+        }
+    
+
+ + +Animator Definition {#animator-definition-desc} +----------------------------------------------- + +An animator definition is a [=struct=] which describes the author defined custom +animation logic. It consists of: + + - : animator name + :: A <>#. + + - : class constructor + :: A {{AnimatorInstanceConstructor}} [=callback function=] type. + + - : animate function + :: A [=Function=] [=callback function=] type. + + - : state function + :: A [=Function=] [=callback function=] type. + + - : stateful flag + :: A boolean flag + + +A stateful animator definition is an [=animator definition=] whose +[=animator definition/stateful flag=] is true. + + +A document animator definition is a [=struct=] which describes the information needed by +the [=document=] about the author defined custom animation. It consists of: + + - : stateful flag + :: A boolean flag + +Registering an Animator Definition {#registering-animator-definition} +--------------------------------------------------------------------- +The [=document=] has a [=map=] of document animator definitions. The map gets populated +when {{registerAnimator(name, animatorCtor)}} is called. + +An {{AnimationWorkletGlobalScope}} has a [=map=] of animator definitions. The map gets +populated when {{registerAnimator(name, animatorCtor)}} is called. + +Note that to register a [=stateful animator definition=] it is simply enough for the registered +class to have a state function. + +
+ +When the registerAnimator(|name|, |animatorCtor|) +method is called in a {{AnimationWorkletGlobalScope}}, the user agent must run the +following steps: + + 1. If |name| is not a valid <>, [=throw=] a [=TypeError=] and abort all these + steps. + + 2. Let |animatorDefinitions| be the {{AnimationWorkletGlobalScope}}'s + [=animator definitions=] [=map=]. + + 3. If |animatorDefinitions|[|name|] [=map/exists=], [=throw=] a [=NotSupportedError=] + and abort all these steps. + + 4. If the result of [=IsConstructor=](|animatorCtor|) is false, [=throw=] a + [=TypeError=] and abort all these steps. + + 5. Let |prototype| be the result of [=Get=](|animatorCtor|, "prototype"). + + 6. Let |animateFuncValue| be the result of [=Get=](|prototype|, "animate"). + + 7. Let |animateFunc| be the result of [=converting=] |animateFuncValue| to the [=Function=] + [=callback function=] type. If an exception is thrown, rethrow the exception and abort + all these steps. + + 8. Let |stateFuncValue| be the result of [=Get=](|prototype|, "state"). + + 9. Let |stateFunc| be the result of [=converting=] |stateFuncValue| to the [=Function=] + [=callback function=] type, If an exception is thrown, set |stateful| to be false, + otherwise set |stateful| to be true and |stateFunc| to be undefined. + + 10. Let |definition| be a new [=animator definition=] with: + + - [=animator name=] being |name| + + - [=class constructor=] being |animatorCtor| + + - [=animate function=] being |animateFunc| + + - [=state function=] being |stateFunc| + + - [=animator definition/stateful flag=] being |stateful| + + + 9. [=map/set=] the |animatorDefinitions|[|name|] to |definition|. + + 10. [=Queue a task=] to run the following steps: + + 1. Let |documentAnimatorDefinitions| be the associated [=document's=] + [=document animator definitions=] [=map=]. + + 2. Let |documentDefinition| be a new [=document animator definition=] with: + + - [=animator definition/stateful flag=] being |stateful| + + 3. If |documentAnimatorDefinitions|[|name|] [=map/exists=], run the following steps: + + 1. Let |existingDocumentDefinition| be the result of [=map/get=] + |documentAnimatorDefinitions|[|name|]. + + 2. If |existingDocumentDefinition| is "invalid", abort all these steps. + + 3. If |existingDocumentDefinition| and |documentDefinition| are not equivalent, (that is + their [=document animator definition/stateful flag=]s are + different), then: + + [=map/set=] |documentAnimatorDefinitions|[|name|] to "invalid". + + Log an error to the debugging console stating that the same class was registered + with different stateful flag. + + 4. Otherwise, [=map/set=] |documentAnimatorDefinitions|[|name|] to + |documentDefinition|. + +
+ + +Animator Effect {#animator-effect-desc} +--------------------------------------- + +A Animator Effect represents the underlying [=animation effect=] inside animation +worklet. + +It has a corresponding effect property which is a reference to the underlying +[=animation effect=]. It also has corresponding properties for the following +[=animation effect=]'s properties: + * [=local time=], + * [=start delay=], + * [=end delay=], + * [=fill mode=], + * [=iteration start=], + * [=iteration count=], + * [=iteration duration=], + * [=playback direction=], and + * [=timing function=]. + +[=Animator Effect=] is represented by the {{WorkletAnimationEffect}} interface +inside {{AnimationWorkletGlobalScope}}. + + + +[ Exposed=AnimationWorklet ] +interface WorkletAnimationEffect { + EffectTiming getTiming(); + ComputedEffectTiming getComputedTiming(); + attribute double? localTime; +}; + + + +Note: {{WorkletAnimationEffect}} is basically a restricted version of {{AnimationEffect}} interface + which does not have {{AnimationEffect/updateTiming}} but additionally allows local time to be set. + +
+ +: getTiming() +:: Returns the specified timing properties using the corresponding properties. + +: getComputedTiming() +:: Returns the calculated timing properties using the corresponding properties. + +: localTime +:: Getting the attribute returns the corresponding [=local time=]. + Setting the attribute updates the local time given this effect as |effect| + and the attribute value as |time|: + + 1. If the |time| is the same as |effect|'s [=local time=] then skip following steps. + + 2. Set the |effect|'s [=local time=] to |time|. + + 3. Set the |effect|'s animator instance's [=sync requested flag=] to true. + +
+ + +Animator Instance {#animator-instance-section} +============================================== + +An animator instance is a [=struct=] which describes a fully realized custom +[=animator=] in an {{AnimationWorkletGlobalScope}}. It has a reference to an +[=animator definition=] and owns the instance specific state such as animation effect and +timeline. It consists of: + + - : [=animator name=] + :: A string used to identify the animator definition. + + - : [=frame requested flag=] + :: A boolean flag that indicates if the animator needs to animate. + + - : sync requested flag + :: A flag that indicates if the animator needs to sync its output. + + - : effect + :: An [=Animator Effect=]. + + - : animator current time + :: A time value equivalent to the corresponding [=worklet animation=]'s current time. + + - : animator timeline + :: The [=timeline=] of the corresponding [=worklet animation=]. + + - : animator serialized options + :: The serializable object representing the options to be used when constructing the animator + instance. + +A stateful animator instance is an [=animator instance=] whose corresponding +definition is a [=stateful animator definition=]. + + + +Creating an Animator Instance {#creating-animator-instance} +----------------------------------------------------------- + +Each [=animator instance=] lives in an {{AnimationWorkletGlobalScope}}. + +Each {{AnimationWorkletGlobalScope}} has an animator instance set. The set is populated +when the user agent constructs a new [=animator instance=] in the {{AnimationWorkletGlobalScope}} +scope. Each [=animator instance=] corresponds to a worklet animation in the document scope. + +
+ +To create a new animator instance given a |name|, |timeline|, |effect|, +|serializedOptions|, |serializedState|, and |workletGlobalScope|, the user agent must run +the following steps: + + 1. Let the |definition| be the result of looking up |name| on the |workletGlobalScope|'s + [=animator definitions=]. + + If |definition| does not exist abort the following steps. + + 2. Let |animatorCtor| be the [=class constructor=] of |definition|. + + 3. Let |options| be [=StructuredDeserialize=](|serializedOptions|). + + 4. Let |state| be [=StructuredDeserialize=](|serializedState|). + + 5. Let |animatorInstance| be the result of [=constructing=] |animatorCtor| with + «|options|, |state|» as arguments. If an exception is thrown, rethrow the exception and + abort all these steps. + + 6. Let |animatorEffect| be the result of [=constructing=] a {{WorkletAnimationEffect}} + with its [=corresponding effect=] being |effect|. + + 7. Set the following on |animatorInstance| with: + - [=animator name=] being |name| + - [=frame requested flag=] being false + - [=sync requested flag=] being false + - [=animator current time=] being unresolved + - [=effect=] being |animatorEffect| + - [=animator timeline=] being |timeline| + - [=animator serialized options=] being |options| + + 8. Add |animatorInstance| to |workletGlobalScope|'s [=animator instance set=]. + +
+ + +Running Animators {#running-animators} +-------------------------------------- + +When the user agent wants to produce a new animation frame, if for any [=animator instance=] the +associated [=frame requested flag=] is true then the the user agent must +[=run animators=] for the current frame in all its associated global scopes. + +Note: The user agent is not required to run animations on every visual frame. It is legal to defer + generating an animation frame until a later frame. This allow the user agent to + provide a different service level according to their policy. + +
+ +When the user agent wants to run animators in a given |workletGlobalScope|, it +must run the following steps: + + 1. Iterate over all [=animator instance=]s in the |workletGlobalScope|'s animator instance + set. For each such |animator| the user agent must perform the following steps: + + 1. Let |animatorName| be |animator|'s [=animator name=] + + 2. Let the |definition| be the result of looking up |animatorName| on the + |workletGlobalScope|'s [=animator definitions=]. + + If |definition| does not exist then abort the following steps. + + 3. If the [=frame requested flag=] for |animator| is false or the effect belonging + to the |animator| will not be visible within the visual viewport of the current frame + the user agent may abort all the following steps. + + Issue: Consider giving user agents permission to skip running individual animator + instances to throttle slow animators. + + 4. Let |animateFunction| be |definition|'s [=animate function=]. + + 5. Let |currentTime| be [=animator current time=] of |animator|. + + 6. Let |effect| be [=effect=] of |animator|. + + 7. [=Invoke=] |animateFunction| with arguments «|currentTime|, |effect|», + and with |animator| as the [=callback this value=]. + + 2. If any [=animator instance=]s in the |workletGlobalScope|'s [=animator instance set=] + has its [=sync requested flag=] set to true then [=sync local times to document=] + given |workletGlobalScope|. + +
+ +Note: Although inefficient, it is legal for the user agent to [=run animators=] multiple times +in the same frame. + + +Issue: should be explicit as to what happens if the animateFunction throws an exception. At least +we should have wording that the localTime values of the effects are ignored to avoid incorrect +partial updates. + +Removing an Animator Instance {#removing-animator} +-------------------------------------------------- + +
+ +To remove an animator instance given |animator| and |workletGlobalScope| the user agent +must run the following steps: + +1. Remove |animator| from |workletGlobalScope|'s [=animator instance set=]. + +
+ + +Migrating an Animator Instance {#migrating-animator} +---------------------------------------------------- + +The migration process allows [=stateful animator instance=] to be migrated to a different +{{AnimationWorkletGlobalScope}} without losing their local state. + +
+ +To migrate an animator instance from one {{AnimationWorkletGlobalScope}} to another, +given |animator|, |sourceWorkletGlobalScope|, |destinationWorkletGlobalScope|, the user agent +must run the following steps : + + 1. Let |serializedState| be undefined. + + 2. [=Queue a task=] on |sourceWorkletGlobalScope| to run the following steps: + + 1. Let |animatorName| be |animator|'s [=animator name=] + + 2. Let |definition| be the result of looking up |animatorName| on |sourceWorkletGlobalScope|'s + [=animator definitions=]. + + If |definition| does not exist then abort the following steps. + + 3. Let |stateful| be the [=animator definition/stateful flag=] of |definition|. + + 4. If |stateful| is false then abort the following steps. + + 5. Let |stateFunction| be |definition|'s [=state function=]. + + 6. Let |state| be the result of [=Invoke=] |stateFunction| with |animator| as the + [=callback this value=]. If any exception is thrown, rethrow the exception and abort + the following steps. + + 7. Set |serializedState| to be the result of [=StructuredSerialize=](|state|). + If any exception is thrown, then abort the following steps. + + 8. Run the procedure to [=remove an animator instance=] given |animator|, and + |sourceWorkletGlobalScope|. + + 2. Wait for the above task to complete. If the task is aborted, abort the following steps. + + 3. [=Queue a task=] on |destinationWorkletGlobalScope| to run the following steps: + + 1. Run the procedure to [=create a new animator instance=] given: + - The |animator|'s [=animator name=] as name. + - The |animator|'s [=animator timeline=] as timeline. + - The |animator|'s [=effect=] as effect. + - The |animator|'s [=animator serialized options=] as options. + - The |serializedState| as state. + - The |destinationWorkletGlobalScope| as workletGlobalScope. + +
+ +If an animator state getter throws the user agent will remove the animator but does not recreate it. +This effectively removes the animator instance. + + +Requesting Animation Frames {#requesting-animation-frames} +---------------------------------------------------------- + +Each [=animator instance=] has an associated frame requested flag. It is initially set +to false. Different circumstances can cause the [=frame requested flag=] to be set to +true. These include the following: + - Changes in the [=current time=] of the animator's [=timeline=] + - Changes in the [=current time=] of the animator's corresponding [=worklet animation=] + +Performing [=run animators=] resets the [=frame requested flag=] on animators to false. + + +Web Animations Integration {#web-animation-integration} +======================================================= + + +Worklet Animation {#worklet-animation-desc} +------------------------------------------- +Worklet animation is a kind of [=animation=] that delegates animating its animation +effect to an [=animator instance=]. It controls the lifetime and playback state of its +[=corresponding animator instance=]. + +Being an [=animation=], [=worklet animation=] has an [=animation effect=] and a +[=timeline=]. However unlike other animations the worklet animation's [=current time=] does +not directly determine the animation effect's [=local time=] (via its [=inherited time=]). +Instead the associated [=animator instance=] controls the animation effect's [=local time=] +directly. Note that this means that the [=timeline's=] current time does not fully determine the +animation's output. + +[=Worklet animation=] has the following properties in addition to the {{Animation}} interface: + - : animation animator name + :: A string that identifies its [=animator definition=]. + - : serialized options + :: A serializable options object that is used whe constructing a new animator instance. + - : corresponding animator instance + :: A [=Animator Instance=]. + + +The existence of [=corresponding animator instance=] for a [=worklet animation=] depends on +the animation [=play state=]. See [[#web-animation-overrides]] for details on when and this +correspondence changes. + + +[Exposed=Window] +interface WorkletAnimation : Animation { + constructor(DOMString animatorName, + optional (AnimationEffect or sequence<AnimationEffect>)? effects = null, + optional AnimationTimeline? timeline, + optional any options); + readonly attribute DOMString animatorName; +}; + + + +
+ Overview of the WorkletAnimation timing model. +
+ Overview of the worklet animation timing model. +
+ The animation current time is input to the animator instance, which produces a local time value + for the animation effect. If the animator instance is running in a parallel global scope the + implementation may also choose to use the local time value to produce the animation output and + update the visuals in parallel. + +
+
+ + +Creating a Worklet Animation {#creating-worklet-animation} +---------------------------------------------------------- + +
+WorkletAnimation(|animatorName|, |effects|, |timeline|, |options|) + +Creates a new {{WorkletAnimation}} object using the following procedure: + + 1. Let |documentAnimatorDefinitions| be the associated [=document's=] document animator + definitions [=map=]. + + 2. If |documentAnimatorDefinitions|[|animatorName|] does not [=map/exists=], [=throw=] an + [=TypeError=] and abort the following steps. + + 3. If |documentAnimatorDefinitions|[|animatorName|] is "invalid", [=throw=] an + [=TypeError=] and abort the following steps. + + 4. Let |workletAnimation| be a new {{WorkletAnimation}} object. + + 5. Run the procedure to [=set the timeline of an animation=] on |workletAnimation| passing + |timeline| as the new timeline or, if a |timeline| argument is not provided, + passing the [=default document timeline=] of the {{Document}} associated with the + {{Window}} that is the [=current global object=]. + + 6. Let |effect| be the result corresponding to the first matching condition from below. + : If |effects| is a {{AnimationEffect}} object, + :: Let effect be |effects|. + : If |effects| is a [=list=] of {{AnimationEffect}} objects, + :: Let |effect| be a new {{WorkletGroupEffect}} with its children set to |effects|. + : Otherwise, + :: Let |effect| be undefined. + + 7. Let |serializedOptions| be the result of [=StructuredSerialize=](|options|). + Rethrow any exceptions. + + 8. Set the [=serialized options=] of |workletAnimation| to |serializedOptions|. + + 9. Set the [=animation animator name=] of |workletAnimation| to |animatorName|. + + 10. Run the procedure to [=set the target effect of an animation=] on |workletAnimation| + passing |effect| as the new effect. Note that this may trigger action to + [=set animator instance of worklet animation=]. See [[#web-animation-overrides]] for more + details. + +
+ + +Worklet Animation Timing and Sync Model {#timing-and-sync-model} +---------------------------------------------------------------- + +This section describes how [=worklet animation's=] timing model differs from other +[=animations=]. + +As described in [[#worklet-animation-desc]], the [=worklet animation's=] [=current time=] does +not determine its [=animation effect's=] [=local time=]. Instead the associated +[=animator instance=] controls the animation effect's [=local time=] directly. This means that the +animation effect's local time is controlled from a {{WorkletGlobalScope}} which may be in a parallel +execution context. + +Here are a few implications of the above semantics: + + - Setting the [=current time=] or [=start time=] of a [=worklet animation=] does not + necessarily change its output, but may change the animation [=play state=]. + - Similarly, invoking {{Animation/finish()}} or updating a [=worklet animation's=] playback + rate does not necessarily change its output, but may change the animation [=play state=] + - Querying the animation effect's local time using {{AnimationEffect/getComputedTiming()}} + may return stale information, in the case where the [=animator instance=] is running in a + parallel execution context. + + +If a Worklet Animation animation is executing in a parallel worklet execution context, the last +known state of its Animator Effects should be periodically synced back to the main javascript +execution context. The synchronization of [=effect values=] from the parallel worklet execution +context to the main javascript execution context must occur before +[=running the animation frame callbacks=] as part of the document lifecycle. + +Note that due to the asynchronous nature of this animation model a script running in the main +javascript execution context may see a stale value when reading a [=target property=] that is +being animated by a Worklet Animation, compared to the value currently being used to produce the +visual frame that is visible to the user. This is similar to the effect of asynchronous scrolling +when reading scroll offsets in the main javascript execution context. + + +
+ +To sync local times to document for a given |workletGlobalScope| the user agent +must perform the action that corresponds to the first matching condition from the +following: + + + : If the |workletGlobalScope| is not running in a parallel execution context + :: perform the following steps immediately: + + : If the |workletGlobalScope| is running in a parallel execution context + :: [=queue a task=] to run the following steps before running the animation frame + callbacks as part of the document lifecycle: + + 1. Iterate over all [=animator instance=]s in the animation worklet's global scope + [=animator instance set=]. For each such |animator| perform the following steps: + + 1. If |animator|'s [=sync requested flag=] is false skip the rest of the steps. + + 2. Let |animatorEffect| be |animator|'s [=effect=]. + + 3. Let |effect| be |animatorEffect|'s [=corresponding effect=]. + + 4. Set |effect|'s local time to |animatorEffect|'s local time. + + 5. Set |animator|'s [=sync requested flag=] to false. + +
+ +
+ +To sync animation timings to worklet for a given |workletAnimation| the user agent +must perform the following steps: + + 1. If |workletAnimation| does not have a [=corresponding animator instance=], abort the + following steps. + + 2. Let |animator| be |workletAnimation|'s [=corresponding animator instance=]. + + 2. Let |workletGlobalScope| be the {{AnimationWorkletGlobalScope}} associated with + |workletAnimation|. + + 3. : If the |workletGlobalScope| is not running in a parallel execution context + :: perform the following steps immediately. + + : If the |workletGlobalScope| is running in a parallel execution context + :: [=queue a task=] to run the following steps: + + 1. Set |animator|'s [=animator current time=] to |workletAnimation|'s [=current time=] + + 2. Let |animatorEffect| be |animator|'s [=effect=]. + + 3. Let |effect| be |animatorEffect|'s [=corresponding effect=]. + + 4. Set the following properties on |animatorEffect| to be the same as |effect|: + * [=start delay=], + * [=end delay=], + * [=fill mode=], + * [=iteration start=], + * [=iteration count=], + * [=iteration duration=], + * [=playback direction=], and + * [=timing function=]. + + +
+ + +Note: Notice that the local time is not synced from the document to animation worklet. + + +Issue(811): Come with appropriate mechanism's for [=animator instance=] to get notified when its + animation currentTime is changing e.g., via reverse(), finish() or playbackRate change. So that + it can react appropriately. + + +Web Animations Overrides {#web-animation-overrides} +--------------------------------------------------- + +In addition to the existing conditions on when the [=animation=] is considered [=ready=], a +[=worklet animation=] is only considered [=ready=] when the following condition is also true: + + - the user agent has completed any setup required to create the [=worklet animation's=] + [=corresponding animator instance=]. + +When a given worklet animation's [=play state=] changes from [=idle=] to [=finished=], +[=running=], or [=paused=], run the procedure to +[=associate animator instance of worklet animation=] given the worket animation as +|workletAnimation|. + +When a given worklet animation's [=play state=] changes from [=finished=], [=running=] or +[=paused=] to [=idle=], run the procedure to +[=disassociate animator instance of worklet animation=]given the worklet animation as +|workletAnimation|. + +When a given worklet animation's [=replace state=] changes from [=active=] to either +[=persisted=] or [=removed=] run the procedure to +[=disassociate animator instance of worklet animation]= given the worklet animation as +|workletAnimation|. + + +Issue: In web-animation play state is updated before the actual change in particular some operations +such as play() are asynchronous. We should really invoke these Animator related operation after the +appropriate animation operation is complete instead of when play state has changed. This will +require either finding (or introducing) q new hook in web animation or having override for each such +async operation. + + +When the procedure to [=set the target effect of an animation=] for a given worklet animation +is called, then [=set animator instance of worklet animation=] given the worklet animation as +|workletAnimation|. + +When the procedure to [=set the timeline of an animation=] for a given |workletAnimation| +is called, then [=set animator instance of worklet animation=] given the worklet animation as +|workletAnimation|. + +When the procedure to [=set the current time=] or [=set the start time=] for a given worklet +animation is called, then [=sync animation timings to worklet=] given the worklet animation as +|workletAnimation|. + +When the procedure to [=update the timing properties of an animation effect=] for a given effect is +called and that effect is owned be a worklet animation, then +[=sync animation timings to worklet=] given that worklet animation as |workletAnimation|. + + +
+ +To associate animator instance of worklet animation given |workletAnimation|, +the user agent must run the following steps: + + 1. If |workletAnimation| has a [=corresponding animator instance=], abort the following steps. + 2. Let |workletGlobalScope| be the {{AnimationWorkletGlobalScope}} associated with + |workletAnimation|. + 3. [=Queue a task=] on |workletGlobalScope| to run the procedure to create a new animator + instance, passing: + * The |workletAnimation|'s [=animation animator name=] as name. + * The |workletAnimation|'s [=timeline=] as timeline. + * The |workletAnimation|'s [=animation effect=] as effect. + * The |workletAnimation|'s [=serialized options=] as options. + * The |workletGlobalScope| as workletGlobalScope. + 4. If the procedure was successful, set the resulting [=animator instance=] to be the + [=corresponding animator instance=] of |workletAnimation|. + +
+ +
+ +To disassociate animator instance of worklet animation given +|workletAnimation|, the user age must run the following steps: + + 1. If |workletAnimation| does not have a [=corresponding animator instance=], abort the + following steps. + 2. Let |workletGlobalScope| be the {{AnimationWorkletGlobalScope}} associated with + |workletAnimation|. + 3. Let |animatorInstance| be |workletAnimation|'s [=corresponding animator instance=]. + 4. [=Queue a task=] on the |workletGlobalScope| to run the procedure to remove an animator + instance, passing |animatorInstance| as instance and |workletGlobalScope| as + workletGlobalScope. + 5. Set |workletAnimation|'s [=corresponding animator instance=] as undefined. + +
+ +
+ +To set animator instance of worklet animation given +|workletAnimation|, the user agent must run the following steps: + + 1. [=disassociate animator instance of worklet animation=] given |workletAnimation|. + 2. [=associate animator instance of worklet animation=] given |workletAnimation|. + +
+ +Effect Stack and Composite Order {#effect-stack-composite-order} +---------------------------------------------------------------- + +As with other animations, [=worklet animations=] participate in the [=effect stack=]. A worklet +animation does not have a specific [=animation class=] which means it has the same composite order +as other Javascript created web animations. + + + + + +Additional Related Concepts {#related-concepts} +=============================================== + + +Worklet Group Effect {#worklet-group-effect} +-------------------------------------------- +This section is not normative. + + +[=Group effect=] is a type of [=animation effect=] that enbales multiple child animation +effects to be animated together as a group. + +{{WorkletGroupEffect}} is a type of [=group effect=] that allows its [=child effect's=] +[=local times=] to be mutated individually. + +When a {{WorkletGroupEffect}} is set as the [=animation effect=] of a [=worklet animation=], +its [=corresponding animator instance=] can directly control the [=child effects=]' local +times. This allows a single worklet animation to coordinate multiple effects - see +[[#example-2]] for an example of such a use-case. + + +[Exposed=AnimationWorklet] +interface WorkletGroupEffect { + sequence<WorkletAnimationEffect> getChildren(); +}; + + + +Issue(w3c/csswg-drafts#2071): The above interface exposes a conservative subset of GroupEffect +proposed as part of web-animation-2. We should instead move this into a delta spec against the +web-animation. + + +Note: Group Effect is currently an experimental feature and not well specified in web animations. So +the concept of {{WorkletGroupEffect}} may change dramatically as [=Group Effect=] get specified. +See https://github.com/yi-gu/group_effect/blob/master/README.md for more details. + + +ScrollTimeline {#scroll-timeline} +--------------------------------- +This section is not normative. + + +{{ScrollTimeline}} is a new concept being proposed for addition to web animation API. It defines +an animation timeline whose time value depends on the scroll position of a scroll container. +[=Worklet animations=] can have a scroll timeline and thus drive their scripted effects based +on a scroll offset. + +Note: Access to input: We are interested on exposing additional user input beside +scrolling (e.g., touch/pointer input) to these animations so that authors can create jank-free +input driven animations which are not really possible today. We are still trying to figure out the +right abstractions and mechanisms to do this. + + + +Security Considerations {#security-considerations} +================================================== + +There are no known security issues introduced by these features. + +Privacy Considerations {#privacy-considerations} +================================================ + +There are no known privacy issues introduced by these features. + +Examples {#examples} +==================== + + +Example 1: Spring timing. {#example-1} +--------------------------------------- +Here we use Animation Worklet to create animation with a custom spring timing. + + + + +<div id='target'></div> + +<script> +await CSS.animationWorklet.addModule('spring-animator.js'); +targetEl = document.getElementById('target'); + +const effect = new KeyframeEffect( + targetEl, + {transform: ['translateX(0)', 'translateX(50vw)']}, + {duration: 1000} +); +const animation = new WorkletAnimation('spring', effect, document.timeline, {k: 2, ratio: 0.7}); +animation.play(); +</script> + + + + + +registerAnimator('spring', class SpringAnimator { + constructor(options = {k: 1, ratio: 0.5}) { + this.timing = createSpring(options.k, options.ratio); + } + + animate(currentTime, effect) { + let delta = this.timing(currentTime); + // scale this by target duration + delta = delta * (effect.getTimings().duration / 2); + effect.localTime = delta; + // TODO: Provide a method for animate to mark animation as finished once + // spring simulation is complete, e.g., this.finish() + // See issue https://github.com/w3c/css-houdini-drafts/issues/808 + } +}); + +function createSpring(springConstant, ratio) { + // Normalize mass and distance to 1 and assume a reasonable init velocit + // but these can also become options to this animator. + const velocity = 0.2; + const mass = 1; + const distance = 1; + + // Keep ratio < 1 to ensure it is under-damped. + ratio = Math.min(ratio, 1 - 1e-5); + + const damping = ratio * 2.0 * Math.sqrt(springConstant); + const w = Math.sqrt(4.0 * springConstant - damping * damping) / (2.0 * mass); + const r = -(damping / 2.0); + const c1 = distance; + const c2 = (velocity - r * distance) / w; + + // return a value in [0..distance] + return function springTiming(timeMs) { + const time = timeMs / 1000; // in seconds + const result = Math.pow(Math.E, r * time) * + (c1 * Math.cos(w * time) + c2 * Math.sin(w * time)); + return distance - result; + } +} + + + +Example 2: Twitter header. {#example-2} +--------------------------------------- +An example of twitter profile header effect where two elements (avatar, and header) are updated in +sync with scroll offset with an additional feature where avatar can have additional physic based +movement based on the velocity and acceleration of the scrolling. + + + +<div id='scrollingContainer'> + <div id='header' style='height: 150px'></div> + <div id='avatar'><img></div> +</div> + +// In document scope. +<script> +const headerEl = document.getElementById('header'); +const avatarEl = document.getElementById('avatar'); +const scrollingContainerEl = document.getElementById('scrollingContainer'); + + +const scrollTimeline = new ScrollTimeline({ + scrollSource: scrollingContainerEl, + orientation: 'block', + timeRange: 1000, + startScrollOffset: 0, + endScrollOffset: headerEl.clientHeight +}); + +const effects = [ + /* avatar scales down as we scroll up */ + new KeyframeEffect(avatarEl, + {transform: ['scale(1)', 'scale(0.5)']}, + {duration: scrollTimeline.timeRange}), + /* header loses transparency as we scroll up */ + new KeyframeEffect(headerEl, + {opacity: [0, 0.8]}, + {duration: scrollTimeline.timeRange}) +]; + +await CSS.animationWorklet.addModule('twitter-header-animator.js'); +const animation = new WorkletAnimation('twitter-header', effects, scrollTimeline); + +animation.play(); +</script> + + + + +// Inside AnimationWorkletGlobalScope. +registerAnimator('twitter-header', class HeaderAnimator { + constructor(options, state = {velocity: 0, acceleration: 0}) { + // `state` is either undefined (first time) or it is the previous state (after an animator + // is migrated between global scopes). + this.velocity = state.velocity; + this.acceleration = state.acceleration; + } + + + animate(currentTime, effect) { + const scroll = currentTime; // scroll is in [0, 1000] range + + if (this.prevScroll) { + this.velocity = scroll - this.prevScroll; + this.acceleration = this.velocity - this.prevVelocity; + } + this.prevScroll = scroll; + this.prevVelocity = velocity; + + + // Drive the output group effect by setting its children local times individually. + effect.children[0].localTime = scroll; + + effect.children[1].localTime = curve(velocity, acceleration, scroll); + } + + state() { + // Invoked before any migration attempts. The returned object must be structure clonable + // and will be passed to constructor to help animator restore its state after migration to the + // new scope. + return { + this.velocity, + this.acceleration + } + } + +}); + +curve(scroll, velocity, acceleration) { + + return /* compute an return a physical movement curve based on scroll position, and per frame + velocity and acceleration. */ ; +} + + + +Example 3: Parallax backgrounds. {#example-3} +--------------------------------------------- +A simple parallax background example. + + +<style> +.parallax { + position: fixed; + top: 0; + left: 0; + opacity: 0.5; +} +</style> +<div id='scrollingContainer'> + <div id="slow" class="parallax"></div> + <div id="fast" class="parallax"></div> +</div> + +<script> +await CSS.animationWorklet.addModule('parallax-animator.js'); + +const parallaxSlowEl = document.getElementById('slow'); +const parallaxFastEl = document.getElementById('fast'); +const scrollingContainerEl = document.getElementById('scrollingContainer'); + +const scrollTimeline = new ScrollTimeline({ + scrollSource: scrollingContainerEl, + orientation: 'block', + timeRange: 1000 +}); +const scrollRange = scrollingContainerEl.scrollHeight - scrollingContainerEl.clientHeight; + +const slowParallax = new WorkletAnimation( + 'parallax', + new KeyframeEffect(parallaxSlowEl, + {'transform': ['translateY(0)', 'translateY(' + -scrollRange + 'px)']}, + {duration: scrollTimeline.timeRange}), + scrollTimeline, + {rate : 0.4} +); +slowParallax.play(); + +const fastParallax = new WorkletAnimation( + 'parallax', + new KeyframeEffect(parallaxFastEl, + {'transform': ['translateY(0)', 'translateY(' + -scrollRange + 'px)']}, + {duration: scrollTimeline.timeRange}), + scrollTimeline, + {rate : 0.8} +); +fastParallax.play(); +</script> + + + + +// Inside AnimationWorkletGlobalScope. +registerAnimator('parallax', class ParallaxAnimator { + constructor(options) { + this.rate_ = options.rate; + } + + animate(currentTime, effect) { + effect.localTime = currentTime * this.rate_; + } +}); + diff --git a/css-animation-worklet-1/README.md b/css-animation-worklet-1/README.md new file mode 100644 index 00000000..f88e06b9 --- /dev/null +++ b/css-animation-worklet-1/README.md @@ -0,0 +1,665 @@ +# Animation Worklet Explainer +--- + +# Overview + +Animation Worklet is a new primitive that provides extensibility in web animations and enables high +performance procedural animations on the web. The feature is developed as part of the +[CSS Houdini task force](https://github.com/w3c/css-houdini-drafts/wiki). + +The Animation Worklet API provides a method to create scripted animations that control a set of +animation effects. These animations are executed inside an isolated execution environment, *worklet* +which makes it possible for user agents to run such animations in their own dedicated thread to +provide a degree of performance isolation from main thread. The API is compatible with Web +Animations and uses existing constructs as much as possible. + +# Background + +Scripted interactive effects (written in response to `requestAnimationFrame`, `pointer events` or +async `onscroll` events) are rich but are subject to main thread jankiness. On the other hand, +accelerated CSS transitions and animations can be fast (for a subset of *accelerated* properties) +but are not rich enough to enable [many common use cases](#motivating-use-cases) and currently have +no way to access key user input (pointer events, gestures, scroll). This is why scripted effects are +still very popular for implementing common effects such as hidey-bars, parallax, pull-to-refresh, +drag-and-drop, swipe to dismiss and etc. Animation Worklet provides is key building block for +enabling creation of smooth rich interactive visual effects on the web while also exposing an +extensibility hook in web animations. + + +See the [Animation Worklet design principles and goals](principles.md) for a more extended overview +of the motivations behind Animation Worklet and how the design will be evolved to support a growing +set of use cases. Also see [the status document](status.md) for high level implementation status and +timeline. [Here][roc-thread] you may find an earlier high level discussion on general approaches to +address this problem. + + +# Motivating Use Cases + +* Scroll driven effects: + * Hidey-bar ([demo](https://googlechromelabs.github.io/houdini-samples/animation-worklet/twitter-header/)): animation depends on both scroll and time input. + * Parallax ([demo](https://googlechromelabs.github.io/houdini-samples/animation-worklet/parallax-scrolling/)): simplest scroll-driven effect. + * Custom paginated slider ([demo](http://aw-playground.glitch.me/amp-scroller.html)). + * Pull-to-refresh: animation depends on both touch and time inputs. + * Custom scrollbars. + * High-fidelity location tracking and positioning + * [More examples](https://github.com/w3c/css-houdini-drafts/blob/master/scroll-customization-api/UseCases.md) of scroll-driven effects. +* Gesture driven effects: + * [Image manipulator](https://github.com/w3c/csswg-drafts/issues/2493#issuecomment-422153926) that scales, rotates etc. + * Swipe to Action. + * Drag-N-Drop. + * Tiled panning e.g., Google maps. +* Stateful script driven effects: + * Spring-Sticky effect ([demo](http://googlechromelabs.github.io/houdini-samples/animation-worklet/spring-sticky/)). + * Touch-driven physical environments. + * Expando ([demo](http://googlechromelabs.github.io/houdini-samples/animation-worklet/expando/)): Procedural animations with multiple elements. +* Animated scroll offsets: + * Having multiple scrollers scroll in sync e.g. diff viewer keeping old/new in sync when you + scroll either ([demo](https://googlechromelabs.github.io/houdini-samples/animation-worklet/sync-scroller/)) + * Custom smooth scroll animations (e.g., physic based fling curves) +* Animation Extensibility: + * Custom animation timings (particularly those that are not calculable a priori e.g., [spring demo](https://googlechromelabs.github.io/houdini-samples/animation-worklet/spring-timing/)) + * Custom animation sequencing which involves complex coordination across multiple effects. + + +Not all of these usecases are immediately enabled by the current proposed API. However Animation +Worklet provides a powerfull primitive (off main-thread scripted animation) which when combined with +other upcoming features (e.g., +[Event in Worklets](https://github.com/w3c/css-houdini-drafts/issues/834), +[ScrollTimeline](https://wicg.github.io/scroll-animations/), +[GroupEffect](https://github.com/w3c/csswg-drafts/issues/2071)) can address all these usecases and +allows many of currently main-thread rAF-based animations to move off thread with significant +improvement to their smoothness. +See [Animation Worklet design principles and goals](principles.md) for a more extended discussion +of this. + + +***Note***: Demos work best in the latest Chrome Canary with the experimental +web platform features enabled (`--enable-experimental-web-platform-features` +flag) otherwise they fallback to using main thread rAF to emulate the behaviour. + + +# Animation Worklet + +Animation Worklet attempts to address the above usecases by introducing a new primitive that enables +extensibility in the web's core animation model [WebAnimations][WA]): custom frame-by-frame animate +function! + + +## How It Works + +Normally, an active animation takes its timeline time and according to its running state (e.g., +playing, finished) and playback rate, computes its own **current time** which it then uses to set +its keyframe effect **local time**. Here is a simple example of a simple animation: + +```js +const effect = new KeyframeEffect(targetEl, + {transform: ['translateX(0)', 'translateX(50vw)']}, + {duration: 1000} +); +const animation = new Animation(effect, document.timeline); +animation.play(); +``` + + +Animation Worklet allows this transformation from **current time** to **local time** to be +customized via a special Javascript function `animate`. Similar to other Houdini worklets, these +animate functions are called inside a restricted [worklet][worklet] context (`AnimationWorkletGlobalScope`) +which means the don't have access to main document. Another implication is that implementor can run +these off-thread to ensure smooth animations even when main thread is busy which is a key +performance goal for animations. + +To leverage this machinery, web developer creates a special Animation subclass, `WorkletAnimation`. +The only difference is that the WorkletAnimation constructor takes a `name` argument that identifies +the custom animate function to be used. Animation Worklet then creates a corresponding *animater* +instance that represent this particlar animation and then on each animation frame calls its +`animate` function to determine the local time which ultimately drives the keyframe effect. + + +![Overview of the WorkletAnimation Timing Model](img/WorkletAnimation-timing-model.svg) + +Here the same simple example but using Animation Worklet instead. + +**index.html** +```js +// Load your custom animator in the worklet +await CSS.animationWorklet.addModule('animator.js'); + +const effect = new KeyframeEffect(targetEl, + {transform: ['translateX(0)', 'translateX(50vw)']}, + {duration: 1000} +); +const animation = new WorkletAnimation('my-awesome-animator', effect); +animation.play(); +``` + +**animator.js** +``` +registerAnimator('my-awesome-animator', class Passthrough extends StatelessAnimator { + animate(currentTime, effect) { + // The simplest custom animator that does exactly what regular animations do! + effect.localTime = currentTime; + } +}); +``` + + +A few notable things: + + - WorkletAnimation behaves the same as regular animations e.g., it can be played/paused/canceled + - WorkletAnimation can optionally accept an options bag to help the corresponding Animator + configure itself during construction. + - Animator controls the output of the animation by setting the AnimationEffect.localTime + - There is two types of Animators: Stateless and Statefull explicitly marked using superclasses. + +Below are a few more complex example each trying to show a different aspect of Animation Worklet. + +# Examples + +## Spring Timing + +Here we use Animation Worklet to create animation with a custom spring timing. + + +```html + +
+ + +``` + +spring-animator.js: + +```js +registerAnimator('spring', class SpringAnimator extends StatelessAnimator { + constructor(options = {k: 1, ratio: 0.5}) { + this.timing = createSpring(options.k, options.ratio); + } + + animate(currentTime, effect) { + let delta = this.timing(currentTime); + // scale this by target duration + delta = delta * (effect.getTimings().duration / 2); + effect.localTime = delta; + // TODO: Provide a method for animate to mark animation as finished once + // spring simulation is complete, e.g., this.finish() + // See issue https://github.com/w3c/css-houdini-drafts/issues/808 + } +}); + +function createSpring(springConstant, ratio) { + // Normalize mass and distance to 1 and assume a reasonable init velocit + // but these can also become options to this animator. + const velocity = 0.2; + const mass = 1; + const distance = 1; + + // Keep ratio < 1 to ensure it is under-damped. + ratio = Math.min(ratio, 1 - 1e-5); + + const damping = ratio * 2.0 * Math.sqrt(springConstant); + const w = Math.sqrt(4.0 * springConstant - damping * damping) / (2.0 * mass); + const r = -(damping / 2.0); + const c1 = distance; + const c2 = (velocity - r * distance) / w; + + // return a value in [0..distance] + return function springTiming(timeMs) { + const time = timeMs / 1000; // in seconds + const result = Math.pow(Math.E, r * time) * + (c1 * Math.cos(w * time) + c2 * Math.sin(w * time)); + return distance - result; + } +} +``` + +Note that ideally once sping simulation is finished, the worklet animation would also dispatch +the `finish` event. Adding the necessary mechanism to enable this is tracked +[here](https://github.com/w3c/css-houdini-drafts/issues/808). + +## Twitter Header + +Note: This assumes experimental [ScrollTimeline][scroll-timeline] feature. + +An example of twitter profile header effect where two elements (avatar, and header) are updated in +sync with scroll offset. + +```html + +
+ +
+
+ + +``` + +twitter-header-animator.js: +```js +registerAnimator('twitter-header', class TwitterHeader extends StatelessAnimator { + constructor(options) { + this.timing_ = new CubicBezier('ease-out'); + } + + clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } + + animate(currentTime, effect) { + const scroll = currentTime; // [0, 1] + + // Drive the output group effect by setting its children local times. + effect.children[0].localTime = scroll; + // Can control the child effects individually +    effect.children[1].localTime = this.timing_(this.clamp(scroll, 0, 1)); + } +}); +``` + +## Swipe-to-Action + +Another usecase for Animation Worklet is to enable interactive input-driven animation effects that +are driven both by input events and time. + +To enable this we need a way to receive pointer events in worklet (e.g. via [CSS custom +variables](https://github.com/w3c/css-houdini-drafts/issues/869) or [other +mechanisms][input-for-worker]) and +also allow [playback controls](https://github.com/w3c/css-houdini-drafts/issues/808) inside +worklets. Both of these are natural planned additions to Animation Worklet. + + +Consider a simple swipe-to-action effect which follows the user swipe gesture and when finger lifts +then continues to completion (either dismissed or returned to origin) with a curve that matches the +swipe gesture's velocity. (See this [example](https://twitter.com/kzzzf/status/917444054887124992)) + +With Animation Worklet, this can be modeled as a stateful animator which consumes both time and +pointer events and have the following state machines: + +![SwipeToCompletionAnimation](img/swipe-to-dismiss-state.png) + + +Here are the three main states: + +1. Animation is idle, where it is `paused` so that it is not actively ticking +2. As soon as the user touches down, the animation moves the target to follow the user touchpoint + while staying `paused` (optionally calculate the movement velocity, and overall delta). +3. As soon as the user lift their finger the animation will the switch to 'playing' so that it is + ticked by time until it reaches its finished state. The final state may be decided on overall + delta and velocity and the animation curve adapts to the movement velocity. + +Note that while in (3), if the user touches down we go back to (2) which ensures responsiveness to +user touch input. + +To make this more concrete, here is how this may be implemented (assuming strawman proposed APIs for +playback controls and also receiving pointer events). Note that all the state machine transitions +and various state data (velocity, phase) and internal to the animator. Main thread only needs to +provide appropriate keyframes that can used to translate the element on the viewport as appropriate +(e.g., `Keyframes(target, {transform: ['translateX(-100vw)', 'translateX(100vw)']})`). + + +```javascript +registerAnimator('swipe-to-dismiss', class SwipeAnimator extends StatefulAnimator { + constructor(options, state = {velocity: 0, phase: 'idle'}) { + this.velocity = state.velocity; + this.phase = state.phase; + + if (phase == 'idle') { + // Pause until we receive pointer events. + this.pause(); + } + + // Assumes we have an API to receive pointer events for our target. + this.addEventListener("eventtargetadded", (event) => { + for (type of ["pointerdown", "pointermove", "pointerup"]) { + event.target.addEventListener(type,onPointerEvent ); + } + }); + } + + onpointerevent(event) { + if (event.type == "pointerdown" || event.type == "pointermove") { + this.phase = "follow_pointer"; + } else { + this.phase = "animate_to_completion"; + // Also decide what is the completion phase (e.g., hide or show) + } + + this.pointer_position = event.screenX; + + // Allow the animation to play for *one* frame to react to the pointer event. + this.play(); + } + + animate(currentTime, effect) { + if (this.phase == "follow_pointer") { + effect.localTime = position_curve(this.pointer_position); + update_velocity(currentTime, this.pointer_position); + // Pause, no need to produce frames until next pointer event. + this.pause(); + } else if (this.phase = "animate_to_completion") { + effect.localTime = time_curve(currentTime, velocity); + + if (effect.localTime == 0 || effect.localTime == effect.duration) { + // The animation is complete. Pause and become idle until next user interaction. + this.phase = "idle"; + this.pause(); + } else { + // Continue producing frames based on time until we complete or the user interacts again. + this.play(); + } + } + + } + + position_curve(x) { + // map finger position to local time so we follow user's touch. + } + + time_curve(time, velocity) { + // Map current time delta and given movement velocity to appropriate local time so that over + // time we animate to a final position. + } + + update_velocity(time, x) { + this.velocity = (x - last_x) / (time - last_time); + this.last_time = time; + this.last_x = x; + } + + state() { + return { + phase: this.phase, + velocity: this.velocity + } + } +}); +``` + +```javascript + +await CSS.animationWorklet.addModule('swipe-to-dismiss-animator.js'); +const target = document.getElementById('target'); +const s2d = new WorkletAnimation( + 'swipe-to-dismiss', + new KeyframeEffect(target, {transform: ['translateX(-100vw)', 'translateX(100vw)']})); +s2d.play(); +``` + + +# Why Extend Animation? + +In [WebAnimation][WA], [Animation][animation] is the main controller. It handles the playback commands +(play/pause/cancel) and is responsible for processing the progressing time (sourced from Timeline) and +driving keyframes effect which defines how a particular target css property is animated and +ultimately pixels moving on the screen. + +By allowing extensibility in Animation we can have the most flexibility in terms of what is possible +for example animation can directly control the following: + - Control animation playback e.g., implement open-ended animations with non-deterministic timings + (e.g., physical-based) or provide "trigger" facilities + - Flexibility in transforming other forms of input into "time" e.g., consume touch events and drive + animations + - Ability to handle multiple timelines e.g., animations that seamlessly transition btween being + touch/scroll driven to time-driven + - Control how time is translated e.g., new custom easing functions + - Drive multiple effects and control how they relate to each other e.g., new effect sequencing + + +While there is benefit in extensibility in other parts of animation stack (custom timeline, custom +effect, custom timing), custom animations provides the largest value in terms of flexibility and +addressing key usecases so it is the one we are tackling first. + +Animation Worklet can be easily augmented in future to support other Houdini style extensibility +features as well. + + +TODO: Also discuss other models that we have considered (e.g., CompositorWorker) that bypassed +web animation altogether. + + + +# Key Concepts + +## Animation Worklet Global Scope +A [worklet global scope](https://drafts.css-houdini.org/worklets/#the-global-scope) that is created +by Animation Worklet. Note that Animation Worklet creates multiple such scopes and uses them to +execute user defined effects. In particular global scopes are regularly switched to enforce +stateless and stateful animator contracts. + + +## Animator + +Animator is a Javascript class that encapsulates the custom animation logic. Similar to other +Houdinig worklets, animators are registered inside the worklet global scope with a unique name which +can be used to uniquely identify them. + + +## WorkletAnimation +`WorkletAnimation` is a subclass of Animation that can be used to create an custom animation that +runs inside a standalone animation worklet scope. A worklet animation has a corresponding animator +instance in a animation worklet scope which is responsible to drive its keyframe effects. Here are +the key differences compared to a regular web animation: + - Name: The name identifies the custom animator class registered in the animation worklet scope. + - Options: `WorkletAnimation` may have a custom properties bag that is cloned and provided to the + corresponding animator constructor when it is being instantiated. + +Note that worklet animations expose same API surface as other web animations and thus they may be +created, played, paused, inspected, and generally controlled from the main document scope. Here is +how various methods roughly translate: + + - `cancel()`: cancels the animation and the corresponding animator instance is removed. + - `play()`: starts the animation and the corresponding animator instance gets constructed and + may get its `animate` function called periodically as a result of changes in its timelines. + - pause(): pauses the animation and the corresponding animator instance no longer receives + `animate` calls. + - finish(), reverse() or mutating playbackRate: these affect the currentTime which is seens by + the animator instance. (We are considering possiblity of having a `onPlaybackRateChanged` + callback) + +## Statefull and Statelss Animators + +Sometimes animation effects require maintaining internal state (e.g., when animation needs to depend +on velocity). Such animators have to explicitly declare their statefulness but by inheritting from +`StatefulAnimator` superclass. + +The animators are not guaranteed to run in the same global scope (or underlying thread) for their +lifetime duration. For example user agents are free to initially run the animator on main thread +but later decide to migrate it off main thread to get certain performance optimizations or to tear +down scopes to save resources. + +Animation Worklet helps stateful animators to maintain their state across such migration events. +This is done through a state() function which is called and animator exposes its state. Here is +an example: + +```js +// in document scope +new WorkletAnimation('animation-with-local-state', keyframes); +``` + +```js +registerAnimator('animation-with-local-state', class FoorAnimator extends StatefulAnimator { + constructor(options, state = {velocity: 0, acceleration: 0}) { + // state is either undefined (first time) or the state after an animator is migrated across + // global scope. + this.velocity = state.velocity; + this.acceleration = state.acceleration; + } + + animate(time, effect) { + if (this.lastTime) { + this.velocity = time - this.prevTime; + this.acceleration = this.velocity - this.prevVelocity; + } + this.prevTime = time; + this.prevVelocity = velocity; + + effect.localTime = curve(velocity, acceleration, currentTime); + } + + state() { + // Invoked before any migration attempts. The returned object must be structure clonable + // and will be passed to constructor to help animator restore its state after migration to the + // new scope. + return { + this.velocity, + this.acceleration + } + } + + curve(velocity, accerlation, t) { + return /* compute some physical movement curve */; + } +}); +``` + + +## Threading Model + +Animation Worklet is designed to be thread-agnostic. Rendering engines may create one or more +parallel worklet execution contexts separate from the main javascript execution context, e.g., on +their own dedicated threads. Rendering engines may then choose to assign Animation Worklet +animations to run in such contexts. Doing so allows Animation Worklet animations to avoid being +impacted by main thread jank. + +Rendering engines may wish to make a best-effort attempt to execute animate callbacks synchronously +with visual frame production to ensure smooth animation. However it is legal for rendering engines +to produce visual frames without blocking to receive animation updates from a worklet (i.e., letting +the effects slip behind). For example, this could occur when the animate function callback is +unable to complete before the frame deadline. + +We believe that scripted animations which are run in a parallel execution environment and which +limit themselves to animating properties which do not require the user agent to consult main thread +will have a much better chance of meeting the strict frame budgets required for smooth playback. + + +Note that due to the asynchronous nature of this animation model a script running in the main +javascript execution context may see a stale value when reading a target property that is +being animated in a Worklet Animation, compared to the value currently being used to produce the +visual frame that is visible to the user. This is similar to the effect of asynchronous scrolling +when reading scroll offsets in the main javascript execution context. + + +
+ Overview of the animation worklet threading model. +
+ Overview of the animation worklet threading model.
+ + A simplified visualization of how animators running in a parallel execution environment can sync + their update to main thread while remaining in sync with visual frame production. +
+
+ + +# Related Concepts + +The following concepts are not part of Animation Worklet specification but Animation Worklet is +designed to take advantage of them to enable a richer set of usecases. These are still in early +stages of the standardization process so their API may change over time. + +## ScrollTimeline +[ScrollTimeline][scroll-timeline] is a concept introduced in +scroll-linked animation proposal. It defines an animation timeline whose time value depends on +scroll position of a scroll container. `ScrollTimeline` can be used an an input timeline for +worklet animations and it is the intended mechanisms to give read access to scroll position. + +We can later add additional properties to this timeline (e.g., scroll phase (active, inertial, +overscroll), velocity, direction) that can further be used by Animation Worklet. + +## GroupEffect + +[GroupEffect][group-effect] is a concept introduced in Web Animation Level 2 specification. It +provides a way to group multiple effects in a tree structure. `GroupEffect` can be used as the +output for Worklet Animations making it possible for it to drive complext effects spanning multiple +elements. Also with some minor [proposed changes](group-effect-changes) to Group Effect timing +model, Animation Worklet can enable creation of new custom sequencing models (e.g., with conditions +and state). + +## Event Dispatching to Worker and Worklets +[Event Dispatching to Worker/Worklets][input-for-worker] is a proposal in WICG which allows workers +and worklets to passively receive DOM events and in particular Pointer Events. This can be +benefitial to Animation Worklet as it provides an ergonomic and low latency way for Animation +Worklet to receive pointer events thus enabling it to implement input driven animations more +effectively. + + +# WEBIDL + +`WorkletAnimation` extends `Animation` and adds a getter for its timelines. +Its constructor takes: + - `animatiorId` which should match the id of an animator which is registered in +the animation worklet scope. + - A sequence of effects which are passed into a `GroupEffect` constructor. + - A sequence of timelines, the first one of which is considered primary timeline and passed to + `Animation` constructor. + + +```webidl + +[Constructor (DOMString animatorName, + optional (AnimationEffectReadOnly or array)? effects = null, + AnimationTimeline? timeline, + optional WorkletAnimationOptions)] +interface WorkletAnimation : Animation { + readonly attribute DOMString animatorName; +} +``` + +`AnimationEffectReadOnly` gets a writable `localTime` attribute which may be used to drive the +effect from the worklet global scope. + +```webidl +partial interface AnimationEffectReadOnly { + [Exposed=Worklet] + // Intended for use inside Animation Worklet scope to drive the effect. + attribute double localTime; +}; + +``` + +# Specification +The [draft specification](https://drafts.css-houdini.org/css-animationworklet) is +the most recent version. + + +[roc-thread]: https://lists.w3.org/Archives/Public/public-houdini/2015Mar/0020.html +[cw-proposal]: https://github.com/w3c/css-houdini-drafts/blob/master/composited-scrolling-and-animation/Explainer.md +[WA]: https://drafts.csswg.org/web-animations/ +[animation]: https://drafts.csswg.org/web-animations/#animations +[worklet]: https://drafts.css-houdini.org/worklets/#worklet-section +[input-for-worker]: https://discourse.wicg.io/t/proposal-exposing-input-events-to-worker-threads/3479 +[group-effect]: https://w3c.github.io/web-animations/level-2/#the-animationgroup-interfaces +[group-effect-changes]: https://github.com/yi-gu/group_effects +[scroll-timeline]: https://wicg.github.io/scroll-animations/#scrolltimeline \ No newline at end of file diff --git a/css-animationworklet/WIP.md b/css-animation-worklet-1/WIP.md similarity index 100% rename from css-animationworklet/WIP.md rename to css-animation-worklet-1/WIP.md diff --git a/css-animation-worklet-1/img/AnimationWorklet-threading-model.svg b/css-animation-worklet-1/img/AnimationWorklet-threading-model.svg new file mode 100644 index 00000000..d463680b --- /dev/null +++ b/css-animation-worklet-1/img/AnimationWorklet-threading-model.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/css-animationworklet/img/WorkletAnimation-timing-model.svg b/css-animation-worklet-1/img/WorkletAnimation-timing-model.svg similarity index 100% rename from css-animationworklet/img/WorkletAnimation-timing-model.svg rename to css-animation-worklet-1/img/WorkletAnimation-timing-model.svg diff --git a/css-animation-worklet-1/img/swipe-to-dismiss-state.png b/css-animation-worklet-1/img/swipe-to-dismiss-state.png new file mode 100644 index 00000000..1f01b8e7 Binary files /dev/null and b/css-animation-worklet-1/img/swipe-to-dismiss-state.png differ diff --git a/css-animationworklet/principles.md b/css-animation-worklet-1/principles.md similarity index 100% rename from css-animationworklet/principles.md rename to css-animation-worklet-1/principles.md diff --git a/css-animationworklet/status.md b/css-animation-worklet-1/status.md similarity index 100% rename from css-animationworklet/status.md rename to css-animation-worklet-1/status.md diff --git a/css-animationworklet/Overview.bs b/css-animationworklet/Overview.bs deleted file mode 100644 index e09c17c3..00000000 --- a/css-animationworklet/Overview.bs +++ /dev/null @@ -1,1041 +0,0 @@ - - - - -
-urlPrefix: https://heycam.github.io/webidl/; type: dfn;
-    text: NotSupportedError
-    urlPrefix: #dfn-;
-        text: callback this value
-        text: exception
-        text: throw
-        url: throw; text: thrown
-    urlPrefix: #;
-        url: Function; text: Function
-        url: VoidFunction; text: VoidFunction
-    url: invoke-a-callback-function; text: Invoke
-    url: construct-a-callback-function; text: constructing
-    url: es-type-mapping; text: converting
-urlPrefix: https://html.spec.whatwg.org/#; type: dfn;
-    url: run-the-animation-frame-callbacks; text: running the animation frame callbacks
-urlPrefix: http://w3c.github.io/html/infrastructure.html#; type: dfn;
-    text: structuredserialize
-    text: structureddeserialize
-urlPrefix: https://www.w3.org/TR/css3-transitions/#; type: dfn;
-    text: animatable properties
-urlPrefix: https://w3c.github.io/web-animations/#; type: dfn;
-    url: the-documents-default-timeline; text: default document timeline
-    url: concept-animation; text: animation
-    text: effect value
-    text: effect stack
-    text: target property
-    text: timeline
-    text: animation effect
-    text: current time
-    text: local time
-    text: inherited time
-    text: ready
-    text: play state
-    text: playback rate
-    text: set the target effect of an animation
-    text: set the timeline of an animation
-    text: finished
-    text: idle
-    text: paused
-    text: pending
-    text: running
-    text: composite operation
-    text: animation class
-urlPrefix: https://w3c.github.io/web-animations/level-2/#;
-    type: dfn;
-        text: group effect
-        text: child effect
-urlPrefix: https://tc39.github.io/ecma262/#sec-; type: dfn;
-    text: IsCallable
-    text: IsConstructor
-    text: HasProperty
-    url: ecmascript-data-types-and-values; text: Type
-    url: map-objects; text:map object
-    url: get-o-p; text: Get
-    url: set-o-p-v-throw; text: Set
-    url: samevalue; text: SameValue
-    urlPrefix: native-error-types-used-in-this-standard-
-        text: TypeError
-urlPrefix: https://www.w3.org/TR/hr-time-2/#dom-; type: dfn
-    text: DOMHighResTimeStamp
-urlPrefix: https://wicg.github.io/scroll-animations/#; type: interface
-    url: scrolltimeline; text: ScrollTimeline
-    url: dictdef-scrolltimelineoptions; text: ScrollTimelineOptions
-    url: dom-scrolltimeline-scrollsource; text: scrollSource
-urlPrefix: https://wicg.github.io/scroll-animations/#; type: dfn
-    url: current-time-algorithm; text: current time of the ScrollTimeline;
-
- -
-{
-    "explainer": {
-        "href": "https://github.com/WICG/animation-worklet/blob/gh-pages/README.md",
-        "title": "Animation Worklet Explainer",
-        "status": "CR",
-        "publisher": "WICG",
-        "deliveredBy": [
-            "https://github.com/WICG/animation-worklet//"
-        ]
-    }
-}
-
- -Introduction {#intro} -===================== -This section is not normative. - -This document introduces a new primitive for creating scroll-linked and other high performance -procedural animations on the web. For details on the rationale and motivation see [[explainer]]. - -The Animation Worklet API provides a method to create scripted animations that control a set -of animation effects. The API is designed to make it possible for user agents to run such -animations in their own dedicated thread to provide a degree of performance isolation from main -thread. - -Relationship to the Web Animations API {#relationship-to-web-animations} ------------------------------------------------------------------------- - -Animations running inside an Animation Worklet execution context expose the {{Animation}} -interface from the Web Animations specification on the main javascript execution context. This means -they can be controlled and inspected from main thread using many of the Web Animation APIs. However -Animation Worklet animations follow a different timing model that enables them to be script-driven, -stateful, and runnable in a parallel worklet execution context. As such Web Animation APIs that seek -or alter the input time (reverse, finish, etc.) have different semantics for Animation Worklet -animations. - - -Threading Model {#threading-model} -================================== -This section is not normative. - -Animation Worklet is designed to be thread-agnostic. Rendering engines may create one or more -parallel worklet execution contexts separate from the main javascript execution context, e.g., on -their own dedicated threads. Rendering engines may then choose to assign Animation Worklet -animations to run in such contexts. Doing so allows Animation Worklet animations to avoid being -impacted by main thread jank. - -Rendering engines may wish to make a best-effort attempt to execute animate callbacks synchronously -with visual frame production to ensure smooth animation. However it is legal for rendering engines -to produce visual frames without blocking to receive animation updates from a worklet (i.e., letting -the effects slip behind). For example, this could occur when the animate function callback is -unable to complete before the frame deadline. - -We believe that scripted animations which are run in a parallel execution environment and which -limit themselves to animating properties which do not require the user agent to consult main thread -will have a much better chance of meeting the strict frame budgets required for smooth playback. - -If a Worklet Animation animation is executing in a parallel worklet execution context, the last -known state of its animation effects should be periodically synced back to the main javascript -execution context. The synchronization of effect values from the parallel worklet execution -context to the main javascript execution context must occur before running the animation -frame callbacks as part of the document lifecycle. Note that due to the asynchronous nature of -this animation model a script running in the main javascript execution context may see a stale value -when reading a target property that is being animated in a Worklet Animation, compared to the -value currently being used to produce the visual frame that is visible to the user. This is similar -to the effect of asynchronous scrolling when reading scroll offsets in the main javascript execution -context. - - -
- Overview of the animation worklet threading model. -
- Overview of the animation worklet threading model.
- - A simplified visualization of how animators running in a parallel execution environment can sync - their update to main thread while remaining in sync with visual frame production. -
-
- -Animation Worklet {#animation-worklet-desc} -============================== -Animation Worklet is a {{Worklet}} responsible for all classes related to custom -animations. The worklet can be accessed via {{animationWorklet}} attribute. - -The {{animationWorklet}}'s worklet global scope type is {{AnimationWorkletGlobalScope}}. - -{{AnimationWorkletGlobalScope}} represents the global execution context of {{animationWorklet}}. - - -[Exposed=Window] -partial namespace CSS { - [SameObject] readonly attribute Worklet animationWorklet; -}; - - - - -Animator {#animator-desc} -======== - -An Animator represents an animation instance that is running on the animation thread. -Animators are identified by a unique name and determine how the animation progresses its keyframe -effects given current input time. Animator instances live in {{AnimationWorkletGlobalScope}} -and each one is associated with a {{WorkletAnimation}} instance. An animator can only be -instantiated by construction of a {{WorkletAnimation}}. - - -Two animators types are supported: {{StatelessAnimator}} and {{StatefulAnimator}} each -providing a different state management strategy. - - -StatelessAnimator Interface {#stateless-animator-desc} ---------------------------- - -This interface represents a stateless animator. This type of animator does not depend on any local -state either stored on the instance or global scope. Effectively, the animate function of an -{{StatelessAnimator}} can be treated as a pure function with the expectation that given the same -input, it produces the same output. - - - - -[Exposed=AnimationWorklet, Global=AnimationWorklet, Constructor (optional any options)] -interface StatelessAnimator { -}; - - - - -
- This is how the class should look. -
-        class FooAnimator extends StatelessAnimator{
-            constructor(options) {
-                // Called when a new animator is instantiated.
-            }
-            animate(currentTime, effect) {
-                // Animation frame logic goes here.
-            }
-        }
-    
-
- -Note: The statelessness allows an animation worklet to perform optimization such as producing -multiple animation frames in parallel and performing very cheap teardown and setup. Using -StatelessAnimator is highly recommended to enable such optimizations. - -StatefulAnimator Interface {#stateful-animator-desc} ---------------------------- - -This interface represents a stateful animator. This animator can have local state and animation -worklet guarantees that it maintains this state as long as the stateful animator fulfills the -contract required by this interface as described following. - - -Animation worklet maintains a set of {{WorkletGlobalScope}}s which may exist across different -threads or processes. Animation worklet may temporarily terminate a global scope (e.g., to preserve -resources) or move a running animator instance across different global scopes (e.g., if its -effect is mutable only in a certain thread). Animation worklet guarantees that a stateful animator -instance's state is maintained even if the instance is respawned in a different global scope. - -The basic mechanism for maintaining the state is that the animation worklet snapshots the local -state that is exposed via {{StatefulAnimator/state}} attribute and then reifies it at a later time -to be passed into the constructor when the animator instance is respawned in a potentially different -global scope. This processes is specified is details in migrate an animator instance -algorithm. - -A user-defined stateful animator is expected to fulfill the required contract which is that its -state attribute is an object representing its state that can be serialized using structured -serialized algorithm and that it can also recreate its state given that same object passed to -its constructor. - - -[Exposed=AnimationWorklet, Global=AnimationWorklet, -Constructor (optional any options, optional any state)] -interface StatefulAnimator { - attribute any state; -}; - - - -
- This is how the class should look. -
-        class BarAnimator extends StatefulAnimator {
-            constructor(options, state) {
-              // Called when a new animator is instantiated (either first time or after being respawned).
-              this.currentVelocity  = state ? state.velocity : 0;
-            }
-            animate(currentTime, effect) {
-                // Animation frame logic goes here and can rely on this.currentVelocity.
-                this.currentVelocity += 0.1;
-            }
-            get state {
-              // The returned object should be serializable using structured clonable algorithm.
-              return {
-                velocity: this.currentVelocity;
-              }
-            }
-        }
-    
-
- - -Animator Definition {#animator-definition-desc} -------------------- - -An animator definition is a struct which describes the author defined custom -animation as needed by {{AnimationWorkletGlobalScope}}. It consists of: - - - An animator name <>#. - - - A class constructor which is a VoidFunction callback function type. - - - An animate function which is a Function callback function type. - - - A stateful flag - - -Registering an Animator Definition {#registering-animator-definition} -------------------------------------- -An {{AnimationWorkletGlobalScope}} has a animator name to animator definition map. -The map gets populated when {{registerAnimator(name, animatorCtorValue)}} is called. - - - -[ Exposed=AnimationWorklet, Global=AnimationWorklet ] -interface AnimationWorkletGlobalScope : WorkletGlobalScope { - void registerAnimator(DOMString name, VoidFunction animatorCtor); -}; - - -
- -When the registerAnimator(|name|, |animatorCtorValue|) -method is called in a {{AnimationWorkletGlobalScope}}, the user agent must run the -following steps: - - 1. If |name| is not a valid <>, throw a TypeError and abort all these - steps. - - 2. If |name| exists as a key in the animator name to animator definition map, - throw a NotSupportedError and abort all these steps. - - 3. If the result of IsConstructor(|animatorCtorValue|) is false, throw a - TypeError and abort all these steps. - - 4. Let |animatorCtor| be the result of converting animatorCtorValue to the - VoidFunction callback function type. If an exception is thrown, rethrow the - exception and abort all these steps. - - 4. Let |prototype| be the result of Get(|animatorCtorValue|, "prototype"). - - - 5. If SameValue(|prototype|, {{StatelessAnimator}}) is true set |stateful| to be - false, otherwise if SameValue(|prototype|, {{StatefulAnimator}}) is - true set |stateful| to be true, otherwise throw a TypeError and - abort all these steps. - - 6. Let |animateValue| be the result of Get(|prototype|, "animate"). - - 7. Let |animate| be the result of converting |animateValue| to the Function - callback function type. If an exception is thrown, rethrow the exception and abort - all these steps. - - 8. Let |definition| be a new animator definition with: - - - animator name being |name| - - - class constructor being |animatorCtor| - - - animate function being |animate| - - - stateful flag being |stateful| - - - 9. Add the key-value pair (|name| - |definition|) to the animator name to animator - definition map. -
- - -Animator Instance {#animator-instance-section} -====================================== - -An animator instance is a struct which describes a fully realized custom animation -instance in an {{AnimationWorkletGlobalScope}}. It has a reference to an animator definition -and owns the instance specific state such as animation effect and timelines. It consists of: - - - An animator name. - - - An animation requested flag. - - - An animator effect which is an animation effect. - - - An animator current time which is the corresponding worklet animation's current - time. - - - An animator timeline which is a timeline. - - - An animator attached timelines which is list of attached timelines - - - An animator serialized options which is a serializable object. - -A stateful animator instance is an animator instance whose corresponding -animator definition's stateful flag is true. - - -Creating an Animator Instance {#creating-animator-instance} ------------------------------------------------------------ - -Each animator instance lives in an {{AnimationWorkletGlobalScope}}. - -Each {{AnimationWorkletGlobalScope}} has an animator instance set. The set is populated -when the user agent constructs a new animator instance in the {{AnimationWorkletGlobalScope}} -scope. Each animator instance corresponds to a worklet animation in the document scope. - -
- -To create a new animator instance given a |name|, |timeline|, |effect|, |serializedOptions|, -|serializedState|, and |workletGlobalScope|, the user agent must run the following steps: - - 1. Let the |definition| be the result of looking up |name| on the |workletGlobalScope|'s - animator name to animator definition map. - - If |definition| does not exist abort the following steps. - - 2. Let |animatorCtor| be the class constructor of |definition|. - - 3. Let |timelineList| be a new list with |timeline| added to it. - - 4. Let |options| be StructuredDeserialize(|serializedOptions|). - - 5. Let |state| be StructuredDeserialize(|serializedState|). - - 6. Let |animatorInstance| be the result of constructing |animatorCtor| with - [|options|, |state| as args. If an exception is thrown, rethrow the exception and abort all - these steps. - - 7. Set the following on |animatorInstance| with: - - animator name being |name| - - animation requested flag being frame-current - - animator current time being unresolved - - animator effect being |effect| - - animator timeline being |timeline| - - animator attached timelines being |timelineList| - - animator serialized options being |options| - - 8. Add |animatorInstance| to |workletGlobalScope|'s animator instance set. - -
- - -Running Animators {#running-animators} --------------------------------------- - -When the user agent wants to produce a new animation frame, if for any animator instance the -associated animation requested flag is frame-requested then the the user agent -must run animators for the current frame. - -Note: The user agent is not required to run animations on every visual frame. It is legal to defer - generating an animation frame until a later frame. This allow the user agent to - provide a different service level according to their policy. - -
- -When the user agent wants to run animators in a given |workletGlobalScope|, it -must iterate over all animator instances in the |workletGlobalScope|'s animator -instance set. For each such |instance| the user agent must perform the following steps: - - 1. Let |animatorName| be |instance|'s animator name - - 2. Let the |definition| be the result of looking up |animatorName| on the |workletGlobalScope|'s - animator name to animator definition map. - - If |definition| does not exist then abort the following steps. - - 3. If the animation requested flag for |instance| is frame-current or the effect - belonging to the |instance| will not be visible within the visual viewport of the current - frame the user agent may abort all the following steps. - - Issue: Consider giving user agents permission to skip running animator instances to - throttle slow animators. - - 4. Let |animateFunction| be |definition|'s animate function. - - 5. Let |currentTime| be animator current time of |instance|. - - 6. Let |effect| be animator effect of |instance|. - - 7. Invoke |animateFunction| with arguments «|currentTime|, |effect|», - and with |instance| as the callback this value. - -
-Note: Although inefficient, it is legal for the user agent to run animators multiple times -in the same frame. - - -Issue: should be explicit as to what happens if the animateFunction throws an exception. At least -we should have wording that the localTime values of the keyframes are ignored to avoid incorrect -partial updates. - -Removing an Animator Instance {#removing-animator} ------------------------------------------ - -
- -To remove an animator instance given |instance| and |workletGlobalScope| the user agent -must run the following steps: - -1. Remove |instance| from |workletGlobalScope|'s animator instance set. - -
- - -Migrating an Animator Instance {#migrating-animator} ------------------------------------------ - -The migration process allows stateful animator instance to be migrated to a different -{{WorkletGlobalScope}} without losing their local state. - -
- -To migrate an animator instance from one {{WorkletGlobalScope}} to another, given -|instance|, |sourceWorkletGlobalScope|, |destinationWorkletGlobalScope|, the user agent -must run the following steps : - - 1. Let |serializedState| be undefined. - - 2. Queue a task on |sourceWorkletGlobalScope| to run the following steps: - - 1. Let |animatorName| be |instance|'s animator name - - 2. Let |definition| be the result of looking up |animatorName| on |sourceWorkletGlobalScope|'s - animator name to animator definition map. - - If |definition| does not exist then abort the following steps. - - 3. Let |stateful| be the stateful flag of |definition|. - - 4. If |stateful| is false then abort the following steps. - - 5. Let |state| be the result of Get(|instance|, "state"). If any exception is thrown, - rethrow the exception and abort the following steps. - - 6. Set |serializedState| to be the result of StructuredSerialize(|state|). - If any exception is thrown, then abort the following steps. - - 7. Run the procedure to remove an animator instance given |instance|, and - |sourceWorkletGlobalScope|. - - 2. Wait for the above task to complete. If the task is aborted, abort the following steps. - - 3. Queue a task on |destinationWorkletGlobalScope| to run the following steps: - - 1. Run the procedure to create a new animator instance given: - - The |instance|'s animator name as name. - - The |instance|'s animator timeline as timeline. - - The |instance|'s animator effect as effect. - - The |instance|'s animator serialized options as options. - - The |serializedState| as state. - - The |destinationWorkletGlobalScope| as workletGlobalScope. - -
- -If an animator state getter throws the user agent will remove the animator but does not recreate it. -This effectively removes the animator instance. - - -Requesting Animation Frames {#requesting-animation-frames} ----------------------------------------------------------- - -Each animator instance has an associated animation requested flag. It must be -either frame-requested or frame-current. It is initially set to -frame-current. Different circumstances can cause the animation requested flag to be -set to frame-requested. These include the following: - - Changes in the current time of any timeline in the animator's animator attached timelines - - Changes in the current time of the animator's corresponding Worklet Animation - -[[#running-animators]] resets the animation requested flag on animators to -frame-current. - - -Web Animations Integration {#web-animation-integration} -=============================== - - -Worklet Animation {#worklet-animation-desc} -------------------------------------------- -Worklet animation is a kind of animation that delegates the animation playback to -an animator instance. It controls the lifetime and playback state of its corresponding -animator instance. - -Being an animation, worklet animation has an animation effect and a -timeline. However unlike other animations the worklet animation's current time does -not directly determine the animation effect's local time (via its inherited time). -Instead the associated animator instance controls the animation effect's local time -directly. Note that this means that the timeline's current time does not fully determine the -animation's output. - -Worklet animation has the following properties in addition to the {{Animation}} interface: - - an animation animator name which identifies its animator definition. - - a serialized options which is serializable object that is used when - constructing a new animator instance. - -
- Overview of the WorkletAnimation timing model. -
- Overview of the WorkletAnimation timing model.
- - The animation current time is input to the animator instance, which produces a local time value - for the animation effect. If the animator instance is running in a parallel global scope the - implementation may also choose to use the local time value to produce the final effect value and - update the visuals in parallel. - -
-
- - -Creating a Worklet Animation {#creating-worklet-animation} ------------------------------------------------------------ - - -[Exposed=Window, - Constructor (DOMString animatorName, - optional (AnimationEffect or sequence<AnimationEffect>)? effects = null, - optional AnimationTimeline? timeline, - optional any options)] -interface WorkletAnimation : Animation { - readonly attribute DOMString animatorName; -}; - - - - -
-WorkletAnimation(|animatorName|, |effects|, |timeline|, |options|) - -Creates a new {{WorkletAnimation}} object using the following procedure. - - 1. Let |workletAnimation| be a new {{WorkletAnimation}} object. - - 2. Run the procedure to set the timeline of an animation on |workletAnimation| passing - |timeline| as the new timeline or, if a |timeline| argument is not provided, - passing the default document timeline of the {{Document}} associated with the - {{Window}} that is the current global object. - - 3. Let |effect| be the result corresponding to the first matching condition from below. - : If |effects| is a {{AnimationEffect}} object, - :: Let effect be |effects|. - : If |effects| is a list of {{AnimationEffect}} objects, - :: Let |effect| be a new {{WorkletGroupEffect}} with its children set to |effects|. - : Otherwise, - :: Let |effect| be undefined. - - 4. Run the procedure to set the target effect of an animation on |workletAnimation| - passing |effect| as the new effect. - - 5. Let |serializedOptions| be the result of StructuredSerialize(|options|). - Rethrow any exceptions. - - 6. Set the serialized options of |workletAnimation| to |serializedOptions|. - - 7. Set the animation animator name of |workletAnimation| to |animatorName|. -
- - -Worklet Animation timing model {#timing-model} ------------------------------------- - -This section describes how worklet animation's timing model differs from other -animations. - -In addition to the existing conditions on when the animation is considered ready, a -worklet animation is only considered ready when the following condition is also true: - - - the user agent has completed any setup required to create the worklet animation's - corresponding animator instance. - -As described in [[#worklet-animation-desc]], the worklet animation's current time does -not determine its animation effect's local time. Instead the associated animator -instance controls the animation effect's local time directly. This means that the -animation effect's local time is controlled from a {{WorkletGlobalScope}} which may be in a parallel -execution context. - -Here are a few implications of the above semantics: - - - Setting the current time or start time of a worklet animation does not - necessarily change its output, but may change the animation play state. - - Similarly, invoking {{Animation/finish()}} or updating a worklet animation's playback - rate will only change the animation play state and may not change the output. - - Querying the animation effect's local time using {{AnimationEffect/getComputedTiming()}} - may return stale information, in the case where the animator instance is running in a - parallel execution context. - -Issue(63): Come with appropriate mechanism's for animator instance to get notified when its - animation currentTime is changing e.g., via reverse(), finish() or playbackRate change. So that - it can react appropriately. - - -Interaction with Animator Instances {#worklet-animation-animator-instances} ------------------------------------ - -A worklet animation corresponds to at most one animator instance at any time, and may -have no current corresponding animator instance. The correspondance of an animator -instance for a worklet animation depends on the animation play state. - -
- -To associate animator instance of worklet animation given |workletAnimation|, -the user agent must run the following steps: - - 1. If |workletAnimation| has a corresponding animator instance, abort the following steps. - 2. Let |workletGlobalScope| be the {{AnimationWorkletGlobalScope}} associated with - |workletAnimation|. - 3. Queue a task on |workletGlobalScope| to run the procedure to create a new animator - instance, passing: - * The |workletAnimation|'s animation animator name as name. - * The |workletAnimation|'s timeline as timeline. - * The |workletAnimation|'s animation effect as effect. - * The |workletAnimation|'s serialized options as options. - * The |workletGlobalScope| as workletGlobalScope. - 4. If the procedure was successful, set the resulting animator instance as corresponding to - |workletAnimation|. - -
- -
- -To disassociate animator instance of worklet animation given -|workletAnimation|, the user age must run the following steps: - - 1. If |workletAnimation| does not have a corresponding animator instance, abort the - following steps. - 2. Let |workletGlobalScope| be the {{AnimationWorkletGlobalScope}} associated with - |workletAnimation|. - 3. Let |animatorInstance| be |workletAnimation|'s corresponding animator instance. - 4. Queue a task on the |workletGlobalScope| to run the procedure to remove an animator - instance, passing |animatorInstance| as instance and |workletGlobalScope| as - workletGlobalScope. - 5. Set |workletAnimation| as having no corresponding animator instance. - -
- - -
- -To set animator instance of worklet animation given -|workletAnimation|, the user agent must run the following steps: - - 1. disassociate animator instance of worklet animation given |workletAnimation|. - 2. associate animator instance of worklet animation given |workletAnimation|. - -
- -When a given |workletAnimation|'s play state changes to pending, running, or -paused, run the procedure to -associate animator instance of worklet animation given |workletAnimation|. - - -When a given |workletAnimation|'s play state changes to idle or finished, -run the procedure to -disassociate animator instance of worklet animation given |workletAnimation|. - -When the procedure to set the target effect of an animation for a given |workletAnimation| -is called, then set animator instance of worklet animation given |workletAnimation|. - -When the procedure to set the timeline of an animation for a given |workletAnimation| -is called, then set animator instance of worklet animation given |workletAnimation|. - - -Timeline Attachment {#timeline-attachment} -------------------- - -Issue(61): Define semantics of attachment and detachment. - -ScrollTimeline {#scroll-timeline} ---------------------------------- -{{ScrollTimeline}} is a new concept being proposed for addition to web animation API. It defines -an animation timeline whose time value depends on the scroll position of a scroll container. -Worklet animations can have a scroll timeline and thus drive their scripted effects based -on a scroll offset. - -Note: Access to input: We are interested on exposing additional user input beside -scrolling (e.g., touch/pointer input) to these animations so that authors can create jank-free -input driven animations which are not really possible today. We are still trying to figure out the -right abstractions and mechanisms to do this. - -WorkletGroupEffect {#worklet-group-effect} ------------------- - -{{WorkletGroupEffect}} is a type of group effect that allows its child effect's -local times to be mutated individually. - -When a {{WorkletGroupEffect}} is set as the animation effect of a {{WorkletAnimation}}, the -corresponding animator instance can directly control the child effects' local -times. This allows a single worklet animation to coordinate multiple effects - see -[[#example-2]] for an example of such a use-case. - - -[Exposed=AnimationWorklet] -interface WorkletGroupEffect { - sequence<AnimationEffect> getChildren(); -}; - -[Exposed=AnimationWorklet] -partial interface AnimationEffect { - // Intended for use inside Animation Worklet scope to drive the effect. - attribute double localTime; -}; - - -
- -To set the {{localTime}} property on a |effect| to value |t|, the user agent should perform the -action that corresponds to the first matching condition from the following: - - : If the |effect| does not have a parent group, - :: Set the |effect| local time to |t|. - : If the |effect| has a parent group and it is of {{WorkletGroupEffect}} type, - :: Set the effect start time to (parent's transformed time - t). Note this effectively set's the - |effect|'s local time to t. - : Otherwise - :: Throw an exception indicating that the child effect time can only be controlled by - its parent group. - -
- -Issue(w3c/csswg-drafts#2071): The above interface exposes a conservative subset -of GroupEffect proposed as part of web-animation-2. Once that is available we -should switch to it. - - -Effect Stack and Composite Order {#effect-stack-composite-order} --------------------------------- - -As with other animations, worklet animations participate in the effect stack. A -worklet animation does not have a specific animation class which means it has the same -composite order as other Javascript created web animations. - - -Examples {#examples} -==================== - -Example 1: Hidey Bar. {#example-1} ------------------------------------------ -An example of header effect where a header is moved with scroll and as soon as finger is lifted it -animates fully to close or open position depending on its current position. - - - -<div id='scrollingContainer'> - <div id='header'>Some header</div> - <div>content</div> -</div> - -<script> -await CSS.animationWorklet.addModule('hidey-bar-animator.js'); -const scrollTimeline = new ScrollTimeline({ - scrollSource: $scrollingContainer, - orientation: 'block', - timeRange: 1000 -}); -const documentTimeline = document.timeline; - -// Note we pass in two timelines in the options bag which allows the animation to read their -// currenTime values directly. -const animation = new WorkletAnimation( - 'hidey-bar', - new KeyframeEffect($header, - [{transform: 'translateX(100px)'}, {transform: 'translateX(0px)'}], - {duration: 1000, iterations: 1, fill: 'both' }]), - scrollTimeline, - {scrollTimeline, documentTimeline}); - -animation.play(); -</script> - - - - -// Inside AnimationWorkletGlobalScope - -registerAnimator('hidey-bar', class HidybarAnimator extends StatefulAnimator { - constructor(options, state) { - this.scrollTimeline_ = options.scrollTimeline; - this.documentTimeline_ = options.documentTimeline; - - if (state) { - this.startTime_ = state.startTime; - this.direction_ = state.direction; - } - } - - animate(currentTime, effect) { - const scroll = this.scrollTimeline_.currentTime; // [0, 100] - const time = this.documentTimeline_.currentTime; - - const activelyScrolling = this.scrollTimeline_.phase == 'active'; - - let localTime; - if (activelyScrolling) { - this.startTime_ = undefined; - localTime = scroll; - } else { - this.startTime_ = this.startTime_ || time; - // Decide on close/open direction depending on how far we have scrolled the header - // This can even do more sophisticated animation curve by computing the scroll velocity and - // using it. - this.direction_ = scroll >= 50 ? +1 : -1; - localTime = this.direction_ * (time - this.startTime_); - } - - // Drive the output effect by setting its local time. - effect.localTime = localTime; - } - - getter state() { - return { - startTime: this.startTime_, - direction: this.direction_ - } - } -}); - - - -Issue: This example uses a hypothetical "phase" property on timeline as a way to detect when user -is no longer actively scrolling. This is a reasonable thing to have on scroll timeline. A simple -fallback can emulate this by detecting when timeline time (i.e. scroll offset) has not changed in -the last few frames. - - -Example 2: Twitter header. {#example-2} --------------------------- -An example of twitter profile header effect where two elements (avatar, and header) are updated in -sync with scroll offset. - - - -// In document scope. -<div id='scrollingContainer'> - <div id='header' style='height: 150px'></div> - <div id='avatar'><img></div> -</div> - -<script> -await CSS.animationWorklet.addModule('twitter-header-animator.js'); -const animation = new WorkletAnimation( - 'twitter-header', - [new KeyframeEffect($avatar, /* scales down as we scroll up */ - [{transform: 'scale(1)'}, {transform: 'scale(0.5)'}], - {duration: 1000, iterations: 1}), - new KeyframeEffect($header, /* loses transparency as we scroll up */ - [{opacity: 0}, {opacity: 0.8}], - {duration: 1000, iterations: 1})], - new ScrollTimeline({ - scrollSource: $scrollingContainer, - orientation: 'block', - timeRange: 1000, - startScrollOffset: 0, - endScrollOffset: $header.clientHeight})); -animation.play(); - -// Since this animation is using a group effect, the same animation instance -// is accessible via different handles: $avatarEl.getAnimations()[0], $headerEl.getAnimations()[0] - -</script> - - - - -// Inside AnimationWorkletGlobalScope. -registerAnimator('twitter-header', class HeaderAnimator extends StatelessAnimator { - constructor(options) { - this.timing_ = new CubicBezier('ease-out'); - } - - animate(currentTime, effect) { - const scroll = currentTime; // scroll is in [0, 1000] range - - // Drive the output group effect by setting its children local times individually. - effect.children[0].localTime = scroll; - effect.children[1].localTime = this.timing_(clamp(scroll, 0, 500)); - } -}); - -function clamp(value, min, max) { - return Math.min(Math.max(value, min), max); -} - - - -Example 3: Parallax backgrounds. {#example-3} ------------------------------------------ -A simple parallax background example. - - -<style> -.parallax { - position: fixed; - top: 0; - left: 0; - opacity: 0.5; -} -</style> -<div id='scrollingContainer'> - <div id="slow" class="parallax"></div> - <div id="fast" class="parallax"></div> -</div> - -<script> -await CSS.animationWorklet.addModule('parallax-animator.js'); -const scrollTimeline = new ScrollTimeline({ - scrollSource: $scrollingContainer, - orientation: 'block', - timeRange: 1000 -}); -const scrollRange = $scrollingContainer.scrollHeight - $scrollingContainer.clientHeight; - -const slowParallax = new WorkletAnimation( - 'parallax', - new KeyframeEffect($parallax_slow, [{'transform': 'translateY(0)'}, {'transform': 'translateY(' + -scrollRange + 'px)'}], {duration: 1000}), - scrollTimeline, - {rate : 0.4} -); -slowParallax.play(); - -const fastParallax = new WorkletAnimation( - 'parallax', - new KeyframeEffect($parallax_fast, [{'transform': 'translateY(0)'}, {'transform': 'translateY(' + -scrollRange + 'px)'}], {duration: 1000}), - scrollTimeline, - {rate : 0.8} -); -fastParallax.play(); -</script> - - - - -// Inside AnimationWorkletGlobalScope. -registerAnimator('parallax', class ParallaxAnimator extends StatelessAnimator { - constructor(options) { - this.rate_ = options.rate; - } - - animate(currentTime, effect) { - effect.localTime = currentTime * this.rate_; - } -}); - diff --git a/css-animationworklet/README.md b/css-animationworklet/README.md deleted file mode 100644 index abb08be1..00000000 --- a/css-animationworklet/README.md +++ /dev/null @@ -1,398 +0,0 @@ -# Animation Worklet Explainer ---- - -# Overview - -Animation Worklet is a new primitive for creating scroll-linked and other high performance -procedural animations on the web. It is being incubated here as part of the -[CSS Houdini task force](https://github.com/w3c/css-houdini-drafts/wiki), and if successful will be -transferred to that task force for full standardization. - -# Introduction - -Scripted effects (written in response to `requestAnimationFrame` or async `onscroll` events) are -rich but are subject to main thread jankiness. On the other hand, accelerated CSS transitions and -animations can be fast (for a subset of *accelerated* properties) but are not rich enough to enable -[many common use cases](#motivating-use-cases) and currently have no way to access scroll offset -and other user input. This is why scripted effects are still very popular for implementing common -effects such as hidey-bars, parallax, position:sticky, and etc. We believe (and others -[agree][roc-thread]) that there is a need for a new primitive for creating fast and rich visual -effects with the ability to respond to user input such as scroll. - -This document proposes an API to create custom animations that execute inside an isolated execution -environment, *worklet*. It aims to be compatible with Web Animations and uses existing constructs as -much as possible. We believe this API hits a sweet spot in balancing among performance, richness, -and rationality for addressing our key use cases. - -See the [Animation Worklet design principles and goals](principles.md) for an overview of the -motivations behind Animation Worklet and how the design will be evolved to support a growing set of -rich use cases. Also see [the status document](status.md) for high level implementation status and -timeline. - -# Motivating Use Cases - -* Scroll-linked effects: - - Parallax ([demo](https://googlechromelabs.github.io/houdini-samples/animation-worklet/parallax-scrolling/)) - - Animated scroll headers, eg. "hidey-bars" ([demo](https://googlechromelabs.github.io/houdini-samples/animation-worklet/twitter-header/), [twitter](https://twitter.com/LEGO_Group), [Polymer `paper-scroll-header-panel`](https://elements.polymer-project.org/elements/paper-scroll-header-panel?view=demo:demo/index.html)) - - Springy sticky elements ([demo](http://googlechromelabs.github.io/houdini-samples/animation-worklet/spring-sticky/)) - -* Animations with custom timing functions (particularly those that are not calculable a priori) - - - Spring timing function ([demo](https://googlechromelabs.github.io/houdini-samples/animation-worklet/spring-timing/)) - -* Location tracking and positioning: - - - Position: sticky - -* Procedural animation of multiple elements in sync: - - - Efficient Expando ([demo](http://googlechromelabs.github.io/houdini-samples/animation-worklet/expando/), [more info](https://developers.google.com/web/updates/2017/03/performant-expand-and-collapse)) - - Compositing growing / shrinking box with border (using 9 patch) - - -These usecases are enabled by the current proposed but [additional usecases](principles.md#animation-worklet-vision) are going to be addressed by extension of the API. - -***Note***: Demos work best in the latest Chrome Canary with the experimental -web platform features enabled (`--enable-experimental-web-platform-features` -flag) otherwise they fallback to using main thread rAF to emulate the behaviour. - - -# Key Concepts - -## Animation Worklet Global Scope -A [worklet global scope](https://drafts.css-houdini.org/worklets/#the-global-scope) that is created -by Animation Worklet. Note that Animation Worklet creates multiple such scopes and uses them to -execute user defined effects. - -## WorkletAnimation -`WorkletAnimation` is a subclass of Animation that can be used to create an custom animation effect -that runs inside a standalone animation worklet scope. A worklet animation has a corresponding -animator instance in a animation worklet scope which is responsible to drive its keyframe effects. -Here are the key differences compared to a regular web animation: - - AnimationId should match a specific animator class registered in the animation worklet scope. - - `WorkletAnimation` may have multiple timelines (including `ScrollTimeline`s). - - `WorkletAnimation` may have a custom properties bag that can be cloned and provided to animator - constructor when it is being instantiated. - -Note that worklet animations expose same API surface as other web animations and thus they may be -created, played, paused, inspected, and generally controlled from main document scope. Here is how -various methods roughly translate: - - - cancel(): cancels the animation and the corresponding animator instance is removed. - - play(): starts the animation and the corresponding animator instance may get its `animate` function - called periodically as a result of changes in its timelines. - - pause(): pauses the animation and the corresponding animator instance no longer receives - `animate` calls. - - finish(): invokes `finish` on the corresponding animator instance. - - reverse() or mutating playbackRate: invokes `playbackRateChanged` on the corresponding - animator instance. - -## ScrollTimeline -[ScrollTimeline](https://wicg.github.io/scroll-animations/#scrolltimeline) is a concept introduced in -scroll-linked animation proposal. It defines an animation timeline whose time value depends on -scroll position of a scroll container. `ScrollTimeline` can be used an an input timeline for -worklet animations and it is the intended mechanisms to give read access to scroll position. - -## GroupEffect -[GroupEffect](https://w3c.github.io/web-animations/level-2/#the-animationgroup-interfaces) is a -concept introduced in Web Animation Level 2 specification. It provides a way to group multiple -effects in a tree structure. `GroupEffect` can be used as the output for worklet animations. It -makes it possible for worklet animation to drive effects spanning multiple elements. - -**TODO**: At the moment, `GroupEffect` only supports just two different scheduling models (i.e., -parallel, sequence). These models governs how the group effect time is translated to its children -effect times by modifying the child effect start time. Animation Worklet allows a much more -flexible scheduling model by making it possible to to set children effect's local time directly. In -other words we allow arbitrary start time for child effects. This is something that needs to be -added to level 2 spec. - -## Multiple Timelines -Unlike typical animations, worklet animations can be attached to multiple timelines. This is -necessary to implement key usecases where the effect needs to smoothly animate across different -timelines (e.g., scroll and wall clock). - -### Primary Timeline -The first timeline is considered the *primary timeline*. The only purpose of the primary timeline is -to make integration with existing web animation machinery easier, in particular the primary timeline -time will be used anywhere the web animation API needs to expose a time value, for example in -[event timeline time](https://w3c.github.io/web-animations/level-2/#event-timeline-time), or -[event current time](https://w3c.github.io/web-animations/level-2/#event-current-time). - - -**TODO**: We are considering API designs that can make it possible for an animation to observe multiple -timelines but only gets activated on a (dynamic) subset of them. This ensures we can be more -efficient when updating the animation. - -## Animator Migration -The animators are not guaranteed to run in the same global scope (or underlying thread) for their -lifetime duration. For example, a user agents is free to initially run the animator on main thread -but later decide to migrate it off main thread to get certain performance optimizations. To allow -worklet animators to keep their state across migrations, the API provides the following lifetime -hooks: - -```js -// in document scope -new WorkletAnimation('animation-with-local-state', [], [], {value: 1}); -``` - - -```js -registerAnimator('animation-with-local-state', class { - constructor(options, state) { - // |options| may be either: - // - The user provided options bag passed into the WorkletAnimation constructor on first initialization i.e, {value: 1}. - // - The object returned by |destroy| after each migration i.e. {value: 42}. - this.options_ = options; - this.state_ = state || {value: Math.random()}; - } - - animate(timelines, effect) { - this.state_.value += 0.1; - effect.localTime = this.state_.value; - } - - destroy() { - // Invoked before each migration attempts. - // The returned object must be structure clonable and will be passed to constructor to help - // animator restore its state after migration to the new scope. - return this.state_; - } -}); -``` - -# Examples - -**TODO**: Add gifs that visualize these effects - -## Hidey Bar -An example of header effect where a header is moved with scroll and as soon as finger is lifted -it animates fully to close or open position depending on its current position. - - -``` html -
- -
content
-
- - -``` - -hidey-bar-animator.js: -```js -registerAnimator('hidey-bar', class { - - constructor(options) { - this.scrollTimeline_ = options.scrollTimeline; - this.documentTimeline_ = options.documentTimeline; - } - - animate(currentTime, effect) { - const scroll = this.scrollTimeline_.currentTime; // [0, 100] - const time = this.documentTimeline_.currentTime; - - // **TODO**: use a hypothetical 'phase' property on timeline as a way to detect when user is no - // longer actively scrolling. This is a reasonable thing to have on scroll timeline but we can - // fallback to using a timeout based approach as well. - const activelyScrolling = this.scrollTimeline_.phase == 'active'; - - let localTime; - if (activelyScrolling) { - this.startTime_ = undefined; - localTime = scroll; - } else { - this.startTime_ = this.startTime_ || time; - // Decide on close/open direction depending on how far we have scrolled the header - // This can even do more sophisticated animation curve by computing the scroll velocity and - // using it. - this.direction_ = scroll >= 50 ? +1 : -1; - localTime = this.direction_ * (time - this.startTime_); - } - - // Drive the output effects by setting its local time. - effect.localTime = localTime; -}); - -``` - - -## Twitter Header -An example of twitter profile header effect where two elements (avatar, and header) are updated in -sync with scroll offset. - -```html - -
- -
-
- - -``` - -twitter-header-animator.js: -```js -registerAnimator('twitter-header', class { - constructor(options) { - this.timing_ = new CubicBezier('ease-out'); - } - - clamp(value, min, max) { - return Math.min(Math.max(value, min), max); - } - - animate(currentTime, effect) { - const scroll = currentTime; // [0, 1] - - // Drive the output group effect by setting its children local times. - effect.children[0].localTime = scroll; - // Can control the child effects individually -    effect.children[1].localTime = this.timing_(this.clamp(scroll, 0, 1)); - } -}); - -``` - -### Parallax -```html - -
-
-
-
- - - -``` - -parallax-animator.js: - -```js -// Inside AnimationWorkletGlobalScope. -registerAnimator('parallax', class { - constructor(options) { - this.rate_ = options.rate; - } - - animate(currentTime, effect) { - effect.localTime = currentTime * this.rate_; - } -}); -``` - - -# WEBIDL - -`WorkletAnimation` extends `Animation` and adds a getter for its timelines. -Its constructor takes: - - `animatiorId` which should match the id of an animator which is registered in -the animation worklet scope. - - A sequence of effects which are passed into a `GroupEffect` constructor. - - A sequence of timelines, the first one of which is considered primary timeline and passed to - `Animation` constructor. - -```webidl - -[Constructor (DOMString animatorName, - optional (AnimationEffectReadOnly or array)? effects = null, - AnimationTimeline? timeline, - optional WorkletAnimationOptions)] -interface WorkletAnimation : Animation { - readonly attribute DOMString animatorName; -} -``` - -**TODO**: At the moment `GroupEffect` constructor requires a timing but this seems unnecessary for -`WorkletAnimation` where it should be possible to directly control individual child effect local -times. We need to bring this up with web-animation spec. - -`AnimationEffectReadOnly` gets a writable `localTime` attribute which may be used to drive the -effect from the worklet global scope. - -```webidl -partial interface AnimationEffectReadOnly { - [Exposed=Worklet] - // Intended for use inside Animation Worklet scope to drive the effect. - attribute double localTime; -}; - -``` - - - - -# Specification -The [draft specification](https://drafts.css-houdini.org/css-animationworklet) is -the most recent version. - - -[roc-thread]: https://lists.w3.org/Archives/Public/public-houdini/2015Mar/0020.html -[cw-proposal]: https://github.com/w3c/css-houdini-drafts/blob/master/composited-scrolling-and-animation/Explainer.md - diff --git a/css-animationworklet/img/AnimationWorklet-threading-model.svg b/css-animationworklet/img/AnimationWorklet-threading-model.svg deleted file mode 100644 index 26d7ba93..00000000 --- a/css-animationworklet/img/AnimationWorklet-threading-model.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/css-layout-api/Overview.bs b/css-layout-api/Overview.bs index 899f1cee..6074248e 100644 --- a/css-layout-api/Overview.bs +++ b/css-layout-api/Overview.bs @@ -55,9 +55,6 @@ spec:css2; type:property; text:max-width
-urlPrefix: https://heycam.github.io/webidl/; type: dfn;
-    urlPrefix: #dfn-;
-        url: throw; text: thrown
 urlPrefix: https://tc39.github.io/ecma262/#sec-; type: dfn;
     text: constructor
     text: Construct
@@ -66,10 +63,6 @@ urlPrefix: https://tc39.github.io/ecma262/#sec-; type: dfn;
     url: terms-and-definitions-function; text: function
     urlPrefix: native-error-types-used-in-this-standard-
         text: TypeError
-urlPrefix: https://drafts.csswg.org/css-sizing/#; type: dfn
-    text: intrinsic sizes
-urlPrefix: https://drafts.csswg.org/css-break/#; type: dfn
-    text: fragmentation break
 urlPrefix: https://www.w3.org/TR/CSS21/; type:dfn
     urlPrefix: box.html#;
         url: box-dimensions; text: box model edges
@@ -216,7 +209,7 @@ The {{LayoutWorkletGlobalScope}} is the global execution context of the {{layout
 
 [Global=(Worklet,LayoutWorklet),Exposed=LayoutWorklet]
 interface LayoutWorkletGlobalScope : WorkletGlobalScope {
-    void registerLayout(DOMString name, VoidFunction layoutCtor);
+    undefined registerLayout(DOMString name, VoidFunction layoutCtor);
 };
 
@@ -1594,8 +1587,9 @@ dictionary FragmentResultOptions { BreakTokenOptions breakToken = null; }; -[Constructor(FragmentResultOptions)] +[Exposed=LayoutWorklet] interface FragmentResult { + constructor(optional FragmentResultOptions options = {}); readonly attribute double inlineSize; readonly attribute double blockSize; }; diff --git a/css-paint-api/EXPLAINER.md b/css-paint-api/EXPLAINER.md index f01f1e56..96a5402c 100644 --- a/css-paint-api/EXPLAINER.md +++ b/css-paint-api/EXPLAINER.md @@ -73,7 +73,7 @@ if ('paintWorklet' in CSS) { } ``` -See the worklets explainer for a more involed explaination of worklets. In short worklets: +See the worklets explainer for a more involved explaination of worklets. In short worklets: - Are similar to workers in that the script runs in a separate global script context. - A script inside a worklet has no DOM, Network, Database, etc access. - The global script context lifetime is not defined (you should expect the script context to be killed at any point). diff --git a/css-paint-api/Overview.bs b/css-paint-api/Overview.bs index f356e559..f7570b54 100644 --- a/css-paint-api/Overview.bs +++ b/css-paint-api/Overview.bs @@ -36,6 +36,9 @@ Ignored Terms: PaintWorklet
@@ -71,6 +74,8 @@ which are related to painting.
 
 The {{paintWorklet}}'s [=worklet global scope type=] is {{PaintWorkletGlobalScope}}.
 
+The {{paintWorklet}}'s worklet destination type is "paintworklet".
+
 
 partial namespace CSS {
     [SameObject] readonly attribute Worklet paintWorklet;
@@ -85,7 +90,7 @@ identical to the Window.{{Window/devicePixelRatio}} property.
 
 [Global=(Worklet,PaintWorklet),Exposed=PaintWorklet]
 interface PaintWorkletGlobalScope : WorkletGlobalScope {
-    void registerPaint(DOMString name, VoidFunction paintCtor);
+    undefined registerPaint(DOMString name, VoidFunction paintCtor);
     readonly attribute unrestricted double devicePixelRatio;
 };
 
@@ -142,7 +147,7 @@ by the paint function). It consists of: DOMStrings. - A input argument syntaxes which is a [=list=] of - parsed [[css-properties-values-api-1#supported-syntax-strings]]. + [=syntax definitions=]. - A PaintRenderingContext2DSettings object. @@ -198,15 +203,15 @@ called, the user agent must run the following steps: 10. If |inputArgumentsIterable| is not undefined, then set |inputArguments| to the result of [=converting=] |inputArgumentsIterable| to a sequence<DOMString>. If an - execption is thrown, rethrow the execption and abort all these steps. + exception is thrown, rethrow the exception and abort all these steps. 11. Let |inputArgumentSyntaxes| be an [=list/empty=] [=list=]. 12. [=list/For each=] |item| in |inputArguments| perform the following substeps: - 1. Let |parsedSyntax| be the result of parsing |item| according to the rules in - [[css-properties-values-api-1#supported-syntax-strings]]. If it fails to parse - [=throw=] a [=TypeError=] and abort all these steps. + 1. Attempt to [=consume a syntax definition=] from |item|. + If failure was returned, [=throw=] a [=TypeError=] and abort all these steps. + Otherwise, let |parsedSyntax| be the returned [=syntax definition=]. 2. [=list/Append=] |parsedSyntax| to |inputArgumentSyntaxes|. @@ -402,6 +407,7 @@ For interfaces which use the {{CanvasDrawImage}} mixin: {{CanvasDrawImage/drawImage}}. Note: This should eventually be moved to the canvas section of the HTML specification. +See Issue 819. Drawing an image {#drawing-an-image} ==================================== @@ -596,8 +602,8 @@ When the user agent wants to invoke a paint callback given |name|, |i [=computed value=]'s for properties listed in |inputProperties|. 8. Let |renderingContext| be the result of [=create a PaintRenderingContext2D object=] given: - - "width" - The width given by |concreteObjectSize|. - - "height" - The height given by |concreteObjectSize|. + - "width" - The width given by |snappedConcreteObjectSize|. + - "height" - The height given by |snappedConcreteObjectSize|. - "paintRenderingContext2DSettings" - The [=paint definition/PaintRenderingContext2DSettings object=] given by |definition|. @@ -991,23 +997,26 @@ There are no known security issues introduced by these features. Privacy Considerations {#privacy-considerations} ================================================ -There are no known privacy issues introduced by these features. +* The timing of paint callbacks can be used as a high-bandwidth channel for detecting "visited" state for links. + (details) + This is not a fundamentally new privacy leak, + as visited state leaks from many interactions, + but absent any further mitigations, + this is a particularly high-bandwidth channel of the information. + + No official mitigations are planned at this time, + as this privacy leak needs to be addressed more directly + to fix all such channels. Changes {#changes} ================== -Changes since the last publication: -
    -
  • - Adding converting the paint function to the WebIDL Function type, instead of just a raw - javascript function. - (Issue 743) +Changes since the 9 August 2018 CR publication: + +* Filtered the list of input properties to a paint worklet to be only known or custom properties. -
  • - Added a step to {{registerPaint()}} to filter out unsupported native CSS properties. These now -do not appear in the {{StylePropertyMapReadOnly}} provided to the paint function. - (Issue 523) -
+* Added alpha flag to {{PaintRenderingContext2D}} to control whether the rendering surface is forced opaque or allows transparency. -A Disposition of Comments is available. +* Fix definition for the size of the output bitmap: + > The size of the output bitmap is the concrete object size of the object it is rendering to size of the fragment it is rendering. diff --git a/css-paint-api/issues-cr-2018.bs b/css-paint-api/issues-cr-2018.bs new file mode 100644 index 00000000..65ee503f --- /dev/null +++ b/css-paint-api/issues-cr-2018.bs @@ -0,0 +1,57 @@ +Draft: https://www.w3.org/TR/2018/CR-css-paint-api-1-20180809/ +Title: CSS Painting API Level 1 +---- +Issue 1. +Summary: Broken LInks +From: Chris Lilley +Comment: https://github.com/w3c/css-houdini-drafts/issues/787 +Response: https://github.com/w3c/css-houdini-drafts/issues/787#issuecomment-411123499 +Closed: Accepted +Resolved: Editorial +---- +Issue 2. +Summary: CSS Paint API leaks browsing history +From: Deian Stefan +Comment: https://github.com/w3c/css-houdini-drafts/issues/791 +Response: https://github.com/w3c/css-houdini-drafts/issues/791#issuecomment-546460085 +Changes: https://github.com/w3c/css-houdini-drafts/commit/3c72275054d9d541e6526e2988567ad4d209f257 +Closed: Accepted +Resolved: Editor discretion +Verified: https://github.com/w3c/css-houdini-drafts/issues/791#issuecomment-546466424 +---- +Issue 3. +Summary: Typo: use snappedConcreteObjectSize in paint callback +From: Chris Harrelson +Comment: https://github.com/w3c/css-houdini-drafts/issues/800 +Changes: https://github.com/w3c/css-houdini-drafts/commit/735ea42edf09f8d69de1eddbc7e7691528f56963 +Closed: Accepted +Resolved: Editorial +---- +Issue 4. +Summary: Disconnect between css-paint-api-1 and HTML specs on CanvasImageSource +From: Alan Jeffrey +Comment: https://github.com/w3c/css-houdini-drafts/issues/819 +Response: https://github.com/w3c/css-houdini-drafts/issues/819#issuecomment-424360647 +Open: Waiting for HTML spec edits +---- +Issue 5. +Summary: Improve passing of large-scale data to PaintWorklet +From: John Wiesz +Comment: https://github.com/w3c/css-houdini-drafts/issues/872 +Response: https://github.com/w3c/css-houdini-drafts/issues/872#issuecomment-499903766 +Open: Waiting on edits to add new features to Properties an Values API +---- +Issue 6. +Summary: Cycle possibe using inputProperties() +From: Stephen McGruer +Comment: https://github.com/w3c/css-houdini-drafts/issues/877 +Response: https://github.com/w3c/css-houdini-drafts/issues/877#issuecomment-499922873 +Open: Needs Edits +---- +Issue 7. +Summary: Two-way communication between main thread and worklet +From: Samad Aghaei +Comment: https://github.com/w3c/css-houdini-drafts/issues/881 +Response: https://github.com/w3c/css-houdini-drafts/issues/881#issuecomment-546456591 +Closed: OutOfScope +---- diff --git a/css-paint-api/issues-cr-2018.html b/css-paint-api/issues-cr-2018.html new file mode 100644 index 00000000..9c7a0ccb --- /dev/null +++ b/css-paint-api/issues-cr-2018.html @@ -0,0 +1,128 @@ + + +CSS Painting API Level 1 Disposition of Comments for 2018-08-09 CR + + +

CSS Painting API Level 1 Disposition of Comments for 2018-08-09 CR

+ +

Dated Draft: https://www.w3.org/TR/2018/CR-css-paint-api-1-20180809/ + +

Editor's Draft: http://drafts.csswg.org/css-paint-api-1/ + +

The following color coding convention is used for comments:

+ +
    +
  • Accepted or Rejected and positive response +
  • Rejected and no response +
  • Rejected and negative response +
  • Deferred +
  • Out-of-Scope or Invalid and not verified +
+ +

Open issues are marked like this

+ +

An issue can be closed as Accepted, OutOfScope, +Invalid, Rejected, or Retracted. +Verified indicates commentor's acceptance of the response.

+
+Issue 1. #
+Summary:  Broken LInks
+From:     Chris Lilley
+Comment:  https://github.com/w3c/css-houdini-drafts/issues/787
+Response: https://github.com/w3c/css-houdini-drafts/issues/787#issuecomment-411123499
+Closed:   Accepted
+Resolved: Editorial
+
+Issue 2. #
+Summary:  CSS Paint API leaks browsing history
+From:     Deian Stefan
+Comment:  https://github.com/w3c/css-houdini-drafts/issues/791
+Response: https://github.com/w3c/css-houdini-drafts/issues/791#issuecomment-546460085
+Changes:  https://github.com/w3c/css-houdini-drafts/commit/3c72275054d9d541e6526e2988567ad4d209f257
+Closed:   Accepted
+Resolved: Editor discretion
+Verified: https://github.com/w3c/css-houdini-drafts/issues/791#issuecomment-546466424
+
+Issue 3. #
+Summary:  Typo: use snappedConcreteObjectSize in paint callback
+From:     Chris Harrelson
+Comment:  https://github.com/w3c/css-houdini-drafts/issues/800
+Changes:  https://github.com/w3c/css-houdini-drafts/commit/735ea42edf09f8d69de1eddbc7e7691528f56963
+Closed:   Accepted
+Resolved: Editorial
+
+Issue 4. #
+Summary:  Disconnect between css-paint-api-1 and HTML specs on CanvasImageSource
+From:     Alan Jeffrey
+Comment:  https://github.com/w3c/css-houdini-drafts/issues/819
+Response: https://github.com/w3c/css-houdini-drafts/issues/819#issuecomment-424360647
+Open:     Waiting for HTML spec edits
+
+Issue 5. #
+Summary:  Improve passing of large-scale data to PaintWorklet
+From:     John Wiesz
+Comment:  https://github.com/w3c/css-houdini-drafts/issues/872
+Response: https://github.com/w3c/css-houdini-drafts/issues/872#issuecomment-499903766
+Open:     Waiting on edits to add new features to Properties an Values API
+
+Issue 6. #
+Summary:  Cycle possibe using inputProperties()
+From:     Stephen McGruer
+Comment:  https://github.com/w3c/css-houdini-drafts/issues/877
+Response: https://github.com/w3c/css-houdini-drafts/issues/877#issuecomment-499922873
+Open:     Needs Edits
+
+Issue 7. #
+Summary:  Two-way communication between main thread and worklet
+From:     Samad Aghaei
+Comment:  https://github.com/w3c/css-houdini-drafts/issues/881
+Response: https://github.com/w3c/css-houdini-drafts/issues/881#issuecomment-546456591
+Closed:   OutOfScope
+ diff --git a/css-properties-values-api/Overview.bs b/css-properties-values-api/Overview.bs index 67cdd395..1caace06 100644 --- a/css-properties-values-api/Overview.bs +++ b/css-properties-values-api/Overview.bs @@ -23,24 +23,29 @@ Ignored Terms: construct, name map of inputs Ignored Vars: arguments, methodPropertyKey, inputStyleMap, workletGlobalScope Ignored Terms: WorkletGlobalContext Repository: w3c/css-houdini-drafts -
- -
-{
-	"css-paint-api": {
-		"title": "CSS Painting API"
-	},
-	"css-layout-api": {
-		"title": "CSS Layout API"
-	}
-}
+Markup Shorthands: css on, markdown on
 
Introduction {#intro} @@ -56,16 +61,540 @@ can only impact document layout or paint by being re-incorporated into the value of other properties via a var() reference. This specification extends [[css-variables]], allowing the registration of properties -that have a value type, an initial value, and a defined inheritance behaviour. +that have a value type, an initial value, and a defined inheritance behaviour, +via two methods: -This specification is complementary to [[css-paint-api]] and [[css-layout-api]], which +* A JS API, the {{registerProperty()}} method +* A CSS at-rule, the ''@property'' rule + +This specification is complementary to [[css-paint-api-1]] and [[css-layout-api-1]], which allow custom properties to directly impact paint and layout behaviours respectively. -Registering custom properties {#registering-custom-properties} -============================================================== + + + + +Registered Custom Properties {#behavior-of-custom-properties} +============================================================= + +A [=custom property=] can become a registered custom property, +making it act more like a UA-defined property: +giving it a syntax that's checked by the UA, +an initial value, +and a specific inheritance behavior. +This can be done by the ''@property'' rule, +or the {{registerProperty()}} JS function. + +A [=custom property=] is considered to be registered for a {{Document}} +if there is a valid ''@property'' rule +defined for its name +in one of the document's stylesheets, +or its name is [=map/contains|contained=] +in the document's {{[[registeredPropertySet]]}} slot +(that is, {{registerProperty()}} was called to register it). + +A [=registered custom property=] acts similarly to an unregistered [=custom property=], +except as defined below. + +Determining the Registration {#determining-registration} +-------------------------------------------------------- + +A [=registered custom property=] has a custom property registration +that contains all the data necessary to treat it like a real property. +It's a [=struct=] consisting of: + +* a property name (a [=custom property name string=]) +* a syntax (a [=syntax string=]) +* an inherit flag (a [=boolean=]) +* optionally, an initial value (a [=string=] which successfully [=CSS/parses=] according to the syntax) + +If the {{Document}}’s {{[[registeredPropertySet]]}} slot +[=set/contains=] a record with the [=custom property’s=] name, +the registration is that record. + +Otherwise, +if the {{Document}}’s active stylesheets contain at least one valid ''@property'' rule +representing a registration with the [=custom property’s=] name, +the last such one in document order is the registration. + +Otherwise there is no registration, +and the [=custom property=] is not a [=registered custom property=]. + +Parse-Time Behavior {#parsing-custom-properties} +------------------------------------------------ + +[=Registered custom properties=] parse exactly like unregistered [=custom properties=]; +almost anything is allowed. +The registered syntax of the property is not checked at parse time. + +Note: However, +the syntax is checked at computed-value time, +before substitution via ''var()''. +See [[#calculation-of-computed-values]]. + +
+ Why aren't custom properties syntax-checked? + + When parsing a page's CSS, + UAs commonly make a number of optimizations + to help with both speed and memory. + + One of those optimizations + is that they only store the properties that will actually have an effect; + they throw away invalid properties, + and if you write the same property multiple times in a single declaration block, + all but the last valid one will be thrown away. + (This is an important part of CSS's error-recovery + and forward-compatibility behavior.) + + This works fine if the syntax of a property never changes over the lifetime of a page. + If a custom property is registered, however, + it can change its syntax, + so that a property that was previously invalid + suddenly becomes valid. + + The only ways to handle this are to either store every declaration, + even those that were initially invalid + (increasing the memory cost of pages), + or to re-parse the entire page's CSS + with the new syntax rules + (increasing the processing cost of registering a custom property). + Neither of these are very desirable. + + Further, + UA-defined properties have their syntax determined + by the version of the UA the user is viewing the page with; + this is out of the page author's control, + which is the entire reason for CSS's error-recovery behavior + and the practice of writing multiple declarations for varying levels of support. + A custom property, on the other hand, + has its syntax controlled by the page author, + according to whatever stylesheet or script they've included in the page; + there's no unpredictability to be managed. + Throwing away syntax-violating custom properties + would thus only be, at best, a convenience for the page author, + not a necessity like for UA-defined properties. +
+ +[=Specified Value=]-Time Behavior {#specified-value} +---------------------------------------------------- + +Just like unregistered [=custom properties=], +all [=registered custom properties=], regardless of registered syntax, +accept the [=CSS-wide keywords=], +such as ''inherit'' or ''revert''. +Their behavior is defined in [[css-cascade-4#defaulting-keywords]]. + +[=Computed Value=]-Time Behavior {#calculation-of-computed-values} +------------------------------------------------------------------ + +The [=computed value=] of a [=registered custom property=] +is determined by the syntax of its [=registration=]. + +If the [=registration’s=] syntax is the [=universal syntax definition=], +the [=computed value=] is the same as for unregistered [=custom properties=] +(either the specified value with variables substituted, +or the [=guaranteed-invalid value=]). + +Otherwise, attempt to [=CSS/parse=] the property's value +according to its registered syntax. +If this fails, +the [=computed value=] is the [=guaranteed-invalid value=]. +If it succeeds, +the [=computed value=] depends on the specifics of the syntax: + +For "<length>", +"<length-percentage>", +"<angle>", +"<time>", +"<resolution>", +"<integer>", +"<number>", +and "<percentage>" values: + +* If the specified value is a [=dimension=] literal + (such as ''50em'' or ''.2s''), + the computed value is the same value, + but with the unit converted to the corresponding [=canonical unit=] + for the type of value. +* If the specified value is any other numeric literal + (such as ''5'' or ''20%''), + the computed value is as specified. + (In particular, percentages are never resolved against anything.) +* If the specified value is a function that evaluates to one of those types + (such as a [=math function=]), + the computed value is defined by that function. + +For "<color>" values, +the value is computed by [=resolving color values=]. + +For "<custom-ident>", ident, or "*" values, +the computed value is as specified. + +For "<url>" values, +the computed value is one of the following: + +* if the URL is a relative URL, + the computed value is the resolved absolute URL as described in [[!css3-values]]. +* otherwise, the computed value is as specified. + +
+ URL behavior examples +
+ Because URLs resolve against the base URL of the stylesheet they appear in, we can + end up with multiple relative URLs that resolve against different base URLs, even though + they appear in the same property. + + For example, suppose '--url-foo' and '--url-bar' are registered + custom properties with ''<url>'' syntax, and that we have a stylesheet at + /style/foo/foo.css: + +
+		div {
+			--url-foo: url("foo.png");
+		}
+		
+ + and another stylesheet at /style/bar/bar.css +
+		div {
+			--url-bar: url("bar.png");
+		}
+		
+ + and finally a document at /index.html: +
+		<link href="/style/foo/foo.css" rel="stylesheet" type="text/css">
+		<link href="/style/bar/bar.css" rel="stylesheet" type="text/css">
+		<div style="background-image: var(--url-foo), var(---url-bar);">
+		</div>
+		
+ + Here, the ''var(--url-foo)'' reference would produce a URL that resolves against + /style/foo, and the ''var(--url-bar)'' reference would produce a URL that resolves + against /style/bar. + + On the other hand, + if both '--url-foo' and '--url-bar' were unregistered, + they would substitute their literal values + (relative URLs) + into the /index.html stylesheet, + which would then resolve the URLs against /index.html instead. +
+
+ +For "<image>" values, +the computed value is the [=computed <image>=]. + +For "<transform-function>" and "<transform-list>" values, +the computed value is as specified but with all lengths resolved to their computed values. + +For values with [[#multipliers|multipliers]], +the computed value is a list of the computed values of the base type. + +For syntaxes specified with [[#combinator|the | combinator]], +the computed value is given by applying the computed-value rules +for the first clause that matches the value. + + +Animation Behavior {#animation-behavior-of-custom-properties} +------------------------------------------------------------- + +Note: As defined by [[css3-animations]] and [[css3-transitions]], it is possible to +specify animations and transitions that reference custom properties. + +When referenced by animations and transitions, +custom property values [=interpolate=] [=by computed value=], +in accordance with the type that they parsed as. + +Note: This implies that a list of values, +such as `+` or `#`, +will interpolate as a simple list, +matching up each component index-by-index, +and failing if the number of components doesn't match. + +As an exception to the above rule, +a value that parsed as a ``, +a ``, +or a `+` +instead interpolates as per the 'transform' property. + +Note: If, +for whatever reason, +a custom property is defined with a syntax of `#`, +this will thus first interpolate as a simple list, +and then each list item will interpolate as a 'transform' value. + +Note: Registering (or changing the registration) of a custom property +can change its computed value, +which can start or interrupt a CSS transition. + +Conditional Rules {#conditional-rules} +-------------------------------------- + +As stated in [[#parsing-custom-properties]], +both unregistered and [=registered=] [=custom properties=] +accept (almost) all possible values at parse-time. +[=Registered=] [=custom properties=] only apply their syntax at [=computed value=] time. + +So, all [=custom properties=], +regardless of whether they're [=registered=] or unregistered, +will test as "true" in an ''@supports'' rule, +so long as you don't violate the (very liberal) generic syntax for [=custom properties=]. + +
+ For example, + even if a custom property is registered + with syntax: "<color>";, + a rule like `@supports (--foo: 1em) {...}` + will still evaluate as true and apply those styles, + because the declaration does successfully parse as a valid property. +
+ + +Substitution via ''var()'' {#substitution} +------------------------------------------ + +Like unregistered custom properties, +the value of a registered custom property can be substituted into another value with the ''var()'' function. +However, registered custom properties substitute as their [[#calculation-of-computed-values|computed value]], +rather than the original token sequence used to produce that value. + +Any ''var()'' function that references a registered custom property +must be replaced with an equivalent token sequence, +which is equal to the token sequence that would have been produced +by [=serialize a CSS value|serializing=] the computed value, +and [[css-syntax-3#tokenization|tokenizing]] the resulting string. + +
+ Suppose that '--x' is registered with ''<length>'' syntax, + and that '--y'is an unregistered custom property. + +
+
+	div {
+		font-size: 10px;
+		--x: 8em;
+		--y: var(--x);
+	}
+	
+ + Because the computed value of '--x' (when serialized) is "80px", + the computed value of '--y' is + a <> with a value of "80" and unit "px". +
+ +### Fallbacks In ''var()'' References ### {#fallbacks-in-var-references} + +References to registered custom properties using the ''var()'' function may +provide a fallback. However, the fallback value must match the +syntax definition of the custom property being referenced, otherwise the +declaration is invalid at computed-value time. + +Note: This applies regardless of whether or not the fallback is being used. + + +### Dependency Cycles via Relative Units ### {#dependency-cycles} + +[=Registered custom properties=] follow the same rules for dependency cycle resolution +as unregistered [=custom properties=], +with the following additional constraints: + +For any registered custom property +with a <> or <> syntax component: + +* If the property contains any of the following units: + ''em'', ''ex'', ''cap'', ''ch'', ''ic'', ''lh''; + then add an edge between the property + and the ''font-size'' of the current element. +* If the property contains the ''lh'' unit, + add an edge between the property + and the ''line-height'' of the current element. +* If the property contains any of the following units: ''rem'', ''rlh''; + then add an edge between the property + and the 'font-size'' of the root element. +* If the property contains the ''rlh'' unit, + add an edge between the property + and the 'line-height'' of the root element. + +
+ For example, given this registration: + +
+	CSS.registerProperty({
+	  name: "--my-font-size",
+	  syntax: "<length>",
+	  initialValue: "0px",
+	  inherits: false
+	});
+	
+ + the following will produce a dependency cycle: + +
+	div {
+		--my-font-size: 10em;
+		font-size: var(--my-font-size);
+	}
+	
+ + and ''font-size'' will behave as if the value ''unset'' was specified. +
+ + + + + +The @property Rule {#at-property-rule} +================================================= + +The ''@property'' rule represents a [=custom property registration=] +directly in a stylesheet +without having to run any JS. +Valid ''@property'' rules result in a [=registered custom property=], +as if {{registerProperty()}} had been called with equivalent parameters. + +The syntax of ''@property'' is: + +
+	@property <> {
+		<>
+	}
+	
+ +A valid ''@property'' rule represents a [=custom property registration=], +with the property name being the serialization of the <> +in the rule's prelude. + +''@property'' rules require a 'syntax' and 'inherits' descriptor; +if either are missing, +the entire rule is invalid and must be ignored. +The 'initial-value' descriptor is optional +only if the syntax is the [=universal syntax definition=], +otherwise the descriptor is required; +if it's missing, the entire rule is invalid and must be ignored. + +Unknown descriptors are invalid and ignored, +but do not invalidate the ''@property'' rule. + +Note: As specified in [[#determining-registration]], +if multiple valid ''@property'' rules are defined for the same <>, +the last one in stylesheet order "wins". +A custom property registration from {{registerProperty()|CSS.registerProperty()}} +further wins over any ''@property'' rules +for the same <>. + +A ''@property'' is invalid if it occurs in a stylesheet inside of a [=shadow tree=], +and must be ignored. + +Issue(939): This will likely change in the future, +as the behavior of concept-defining at-rules in shadow trees +becomes more consistently defined. + +The 'syntax' Descriptor {#the-syntax-descriptor} +------------------------------------------------ + +
+	Name: syntax
+	Value: <>
+	For: @property
+	Initial: n/a (see prose)
+	
+ +Specifies the syntax of the [=custom property registration=] +represented by the ''@property'' rule, +controlling how the property's value is parsed at [=computed value=] time. + +The 'syntax' descriptor is required for the ''@property'' rule to be valid; +if it's missing, the ''@property'' rule is invalid. + +If the provided string is not a valid [=syntax string=] +(if it returns failure when [=consume a syntax definition=] is called on it), +the descriptor is invalid and must be ignored. + + +The 'inherits' Descriptor {#inherits-descriptor} +------------------------------------------------ + +
+	Name: inherits
+	Value: true | false
+	For: @property
+	Initial: n/a (see prose)
+	
+ +Specifies the inherit flag of the [=custom property registration=] +represented by the ''@property'' rule, +controlling whether or not the property inherits by default. + +The 'inherits' descriptor is required for the ''@property'' rule to be valid; +if it's missing, the ''@property'' rule is invalid. + + +The 'initial-value' Descriptor {#initial-value-descriptor} +---------------------------------------------------------- + +
+	Name: initial-value
+	Value: <>
+	For: @property
+	Initial: the [=guaranteed-invalid value=] (but see prose)
+	
+ +Specifies the initial value of the [=custom property registration=] +represented by the ''@property'' rule, +controlling the property’s [=initial value=]. + +If the value of the 'syntax' descriptor is the [=universal syntax definition=], +then the 'initial-value' descriptor is optional. +If omitted, the [=initial value=] of the property is the [=guaranteed-invalid value=]. + +Otherwise, +if the value of the 'syntax' descriptor is not the [=universal syntax definition=], +the following conditions must be met for the the ''@property'' rule to be valid: + + * The 'initial-value' descriptor must be present. + * The 'initial-value' descriptor's value must [=consume a syntax definition|parse successfully=] + according to the grammar specified by the [=syntax definition=]. + * The 'initial-value' must be [=computationally independent=]. + +If the above conditions are not met, the ''@property'' rule is invalid. + + + + +Registering Custom Properties in JS {#registering-custom-properties} +==================================================================== + +To register a custom property via JS, +the {{CSS}} object is extended with a {{registerProperty()}} method:
-dictionary PropertyDescriptor {
+dictionary PropertyDefinition {
 	required DOMString name;
 	         DOMString syntax       = "*";
 	required boolean   inherits;
@@ -73,41 +602,22 @@ dictionary PropertyDescriptor {
 };
 
 partial namespace CSS {
-	void registerProperty(PropertyDescriptor descriptor);
+	undefined registerProperty(PropertyDefinition definition);
 };
 
Additional, the {{Document}} object gains a new \[[registeredPropertySet]] private slot, which is a set of records that describe registered custom properties. -The {{PropertyDescriptor}} dictionary {#the-propertydescriptor-dictionary} --------------------------------------------------------------------------- - -A PropertyDescriptor dictionary represents author-specified configuration -options for a custom property. {{PropertyDescriptor}} dictionaries contain the -following members: - -: name -:: The name of the custom property being defined. - -: syntax -:: A string representing how this custom property is parsed. - -: inherits -:: True if this custom property should inherit down the DOM tree; False otherwise. - -: initialValue -:: The initial value of this custom property. - -The {{registerProperty()}} function {#the-registerproperty-function} +The {{registerProperty()}} Function {#the-registerproperty-function} -------------------------------------------------------------------- -The registerProperty(PropertyDescriptor descriptor) method +The registerProperty(PropertyDefinition definition) method registers a custom property according to the configuration options provided in -descriptor. +definition. When it is called, it executes the register a custom property algorithm, -passing the options in its descriptor argument +passing the options in its definition argument as arguments of the same names.
@@ -125,40 +635,21 @@ as arguments of the same names. associated Document's {{[[registeredPropertySet]]}} slot. - 2. Attempt to parse |name| - as a <>. - If this fails, + 2. If |name| is not a [=custom property name string=], throw a {{SyntaxError}} and exit this algorithm. - Otherwise, - let |parsed name| be the parsed value. - If |property set| - already contains an entry with |parsed name| as its property name + already contains an entry with |name| as its property name (compared codepoint-wise), throw an {{InvalidModificationError}} and exit this algorithm. - 3. If |syntax| is not present, - or is equal to "*" (U+002A ASTERISK), - let |parsed syntax| be undefined, - and skip to the next step of this algorithm. - - Otherwise, attempt to parse |syntax| - according to the rules in [[#supported-syntax-strings]]. - If it does not parse successfully, - throw a {{SyntaxError}}. - Otherwise, - let |parsed syntax| be the parsed syntax. - - Note: For example, a valid syntax string is something like "<length>", - or "<number>+"; - the allowed syntax is a subset of [[css-values-3#value-defs]]. - Future levels of this specification are expected to expand the complexity of allowed syntax strings, - allowing custom properties that more closely resemble the full breadth of what CSS properties allow. + 3. Attempt to [=consume a syntax definition=] from |syntax|. + If it returns failure, throw a {{SyntaxError}}. + Otherwise, let |syntax definition| be the returned syntax definition. - 4. If |parsed syntax| is undefined, + 4. If |syntax definition| is the universal syntax definition, and |initialValue| is not present, let |parsed initial value| be empty. This must be treated identically to the "default" initial value of custom properties, @@ -166,8 +657,8 @@ as arguments of the same names. Skip to the next step of this algorithm. Otherwise, - if |parsed syntax| is undefined, - parse |initialValue| as a <>. + if |syntax definition| is the universal syntax definition, + [=CSS/parse=] |initialValue| as a <>. If this fails, throw a {{SyntaxError}} and exit this algorithm. @@ -180,8 +671,8 @@ as arguments of the same names. and exit this algorithm. Otherwise, - parse {{PropertyDescriptor/initialValue}} - according to |parsed syntax|. + [=CSS/parse=] {{PropertyDefinition/initialValue}} + according to |syntax definition|. If this fails, throw a {{SyntaxError}} and exit this algorithm. @@ -193,12 +684,12 @@ as arguments of the same names. 5. Set |inherit flag| to the value of |inherits|. - 6. Let |registered property| be a record - with a property name of |parsed name|, - a syntax of |parsed syntax|, + 6. Let |registered property| be a [=struct=] + with a property name of |name|, + a syntax of |syntax definition|, an initial value of |parsed initial value|, and an inherit flag of |inherit flag|. - Add |registered property| + [=set/Append=] |registered property| to |property set|.
@@ -233,7 +724,7 @@ Registering a custom property must not affect the [=cascade=] i Regardless of what syntax is specified for a registered property, at parse time it is still parsed as normal for a [=custom property=], accepting nearly anything. -If the [=specified value=] for a registered [=custom property=] +If the [=specified value=] for a [=registered custom property=] violates the registered syntax, however, the property becomes [=invalid at computed-value time=] @@ -281,91 +772,54 @@ the property becomes [=invalid at computed-value time=] and becoming ''inherit''. -Supported syntax strings {#supported-syntax-strings} ----------------------------------------------------- +The {{PropertyDefinition}} Dictionary {#the-propertydefinition-dictionary} +-------------------------------------------------------------------------- -The following syntax strings are supported: - -: Primitive Terms -:: The following syntax strings are primitive terms that can be - combined as described below: - - : "<length>" - :: Any valid <> value - : "<number>" - :: <> values - : "<percentage>" - :: Any valid <> value - : "<length-percentage>" - :: Any valid <> or <> value, any valid <> - expression combining <> and <> components. - : "<color>" - :: Any valid <> value - : "<image>" - :: Any valid <> value - : "<url>" - :: Any valid <> value - : "<integer>" - :: Any valid <> value - : "<angle>" - :: Any valid <> value - : "<time>" - :: Any valid <
-Declared StylePropertyMap objects +Declared StylePropertyMap objects represent style property-value pairs embedded in a style rule or inline style, and are accessed via the styleMap attribute of {{CSSStyleRule}} objects, or the attributeStyleMap attribute -of objects implementing the {{ElementCSSInlineStyle}} interface -(such as {{Element}}s). +of objects implementing the {{ElementCSSInlineStyle}} interface mixin +(such as {{HTMLElement}}s). When constructed, the {{[[declarations]]}} internal slot for [=declared StylePropertyMap=] objects is initialized to contain an entry @@ -766,9 +767,9 @@ in the same order as the {{CSSStyleRule}} or inline style. ---------------------------------------------- -[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(sequence<CSSUnparsedSegment> members)] +[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSUnparsedValue : CSSStyleValue { + constructor(sequence<CSSUnparsedSegment> members); iterable<CSSUnparsedSegment>; readonly attribute unsigned long length; getter CSSUnparsedSegment (unsigned long index); @@ -777,9 +778,9 @@ interface CSSUnparsedValue : CSSStyleValue { typedef (USVString or CSSVariableReferenceValue) CSSUnparsedSegment; -[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(USVString variable, optional CSSUnparsedValue? fallback = null)] +[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSVariableReferenceValue { + constructor(USVString variable, optional CSSUnparsedValue? fallback = null); attribute USVString variable; readonly attribute CSSUnparsedValue? fallback; }; @@ -873,9 +874,9 @@ of the {{CSSUnparsedValue/[[tokens]]}} internal slot. {{CSSKeywordValue}} objects represent CSS keywords and other [=identifiers=]. <pre class='idl'> -[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(USVString value)] +[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSKeywordValue : CSSStyleValue { + constructor(USVString value); attribute USVString value; }; </pre> @@ -895,6 +896,23 @@ Any place that accepts a {{CSSKeywordValue}} also accepts a raw {{USVString}}, by using the following typedef and algorithm: +<pre class=idl> +typedef (DOMString or CSSKeywordValue) CSSKeywordish; +</pre> + +<div algorithm> + To <dfn export>rectify a keywordish value</dfn> |val|, + perform the following steps: + + 1. If |val| is a {{CSSKeywordValue}}, + return |val|. + + 2. If |val| is a {{DOMString}}, + return a new {{CSSKeywordValue}} + with its {{CSSKeywordValue/value}} internal slot + set to |val|. +</div> + <div algorithm="CSSKeywordValue.value"> The <dfn attribute for=CSSKeywordValue>value</dfn> attribute of a {{CSSKeywordValue}} |this| must, on setting a value |value|, @@ -1084,7 +1102,7 @@ The following are the arithmetic operations you can perform on dimensions: </div> <div algorithm> - To <dfn noexport for=CSSMath>negate</dfn> a {{CSSNumericValue}} |this|: + To <dfn export for=CSSMath local-lt="negate">negate a {{CSSNumericValue}}</dfn> |this|: 1. If |this| is a {{CSSMathNegate}} object, return |this|’s {{CSSMathNegate/value}} internal slot. @@ -1164,7 +1182,7 @@ The following are the arithmetic operations you can perform on dimensions: </div> <div algorithm> - To <dfn noexport for=CSSMath>invert</dfn> a {{CSSNumericValue}} |this|: + To <dfn export for=CSSMath local-lt="invert">invert a {{CSSNumericValue}}</dfn> |this|: 1. If |this| is a {{CSSMathInvert}} object, return |this|’s {{CSSMathInvert/value}} internal slot. @@ -1439,15 +1457,15 @@ The following are the arithmetic operations you can perform on dimensions: </div> <div algorithm="sum value"> - A <dfn>sum value</dfn> + A <dfn export for="CSSNumericValue">sum value</dfn> is an abstract representation of a {{CSSNumericValue}} as a sum of numbers with (possibly complex) units. Not all {{CSSNumericValue}}s can be expressed as a [=sum value=]. A [=sum value=] is a [=list=]. - Each entry in the list is a [=tuple=] of a <dfn for="sum value">value</dfn>, + Each entry in the list is a [=tuple=] of a <dfn export for="sum value">value</dfn>, which is a number, - and a <dfn for="sum value">unit map</dfn>, + and a <dfn export for="sum value">unit map</dfn>, which is a [=ordered map|map=] of units (strings) to powers (integers). <div class=example> @@ -1469,7 +1487,7 @@ The following are the arithmetic operations you can perform on dimensions: * ''calc(1px * 2deg)'' becomes `«(2, «["deg" → 1, "px" → 1]»)»` </div> - To <dfn lt="create a sum value|creating a sum value">create a sum value</dfn> from a {{CSSNumericValue}} |this|, + To <dfn export lt="create a sum value|creating a sum value">create a sum value</dfn> from a {{CSSNumericValue}} |this|, the steps differ based on |this|’s class: <dl class=switch> @@ -1612,35 +1630,35 @@ The following are the arithmetic operations you can perform on dimensions: 4. Return the [=list/item=] of |args| whose sole [=list/item=] has the largest [=sum value/value=]. </div> </dl> +</div> - <div algorithm> - To <dfn>create a type from a unit map</dfn> |unit map|: +<div algorithm> + To <dfn export>create a type from a unit map</dfn> |unit map|: - 1. Let |types| be an initially empty [=list=]. + 1. Let |types| be an initially empty [=list=]. - 2. [=map/For each=] |unit| → |power| in |unit map|: + 2. [=map/For each=] |unit| → |power| in |unit map|: - 1. Let |type| be the result of [=creating a type=] from |unit|. - 2. Set |type|’s sole [=map/value=] to |power|. - 3. [=list/Append=] |type| to |types|. + 1. Let |type| be the result of [=creating a type=] from |unit|. + 2. Set |type|’s sole [=map/value=] to |power|. + 3. [=list/Append=] |type| to |types|. - 3. Return the result of [=CSSNumericValue/multiplying=] all the [=list/items=] of |types|. - </div> + 3. Return the result of [=CSSNumericValue/multiplying=] all the [=list/items=] of |types|. +</div> - <div algorithm> - The <dfn>product of two unit maps</dfn> |units1| and |units2| - is the result given by the following steps: +<div algorithm> + The <dfn export>product of two unit maps</dfn> |units1| and |units2| + is the result given by the following steps: - 1. Let |result| be a copy of |units1|. + 1. Let |result| be a copy of |units1|. - 2. [=map/For each=] |unit| → |power| in |units2|: + 2. [=map/For each=] |unit| → |power| in |units2|: - 1. If |result|[|unit|] [=map/exists=], - increment |result|[|unit|] by |power|. - 3. Otherwise, set |result|[|unit|] to |power|. + 1. If |result|[|unit|] [=map/exists=], + increment |result|[|unit|] by |power|. + 3. Otherwise, set |result|[|unit|] to |power|. - 3. Return |result|. - </div> + 3. Return |result|. </div> The {{CSSNumericValue/parse()}} method allows a {{CSSNumericValue}} @@ -1800,7 +1818,7 @@ and to the CSS [=math functions=]. </div> <div algorithm> - To <dfn>apply the percent hint</dfn> |hint| to a |type|, + To <dfn export>apply the percent hint</dfn> |hint| to a |type|, perform the following steps: 1. If |type| doesn't [=map/contain=] |hint|, set |type|[|hint|] to 0. @@ -1917,9 +1935,9 @@ are represented as {{CSSUnitValue}}s. </div> <xmp class=idl> - [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(double value, USVString unit)] + [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSUnitValue : CSSNumericValue { + constructor(double value, USVString unit); attribute double value; readonly attribute USVString unit; }; @@ -2018,45 +2036,45 @@ interface CSSMathValue : CSSNumericValue { readonly attribute CSSMathOperator operator; }; -[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(CSSNumberish... args)] +[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSMathSum : CSSMathValue { + constructor(CSSNumberish... args); readonly attribute CSSNumericArray values; }; -[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(CSSNumberish... args)] +[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSMathProduct : CSSMathValue { + constructor(CSSNumberish... args); readonly attribute CSSNumericArray values; }; -[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(CSSNumberish arg)] +[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSMathNegate : CSSMathValue { + constructor(CSSNumberish arg); readonly attribute CSSNumericValue value; }; -[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(CSSNumberish arg)] +[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSMathInvert : CSSMathValue { + constructor(CSSNumberish arg); readonly attribute CSSNumericValue value; }; -[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(CSSNumberish... args)] +[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSMathMin : CSSMathValue { + constructor(CSSNumberish... args); readonly attribute CSSNumericArray values; }; -[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(CSSNumberish... args)] +[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSMathMax : CSSMathValue { + constructor(CSSNumberish... args); readonly attribute CSSNumericArray values; }; -[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(CSSNumberish min, CSSNumberish val, CSSNumberish max)] +[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSMathClamp : CSSMathValue { + constructor(CSSNumberish min, CSSNumberish val, CSSNumberish max); readonly attribute CSSNumericValue min; readonly attribute CSSNumericValue val; readonly attribute CSSNumericValue max; @@ -2216,7 +2234,7 @@ of all the "math" operations. The <dfn attribute for=CSSNumericArray>length</dfn> attribute of {{CSSNumericArray}} indicates how many {{CSSNumericValue}}s are contained within the {{CSSNumericArray}}. -The <dfn export for=CSSNumericArray>indexed getter</dfn> of {{CSSNumericArray}} retrieves the {{CSSNumericValue}} at the provided index. +The <dfn export for=CSSNumericArray>[=/indexed property getter=]</dfn> of {{CSSNumericArray}} retrieves the {{CSSNumericValue}} at the provided index. <!-- ██ ██ ██ ██ ██ ██ ██████ ██████ ██████ @@ -2321,9 +2339,9 @@ They "contain" one or more {{CSSTransformComponent}}s, which represent individual <<transform-function>> values. <xmp class='idl'> -[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(sequence<CSSTransformComponent> transforms)] +[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSTransformValue : CSSStyleValue { + constructor(sequence<CSSTransformComponent> transforms); iterable<CSSTransformComponent>; readonly attribute unsigned long length; getter CSSTransformComponent (unsigned long index); @@ -2421,63 +2439,63 @@ This list is the object's [=values to iterate over=]. DOMMatrix toMatrix(); }; - [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(CSSNumericValue x, CSSNumericValue y, optional CSSNumericValue z)] + [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSTranslate : CSSTransformComponent { + constructor(CSSNumericValue x, CSSNumericValue y, optional CSSNumericValue z); attribute CSSNumericValue x; attribute CSSNumericValue y; attribute CSSNumericValue z; }; - [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(CSSNumericValue angle), - Constructor(CSSNumberish x, CSSNumberish y, CSSNumberish z, CSSNumericValue angle)] + [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSRotate : CSSTransformComponent { + constructor(CSSNumericValue angle); + constructor(CSSNumberish x, CSSNumberish y, CSSNumberish z, CSSNumericValue angle); attribute CSSNumberish x; attribute CSSNumberish y; attribute CSSNumberish z; attribute CSSNumericValue angle; }; - [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(CSSNumberish x, CSSNumberish y, optional CSSNumberish z)] + [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSScale : CSSTransformComponent { + constructor(CSSNumberish x, CSSNumberish y, optional CSSNumberish z); attribute CSSNumberish x; attribute CSSNumberish y; attribute CSSNumberish z; }; - [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(CSSNumericValue ax, CSSNumericValue ay)] + [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSSkew : CSSTransformComponent { + constructor(CSSNumericValue ax, CSSNumericValue ay); attribute CSSNumericValue ax; attribute CSSNumericValue ay; }; - [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(CSSNumericValue ax)] + [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSSkewX : CSSTransformComponent { + constructor(CSSNumericValue ax); attribute CSSNumericValue ax; }; - [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(CSSNumericValue ay)] + [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSSkewY : CSSTransformComponent { + constructor(CSSNumericValue ay); attribute CSSNumericValue ay; }; /* Note that skew(x,y) is *not* the same as skewX(x) skewY(y), thus the separate interfaces for all three. */ - [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(CSSNumericValue length)] + [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSPerspective : CSSTransformComponent { + constructor(CSSNumericValue length); attribute CSSNumericValue length; }; - [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet), - Constructor(DOMMatrixReadOnly matrix, optional CSSMatrixComponentOptions options)] + [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] interface CSSMatrixComponent : CSSTransformComponent { + constructor(DOMMatrixReadOnly matrix, optional CSSMatrixComponentOptions options = {}); attribute DOMMatrix matrix; }; @@ -2845,17 +2863,17 @@ has the same behavior as in normal CSS. {{CSSStyleValue}} Reification {#reify-stylevalue} =========================================================== -This section describes how Typed OM objects are constructed from [=underlying values=], +This section describes how Typed OM objects are constructed from [=internal representations=], a process called <dfn export for=CSS lt="reify | reification">reification</dfn>. Some general principles apply to all [=reification=], and so aren't stated in each individual instance: -* If an [=underlying value=] is from a [=list-valued=] property, +* If an [=internal representation=] is from a [=list-valued=] property, this list defines how to reify a single iteration of the property; multiple iterations are reflected by returning multiple values from {{StylePropertyMap}}.{{getAll()}}. -* If an [=underlying value=] contains a ''var()'' reference, +* If an [=internal representation=] contains a ''var()'' reference, then it is reified by [=reify a list of component values|reifying a list of component values=], regardless of what property it is for. @@ -2871,8 +2889,8 @@ for both specified and computed values. [=reify a list of component values=] from the value, and return the result. -: registered [=custom properties=] [[css-properties-values-api-1]] -:: Issue: Should be defined by the Props & Values spec +: registered [=custom properties=] +:: Reified as described by [[css-properties-values-api-1#css-style-value-reification]]. : <a>align-content</a> :: @@ -4615,10 +4633,12 @@ for both specified and computed values. Unrepresentable Values {#reify-failure} --------------------------------------- -Not all [=underlying values=] are simple enough to be [=reified=] +Not all [=internal representations=] are simple enough to be [=reified=] with the current set of {{CSSStyleValue}} subclasses. When this is the case, -the property is [=reified as a CSSStyleValue=]. +the property is [=reified as a CSSStyleValue=] for a particular property, +ensuring that it can be used as a value for that property, +and nothing else. <div algorithm> To <dfn export lt="reify as a CSSStyleValue | reified as a CSSStyleValue">reify as a CSSStyleValue</dfn> diff --git a/css-typed-om/README.md b/css-typed-om/README.md index 76eb0c84..af829a3b 100644 --- a/css-typed-om/README.md +++ b/css-typed-om/README.md @@ -22,7 +22,7 @@ We get them of `StylePropertyMaps` on elements. To get the specified height from an element we would use the following JS: ``` -element.attributeStyleMap.get('height'); // returns a string +element.attributeStyleMap.get('height'); // returns a CSSUnitValue ``` Similarly to get the computed value of a CSS property we would do the following: @@ -86,10 +86,6 @@ The class can be broken down into two subclasses: [CSSTransformValue](https://drafts.css-houdini.org/css-typed-om-1/#csstransformvalue) objects represent via [CSSTransformComponents](https://drafts.css-houdini.org/css-typed-om-1/#csstransformcomponent) values, the different functions used by the `transform` property. -#### CSSPositionValue - -[CSSPositionValue](https://drafts.css-houdini.org/css-typed-om-1/#positionvalue-objects) help express the position of an object (e.g. background image) inside a positioning area (e.g. a div). - #### CSSResourceValue [CSSResourceValue](https://drafts.css-houdini.org/css-typed-om-1/#resourcevalue-objects) objects represent CSS values that require an asynchronous network fetch. Hence, they also describe the status the resource is in. Properties with image values (e.g. `background-image`), are represented by [CSSImageValues](https://drafts.css-houdini.org/css-typed-om-1/#cssimagevalue) diff --git a/font-metrics-api/Overview.bs b/font-metrics-api/Overview.bs index 9b1e54ab..4c607ddd 100644 --- a/font-metrics-api/Overview.bs +++ b/font-metrics-api/Overview.bs @@ -55,9 +55,10 @@ those that are passed in as a part of the styleMap. Document styles do not apply ---------------------------- <pre class='idl'> +[Exposed=Window] interface FontMetrics { readonly attribute double width; - readonly attribute sequence&lt;double> advances; + readonly attribute FrozenArray&lt;double> advances; readonly attribute double boundingBoxLeft; readonly attribute double boundingBoxRight; @@ -71,8 +72,8 @@ interface FontMetrics { readonly attribute double fontBoundingBoxDescent; readonly attribute Baseline dominantBaseline; - readonly attribute sequence&lt;Baseline> baselines; - readonly attribute sequence&lt;Font> fonts; + readonly attribute FrozenArray&lt;Baseline> baselines; + readonly attribute FrozenArray&lt;Font> fonts; }; </pre> @@ -166,6 +167,7 @@ List of all {{Baseline}}s for the given text. ---------------------------- <pre class='idl'> +[Exposed=Window] interface Baseline { readonly attribute DOMString name; readonly attribute double value; @@ -188,6 +190,7 @@ Positive numbers indicating a distance going down from the ---------------------------- <pre class='idl'> +[Exposed=Window] interface Font { readonly attribute DOMString name; readonly attribute unsigned long glyphsRendered; diff --git a/worklets/Overview.bs b/worklets/Overview.bs index 3704029e..5601ca4f 100644 --- a/worklets/Overview.bs +++ b/worklets/Overview.bs @@ -4,7 +4,7 @@ Status: ED Group: houdini ED: https://drafts.css-houdini.org/worklets/ TR: http://www.w3.org/TR/worklets-1/ -Previous Version: http://www.w3.org/TR/2016/WD-worklets-1-20160607/ +Previous Version: https://www.w3.org/TR/2020/WD-worklets-1-20200908/ Shortname: worklets Level: 1 Abstract: This specification defines an API for running scripts in stages of the rendering pipeline independent of the main javascript execution environment. @@ -30,15 +30,18 @@ Editor: Ian Kilpatrick, ikilpatrick@chromium.org, w3cid 73001 spec:fetch; type:dfn; for:/; text:fetch spec:html; type:dfn; for:/; text:browsing context spec:html; type:interface; for:/; text:Document -spec:html; type:dfn; for:environment settings object; - text: global object - text: https state - text: referrer policy -spec:html; type:dfn; for:environment; text:id spec:webidl; type:dfn; for:interface; text:inherit +spec:url; type:dfn; text:url; for:url +spec:html; type:dfn; for:/; text:event loop </pre> <pre class="anchors"> +urlPrefix: https://fetch.spec.whatwg.org/; type: dfn; + urlPrefix: #concept-; + text: fetch +urlPrefix: https://html.spec.whatwg.org/multipage/browsers.html; type: dfn; + text: effective script origin + url: #origin-2; text: origin urlPrefix: http://heycam.github.io/webidl/; type: dfn; urlPrefix: #es-; text: invoking callback functions @@ -209,28 +212,26 @@ Note: When a user agent is to <dfn export>create a WorkletGlobalScope</dfn>, given |workletGlobalScopeType|, |moduleResponsesMap|, and |outsideSettings|, it <em>must</em> run the following steps: - 1. Create the <a>worklet global scope execution environment</a> and run the rest of these steps - in that context. + 1. Let |agent| be the result of [=obtain a worklet agent|obtaining a worklet agent=] given + |outsideSettings|. This agent corresponds to the a <a>worklet global scope execution + environment</a>. Run the rest of these steps in that context. - 2. Call the JavaScript <a>InitializeHostDefinedRealm</a> abstract operation with the following - customizations: + 2. Let |realmExecutionContext| be the result of + [=create a new JavaScript realm|creating a new JavaScript realm=] given |agent| and + the following customizations: - For the global object, create a new |workletGlobalScopeType| object. Let |workletGlobalScope| be the created object. - - Let |realmExecutionContext| be the created JavaScript execution context. - 3. Let |insideSettings| be the result of <a>set up a worklet environment settings object</a> given |realmExecutionContext|, and |outsideSettings|. - 4. Associate the |insideSettings| with |workletGlobalScope|. - - 5. Set |workletGlobalScope|'s <a>owner document</a> to |outsideSettings|'s <a>responsible + 4. Set |workletGlobalScope|'s <a>owner document</a> to |outsideSettings|'s <a>responsible document</a>. - 6. Invoke the <a>initialize a global object's CSP list</a> algorithm given |workletGlobalScope|. + 5. Invoke the <a>initialize a global object's CSP list</a> algorithm given |workletGlobalScope|. - 7. For each |entry| in the given |moduleResponsesMap| (in insertion order), run the following + 6. For each |entry| in the given |moduleResponsesMap| (in insertion order), run the following substeps: 1. Let |moduleURLRecord| be |entry|'s key. @@ -254,64 +255,55 @@ When a user agent is to <dfn export>create a WorkletGlobalScope</dfn>, given |wo <div algorithm> When a user agent is to <dfn export>set up a worklet environment settings object</dfn>, given a |executionContext|, and |outsideSettings|, it must run the following steps: - 1. Let |inheritedResponsibleBrowsingContext| be |outsideSettings|'s <a>responsible browsing - context</a>. + 1. Let |origin| be a unique <a>opaque origin</a>. 2. Let |inheritedAPIBaseURL| be |outsideSettings|'s <a>API base URL</a>. - 3. Let |origin| be a unique <a>opaque origin</a>. - - 4. Let |inheritedHTTPSState| be |outsideSettings|'s <a>HTTPS state</a>. + 3. Let |inheritedReferrerPolicy| be |outsideSettings|'s <a for="environment settings object">referrer policy</a>. - 5. Let |inheritedReferrerPolicy| be |outsideSettings|'s <a>referrer policy</a>. + 4. Let |inheritedEmbedderPolicy| be |outsideSettings|'s <a for="environment settings object">embedder policy</a>. - 6. Let |workletEventLoop| be a newly created <a>event loop</a>. + 5. Let |realm| be the value of |executionContext|'s Realm component. - 7. Let |realm| be the value of |executionContext|'s Realm component. + 6. Let |workletGlobalScope| be |realm|'s <a for=Realm>global object</a>. - 8. Let |workletGlobalScope| be |realm|'s <a>global object</a>. - - 9. Let |settingsObject| be a new <a>environment settings object</a> whose algorithms are defined + 7. Let |settingsObject| be a new <a>environment settings object</a> whose algorithms are defined as follows: - : The <a>realm execution context</a> + : The <a for="environment settings object">realm execution context</a> :: Return |executionContext|. - : The <a>module map</a>. + : The <a for="environment settings object">module map</a>. :: Return |workletGlobalScope|'s <a for=WorkletGlobalScope>module map</a>. - : The <a>responsible browsing context</a> - :: Return |inheritedResponsibleBrowsingContext|. - - : The <a>responsible event loop</a> - :: Return |workletEventLoop|. + : The <a for="environment settings object">responsible document</a> + :: Not applicable. - : The <a>responsible document</a> - :: Not applicable (the <a>responsible event loop</a> is not a <a>browsing context</a> - <a>event loop</a>). - - : The <a>API URL character encoding</a> + : The <a for="environment settings object">API URL character encoding</a> :: Return <a>UTF-8</a>. - : The <a>API base URL</a> + : The <a for="environment settings object">API base URL</a> :: Return |inheritedAPIBaseURL|. : The <a for="environment settings object">origin</a> :: Return |origin|. - : The <a>HTTPS state</a> - :: Return |inheritedHTTPSState|. - - : The <a>referrer policy</a> + : The <a for="environment settings object">referrer policy</a> :: Return |inheritedReferrerPolicy|. - 10. Set |settingsObject|'s <a>id</a> to a new unique opaque string, |settingsObject|'s - <a>creation URL</a> to |inheritedAPIBaseURL|, |settingsObject|'s <a>target browsing - context</a> to null, and |settingsObject|'s <a>active service worker</a> to null. + : The <a for="environment settings object">embedder policy</a> + :: Return |inheritedEmbedderPolicy|. + + 8. Set |settingsObject|'s <a for=environment>id</a> to a new unique opaque string, + <a for=environment>creation URL</a> to |inheritedAPIBaseURL|, + <a for=environment>top-level creation URL</a> to null, + <a for=environment>top-level origin</a> to |outsideSettings|'s <a for=environment>top-level origin</a>, + <a for=environment>target browsing context</a> to null, and + <a for=environment>active service worker</a> to null. - 11. Set |realm|'s \[[HostDefined]] field to |settingsObject|. + 9. Set |realm|'s \[[HostDefined]] field to |settingsObject|. - 12. Return |settingsObject|. + 10. Return |settingsObject|. </div> Issue: Merge this with https://html.spec.whatwg.org/multipage/workers.html#set-up-a-worker-environment-settings-object @@ -324,8 +316,9 @@ The {{Worklet}} object provides the capability to add module scripts into its as {{WorkletGlobalScope}}s and invoke their methods. <pre class='idl'> +[Exposed=Window] interface Worklet { - [NewObject] Promise&lt;void> addModule(USVString moduleURL, optional WorkletOptions options); + [NewObject] Promise&lt;undefined> addModule(USVString moduleURL, optional WorkletOptions options = {}); }; dictionary WorkletOptions { @@ -341,6 +334,9 @@ Note: As an example the <a>worklet global scope type</a> might be a {{PaintWorkl A {{Worklet}} has a list of the <dfn export>worklet's WorkletGlobalScopes</dfn>. Initially this list is empty; it is populated when the user agent chooses to create its {{WorkletGlobalScope}}. +A {{Worklet}} has a <dfn export>worklet destination type</dfn>. This is used for setting the <a +for=request>destination</a> requests from <a>fetch a module worker script graph</a>. + A {{Worklet}} has a <dfn>module responses map</dfn>. This is a ordered map of module URLs to values that are a <a>fetch</a> responses. The map's entries are ordered based on their insertion order. Access to this map should be thread-safe. @@ -386,7 +382,9 @@ When the <dfn method for=Worklet>addModule(|moduleURL|, |options|)</dfn> method 9. Let |workletGlobalScopeType| be |worklet|'s <a>worklet global scope type</a>. - 10. If the <a>worklet's WorkletGlobalScopes</a> is empty, run the following steps: + 10. Let |destination| be |worklet|'s <a>worklet destination type</a>. + + 11. If the <a>worklet's WorkletGlobalScopes</a> is empty, run the following steps: 1. <a>Create a WorkletGlobalScope</a> given |workletGlobalScopeType|, |moduleResponsesMap|, and |outsideSettings|. @@ -401,14 +399,14 @@ When the <dfn method for=Worklet>addModule(|moduleURL|, |options|)</dfn> method Wait for this step to complete before continuing. - 11. Let |pendingTaskStruct| be a new <a>pending tasks struct</a> with <a + 12. Let |pendingTaskStruct| be a new <a>pending tasks struct</a> with <a for="pending tasks struct">counter</a> initialized to the length of <a>worklet's WorkletGlobalScopes</a>. - 12. For each |workletGlobalScope| in the <a>worklet's WorkletGlobalScopes</a>, <a>queue a + 13. For each |workletGlobalScope| in the <a>worklet's WorkletGlobalScopes</a>, <a>queue a task</a> on the |workletGlobalScope| to <a>fetch and invoke a worklet script</a> given - |workletGlobalScope|, |moduleURLRecord|, |moduleResponsesMap|, |credentialOptions|, - |outsideSettings|, |pendingTaskStruct|, and |promise|. + |workletGlobalScope|, |moduleURLRecord|, |destination|, |moduleResponsesMap|, + |credentialOptions|, |outsideSettings|, |pendingTaskStruct|, and |promise|. Note: The rejecting and resolving of the |promise| occurs within the <a>fetch and invoke a worklet script</a> algorithm. @@ -416,7 +414,7 @@ When the <dfn method for=Worklet>addModule(|moduleURL|, |options|)</dfn> method <div algorithm> When the user agent is to <dfn>fetch and invoke a worklet script</dfn> given |workletGlobalScope|, -|moduleURLRecord|, |moduleResponsesMap|, |credentialOptions|, |outsideSettings|, +|moduleURLRecord|, |destination|, |moduleResponsesMap|, |credentialOptions|, |outsideSettings|, |pendingTaskStruct|, and |promise|, the user agent <em>must</em> run the following steps: Note: This algorithm is to be run within the <a>worklet global scope execution environment</a>. @@ -425,8 +423,8 @@ When the user agent is to <dfn>fetch and invoke a worklet script</dfn> given |wo object</a>. 2. Let |script| by the result of <a>fetch a worklet script</a> given |moduleURLRecord|, - |moduleResponsesMap|, |credentialOptions|, |outsideSettings|, and |insideSettings| when it - asynchronously completes. + |destination|, |moduleResponsesMap|, |credentialOptions|, |outsideSettings|, and + |insideSettings| when it asynchronously completes. 3. If |script| is null, then <a>queue a task</a> on |outsideSettings|'s <a>responsible event loop</a> to run these steps: @@ -438,9 +436,20 @@ When the user agent is to <dfn>fetch and invoke a worklet script</dfn> given |wo 2. Reject |promise| with an "{{AbortError}}" {{DOMException}}. - 4. <a>Run a module script</a> given |script|. + 4. If |script|'s <a>error to rethrow</a> is not null, then <a>queue a task</a> on + |outsideSettings|'s <a>responsible event loop</a> given |script|'s <a>error to rethrow</a> + to run these steps: - 5. <a>Queue a task</a> on |outsideSettings|'s <a>responsible event loop</a> to run these steps: + 1. If |pendingTaskStruct|'s <a for="pending tasks struct">counter</a> is not <b>-1</b>, then + run these steps: + + 1. Set |pendingTaskStruct|'s <a for="pending tasks struct">counter</a> to <b>-1</b>. + + 2. Reject |promise| with <a>error to rethrow</a>. + + 5. <a>Run a module script</a> given |script|. + + 6. <a>Queue a task</a> on |outsideSettings|'s <a>responsible event loop</a> to run these steps: 1. If |pendingTaskStruct|'s <a for="pending tasks struct">counter</a> is not <b>-1</b>, then run these steps: @@ -453,14 +462,14 @@ When the user agent is to <dfn>fetch and invoke a worklet script</dfn> given |wo </div> <div algorithm> -When the user agent is to <dfn>fetch a worklet script</dfn> given |moduleURLRecord|, +When the user agent is to <dfn>fetch a worklet script</dfn> given |moduleURLRecord|, |destination|, |moduleResponsesMap|, |credentialOptions|, |outsideSettings|, and |insideSettings|, the user agent <em>must</em> run the following steps: Note: This algorithm is to be run within the <a>worklet global scope execution environment</a>. -1. <a>Fetch a module worker script graph</a> given |moduleURLRecord|, |outsideSettings|, "script", - |credentialOptions|, and |insideSettings|. +1. <a>Fetch a module worker script graph</a> given |moduleURLRecord|, |outsideSettings|, + |destination|, |credentialOptions|, and |insideSettings|. To <a>perform the fetch</a> given |request|, perform the following steps: @@ -553,8 +562,6 @@ For these examples we'll use a fake worklet on window. </pre> <pre class='idl'> - callback Function = any (any... arguments); - [Global=(Worklet,FakeWorklet),Exposed=FakeWorklet] interface FakeWorkletGlobalScope : WorkletGlobalScope { void registerAnArbitaryClass(DOMString type, Function classConstructor); @@ -621,4 +628,3 @@ could follow the following steps: Arguments=["true"]). 5. Return |result|. -