diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE
new file mode 100644
index 00000000..a9cdee70
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE
@@ -0,0 +1,2 @@
+* please tag the issue title with the spec's shortname, like `[css-foo]`
+* please link to the spec section you're talking about, or at least the spec
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..04f41dc4
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,3 @@
+# Code of Conduct
+
+All documentation, code and communication under this repository are covered by the [W3C Code of Ethics and Professional Conduct](https://www.w3.org/Consortium/cepc/).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..6e204604
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,24 @@
+# Cascading Style Sheets (CSS) Working Group
+
+Contributions to this repository are intended to become part of Recommendation-track documents governed by the
+[W3C Patent Policy](https://www.w3.org/Consortium/Patent-Policy-20040205/) and
+[Software and Document License](https://www.w3.org/Consortium/Legal/copyright-software). To make substantive contributions to specifications, you must either participate
+in the relevant W3C Working Group or make a non-member patent licensing commitment.
+
+If you are not the sole contributor to a contribution (pull request), please identify all
+contributors in the pull request comment.
+
+To add a contributor (other than yourself, that's automatic), mark them one per line as follows:
+
+```
++@github_username
+```
+
+If you added a contributor by mistake, you can remove them in a comment with:
+
+```
+-@github_username
+```
+
+If you are making a pull request on behalf of someone else but you had no part in designing the
+feature, you can remove yourself with the above syntax.
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 00000000..0f7c218c
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,4 @@
+All documents in this Repository are licensed by contributors
+under the
+[W3C Software and Document License](https://www.w3.org/Consortium/Legal/copyright-software).
+
diff --git a/README.markdown b/README.markdown
index dc1b3b40..68254b6e 100755
--- a/README.markdown
+++ b/README.markdown
@@ -1,9 +1,22 @@
### [CSS-TAG Houdini Task Force](https://wiki.css-houdini.org/) Specifications
-This is the repository containing all the [CSS/TAG Houdini Task Force specifications](https://drafts.css-houdini.org/).
+This is the repository containing the [CSS/TAG Houdini Task Force specifications](https://drafts.css-houdini.org/).
In addition to this git repository, a Mercurial mirror is maintained at `https://hg.css-houdini.org/drafts`, if for whatever reason you prefer Mercurial.
Specification issues are raised and discussed in GitHub Issues in this repository.
We also maintain the [public-houdini mailing list](http://lists.w3.org/Archives/Public/public-houdini/) for general-interest topics.
+
+New specifications are generally first incubated in the WICG, in particular:
+- [Animation Worklet](https://github.com/WICG/animation-worklet)
+
+# Tests
+
+For normative changes, a corresponding
+[web-platform-tests](https://github.com/web-platform-tests/wpt) PR is highly appreciated. Typically,
+both PRs will be merged at the same time. Note that a test change that contradicts the spec should
+not be merged before the corresponding spec change. If testing is not practical, please explain why
+and if appropriate [file an issue](https://github.com/web-platform-tests/wpt/issues/new) to follow
+up later. Add the `type:untestable` or `type:missing-coverage` label as appropriate.
+
diff --git a/box-tree-api/Overview.bs b/box-tree-api/Overview.bs
index 572a9ef6..2b71cd5b 100644
--- a/box-tree-api/Overview.bs
+++ b/box-tree-api/Overview.bs
@@ -6,15 +6,15 @@ ED: https://drafts.css-houdini.org/box-tree-api-1/
Shortname: box-tree-api
Level: 1
Abstract: Layout as described by CSS produces boxes that control how content is displayed and positioned. This specification describes an API for accessing information about these boxes.
-Editor: Tab Atkins, jackalmage@gmail.com
-Editor: Peter Linss, peter.linss@hp.com
-Editor: Ian Kilpatrick, ikilpatrick@chromium.org
-Editor: Rossen Atanassov, rossen.atanassov@microsoft.com
-Editor: Shane Stephens, shanestephens@google.com
+Editor: Tab Atkins-Bittner, Google, http://xanthir.com/contact/, w3cid 42199
+Editor: Peter Linss, peter.linss@hp.com, w3cid 4200
+Editor: Ian Kilpatrick, ikilpatrick@chromium.org, w3cid 73001
+Editor: Rossen Atanassov, rossen.atanassov@microsoft.com, w3cid 49885
+Former Editor: Shane Stephens, shanestephens@google.com, w3cid 47691
@@ -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;
@@ -79,7 +80,7 @@ interface DeadFragmentInformation {
readonly attribute double top;
readonly attribute double left;
readonly attribute boolean isOverflowed;
- readonly attribute sequence<DeadFragmentInformation>? children;
+ readonly attribute FrozenArray<DeadFragmentInformation>? children;
readonly attribute DeadFragmentInformation? nextSibling;
readonly attribute DeadFragmentInformation? previousSibling;
readonly attribute DeadFragmentInformation? nextInBox;
diff --git a/composited-scrolling-and-animation/Explainer.md b/composited-scrolling-and-animation/Explainer.md
deleted file mode 100644
index a1a9c1b9..00000000
--- a/composited-scrolling-and-animation/Explainer.md
+++ /dev/null
@@ -1,182 +0,0 @@
-# Compositor Worker explained
-
-## tl;dr
-
-requestAnimationFrame on the "compositor" thread.
-
-## So, what's the problem here?
-
-Scripted effects (driven by requestAnimationFrame, response to onscroll, etc)
-are flexible and powerful, but are subject to main thread jank (where jank
-refers to unpredictable interruptions in the rate that animations are serviced
-on a thread due to other, unrelated work on that thread). And although script
-can run on a web worker, DOM access and CSS property animations are not
-permitted. Despite their susceptibility to main thread jank, main thread
-animations are widely used; they're the only way to create common effects such
-as position-sticky, image carousels, custom scroll animations, iphone-style
-contact lists, and physics-based animations. In a perfect world, the main thread
-would always be responsive enough to guarantee that an animation callback would
-be serviced every frame. In reality, this is often extremely hard to achieve,
-both for user agents and developers of large sites composed of disparate, 3rd
-party components not under the author's control. The result is a lot of janky
-pages.
-
-Why can't we update CSS properties from another thread? Updating CSS properties
-could effect a style recalc or a layout and those operations must happen on the
-main thread. That said, there are certain 'layout-free' properties that can be
-modified without these side effects. These properties include transform,
-opacity, background color, background position, etc. Updating the scroll offset
-is also layout-free. Clearly identifying these layout-free properties and
-allowing them to be animated from a separate scheduling domain would provide a
-simple and powerful way to achieve smooth animations.
-
-Indeed, all major browsers have had the ability to asynchronously update
-layout-free properties for years. Some examples include WebAnimations, CSS
-animations, and transitions involving layout-free properties as well as
-compositor driven scrolling. A few new approaches are in the works such as
-position:sticky and snap points, but it's unfortunate to have to wait for specs
-and consistent browser implementations to get these new "acclerated" effects.
-
-## Goal
-
-Allow new accelerated, input/time-based effects to be authored in script.
-Address
-[this](https://github.com/w3c/css-houdini-drafts/blob/master/scroll-customization-api/UseCases.md) list of use cases.
-
-## High level approach
-
- - Introduce CompositorWorker whose global scope exposes requestAnimationFrame
- which runs at the rate of threaded scrolling and animation.
- - Allow DOM elements to be wrapped in a CompositorProxy which may be sent to a
- CompositorWorker and which exposes a limited set of layout-free properties
- and input events.
-
-## A tiny, but expressive kernel
-
-This small set of primitives would permit a surprisingly large number of
-existing and proposed browser features to be implemented as polyfills.
-
- - Portions of [Web Animations](http://dev.w3.org/fxtf/web-animations/)
- - [Touch-based Animation Scrubbing](https://docs.google.com/document/d/1vRUo_g1il-evZs975eNzGPOuJS7H5UBxs-iZmXHux48/edit)
- - [position:sticky](http://updates.html5rocks.com/2012/08/Stick-your-landings-position-sticky-lands-in-WebKit)
- - [Smooth Scrolling](http://dev.w3.org/csswg/cssom-view/), Sections 4, 5, 7, 12, and 13.
- - Accelerated CSS animations. ([I/O talk](http://www.youtube.com/watch?v=hAzhayTnhEI))
- - [Snap Points](https://www.w3.org/TR/css-snappoints-1/)
-
-## Examples
-
-### Example 1. Hello, World!
-
-Here we create a trivial, time-based animation.
-
-Main Thread
-```JavaScript
-// The list of properties determine which properties may be
-// modified on the CompositorWorker.
-var proxy = new CompositorProxy(element, ['transform']);
-
-// If a requested property may not modified, then |supports|
-// will return false as follows.
-console.log('transform may be modified: ' + proxy.supports('transform'));
-
-// The UA is free to drive a CompositorWorker from a thread of its
-// choosing, provided it fires requestAnimationFrame callbacks in
-// sync with compositor driven scrolling, modulo script exceeding
-// frame budget. If, for whatever reason, a CompositorWorker’s
-// requested compositor frame callbacks are unable to complete in
-// time, the worker’s onerror handler will be called with a
-// TimeoutError. See comment below about the application of effects.
-// In future, we will almost certainly specify other behaviors when
-// the worker’s frame callbacks cannot complete (e.g., kill the
-// worker), but the initial behavior will simply be to let the
-// effect fall out of sync with other compositor-driven effects.
-var worker = new CompositorWorker(‘my_script.js’);
-worker.postMessage(proxy);
-```
-On the CompositorWorker
-```JavaScript
-onmessage = function(e) {
- var proxy = e.data;
- var tick = function(timestamp) {
- var t = proxy.transform;
- t.m42 = 100.0 * Math.sin(timestamp / 1000.0);
- proxy.transform = t;
- requestAnimationFrame(tick);
- };
- // All requestAnimationFrame callbacks are processed as a unit;
- // their effects will all be applied in the same frame.
- requestAnimationFrame(tick);
-};
-```
-### Example 2. Parallax
-
-Although we could implement parallax by checking the scroll position
-every frame, it's more efficient if we only update when we've actually
-scrolled. Here's a proposal for how that might look.
-
-Main Thread
-```JavaScript
-var scroller_proxy = new CompositorProxy(scroller, ['scrollTop']);
-var background_proxy = new CompositorProxy(background, ['transform']);
-var worker = new CompositorWorker('parallax.js');
-worker.postMessage({
- 'scroller': scroller_proxy,
- 'background': background_proxy
-});
-```
-
-On the CompositorWorker
-```JavaScript
-onmessage = function(e) {
- var scroller = e.data.scroller;
- var background = e.data.background;
- var update = function(timestamp) {
- var t = background.transform;
- t.m42 = 0.8 * scroller.scrollTop;
- background.transform = t;
- // In this case, |update| will be called iff a property
- // of scroller's has been updated.
- requestAnimationFrame(update, [scroller]);
- };
- requestAnimationFrame(update, [scroller]);
-};
-```
-
-### Example 3. Input
-
-This example shows how we might implement a simple drawer. The way input will be provided to a CompositorWorker is very much an open question, so treat the following as speculative and likely to change.
-
-Main Thread
-```JavaScript
-var drawer_proxy = new CompositorProxy(drawer, [transform]);
-var worker = new CompositorWorker('drawer.js');
-worker.postMessage(drawer_proxy);
-```
-
-On the CompositorWorker
-```JavaScript
-onmessage = function(e) {
- var drawer = e.data;
- drawer.addEventListener('touchstart', function(e) {
- drawer.initialX = e.touches[0].pageX;
- });
- drawer.addEventListener('touchmove', function(e) {
- var t = drawer.transform;
- t.m41 = e.touches[0].pageX - drawer.initialX;
- drawer.transform = t;
- });
-};
-```
-
-## Common Concerns
-
-### Are we marrying ourselves to implementation details?
-
-Virtually all user agents support (via CSS) accelerated opacity and transform animations, and they’re going to have to support them for the foreseeable future. Threaded scrolling is also increasingly common. By whatever means browsers are currently able to guarantee that things can slide around, scroll and fade in and out efficiently, they could potentially permit these effects to be driven by JavaScript on a web worker. It doesn’t, for example, tie us to the idea of a composited layer or a layer tree, concepts that may not be meaningful in all browser implementations. The animated elements might, say, be redrawn by the GPU each frame. But this doesn’t matter. These implementation details are orthogonal to the animation proxy concept.
-
-### What happens when script runs long?
-
-On some platforms or user agents, it may be unacceptable or impossible to slow down native scrolling. We must have a fallback. There are a number of reasonable failure modes, and one that seems like a natural default.
- * _Default_: Fall out of sync. In this case, the user agent would attempt to retrieve a value from the various CompositorWorker requestAnimationFrame callbacks, but if they have not finished within the frame budget, they will be applied in a subsequent frame, falling out of sync with, say, compositor driven scrolling. When this happens the worker's onerror function will be called with a TimeoutError so that the author has the option to react.
- * Force all compositor-driven effects to the main thread. In this case, the effects will remain synchronized, though they will become susceptible to main thread jank. Again, we would communicate failure via onerror.
- * Abort the effect. I.e., if we cannot keep up with the compositor, do not call requestAnimationFrame for that CompositorWorker again. This would be useful in cases where scroll/animation performance is critical, but the effect is a nice-to-have. As always, failure is communicated via onerror.
diff --git a/css-scroll-api/Makefile b/css-animation-worklet-1/Makefile
old mode 100755
new mode 100644
similarity index 94%
rename from css-scroll-api/Makefile
rename to css-animation-worklet-1/Makefile
index 8658b1f2..63fe58ab
--- a/css-scroll-api/Makefile
+++ b/css-animation-worklet-1/Makefile
@@ -6,7 +6,7 @@
SOURCEFILE=Overview.bs
OUTPUTFILE=Overview.html
-PREPROCESSOR=bikeshed.py
+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
+
+
+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)? effects = null,
+ optional AnimationTimeline? timeline,
+ optional any options);
+ readonly attribute DOMString animatorName;
+};
+
+
+
+
+
+
+ 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 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.
+
+
+
+
+
+
+
+
+
+
+
+
+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.
+
+
+
+
+
+
+
+
+// In document scope.
+
+
+
+
+
+// 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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+// 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.
+
+
+
+
+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:
+
+
+
+
+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.
+
+ 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-animation-worklet-1/WIP.md b/css-animation-worklet-1/WIP.md
new file mode 100644
index 00000000..9b36c15a
--- /dev/null
+++ b/css-animation-worklet-1/WIP.md
@@ -0,0 +1,103 @@
+# Open API Questions
+---
+
+
+## Creation/Registration timing
+
+What should happen if an animation is created before the animator is registered?
+
+## Timelines
+
+* observe-only timelines? i.e., have access to timeline but the animate is not triggered when its
+ value changes.
+
+* Should we have a Timeline.currentTime and Timeline.localTime, where the latter is
+ the former but offset by startTime & scaled by playbackRate?
+
+* Access to the actual scroll position in the ScrollTimeline
+
+* Access to scroll phase (inactive, active, inertial etc.)
+
+
+## Updating Elements
+
+For some effects we need to be able to add new participating elements without
+restarting the effect. Here is an initial idea on how this can work.
+
+```js
+// Effects and data can change after some time.
+// We might want to break this out into separate functions or optional
+// updates so you can just update options or just effects and options
+// without having to pass other parameters again.
+anim.update({
+ [ /* new list of effects? */],
+ [ /* new list of timelines */],
+ {/* options */}
+});
+```
+
+```js
+// In worklet scope
+class MyAnimator{
+ update(options) {
+ // this is a V2 concept,
+ }
+}
+```
+
+## CSS Notation
+
+We are not proposing including this in the initial spec, but including some
+preliminary thoughts here so that we can keep the eventual declarative CSS
+specification in mind.
+
+index.html:
+```html
+<-- animator instance is declared here, with its timelines -->
+
+ <-- effect timing is declared here and assigned to above animator -->
+
+
+
+
+```
+
+style.css:
+```css
+#main {
+ animation: worklet('twitter-header')
+ animation-timeline: scroll(#scroller_element.....) /* https://wicg.github.io/scroll-animations/#animation-timeline */
+}
+
+/* These are descendants of the animation */
+#main .header {
+ animation-group: 'twitter-header' 'header' #...
+ /* This syntax should be similar to what the plan is for Web Animation Group Effects */
+}
+
+#main .avatar {
+ animation-group: 'twitter-header' 'avatar' #...
+}
+```
+
+This is equivalent to calling:
+
+```js
+new WorkletAnimation('twitter-header',
+ [
+ new KeyFrameEffect(.header[0], [], {}),
+ new KeyFrameEffect(.avatar, [], {}),
+ new KeyFrameEffect(.header[1], [], {}),
+ ],
+ [new ScrollingTimeline(#selector, {...})],
+ { elements: [
+ /* This is admittedly a bit magical. */
+ {'name': 'header'},
+ {'name': 'avatar'},
+ {'name': 'header'},
+ ]}
+).play();
+
+```
+
+Adding new elements that match the selector will be equivalent to invoking `update`.
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-animation-worklet-1/img/WorkletAnimation-timing-model.svg b/css-animation-worklet-1/img/WorkletAnimation-timing-model.svg
new file mode 100644
index 00000000..c29d115e
--- /dev/null
+++ b/css-animation-worklet-1/img/WorkletAnimation-timing-model.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
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-animation-worklet-1/principles.md b/css-animation-worklet-1/principles.md
new file mode 100644
index 00000000..4278df31
--- /dev/null
+++ b/css-animation-worklet-1/principles.md
@@ -0,0 +1,136 @@
+# Animation Worklet Design Principles and Goals
+***for rich interactive effects on the Web Platform***
+
+
+## Problem and Motivation
+
+**Fact**: Fact: It is difficult to build smooth rich interactive effects on the web.
+
+**Why do we care?**
+Silky smooth rich interactive user interfaces are now a [basic user expectation][performance] on
+modern computing platforms. For web platform to remain competitive it should be capable of high
+fidelity, smooth, responsive UI.
+
+
+**Where is the difficulty?**
+Three key aspects of rich interactive effects are: smoothness, responsiveness to input (a.k.a. R & A
+of [RAILS model][rails]) and their rich interaction model.
+
+The two main methods for creating animations on the web fall short in at least one of these aspects:
+
+- CSS (Web) Animations: Aimed at supporting stateless declarative time-driven effects. The
+ expressiveness is sufficient for common time-based effects. The resulting animation can be smooth
+ [1](#footnote1). However it's unclear how this model can be scaled to handle the
+ multi-dimensional inputs, conditional values, and statefulness required by the use cases below.
+- requestAnimationFrame: Aimed at creating scripted animation effects. It can support rich
+ interaction models but it is difficult to make smooth or responsive. This expressiveness of
+ Javascript coupled with access to all input methods, application state and dom makes this API
+ capable of building rich interactive effects. However these can only run on main thread alongside
+ all other scripts[2](#footnote2) which severely hampers their responsiveness and
+ smoothness. Chrome [studies](https://tdresser.github.io/input-latency-deep-reports/) have shown that script is the main culprit to user responsiveness issues.
+
+Animation Worklet aims to help bridge the gap between these two.
+
+## Animation Worklet Vision
+
+[Animation Worklet][specification] aims to rectify this shortcomings by enabling animations that can
+be:
+
+* rich (imperative, stateful)
+* fast-by-default (isolated from main thread)
+* respond to rich input e.g., touch, gesture, scroll.
+
+Animation Worklet is a primitive in the [extensible web](https://extensiblewebmanifesto.org/) spirit.
+It exposes browser's fast path to applications in a way that it was never before and reduces browser
+magic.
+
+Examples of rich interactive effects that are (or will be made) possible with Animation Worklet:
+
+
+* Scroll driven effects:
+ * [Hidey-bar](https://googlechromelabs.github.io/houdini-samples/animation-worklet/twitter-header/): animation depends on both time and scroll input.
+ * [Parallax](https://googlechromelabs.github.io/houdini-samples/animation-worklet/parallax-scrolling/): Simplest scroll-drive effect.
+ * [Custom paginated slider](http://aw-playground.glitch.me/amp-scroller.html).
+ * Pull-to-refresh: animation depends on both touch and time inputs.
+ * Custom scrollbars.
+ * [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 dismiss.
+ * Drag-N-Drop.
+ * Tiled panning e.g., Google maps.
+* Stateful script driven effects:
+ * [Spring-based emulations](https://googlechromelabs.github.io/houdini-samples/animation-worklet/spring-timing/).
+ * [Spring-Sticky effect](http://googlechromelabs.github.io/houdini-samples/animation-worklet/spring-sticky/).
+ * Touch-driven physical environments.
+ * [Expando](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)
+
+
+## First Principle - Richness
+
+Animation Worklet enables developers to create custom animations by providing an `animate` function
+that runs inside animation worklet global scope. The animation logic can take advantage of the full
+expressive power of JavaScript, maintain local state, modify and coordinated across many elements.
+This new extension point in browser animation system enables richer effects that go well beyond what
+can be achieved today with Web Animations and closer to what is possible with requestAnimationFrame.
+
+**Further explorations in this direction:** Allow richer access to scrolling machinery (e.g., scroll
+customization), custom paint worklets, and outputs beyond existing KeyframeEffect interface.
+
+
+## Second Principle - Performance
+
+Animation Worklet is designed to be thread agnostic. In particular, it can run off main thread
+keeping its performance isolated from main thread (also reducing main thread load).
+
+Animation Worklet API encourages the developers to isolate their critical UI work inside limited
+worklet scope with well-specified input and output. This allows user-agent to make much better
+scheduling decision about this work and in particular, be much better at maintaining a strict
+frame-budget to successfully run these animations on its fast path. Presently the above performance
+guarantees are only accessible to a limited set of declarative time-based effects.
+
+**Further explorations in this direction**: Introduce more sophisticated per-animation scheduling
+where a slow animation may run at slower frame-rate without affecting other well-behaved animations,
+experiment with translating animation code to native code or even GL shaders moving the computation
+to GPU for even stronger performance guarantees!
+
+
+## Third Principle - Interactivity
+
+Animation Worklet is designed to enable support for animations whose input goes beyond just time, a
+single-dimensional variable.
+
+Web animation timing model is stateless and driven by a single dimensional variable, time.
+
+Although this model works well for declarative time-based animation, it falls short when it comes to
+interactive input-driven effects that are inherently stateful. While it is possible to map simple
+forms of input (e.g., [single dimensional scroll](https://wicg.github.io/scroll-animations/#intro))
+into time, it is much more difficult (almost impossible) to do so for multi-dimensional stateful
+input such as multi-touch and gesture input.
+
+Animation Worklet has the necessary expressive power and richness to easily accommodate the full
+richness of multi-dimensional input such as touch, gesture, scroll etc. For example it is trivial to
+react to scroll phase change, pointer state change, addition/removal of new pointer or state,
+calculate pointer velocity, acceleration and other computed values inside an animation worklet.
+
+**Further explorations in this direction:** Expose pointer and gesture as input to animation
+([current proposal](https://github.com/w3c/csswg-drafts/issues/2493#issuecomment-422109535))
+
+
+# Appendix
+
+
+## Footnotes
+
+* 1: If authors limit themselves to cheap-to-update properties. In Chrome
+ these are composited properties e.g., transform, opacity, filter but other engines may have a
+ slightly difference subset.
+
+
+[performance]: https://paul.kinlan.me/what-news-readers-want/
+[rails]: https://developers.google.com/web/fundamentals/performance/rail#goals-and-guidelines
+[specification]: https://github.com/w3c/css-houdini-drafts/tree/master/css-animationworklet
diff --git a/css-animation-worklet-1/status.md b/css-animation-worklet-1/status.md
new file mode 100644
index 00000000..bdaa1dd3
--- /dev/null
+++ b/css-animation-worklet-1/status.md
@@ -0,0 +1,18 @@
+# Implementation Status
+
+## Chrome
+
+This is a rough sketch of how Chrome plans to deliver Animation Worklet features:
+
+1. Animation Worktlet Prototype (done): scripted custom animation, single effect, only fast
+ properties, off-thread.
+2. Animation Worktlet [Origin Trial][ot-blogpost] (in progress, [signup][ot-signup]): good
+ performance, scroll input (ScrollTimeline), basic web-animation controls (play/cancel).
+3. Animation Worktlet MVP (in development): animate all properties (slow path ones running in sync
+ with main thread), multiple effects (i.e., GroupEffect), full web-animation integration.
+4. Animation Worktlet V2 (future): touch/gesture input, multiple inputs in single animation,
+ sophisticated scheduling, other outputs.
+
+[ot-blogpost]: https://developers.google.com/web/updates/2018/10/animation-worklet
+[ot-signup]:https://docs.google.com/forms/d/e/1FAIpQLSfO0_ptFl8r8G0UFhT0xhV17eabG-erUWBDiKSRDTqEZ_9ULQ/viewform
+
diff --git a/css-layout-api/EXPLAINER.md b/css-layout-api/EXPLAINER.md
new file mode 100644
index 00000000..6aaf3107
--- /dev/null
+++ b/css-layout-api/EXPLAINER.md
@@ -0,0 +1,478 @@
+CSS Layout API Explained
+========================
+
+The CSS Layout API is being developed to improve the extensibility of CSS.
+
+Specifically the API is designed to give web developers the ability to write their own layout
+algorithms in addition to the native algorithms user agents ship with today.
+
+For example user agents today currently ship with:
+ - Block Flow Layout
+ - Flexbox Layout
+
+However with the CSS Layout API web developers could write their own layouts which implement:
+ - Constraint based layouts
+ - Masonry layouts
+ - Line spacing and snapping
+
+Initial Concepts - Writing Modes
+--------------------------------
+
+This API uses terminology which may be foreign to many web developers initially. Everything in the
+CSS Layout API is computed in the [logical coordinate
+system](https://drafts.csswg.org/css-writing-modes-3/#text-flow).
+
+This has the primary advantage that when you write your layout using this system it will
+automatically work for writing modes which are right-to-left (e.g. Arabic or Hebrew), or for writing
+modes which are vertical (many Asian scripts including Chinese scripts, Japanese and Korean).
+
+For a developer who is used to left-to-right text, the way to translate this back into "physical"
+coordinates is:
+
+| Logical | Physical |
+| -----------:|:-------- |
+| inlineSize | width |
+| inlineStart | left |
+| inlineEnd | right |
+| blockSize | height |
+| blockStart | top |
+| blockEnd | bottom |
+
+Getting Started
+---------------
+
+First you'll need to add a module script into the layout worklet.
+
+```js
+if ('layoutWorklet' in CSS) {
+ await CSS.layoutWorklet.addModule('my-layout-script.js');
+ console.log('layout script installed!');
+}
+```
+
+See the worklets [explainer](../worklets/EXPLAINER.md) for a more involved explanation of worklets.
+
+After the promise returned from the `addModule` method resolves the layouts defined in the script
+will apply to the page.
+
+A Centering Layout
+------------------
+
+The global script context for the layout worklet has exactly one entry method exposed to developers:
+`registerLayout`.
+
+There are a lot of things going on in the following example so we'll step through them one-by-one
+below. You should read the code below with its explanatory section.
+
+```js
+registerLayout('centering', class {
+ async layout(children, edges, constraints, styleMap) {
+ // (1) Determine our (inner) available size.
+ const availableInlineSize = constraints.fixedInlineSize - edges.inline;
+ const availableBlockSize = constraints.fixedBlockSize ?
+ constraints.fixedBlockSize - edges.block :
+ null;
+
+ let maxChildBlockSize = 0;
+
+ const childFragments = [];
+ for (let child of children) {
+ // (2) Perform layout upon the child.
+ const fragment = await child.layoutNextFragment({
+ availableInlineSize,
+ availableBlockSize,
+ });
+
+ // Determine the max fragment size so far.
+ maxChildBlockSize = Math.max(maxChildBlockSize, fragment.blockSize);
+
+ // Position our child fragment.
+ fragment.inlineOffset = edges.inlineStart +
+ (constraints.fixedInlineSize - fragment.inlineSize) / 2;
+ fragment.blockOffset = edges.blockStart +
+ Math.max(0, (constraints.fixedBlockSize - fragment.blockSize) / 2);
+
+ childFragments.push(fragment);
+ }
+
+ // (3) Determine our "auto" block size.
+ const autoBlockSize = maxChildBlockSize + edges.block;
+
+ // (4) Return our fragment.
+ return {
+ autoBlockSize,
+ childFragments,
+ }
+ }
+});
+```
+
+The `layout` function is your callback into the browsers layout phase in the
+rendering engine. You are given:
+ - `children`, the list of children boxes you should perform layout upon.
+ - `edges`, the size of *your* borders, scrollbar, and padding in the logical coordinate system.
+ - `constraints`, the constraints which the fragment you produce should meet.
+ - `style`, the _readonly_ style for the current layout.
+
+Layout eventually will return a dictionary will what the resulting fragment of that layout should
+be.
+
+The above example would be used in CSS by:
+```css
+.centering {
+ display: layout(centering);
+}
+```
+
+### Step (1) - Determine our (inner) available size ###
+
+The first thing that you'll probably want to do for most layouts is to determine your "inner" size.
+
+The `constraints` object passed into the layout function pre-calculates your inline-size (width),
+and potentially your block-size (height) if there is enough information to do so (e.g. the element
+has `height: 100px` specified).
+
+See [developer.mozilla.org](https://developer.mozilla.org) for an explanation of what
+[width](https://developer.mozilla.org/en-US/docs/Web/CSS/width) and
+[height](https://developer.mozilla.org/en-US/docs/Web/CSS/height), etc will resolve to.
+
+The `edges` object represents the border, scrollbar, and padding of your element. In order to
+determine our "inner" size we subtract the `edges.all` from our calculated sizes. For example:
+
+```js
+const availableInlineSize = constraints.fixedInlineSize - edges.inline;
+const availableBlockSize = constraints.fixedBlockSize ?
+ constraints.fixedBlockSize - edges.block :
+ null;
+```
+
+We keep `availableBlockSize` null if `constraints.fixedBlockSize` wasn't able to be computed.
+
+### Step (2) - Perform layout upon the child ###
+
+Performing layout on a child can be done with the `layoutNextFragment` method. E.g.
+
+```js
+const fragment = await child.layoutNextFragment({
+ availableInlineSize,
+ availableBlockSize,
+});
+```
+
+The first argument is the "constraints" which you are giving to the child. They can be:
+ - `availableInlineSize` & `availableBlockSize` - A child fragment will try and "fit" within this
+ given space.
+ - `fixedInlineSize` & `fixedBlockSize` - A child fragment will be "forced" to be this size.
+ - `percentageInlineSize` & `percentageBlockSize` - Percentages will be resolved against this size.
+
+As layout may be paused or run on a different thread, the API is asynchronous.
+
+The result of performing layout on a child is a `LayoutFragment`. A fragment is read-only except for
+setting the offset relative to the parent fragment.
+
+```js
+fragment instanceof LayoutFragment; // true
+
+// The resolved size of the fragment.
+fragment.inlineSize;
+fragment.blockSize;
+
+// We can set the offset relative to the current layout.
+fragment.inlineOffset = 10;
+fragment.blockOffset = 20;
+```
+
+### Step (3) - Determine our "auto" block size ###
+
+Now that we know how large our biggest child is going to be, we can calculate our "auto" block size.
+This is the size the element will be if there are no other block-size constraints (e.g. `height:
+100px`).
+
+In this layout algorithm, we just add the `edges.block` size to the largest child we found:
+```js
+const autoBlockSize = maxChildBlockSize + edges.block;
+```
+
+### Step (4) - Return our fragment ###
+
+Finally we return a dictionary which represents the fragment we wish the rendering engine to create
+for us. E.g.
+```js
+const result = {
+ autoBlockSize,
+ childFragments,
+};
+```
+
+The important things to note here are that you need to explicitly say which `childFragments` you
+would like to render. If you give this an empty array you won't render any of your children.
+
+Querying Style
+--------------
+
+While not present in the "centering" example, it is possible to query the style of the element you
+are performing layout for, and all children. E.g.
+
+```html
+
+
+
+
+
+```
+
+```js
+registerLayout('style-read', class {
+ static inputProperties = ['--a-number'];
+ static childInputProperties = ['--a-string'];
+
+ async layout(children, edges, constraints, styleMap) {
+ // We can read our own style:
+ styleMap.get('--a-number').value === 42;
+
+ // And our children:
+ children[0].styleMap.get('--a-string').toString() === 'hello';
+ }
+});
+```
+
+You can use this to implement properties which your layout depends on, a similar thing that native
+layouts use is `flex-grow` for flexbox, or `grid-template-areas` for grid.
+
+Text Layout
+-----------
+
+By default layouts force all of their children to be blockified. This means for example if you have:
+```html
+
+ I am some text
+
+
+```
+
+The engine will conceptually force the text `I am some text` to be surrounded by a `
`. E.g.
+```html
+
+
I am some text
+
+
+```
+
+This is important as the above `centering` layout would have to deal with text _fragmentation_, a
+few native layouts use this trick to simplify their algorithms, for example grid and flexbox.
+
+### Text Fragmentation ###
+
+In the above `centering` example, we forced each `LayoutChild` to produce exactly one
+`LayoutFragment`.
+
+We are able to ensure children do not blockify by setting the `childDisplay` to `normal`, e.g.
+```js
+registerLayout('example', class {
+ static layoutOptions = {childDisplay: 'normal'};
+});
+```
+
+Now a `LayoutChild` which represents some text is able to produce more than one `Fragment`. E.g.
+
+```text
+|---- Inline Available Size ----|
+The quick brown fox jumped over the lazy dog.
+```
+
+```js
+child instanceof LayoutChild;
+
+const fragment1 = yield child.layoutNextFragment(constraints);
+const fragment2 = yield child.layoutNextFragment(constraints, fragment1.breakToken);
+
+fragment2.breakToken == null;
+```
+
+In the above example the text child produces two fragments. Containing:
+1. `The quick brown fox jumped over`
+2. `the lazy dog.`
+
+The critical detail here to be aware of is the concept of a `BreakToken`. The `BreakToken` contains
+all of the information necessary to continue/resume the layout where the child finished.
+
+We pass the `BreakToken` to add back into the `layout()` call in order to produce the next fragment.
+
+### A Basic Text Layout ###
+
+```js
+registerLayout('basic-inline', class {
+ static layoutOptions = {childDisplay: 'normal'};
+
+ async layout(children, edges, constraints, styleMap) {
+ // Determine our (inner) available size.
+ const availableInlineSize = constraints.fixedInlineSize - edges.inline;
+ const availableBlockSize = constraints.fixedBlockSize !== null ?
+ constraints.fixedBlockSize - edges.block : null;
+
+ const constraints = {
+ availableInlineSize,
+ availableBlockSize,
+ };
+
+ const childFragments = [];
+
+ let blockOffset = edges.blockStart;
+ let child = children.shift();
+ let childBreakToken = null;
+ while (child) {
+ // Layout the next line, the produced line will try and respect the
+ // availableInlineSize given, you could use this to achieve a column
+ // effect or similar.
+ const fragment = await child.layoutNextFragment(constraints, childBreakToken);
+ childFragments.push(fragment);
+
+ // Position the fragment, note we coulld do something special here, like
+ // placing all the lines on a "rythimic grid", or similar.
+ fragment.inlineOffset = edges.inlineStart;
+ fragment.blockOffset = blockOffset;
+
+ blockOffset += fragment.blockSize;
+
+ if (fragment.breakToken) {
+ childBreakToken = fragment.breakToken;
+ } else {
+ // If a fragment doesn't have a break token, we move onto the next
+ // child.
+ child = children.shift();
+ childBreakToken = null;
+ }
+ }
+
+ // Determine our "auto" block size.
+ const autoBlockSize = blockOffset + edges.blockEnd;
+
+ // Return our fragment.
+ return {
+ autoBlockSize,
+ childFragments,
+ };
+ }
+});
+```
+
+The above example is slightly more complex than the previous centering layout because of the ability
+for text children to fragment.
+
+That said it has all the same steps as before:
+ 1. Resolving the (inner) available size.
+ 2. Performing layout and positioning children fragments.
+ 3. Resolving the "auto" block size.
+ 4. Returning the fragment.
+
+Scrolling
+---------
+
+We have been handling scrolling in the above example but we haven't talked about it yet.
+
+The `edges` object passed into `layout()` respects the `overflow` property.
+For example if we are `overflow: hidden`, `edges` object won't include the scrollbar width.
+
+For `overflow: auto` the engine will typically perform a layout without a scrollbar, then if it
+detects overflow, with a scrollbar. As long as you respect the layout "edges" your layout algorithm
+should work as expected.
+
+Block Fragmentation
+-------------------
+
+Some native layouts on the web support what is known as block fragmentation. For example:
+
+```html
+
+
+This is some text.
+
+
+
+This is some more text.
+
+```
+
+In the above example the `multicol` div may produce three (3) fragments.
+ 1. `{fragment}This is some text.{/fragment}`
+ 2. `{fragment}{fragment type=table}{/fragment} This is{/fragment}`
+ 3. `{fragment}some more text.{/fragment}`
+
+We can make our children fragment by passing them a constraint space with a fragmentation line. E.g.
+
+```js
+registerLayout('special-multi-col', class {
+ async layout(children, edges, constraints, styleMap, breakToken) {
+ for (let child of children) {
+ // Create a constraint space with a fragmentation line.
+ const childConstraints = {
+ availableInlineSize,
+ availableBlockSize,
+ blockFragmentationOffset: availableBlockSize,
+ blockFragmentationType: 'column',
+ });
+
+ const fragment = await child.layoutNextFragment(childConstraints);
+ }
+
+ // ...
+ }
+});
+```
+
+In the above example each of the children will attempt to fragment in the block direction when they
+exceed `blockFragmentationOffset`. The type is a `'column'` which will mean it works in conjunction
+with rules like `break-inside: avoid-column`.
+
+We can also allow our own layout to be fragmented by respecting the fragmentation line. E.g.
+
+```js
+registerLayout('basic-inline', class {
+ async layout(children, edges, constraints, styleMap, breakToken) {
+
+ // We can check if we need to fragment in the block direction.
+ if (constraints.blockFragmentationType != 'none') {
+ // We need to fragment!
+ }
+
+ // We can get the start child to start layout at with the breakToken. E.g.
+ let child = null;
+ let childToken = null;
+ if (breakToken) {
+ childToken = breakToken.childTokens[0]; // We can actually have multiple
+ // children break. But for now
+ // we'll just use one.
+ child = childToken.child;
+ } else {
+ child = children[0];
+ }
+
+ // SNIP!
+
+ return {
+ autoBlockSize,
+ childFragments,
+ breakToken: {
+ data: /* you can place arbitary data here */,
+ childTokens: [childToken]
+ }
+ }
+ }
+});
+```
+
+The additional complexity here is that you need to create and receive your own break tokens.
+
+Closing Words
+-------------
+
+This is a complex API and it uses foreign terminology. But we really want to give you, the web
+developer, the power that the rendering engines have when it comes to layout. Enjoy! :)
+
diff --git a/css-layout-api/Overview.bs b/css-layout-api/Overview.bs
index 7a202065..6074248e 100644
--- a/css-layout-api/Overview.bs
+++ b/css-layout-api/Overview.bs
@@ -1,15 +1,2467 @@
Title: CSS Layout API Level 1
-Status: DREAM
+Status: ED
Group: houdini
+TR: https://www.w3.org/TR/css-layout-api-1/
ED: https://drafts.css-houdini.org/css-layout-api-1/
Shortname: css-layout-api
Level: 1
Abstract:
-Editor: Greg Whitworth, gwhit@microsoft.com
-Editor: Ian Kilpatrick, ikilpatrick@chromium.org
-Editor: Tab Atkins, jackalmage@gmail.com
-Editor: Shane Stephens, shanestephens@google.com
+ An API for allowing web developers to define their own layout modes with javascript.
+ See EXPLAINER.
+Editor: Greg Whitworth, gwhit@microsoft.com, w3cid 69511
+Editor: Ian Kilpatrick, ikilpatrick@chromium.org, w3cid 73001
+Editor: Tab Atkins-Bittner, Google, http://xanthir.com/contact/, w3cid 42199
+Former Editor: Shane Stephens, shanestephens@google.com, w3cid 47691
Editor: Robert O'Callahan, robert@ocallahan.org
-Editor: Rossen Atanassov, rossen.atanassov@microsoft.com
+Editor: Rossen Atanassov, rossen.atanassov@microsoft.com, w3cid 49885
+Ignored Terms: LayoutWorklet
+Ignored Terms: create a workletglobalscope
+
+Introduction {#intro}
+=====================
+
+This section is not normative.
+
+The layout stage of CSS is responsible for generating and positioning [=fragments=] from the [=box
+tree=].
+
+This specification describes an API which allows developers to layout a [=box=] in response to
+computed style and [=box tree=] changes.
+
+For a high level overview of this API, see the EXPLAINER.
+
+Layout API Containers {#layout-api-containers}
+==============================================
+
+A new alternative value is added
+to the <> production: layout(<>).
+
+
+
layout()
+
+ This value causes an element to generate a [=layout API container=] box.
+
+
+A layout API container is the box generated by an element with a <>
+[=computed value=] ''layout()''.
+
+A [=layout API container=] establishes a new layout API formatting context for its
+contents. This is the same as establishing a block formatting context, except that the layout
+provided by the author is used instead of the block layout.
+For example, floats do not intrude into the layout API container, and the layout API container's
+margins do not collapse with the margins of its contents.
+
+[=Layout API containers=] form a containing block for their contents exactly like block
+containers do. [[!CSS21]]
+
+Note: In a future level of the specification there may be a way to override the containing block
+ behaviour.
+
+The 'overflow' property applies to [=layout API containers=]. This is discussed in
+[[#interaction-overflow]].
+
+As the layout is entirely up to the author, properties which are used in other layout modes (e.g.
+flex, block) may not apply. For example an author may not respect the 'margin' property on children.
+
+
+The HTML below shows an example of setting the ''display'' to a ''layout()'' function, if the CSS
+Layout API is supported.
+
+
+
+Layout API Container Painting {#painting}
+-----------------------------------------
+
+[=Layout API Container=] children paint exactly the same as inline blocks [[!CSS21]], except that
+the order in which they are returned from the layout method (via
+{{FragmentResultOptions/childFragments}}) is used in place of raw document order, and 'z-index'
+values other than ''z-index/auto'' create a stacking context even if 'position' is ''static''.
+
+Box Tree Transformations {#layout-api-box-tree}
+-----------------------------------------------
+
+The inflow children of a [=layout API container=] can act in different ways depending on the value
+of [=document layout definition/layout options'=] {{LayoutOptions/childDisplay}} (set by
+layoutOptions on the class).
+
+If the value of [=document layout definition/layout options'=] {{LayoutOptions/childDisplay}} is
+"block" the 'display' value of that child is [=blockified=]. This is similar to
+children of [=flex containers=] or [=grid containers=]. See [[!css3-display]].
+
+If the value of [=document layout definition/layout options'=] {{LayoutOptions/childDisplay}} is
+"normal", no [=blockification=] occurs. Instead children with a <>
+[=computed value=] of ''inline'' (a [=root inline box=]) will produce a single {{LayoutFragment}}
+representing each line when {{LayoutChild/layoutNextFragment()}} is called.
+
+Note: This allows authors to adjust the available inline size of each line, and position each line
+ separately.
+
+Children of a {{LayoutChild}} which represents [=root inline box=] also have some additional
+transformations.
+
+ - A [=block-level=] box inside a [=inline-level=] box is [=inlinified=] I.e. its
+ <> is set to ''inline''.
+
+ - A [=float=] inside a [=inline-level=] box is not taken out of flow. Instead it must be treated as
+ inflow, and be [=inlinified=].
+
+In both of the above cases the children become [=atomic inlines=].
+
+Note: User agents would not perform any "inline splitting" or fragmenting when they encounter a
+ [=block-level=] box.
+
+
+ Note: In the example below "inline-span" would be represented as a single {{LayoutChild}} with
+ both "block" and "float" being [=atomic inlines=].
+
+ <span id="inline-span">
+ Text
+ <div id="block"></div>
+ <div id="float"></div>
+ Text
+ </span>
+
+
+
+Layout Worklet {#layout-worklet}
+================================
+
+The {{layoutWorklet}} attribute allows access to the {{Worklet}} responsible for all the classes
+which are related to layout.
+
+The {{layoutWorklet}}'s [=worklet global scope type=] is {{LayoutWorkletGlobalScope}}.
+
+
+ if ('layoutWorklet' in CSS) {
+ console.log('CSS Layout API available!');
+ }
+
+
+
+Concepts {#concepts}
+--------------------
+
+This section describes internal data-structures created when {{registerLayout(name, layoutCtor)}} is
+called.
+
+A layout definition is a [=struct=] which describes the information needed by the
+{{LayoutWorkletGlobalScope}} about the author defined layout (which can be referenced by the
+''layout()'' function). It consists of:
+
+ - class constructor which is the class [=constructor=].
+
+ - layout function which is the layout [=function=] callback.
+
+ - intrinsic sizes function which is the intrinsic sizes
+ [=function=] callback.
+
+ - constructor valid flag.
+
+ - input properties which is a [=list=] of
+ DOMStrings.
+
+ - child input properties which is a [=list=] of
+ DOMStrings.
+
+ - layout options a {{LayoutOptions}}.
+
+A document layout definition is a [=struct=] which describes the information needed by
+the [=document=] about the author defined layout (which can be referenced by the ''layout()''
+function). It consists of:
+
+ - input properties which is a [=list=] of
+ DOMStrings
+
+ - child input properties which is a [=list=] of
+ DOMStrings.
+
+ - layout options a {{LayoutOptions}}.
+
+Registering A Layout {#registering-layout}
+------------------------------------------
+
+The section describes how a web developer uses {{registerLayout(name, layoutCtor)}} to register a
+layout.
+
+
+[Exposed=LayoutWorklet]
+dictionary LayoutOptions {
+ ChildDisplayType childDisplay = "block";
+ LayoutSizingMode sizing = "block-like";
+};
+
+[Exposed=LayoutWorklet]
+enum ChildDisplayType {
+ "block", // default - "blockifies" the child boxes.
+ "normal",
+};
+
+[Exposed=LayoutWorklet]
+enum LayoutSizingMode {
+ "block-like", // default - Sizing behaves like block containers.
+ "manual", // Sizing is specified by the web developer.
+};
+
+
+The [=document=] has a [=map=] of document layout definitions. Initially this map is
+empty; it is populated when {{registerLayout(name, layoutCtor)}} is called.
+
+The {{LayoutWorkletGlobalScope}} has a [=map=] of layout definitions. Initially this map
+is empty; it is populated when {{registerLayout(name, layoutCtor)}} is called.
+
+Each [=box=] representing a [=layout API container=] has a [=map=] of layout class
+instances. Initially this map is empty; it is populated when the user agent calls either
+[=determine the intrinsic sizes=] or [=generate a fragment=] for a [=box=].
+
+Each [=box=] representing a [=layout API container=] has a styleMap internal slot.
+This is a {{StylePropertyMapReadOnly}} which contains the properties listed in
+inputProperties.
+
+The user agent clear the [=styleMap=] internal slot for a [=box=] when:
+
+ - The [=computed values=] of [=document layout definition/input properties=] for the [=box=]
+ changes.
+
+ - When the [=box=] is removed from the [=box tree=].
+
+ - Every 1000 layout passes.
+
+ Note: The above rule exists to ensure that web developers do not rely on being able to store
+ non-regeneratable state on the {{StylePropertyMapReadOnly}} object.
+ The 1000 limit was picked as a high upper bound, this limit may improve (downwards) over
+ time.
+
+
+
+The algorithm below is run when the {{registerLayout(name, layoutCtor)}} is called. It notifies the
+user agent layout engine about the new user defined layout.
+
+
+When the registerLayout(|name|, |layoutCtor|) method
+is called, the user agent must run the following steps:
+ 1. If the |name| is an empty string, [=throw=] a [=TypeError=] and abort all these steps.
+
+ 2. Let |layoutDefinitionMap| be {{LayoutWorkletGlobalScope}}'s [=layout definitions=] map.
+
+ 3. If |layoutDefinitionMap|[|name|] [=map/exists=] [=throw=] a "{{InvalidModificationError}}"
+ {{DOMException}} and abort all these steps.
+
+ 4. Let |inputProperties| be an empty sequence<DOMString>.
+
+ 5. Let |inputPropertiesIterable| be the result of [=Get=](|layoutCtor|, "inputProperties").
+
+ 6. If |inputPropertiesIterable| is not undefined, then set |inputProperties| to the result of
+ [=converting=] |inputPropertiesIterable| to a sequence<DOMString>. If an
+ exception is [=thrown=], rethrow the exception and abort all these steps.
+
+ 7. Filter |inputProperties| so that it only contains [=supported CSS properties=] and [=custom
+ properties=].
+
+ Note: The list of CSS properties provided by the input properties getter can either be
+ custom or native CSS properties.
+
+ Note: The list of CSS properties may contain shorthands.
+
+ Note: In order for a layout class to be forwards compatible, the list of CSS properties can
+ also contains currently invalid properties for the user agent. For example
+ margin-bikeshed-property.
+
+ 8. Let |childInputProperties| be an empty sequence<DOMString>.
+
+ 9. Let |childInputPropertiesIterable| be the result of [=Get=](|layoutCtor|,
+ "childInputProperties").
+
+ 10. If |childInputPropertiesIterable| is not undefined, then set |childInputProperties| to the
+ result of [=converting=] |childInputPropertiesIterable| to a
+ sequence<DOMString>. If an exception is [=thrown=], rethrow the exception
+ and abort all these steps.
+
+ 11. Filter |childInputProperties| so that it only contains [=supported CSS properties=] and [=custom
+ properties=].
+
+ 12. Let |layoutOptionsValue| be the result of [=Get=](|layoutCtor|, "layoutOptions").
+
+ 13. Let |layoutOptions| be the result of [=converting=] |layoutOptionsValue| to a
+ {{LayoutOptions}}. If an exception is [=thrown=], rethrow the exception and abort all these
+ steps.
+
+ 14. Let |prototype| be the result of [=Get=](|layoutCtor|, "prototype").
+
+ 15. If the result of [=Type=](|prototype|) is not Object, [=throw=] a [=TypeError=] and abort
+ all these steps.
+
+ 16. Let |intrinsicSizesValue| be the result of [=Get=](|prototype|, "intrinsicSizes").
+
+ 17. Let |intrinsicSizes| be the result of [=converting=] |intrinsicSizesValue| to the
+ [=Function=] [=callback function=] type. Rethrow any exceptions from the conversion.
+
+ 18. Let |layoutValue| be the result of [=Get=](|prototype|, "layout").
+
+ 19. Let |layout| be the result of [=converting=] |layoutValue| to the [=Function=] [=callback
+ function=] type. Rethrow any exceptions from the conversion.
+
+ 20. Let |definition| be a new [=layout definition=] with:
+
+ - [=class constructor=] being |layoutCtor|.
+
+ - [=layout function=] being |layout|.
+
+ - [=intrinsic sizes function=] being |intrinsicSizes|.
+
+ - [=constructor valid flag=] being true.
+
+ - [=layout definition/child input properties=] being |childInputProperties|.
+
+ - [=layout definition/input properties=] being |inputProperties|.
+
+ - [=layout definition/layout options=] being |layoutOptions|.
+
+ 21. [=map/Set=] |layoutDefinitionMap|[|name|] to |definition|.
+
+ 22. [=Queue a task=] to run the following steps:
+
+ 1. Let |documentLayoutDefinitionMap| be the associated [=document's=] [=document layout
+ definitions=] [=map=].
+
+ 2. Let |documentDefinition| be a new [=document layout definition=] with:
+
+ - [=document layout definition/child input properties=] being |childInputProperties|.
+
+ - [=document layout definition/input properties=] being |inputProperties|.
+
+ - [=document layout definition/layout options=] being |layoutOptions|.
+
+ 3. If |documentLayoutDefinitionMap|[|name|] [=map/exists=], run the following steps:
+
+ 1. Let |existingDocumentDefinition| be the result of [=map/get=]
+ |documentLayoutDefinitionMap|[|name|].
+
+ 2. If |existingDocumentDefinition| is "invalid", abort all these steps.
+
+ 3. If |existingDocumentDefinition| and |documentDefinition| are not equivalent, (that is
+ [=document layout definition/input properties=], [=document layout definition/child
+ input properties=], and [=document layout definition/layout options=] are
+ different), then:
+
+ [=map/Set=] |documentLayoutDefinitionMap|[|name|] to "invalid".
+
+ Log an error to the debugging console stating that the same class was registered
+ with different inputProperties, childInputProperties, or
+ layoutOptions.
+
+ 4. Otherwise, [=map/set=] |documentLayoutDefinitionMap|[|name|] to |documentDefinition|.
+
+
+Terminology {#terminology}
+--------------------------
+
+We define the following terms to be clear about which layout algorithm (formatting context) we are
+talking about.
+
+The current layout is the layout algorithm for the [=box=] we are currently performing
+layout for.
+
+The parent layout is the layout algorithm for the [=box's=] direct parent, (the layout
+algorithm which is requesting the [=current layout=] to be performed).
+
+A child layout is the layout algorithm for a {{LayoutChild}} of the [=current layout=].
+
+Layout API {#layout-api}
+========================
+
+This section describes the objects of the Layout API provided to web developers.
+
+Layout Children {#layout-children}
+----------------------------------
+
+A {{LayoutChild}} represents a inflow CSS generated [=box=] before layout has occurred. (The box or
+boxes will all have a computed value of 'display' that is not ''none'').
+
+The {{LayoutChild}} does not contain any layout information itself (like inline or block size) but
+can be used to generate {{LayoutFragment}}s which do contain layout information.
+
+An author cannot construct a {{LayoutChild}} with this API, this happens at a separate stage of the
+user agent rendering engine (post style resolution).
+
+An array of {{LayoutChild}}ren is passed into the layout/intrinsicSizes methods which represents the
+children of the current box which is being laid out.
+
+
+
+The {{LayoutChild}} has internal slot(s):
+
+ - \[[box]] a CSS [=box=].
+
+ - \[[styleMap]] a {{StylePropertyMapReadOnly}}, this is the
+ computed style for the child, it is populated with only the properties listed in
+ childInputProperties.
+
+ - [[unique id]] the [=unique id=] of the current [=layout
+ api context=]. This slot is used so that a {{LayoutChild}} used outside the current layout
+ pass is invalid.
+
+The {{LayoutChild/[[styleMap]]}} may be pre-populated when the [=computed value=] for properties
+listed in the in [=layout definition/child input properties=] for the {{LayoutChild/[[box]]}}.
+
+
+The example below shows the basic usage of a {{LayoutChild}}.
+
+registerLayout('example-layout-child', class {
+ static childInputProperties = ['--foo'];
+
+ async layout(children, edges, constraints, styleMap) {
+
+ // An array of LayoutChildren is passed into both the layout function,
+ // and intrinsic sizes function below.
+ const child = children[0];
+
+ // You can query the any properties listed in "childInputProperties".
+ const fooValue = child.styleMap.get('--foo');
+
+ // And perform layout!
+ const fragment = await child.layoutNextFragment({});
+
+ }
+
+ async intrinsicSizes(children, edges, styleMap) {
+
+ // Or request the intrinsic size!
+ const childIntrinsicSize = await children[0].intrinsicSizes();
+
+ }
+});
+
+
+
+A {{LayoutChild}} could be generated by:
+
+ - An [=element=].
+
+ - A [=root inline box=].
+
+ - A ::before or ::after pseudo-element.
+
+ Note: Other pseudo-elements such as ::first-letter or ::first-line do not
+ generate a {{LayoutChild}} for layout purposes. They are additional
+ styling information for a text node.
+
+ - An [=anonymous box=]. For example an anonymous box may be inserted as a result of:
+
+ - A text node which has undergone [=blockification=]. (Or more generally a [=root inline box=]
+ which has undergone [=blockification=]).
+
+ - An element with ''display: table-cell'' which doesn't have a parent with ''display: table''.
+
+
+ Note: As an example the following would be placed into three {{LayoutChild}}ren:
+
+ Note: As an example the following would be placed into a single {{LayoutChild}} as they share a
+ [=root inline box=]:
+
+ This is a next node, <span>with some additional styling,
+ that may</span> break over<br>multiple lines.
+
+
+
+Multiple non-[=atomic inlines=] are placed within the same {{LayoutChild}} to allow rendering
+engines to perform text shaping across element boundaries.
+
+
+ Note: As an example the following should produce one {{LayoutFragment}} but is from
+ three non-[=atomic inlines=]:
+
+ ع<span style="color: blue">ع</span>ع
+
+
+
+Note: When accessing the {{LayoutChild/styleMap}} the user agent can create a new
+ {{StylePropertyMapReadOnly}} if none exists yet.
+
+
+The styleMap, on getting from a {{LayoutChild}} |this|, the
+user agent must perform the following steps:
+
+ 1. If |this|' {{[[styleMap]]}} is null, then:
+
+ 1. Let |box| be |this|' {{LayoutChild/[[box]]}}.
+
+ 2. Let |definition| be the result of [=get a layout definition=].
+
+ 3. Let |childInputProperties| be |definition|'s [=layout definition/child input
+ properties=].
+
+ 4. Let |styleMap| be a new {{StylePropertyMapReadOnly}} populated with only the
+ [=computed values=] for properties listed in |childInputProperties| for |box|.
+
+ 5. Set |this|' {{LayoutChild/[[styleMap]]}} internal slot to |styleMap|.
+
+ Note: If the user agent always pre-populates {{LayoutChild/[[styleMap]]}} then this branch
+ of the algorithm won't be reached.
+
+ 2. Return |this|' {{StylePropertyMapReadOnly}} contained in the {{LayoutChild/[[styleMap]]}}
+ internal slot.
+
+
+Note: The {{intrinsicSizes()}} method allows the web developer to query the intrinsic sizes of the
+ {{LayoutChild}}.
+
+
+When the intrinsicSizes() method is called on a {{LayoutChild}}
+|this|, the user agent must perform the following steps:
+
+ 1. Let |p| be a new promise.
+
+ 2. Let |context| be the [=current layout's=] [=layout API context=].
+
+ 3. If |this|' {{LayoutChild/[[unique id]]}} is not equal to |context|'s [=unique id=], reject
+ |p| with a "{{InvalidStateError}}" {{DOMException}}, and abort all these steps.
+
+ Note: This is to ensure that only {{LayoutChild}}ren passed in as arguments to either the
+ layout or intrinsicSizes method are used.
+
+ 4. Let |task| be a new [=layout API work task=] with:
+
+ - [=layout api work task/layout child=] being |this|.
+
+ - [=layout api work task/task type=] being "intrinsic-sizes".
+
+ - [=layout api work task/promise=] being |p|.
+
+ 5. [=list/Append=] |task| to |context|'s [=work queue=].
+
+ 6. Return |p|.
+
+
+Note: The {{layoutNextFragment()}} method allows the web developer to produce a {{LayoutFragment}}
+ for a given {{LayoutChild}} (the result of performing layout).
+
+
+When the layoutNextFragment(|constraints|, |breakToken|) method is
+called on a {{LayoutChild}} |this|, the user agent must perform the following steps:
+
+ 1. Let |p| be a new promise.
+
+ 2. Let |context| be the [=current layout's=] [=layout API context=].
+
+ 3. If |this|' {{LayoutChild/[[unique id]]}} is not equal to |context|'s [=unique id=], reject
+ |p| with a "{{InvalidStateError}}" {{DOMException}}, and abort all these steps.
+
+ Note: This is to ensure that only {{LayoutChild}}ren passed in as arguments to either the
+ layout or intrinsicSizes method are used.
+
+ 4. If |breakToken|'s {{ChildBreakToken/[[unique id]]} is not equal to |context|'s [=unique id=],
+ reject |p| with a "{{InvalidStateError}}" {{DOMException}}, and abort all these steps.
+
+ 5. If |context|'s [=layout API context/mode=] is "intrinsic-sizes", reject |p| with
+ a "{{NotSupportedError}}" {{DOMException}}.
+
+ Note: This is to ensure that inside a intrinsicSizes callback,
+ {{LayoutChild/layoutNextFragment()}} cannot be called.
+
+ 6. Let |task| be a new [=layout API work task=] with:
+
+ - [=layout api work task/layout constraints=] being |constraints|.
+
+ - [=layout api work task/layout child=] being |this|.
+
+ - [=layout api work task/child break token=] being |breakToken|.
+
+ - [=layout api work task/task type=] being "layout".
+
+ - [=layout api work task/promise=] being |p|.
+
+ 7. [=list/Append=] |task| to |context|'s [=work queue=].
+
+ 8. Return |p|.
+
+
+### LayoutChildren and the Box Tree ### {#layout-child-box-tree}
+
+Each [=box=] has a \[[layoutChildMap]] internal slot, which is a
+[=map=] of {{LayoutWorkletGlobalScope}}s to {{LayoutChild}}ren.
+
+Note: [=Get a layout child=] returns a {{LayoutChild}} object for the correct
+ {{LayoutWorkletGlobalScope}} and creates one if it doesn't exist yet.
+
+
+When the user agent wants to get a layout child given |workletGlobalScope|, |name|,
+ |box|, and |uniqueId|, it must run the following steps:
+
+ 1. Assert that:
+ - |box| is currently attached to the [=box tree=].
+ - |box|'s [=containing block=] is a [=layout API container=].
+ - The [=containing block's=] ''layout()'' function's first argument is |name|.
+
+ 2. Let |layoutChildMap| be |box|'s {{[[layoutChildMap]]}}.
+
+ 3. If |layoutChildMap|[|workletGlobalScope|] does not exist, run the following
+ steps:
+
+ 1. Let |definition| be the result of [=get a layout definition=] given |name|, and
+ |workletGlobalScope|.
+
+ Assert that [=get a layout definition=] succeeded, and |definition| is not
+ "invalid".
+
+ 2. Let |childInputProperties| be |definition|'s child input
+ properties.
+
+ 3. Let |layoutChild| be a new {{LayoutChild}} with internal slot(s):
+ - {{LayoutChild/[[box]]}} set to |box|.
+ - {{[[styleMap]]}} set to a new {{StylePropertyMapReadOnly}} populated with
+ only the [=computed values=] for properties listed in
+ |childInputProperties|.
+
+ 4. Set |layoutChildMap|[|workletGlobalScope|] to |layoutChild|.
+
+ 4. Let |layoutChild| be the result of get |layoutChildMap|[|workletGlobalScope|].
+
+ 5. Set |layoutChild|'s {{LayoutChild/[[unique id]]}} internal slot to |uniqueId|.
+
+ 6. Return |layoutChild|.
+
+
+When a [=box=] is inserted into the [=box tree=] the user agent may pre-populate the
+{{[[layoutChildMap]]}} for all {{LayoutWorkletGlobalScope}}s.
+
+When a [=box=] is removed from the [=box tree=] the user agent must clear the
+{{[[layoutChildMap]]}}.
+
+The user agent must clear the {{[[layoutChildMap]]}} internal slot every 1000 layout
+passes.
+
+Note: The above rule exists to ensure that web developers do not rely on being able to store
+ non-regeneratable state on the {{LayoutChild}} object.
+ The 1000 limit was picked as a high upper bound, this limit may improve (downwards) over time.
+
+
+When the user agent wants to update a layout child style given |box|, it must
+run the following steps:
+
+ 1. Assert that:
+ - |box| is currently attached to the [=box tree=].
+
+ 2. If |box|'s [=containing block=] is not a [=layout API container=], abort all these
+ steps.
+
+ 3. Let |layoutChildMap| be |box|'s {{[[layoutChildMap]]}}.
+
+ 4. For each |layoutChild| in |layoutChildMap|:
+
+ 1. |layoutChild|'s {{[[styleMap]]}} to null.
+
+
+When the [=computed values=] of [=document layout definition/child input properties=] for a [=box=]
+changes the user agent must run the [=update a layout child style=] algorithm.
+
+Layout Fragments {#layout-fragments}
+------------------------------------
+
+A {{LayoutFragment}} represents a CSS [=fragment=] of a {{LayoutChild}} after layout has occurred on
+that child. This is produced by the {{LayoutChild/layoutNextFragment()}} method.
+
+
+
+The {{LayoutFragment}} has internal slot(s):
+ - [[unique id]] the [=unique id=] of the [=layout api
+ context=] which produced this child fragment. This slot is used so that a {{LayoutFragment}}
+ from a previous layout pass is invalid.
+
+
+
+The {{LayoutFragment}} has {{LayoutFragment/inlineSize}} and {{LayoutFragment/blockSize}}
+attributes, which are set by the respective child's layout algorithm. They represent the border
+box size of the CSS [=fragment=], and are relative to the [=current layout's=] writing mode.
+
+The {{LayoutFragment/inlineSize}} and {{LayoutFragment/blockSize}} attributes cannot be changed. If
+the [=current layout=] requires a different {{LayoutFragment/inlineSize}} or
+{{LayoutFragment/blockSize}} the author must perform {{LayoutChild/layoutNextFragment()}} again with
+different arguments in order to get different results.
+
+The author inside the current layout can position a resulting {{LayoutFragment}} by setting its
+{{LayoutFragment/inlineOffset}} and {{LayoutFragment/blockOffset}} attributes. If not set by the
+author they default to zero. The {{LayoutFragment/inlineOffset}} and {{LayoutFragment/blockOffset}}
+attributes represent the position of the {{LayoutFragment}} relative to its parent's border
+box, before transform or positioning (e.g. if a fragment is [=relatively positioned=]) has
+been applied.
+
+
+
+
+ A simple visualization showing positioning a {{LayoutFragment}} using
+ {{LayoutFragment/inlineOffset}} and {{LayoutFragment/blockOffset}} in different writing
+ modes.
+
+
+
+
+
+The example below shows the basic usage of a {{LayoutFragment}}.
+
+registerLayout('example-layout-fragment', class {
+ async layout(children, edges, constraints, styleMap) {
+
+ // You must perform layout to generate a fragment.
+ const fragment = await child.layoutNextFragment({});
+
+ // You can query the size of the fragment produced:
+ console.log(fragment.inlineSize);
+ console.log(fragment.blockSize);
+
+ // You can set the position of the fragment, e.g. this will set it to the
+ // top-left corner:
+ fragment.inlineOffset = edges.inlineStart;
+ fragment.blockOffset = edges.blockStart;
+
+ // Data may be passed from the child layout:
+ console.log(fragment.data);
+
+ // If the child fragmented, you can use the breakToken to produce the next
+ // fragment in the chain.
+ const nextFragment = await child.layoutNextFragment({}, fragment.breakToken);
+ }
+});
+
+
+
+A [=layout API container=] can communicate with other [=layout API containers=] by using the
+{{LayoutFragment/data}} attribute. This is set by the {{FragmentResultOptions/data}} member in the
+{{FragmentResultOptions}} dictionary.
+
+The {{LayoutFragment}}'s {{LayoutFragment/breakToken}} specifies where the {{LayoutChild}} last
+fragmented. If the {{LayoutFragment/breakToken}} is null the {{LayoutChild}} wont produce any more
+{{LayoutFragment}}s for that token chain. The {{LayoutFragment/breakToken}} can be passed to the
+{{LayoutChild/layoutNextFragment()}} function to produce the next {{LayoutFragment}} for a
+particular child. The {{LayoutFragment/breakToken}} cannot be changed.
+If the [=current layout=] requires a different {{LayoutFragment/breakToken}} the author must perform
+{{LayoutChild/layoutNextFragment()}} again with different arguments.
+
+Intrinsic Sizes {#intrinsic-sizes}
+----------------------------------
+
+
+
+A {{IntrinsicSizes}} object represents the [=min-content size=] and [=max-content size=] of a CSS
+[=box=]. It has {{IntrinsicSizes/minContentSize}} and {{IntrinsicSizes/maxContentSize}} attributes
+which represent the border box min/max-content contribution of the {{LayoutChild}} for the
+[=current layout=]. The attributes are relative to the inline direction of the [=current layout's=]
+writing mode.
+
+The {{IntrinsicSizes/minContentSize}} and {{IntrinsicSizes/maxContentSize}} cannot be changed. They
+must not change for a {{LayoutChild}} within the current layout pass.
+
+
+The example below shows the border-box intrinsic sizes of two children.
+
+
+registerLayout('intrinsic-sizes-example', class {
+ async intrinsicSizes(children, edges, styleMap) {
+ const childrenSizes = await Promise.all(children.map((child) => {
+ return child.intrinsicSizes();
+ }));
+
+ childrenSizes[0].minContentSize; // 400, (380+10+10) child has a fixed size.
+ childrenSizes[0].maxContentSize; // 400, (380+10+10) child has a fixed size.
+
+ childrenSizes[1].minContentSize; // 100, size of "XXXX".
+ childrenSizes[1].maxContentSize; // 200, size of "XXX XXXX".
+ }
+
+ layout() {}
+});
+
+
+
+Layout Constraints {#layout-constraints}
+----------------------------------------
+
+A {{LayoutConstraints}} object is passed into the layout method which represents the all the
+constraints for the [=current layout=] to perform layout within.
+
+
+
+The {{LayoutConstraints}} object has {{LayoutConstraints/availableInlineSize}} and
+{{LayoutConstraints/availableBlockSize}} attributes. This represents the [=available space=] for the
+[=current layout=] to respect.
+
+Note: Some layouts may need to produce a {{LayoutFragment}} which exceed this size. For example a
+ [=replaced element=]. The [=parent layout=] should expect this to occur and deal with it
+ appropriately.
+
+A [=parent layout=] may require the [=current layout=] to be exactly a particular size. If the
+{{LayoutConstraints/fixedInlineSize}} or {{LayoutConstraints/fixedBlockSize}} are specified the
+[=current layout=] should produce a {{LayoutFragment}} with a the specified size in the appropriate
+direction.
+
+The {{LayoutConstraints}} object has {{LayoutConstraints/percentageInlineSize}} and
+{{LayoutConstraints/percentageBlockSize}} attributes. These represent the size that percentages
+should be resolved against while performing layout.
+
+The {{LayoutConstraints}} has a {{LayoutConstraints/blockFragmentationType}} attribute. The
+[=current layout=] should produce a {{LayoutFragment}} which fragments at the
+{{LayoutConstraints/blockFragmentationOffset}} if possible.
+
+The [=current layout=] can choose not to fragment a {{LayoutChild}} based on the
+{{LayoutConstraints/blockFragmentationType}}, for example if the child has a property like
+''break-inside: avoid-page;''.
+
+
+The example below shows the basic usage of the {{LayoutConstraints}} object.
+
+
+// The class below is registered with a "block-like" sizingMode, and can use the fixedInlineSize,
+// fixedBlockSize attributes.
+registerLayout('layout-constraints-example', class {
+ async layout(children, edges, constraints, styleMap) {
+
+ // Calculate the available size.
+ const availableInlineSize = constraints.fixedInlineSize - edges.inline;
+ const availableBlockSize = constraints.fixedBlockSize ?
+ constraints.fixedBlockSize - edges.inline : null;
+
+ // Web developers should resolve any percentages against the percentage sizes.
+ const value = constraints.percentageInlineSize * 0.5;
+
+ }
+});
+
+
+
+
+
+The [=create a layout constraints object=] algorithm is used to create the {{LayoutConstraints}}
+object. Depending on the {{LayoutOptions/sizing}} it will either pre-calculate the
+{{LayoutConstraints/fixedInlineSize}} and {{LayoutConstraints/fixedBlockSize}} upfront.
+
+
+When the user agent wants to create a layout constraints object given |sizingMode|, |box|,
+and |internalLayoutConstraints|, it must run the following steps:
+
+ 1. If |sizingMode| is "block-like" then:
+
+ 1. Let |fixedInlineSize| be the result of calculating |box|'s border-box
+ [=inline size=] (relative to |box|'s writing mode) exactly like block containers do.
+
+ 2. Let |fixedBlockSize| be null if |box|'s [=block size=] is unable to be calculated at this
+ stage, (e.g. [=block size=] is ''height/auto''), otherwise the result of calculating
+ |box|'s border-box [=block size=] exactly like block containers do.
+
+ 3. Return a new {{LayoutConstraints}} object with:
+
+ - {{LayoutConstraints/fixedInlineSize}}, and {{LayoutConstraints/availableInlineSize}}
+ set to |fixedInlineSize|.
+
+ - {{LayoutConstraints/percentageInlineSize}} set to |internalLayoutConstraints|'
+ percentage resolution size in the inline axis (relative to |box|'s writing mode).
+
+ - {{LayoutConstraints/fixedBlockSize}} set to |fixedBlockSize|.
+
+ - {{LayoutConstraints/availableBlockSize}} set to |fixedBlockSize| if not null,
+ otherwise |internalLayoutConstraints|' [=available space=] in the block axis
+ (relative to |box|'s writing mode).
+
+ - {{LayoutConstraints/percentageBlockSize}} set to |internalLayoutConstraints|'
+ percentage resolution size in the block axis (relative to |box|'s writing mode).
+
+ 2. If |sizingMode| is "manual" then:
+
+ 1. Return a new {{LayoutConstraints}} object with:
+ - {{LayoutConstraints/fixedInlineSize}}/{{LayoutConstraints/fixedBlockSize}} set to
+ |internalLayoutConstraints|' fixed inline/block size (relative to |box|'s writing
+ mode) imposed by the [=parent layout=]. Either may be null.
+
+ Note: See [[#interaction-sizing]] for different scenarios when this can occur.
+
+ - {{LayoutConstraints/availableInlineSize}}/{{LayoutConstraints/availableBlockSize}} set
+ to |internalLayoutConstraints|' [=available space=].
+
+ - {{LayoutConstraints/percentageInlineSize}}/{{LayoutConstraints/percentageBlockSize}}
+ set to |internalLayoutConstraints|' percentage resolution size.
+
+
+### Constraints for Layout Children ### {#layout-constraints-children}
+
+The {{LayoutConstraintsOptions}} dictionary represents the set of constraints which can be passed to
+a {{LayoutChild}} to produce a {{LayoutFragment}}.
+
+
+
+Note: The [=translate a LayoutConstraintsOptions to internal constraints=] describes how to convert
+ a {{LayoutConstraintsOptions}} object into a user agents internal representation.
+
+
+When the user agent wants to translate a LayoutConstraintsOptions to internal constraints
+given |options|, it must run the following steps:
+
+ 1. Let the [=available space=] in the inline direction (with respect to the [=current layout=],
+ be the result of:
+
+ - If |options|' {{LayoutConstraintsOptions/availableInlineSize}} is not null, and
+ {{LayoutConstraintsOptions/availableInlineSize}} is greater than zero, let the result be
+ {{LayoutConstraintsOptions/availableInlineSize}}.
+
+ - Otherwhise, let the result be zero.
+
+ 2. Let the [=available space=] in the block direction (with respect to the [=current layout=]),
+ be the result of:
+
+ - If |options|' {{LayoutConstraintsOptions/availableBlockSize}} is not null, and
+ {{LayoutConstraintsOptions/availableBlockSize}} is greater than zero, let the result be
+ {{LayoutConstraintsOptions/availableBlockSize}}.
+
+ - Otherwhise, let the result be zero.
+
+ 3. Let the override size in the inline direction (with respect to the [=current layout=], be the
+ result of:
+
+ - Let the result be |options|' {{LayoutConstraintsOptions/fixedInlineSize}}.
+
+ Note: If the {{LayoutConstraintsOptions/fixedInlineSize}} is null, no override size is
+ applied.
+
+ 4. Let the override size in the block direction (with respect to the [=current layout=], be the
+ result of:
+
+ - Let the result be |options|' {{LayoutConstraintsOptions/fixedBlockSize}}.
+
+ Note: If the {{LayoutConstraintsOptions/fixedBlockSize}} is null, no override size is
+ applied.
+
+ 5. Let the percentage resultion size in the inline direction (with respect to the [=current
+ layout=], be the result of:
+
+ - If |options|' {{LayoutConstraintsOptions/percentageInlineSize}} is not null, and
+ {{LayoutConstraintsOptions/percentageInlineSize}} is greater than zero, let the result
+ be {{LayoutConstraintsOptions/percentageInlineSize}}.
+
+ - If |options|' {{LayoutConstraintsOptions/availableInlineSize}} is not null, and
+ {{LayoutConstraintsOptions/availableInlineSize}} is greater than zero, let the result be
+ {{LayoutConstraintsOptions/availableInlineSize}}.
+
+ - Otherwhise, let the result be zero.
+
+ 6. Let the percentage resultion size in the block direction (with respect to the [=current
+ layout=], be the result of:
+
+ - If |options|' {{LayoutConstraintsOptions/percentageBlockSize}} is not null, and
+ {{LayoutConstraintsOptions/percentageBlockSize}} is greater than zero, let the result
+ be {{LayoutConstraintsOptions/percentageBlockSize}}.
+
+ - If |options|' {{LayoutConstraintsOptions/availableBlockSize}} is not null, and
+ {{LayoutConstraintsOptions/availableBlockSize}} is greater than zero, let the result be
+ {{LayoutConstraintsOptions/availableBlockSize}}.
+
+ - Otherwhise, let the result be zero.
+
+ 7. If the [=child layout=] is a [=layout API container=], then let the store the data (passed by
+ {{LayoutConstraints/data}}) be the result of:
+
+ - Invoking [=StructuredSerializeForStorage=] on |options|'
+ {{LayoutConstraintsOptions/data}}.
+
+
+
+The example below shows the basic usage of the {{LayoutConstraintsOptions}} dictionary.
+
+
+// The class below is registered with a "block-like" sizingMode, and can use the
+// fixedInlineSize, fixedBlockSize attributes.
+registerLayout('child-layout-constraints-example', class {
+ async layout(children, edges, constraints, styleMap) {
+
+ // The call below gives the child an "available" space. It will try and
+ // fit within this.
+ const fragment = children[0].layoutNextFragment({
+ availableInlineSize: 100,
+ availableBlockSize: 200,
+ });
+
+ // The call below gives the child a "fixed" size, it will be forced to
+ // this size ignoring any style set.
+ const fragment = children[0].layoutNextFragment({
+ fixedInlineSize: 20,
+ fixedBlockSize: 30,
+ });
+
+ }
+});
+
+
+
+Breaking and Fragmentation {#breaking-and-fragmentation}
+--------------------------------------------------------
+
+A {{LayoutChild}} can produce multiple {{LayoutFragment}}s. A {{LayoutChild}} may fragment in the
+block direction if a {{LayoutConstraints/blockFragmentationType}} is not none. Additionally
+{{LayoutChild}} which represents [=inline-level=] content, may fragment line by line if the
+layout options' {{LayoutOptions/childDisplay}} (set by
+layoutOptions) is "normal".
+
+
+
+The {{ChildBreakToken}} has internal slot(s):
+ - [[unique id]] the [=unique id=] of the [=layout api
+ context=] which produced this child break token. This slot is used so that a
+ {{ChildBreakToken}} from a previous layout pass is invalid.
+
+
+
+A subsequent {{LayoutFragment}} is produced by using the previous {{LayoutFragment}}'s
+{{LayoutFragment/breakToken}}. This tells the [=child layout=] to produce a {{LayoutFragment}}
+starting at the point encoded in the {{ChildBreakToken}}.
+
+Edges {#edges}
+--------------
+
+A {{LayoutEdges}} object is passed into the layout method. This represents the sum of all the [=box
+model edges=] (border, scrollbar, padding), for the current box which is being laid out.
+
+
+[Exposed=LayoutWorklet]
+interface LayoutEdges {
+ readonly attribute double inlineStart;
+ readonly attribute double inlineEnd;
+
+ readonly attribute double blockStart;
+ readonly attribute double blockEnd;
+
+ // Convenience attributes for the sum in one direction.
+ readonly attribute double inline;
+ readonly attribute double block;
+};
+
+
+Each of the accessors represents the width in CSS pixels of an edge in each of the [=abstract
+dimensions=] ({{LayoutEdges/inlineStart}}, {{LayoutEdges/inlineEnd}}, {{LayoutEdges/blockStart}},
+{{LayoutEdges/blockEnd}}).
+
+The {{LayoutEdges/inline}}, and {{LayoutEdges/block}} on the {{LayoutEdges}} object are convenience
+attributes which represent the sum in that direction.
+
+
+
+
+ A visualization showing the {{LayoutEdges}} object for a [=box=] with different sized
+ scrollbar, border, and padding.
+
+
+
+
+This example shows an node styled by CSS, and what its respective {{LayoutEdges}} could contain.
+
+
+registerLayout('box-edges', class {
+ async layout(children, edges, constraints, styleMap, breakToken) {
+ edges.inlineStart; // 2 + 5 (as 10% * 50px = 5px).
+ edges.blockEnd; // 7 (2 + 5)
+ edges.inlineEnd; // UA-dependent, due to scrollbar.
+ // Could be 2 + 5 + 0 or 2 + 5 + 16 for example.
+ edges.block; // 14 (2 + 5 + 5 + 2).
+ }
+}
+
+
+
+Interactions with other Modules {#interactions-with-other-modules}
+==================================================================
+
+This section describes how other CSS modules interact with the CSS Layout API.
+
+Sizing {#interaction-sizing}
+----------------------------
+
+User agents must use the {{LayoutConstraints}} object to communicate to the [=current layout=] the
+size they would like the fragment to be.
+
+If the user agent wishes to force a size on the box, it can use the
+{{LayoutConstraints/fixedInlineSize}} and {{LayoutConstraints/fixedBlockSize}} attributes to do so.
+
+The [=layout API container=] can be passed size information in different ways depending on the value
+of layout options' {{LayoutOptions/sizing}} (set by
+layoutOptions on the class).
+
+If the value of layout options' {{LayoutOptions/sizing}} is
+"block-like", then the {{LayoutConstraints}} passed to the [=layout API container=]:
+ - Must calculate and set {{LayoutConstraints/fixedInlineSize}} based off the rules
+ specified in [[!css-sizing-3]] and the formatting context in which it participates, e.g.
+
+ - As a [=block-level=] box in a [=block formatting context=], it is sized like a [=block
+ box=] that establishes a formatting context, with an ''width/auto'' [=inline size=]
+ calculated as for non-replaced block boxes.
+
+ - As an [=inline-level=] box in an [=inline formatting context=], it is sized as an atomic
+ inline-level box (such as an inline-block).
+
+ - Must calculate and set {{LayoutConstraints/fixedBlockSize}} based off the rules
+ specified in [[!css-sizing-3]], and the formatting context in which it participates. If the
+ [=layout API container=] has an ''height/auto'' [=block size=], and cannot be determined
+ ahead of time, {{LayoutConstraints/fixedBlockSize}} must be set to null.
+
+If the value of layout options' {{LayoutOptions/sizing}} is
+"manual", then the user-agent must not pre-calculate
+{{LayoutConstraints/fixedInlineSize}} and/or {{LayoutConstraints/fixedBlockSize}} ahead of time,
+except when it is being forced to a particular size by the formatting context in which it
+participates, for example:
+
+ - If the [=layout API container=] is within a [=block formatting context=], is inflow, and has
+ an ''width/auto'' inline size, the user agent must set the
+ {{LayoutConstraints/fixedInlineSize}} to the [=stretch-fit inline size=].
+
+
+ Note: In the example below the [=layout API container=] has its inline size set to 50.
+
+
+
+### Positioned layout sizing ### {#interaction-sizing-positiong-layout}
+
+If a [=layout API container=] is out-of-flow positioned the user agent must solve the
+positioned size equations ([[css-position-3#abs-non-replaced-width]],
+[[css-position-3#abs-non-replaced-height]]), and set the appropriate
+{{LayoutConstraints/fixedInlineSize}} and {{LayoutConstraints/fixedBlockSize}}.
+
+
+ Note: In the example below the [=layout API container=] has its inline and block size fixed to
+ 80.
+
+
+
+Positioning {#interaction-positioning}
+--------------------------------------
+
+All positioning in this level of the specification is handled by the user agent.
+
+As a result:
+ - Out-of-flow children do not appear as {{LayoutChild}}ren.
+
+ - [=Layout API containers=] establish [=containing blocks=] exactly like block
+ containers do. [[!CSS21]]
+
+ - The {{LayoutFragment/inlineOffset}} and {{LayoutFragment/blockOffset}} represent the position of
+ the fragment before any positioning and transforms have occured.
+
+ - The [=static position=] of an absolutely-positioned child of a [=layout API container=] is set
+ to the [=inline-start=], [=block-start=] padding edge of the [=layout API container=]. Auto
+ margins are treated as zero for the child.
+
+
+ Note: In the example below:
+ - "child-relative" would be the only child passed to the author's layout. If it was positioned
+ at ({{LayoutFragment/inlineOffset}} = 20, {{LayoutFragment/blockOffset}}
+ = 30), its final position would be (25, 40) as the
+ relative positioning was handled by the user agent.
+
+ - "child-absolute" would not appear as a {{LayoutChild}}, and instead would be laid out and
+ positioned by the user agent.
+
+ - The examples above also apply in a similar way to sticky and fixed positioned children.
+
+
+
+Overflow {#interaction-overflow}
+--------------------------------
+
+The [=scrollable overflow=] for a [=layout API container=] is handled by the user agent in this
+level of the specification.
+
+A [=layout API container=] should calculate its scrollable overflow exactly like block containers
+do.
+
+Even if the author's [=layout API container=] positions a fragment into the [=scrollable overflow=]
+region, relative positioning or transforms may cause the fragment to shift such that its
+[=scrollable overflow=] region, causing no overflow to occur.
+
+Fragmentation {#interaction-fragmentation}
+------------------------------------------
+
+A [=parent layout=] can ask the [=current layout=] to [=fragment=] by setting the
+{{LayoutConstraints/blockFragmentationType}} and {{LayoutConstraints/blockFragmentationOffset}}.
+
+E.g. [[css-multicol-1]] layout would set a {{LayoutConstraints/blockFragmentationType}} to
+"column" and set the {{LayoutConstraints/blockFragmentationOffset}} to where it needs the
+child to fragment.
+
+Alignment {#interaction-alignment}
+----------------------------------
+
+The first/last baseline sets of a [=layout API container=] is generated exactly like block
+containers do (see [[css-align-3#baseline-export]]). Except that the order of the in-flow children
+should be determined by the in which they are returned form the layout method (via
+{{FragmentResultOptions/childFragments}}) instead of the document order.
+
+
+Note: In a future level of the specification there will be the ability for the author to define the
+baselines themselves. This will be of the form:
+
+To query baseline information from a {{LayoutChild}}.
+
+
+To produce baseline information for a [=parent layout=]:
+
+registerLayout('baseline-producing', class {
+ async layout(children, edges, constraints, styleMap) {
+ const result = {baselines: {}};
+
+ for (let baselineRequest of constraints.baselineRequests) {
+ // baselineRequest === 'alphabetic', or something else.
+ result.baselines[baselineRequest] = 25;
+ }
+
+ return result;
+ }
+});
+
+
+
+Layout {#layout}
+================
+
+This section describes how the CSS Layout API interacts with the user agent's layout engine.
+
+Processing Model {#processing-model}
+------------------------------------
+
+A layout API work task is a [=struct=] which describes the information needed by the user
+agent layout engine to perform layout work. It consists of:
+
+ - layout constraints a {{LayoutConstraintsOptions}}.
+
+ - layout child a {{LayoutChild}}.
+
+ - child break token a {{ChildBreakToken}}.
+
+ - task type which is either "layout", or
+ "intrinsic-sizes"
+
+ - promise a promise object.
+
+A layout API context is a [=struct=] which describes the information needed by the
+[=current layout=] to produce either a fragment or determine the intrinsic-sizes for a [=box=]. It
+consits of:
+
+ - work queue which is a [=list=] of [=layout API work
+ tasks=]. The user agent will alternate between processing these tasks, and running the
+ microtask queue.
+
+ - unique id a internal unique identifier. This is used for
+ determining that objects exposed to the web developer are only used within the correct
+ layout pass. E.g. {{LayoutFragment}}s returned in the {{FragmentResultOptions}} dictionary
+ belong to the current layout pass.
+
+ - mode which is either "layout", or
+ "intrinsic-sizes". This is used for determining what the user agent layout
+ engine is producing, and if a call to {{LayoutChild/layoutNextFragment()}} is valid.
+
+
+When the user agent wants to create a layout API context given |mode|, it must
+run the following steps:
+
+ 1. Return a new [=layout API context=] with:
+
+ - [=work queue=] being a new [=list/empty=] [=list=].
+
+ - [=unique id=] being a unique id.
+
+ - [=mode=] being |mode|.
+
+
+
+Performing Layout {#performing-layout}
+--------------------------------------
+
+The section describes how a user agent calls the web developer defined layout to produces intrinsic
+sizes, and fragments.
+
+
+// This is the final return value from the author defined layout() method.
+dictionary FragmentResultOptions {
+ double inlineSize = 0;
+ double blockSize = 0;
+ double autoBlockSize = 0;
+ sequence<LayoutFragment> childFragments = [];
+ any data = null;
+ BreakTokenOptions breakToken = null;
+};
+
+[Exposed=LayoutWorklet]
+interface FragmentResult {
+ constructor(optional FragmentResultOptions options = {});
+ readonly attribute double inlineSize;
+ readonly attribute double blockSize;
+};
+
+dictionary IntrinsicSizesResultOptions {
+ double maxContentSize;
+ double minContentSize;
+};
+
+
+The {{FragmentResult}} has internal slot(s):
+
+ - \[[box]] a CSS [=box=].
+
+ - [[inline size]] the inline size of the resulting
+ fragment.
+
+ - [[block size]] the block size of the resulting
+ fragment.
+
+ - [[child fragments]] the list of child fragments.
+
+ - \[[data]] some optional serialized data.
+
+ - [[internal break token]] an internal representation of
+ the break information for this fragment.
+
+ - [[unique id]] the [=unique id=] of the current
+ [=layout api context=]. This slot is used so that a {{FragmentResult}} used outside the
+ current layout pass is invalid.
+
+
+
+The web developer defined layout method can return either a {{FragmentResultOptions}} or a
+{{FragmentResult}}. The {{FragmentResult}} can be used for determining the final size of the
+fragment or detecting if the provided {{FragmentResultOptions}} would result in triggering a
+fallback to [=flow layout=].
+
+
+This example show the web developer using the {{FragmentResult}} instead of just returning the
+{{FragmentResultOptions}} object.
+
+
+registerLayout('feature-detection', class {
+ async layout(children, edges, constraints, styleMap, breakToken) {
+
+ let result;
+ try {
+ result = new FragmentResult({
+ childFragments: [],
+ autoBlockSize: 100
+ });
+ } catch (e) {
+ // The above call may throw, if the dictionary was just returned, it
+ // would fallback to flow layout.
+ }
+
+ // The web developer can test what size the fragment will be.
+ result.blockSize;
+
+ // Instead of returning the dictionary, we can just return this object.
+ return result;
+ }
+}
+
+
+
+
+The inlineSize, on getting from a {{FragmentResult}} |this|,
+the user agent must perform the following steps:
+
+ 1. Return |this|' {{FragmentResult/[[inline size]]}} internal slot.
+
+
+
+The blockSize, on getting from a {{FragmentResult}} |this|,
+the user agent must perform the following steps:
+
+ 1. Return |this|' {{FragmentResult/[[block size]]}} internal slot.
+
+
+
+When the FragmentResult(|options|) constructor is called,
+the user agent must perform the following steps:
+
+ 1. Let |context| be the [=current layout's=] [=layout API context=].
+
+ 2. Return the result of [=construct a fragment result=] given |context|, and |options|.
+
+
+
+Note: The [=construct a fragment result=] algorithm performs a series of validation checks (the
+ web developer isn't using an object from a previous invocation, and determines the final size of
+ the resulting fragment.
+
+
+When the user agent wants to construct a fragment result given |context|, and |options|
+the user agent must perform the following steps:
+
+ 1. Let |uniqueId| be |context|'s [=unique id=].
+
+ 2. Let |box| be the [=current layout's=] [=box=].
+
+ 3. Let |breakTokenOptions| be |options|'s {{FragmentResultOptions/breakToken}}.
+
+ 4. [=list/For each=] |childFragment| in |options|'s {{FragmentResultOptions/childFragments}},
+ perform the following stubsteps:
+
+ 1. If |childFragment|'s {{LayoutFragment/[[unique id]]}} internal slot is not equal to
+ |uniqueId|, then [=throw=] a [=TypeError=], and abort all these steps.
+
+ 5. [=list/For each=] |childBreakToken| in |breakTokenOptions|'s
+ {{BreakTokenOptions/childBreakTokens}}, perform the following stubsteps:
+
+ 1. If |childBreakToken|'s {{ChildBreakToken/[[unique id]]}} internal slot is not equal to
+ |uniqueId|, then [=throw=] a [=TypeError=], and abort all these steps.
+
+ 6. If |sizingMode| is "block-like":
+
+ - Then:
+
+ 1. Let |inlineSize| be the result of calculating |box|'s border-box [=inline
+ size=] (relative to |box|'s writing mode) exactly like block containers do.
+
+ 2. Let |blockSize| be the result of calculating |box|'s border-box
+ [=block size=] (relative to |box|'s writing mode) exactly like block containers do,
+ given |fragment|'s {{FragmentResultOptions/autoBlockSize}} as the "intrinsic
+ block size".
+
+ - Otherwise (|sizingMode| is "manual"):
+
+ 1. Let |inlineSize| be |fragment|'s {{FragmentResultOptions/inlineSize}}.
+
+ 2. Let |blockSize| be |fragment|'s {{FragmentResultOptions/blockSize}}.
+
+ 7. Let |clonedData| be the result of invoking [=StructuredSerializeForStorage=] on |options|'s
+ {{FragmentResultOptions/data}}.
+
+ 8. Let |clonedBreakTokenData| be the result of invoking [=StructuredSerializeForStorage=] on
+ |breakTokenOptions|'s {{BreakTokenOptions/data}}.
+
+ 9. Let |internalBreakToken| be the internal representation of the [=fragmentation break=]
+ containing |clonedBreakTokenData|, and |breakTokenOptions|.
+
+ 10. Return a new {{FragmentResult}} with:
+
+ - {{FragmentResult/[[box]]}} being |box|.
+
+ - {{FragmentResult/[[inline size]]}} being |inlineSize|.
+
+ - {{FragmentResult/[[block size]]}} being |blockSize|.
+
+ - {{FragmentResult/[[child fragments]]}} being |options|'s
+ {{FragmentResultOptions/childFragments}}.
+
+ - {{FragmentResult/[[data]]}} being |clonedData|.
+
+ - {{FragmentResult/[[internal break token]]}} being |internalBreakToken|.
+
+ - {{FragmentResult/[[unique id]]}} being |uniqueId|.
+
+
+
+### Determining Intrinsic Sizes ### {#determining-intrinsic-sizes}
+
+The [=determine the intrinsic sizes=] algorithm defines how a user agent is to query the author
+defined layout for a [=box's=] [=intrinsic sizes=] information.
+
+Note: The [=determine the intrinsic sizes=] algorithm allows for user agents to cache an arbitrary
+ number of previous invocations to reuse.
+
+
+When the user agent wants to determine the intrinsic sizes of a [=layout API formatting
+context=] for a given |box|, |childBoxes| it must run the following steps:
+
+ 1. Let |layoutFunction| be the ''layout()'' for the [=computed value=] of <> for
+ |box|.
+
+ 2. Let |name| be the first argument of the |layoutFunction|.
+
+ 3. Let |documentDefinition| be the result of [=get a document layout definition=] given |name|.
+
+ If [=get a document layout definition=] returned failure, or if |documentDefinition| is
+ "invalid", then let |box| fallback to the [=flow layout=] and abort all these
+ steps.
+
+ 4. Let |workletGlobalScope| be a {{LayoutWorkletGlobalScope}} from the list of [=worklet's
+ WorkletGlobalScopes=] from the layout {{Worklet}}, following the rules defined in
+ [[#global-scope-selection]].
+
+ The user agent may also [=create a WorkletGlobalScope=] at this time, given the
+ layout {{Worklet}}.
+
+ 5. Run [=invoke an intrinsic sizes callback=] given |name|, |box|, |childBoxes|, and
+ |workletGlobalScope| optionally [=in parallel=].
+
+ Note: If the user agent runs [=invoke an intrinsic sizes callback=] on a thread [=in
+ parallel=], it should select a layout worklet global scope which can be used on that
+ thread.
+
+
+
+When the user agent wants to invoke an intrinsic sizes callback given |name|, |box|,
+|childBoxes|, and |workletGlobalScope|, it must run the following steps:
+
+ 1. Let |definition| be the result of [=get a layout definition=] given |name|, and
+ |workletGlobalScope|.
+
+ If [=get a layout definition=] returned failure, let the |box| fallback to the [=flow
+ layout=] and abort all these steps.
+
+ 2. Let |layoutInstance| be the result of [=get a layout class instance=] given |box|,
+ |definition|, |workletGlobalScope|.
+
+ If [=get a layout class instance=] returned failure, let the |box| fallback to the [=flow
+ layout=] and abort all these steps.
+
+ 3. Let |inputProperties| be |definition|'s [=layout definition/input properties=].
+
+ 4. Let |children| be a new [=list=].
+
+ 5. [=list/For each=] |childBox| in |childBoxes| perform the following substeps:
+
+ 1. Let |layoutChild| be the result of [=get a layout child=] given |workletGlobalScope|,
+ |name|, |childBox|, and |context|'s [=unique id=].
+
+ 2. [=list/Append=] |layoutChild| to |children|.
+
+ 6. Let |edges| be a new {{LayoutEdges}} populated with the [=computed value=] for all the [=box
+ model edges=] for |box|.
+
+ 7. Let |styleMap| be the result of [=get a style map=] given |box|, and |inputProperties|.
+
+ 8. At this stage the user agent may re-use the [=intrinsic sizes=] from a previous invocation if
+ |children|, |edges|, and |styleMap| are equivalent to that previous invocation. If so let
+ the intrinsic sizes the cached intrinsic sizes and abort all these steps.
+
+ 9. Let |context| be the result of [=create a layout API context=] given
+ "intrinsic-sizes".
+
+ 10. Let |intrinsicSizesFunction| be |definition|'s [=intrinsic sizes function=].
+
+ 11. Let |value| be the result of [=Invoke=](|intrinsicSizesFunction|, |layoutInstance|,
+ «|children|, |edges|, |styleMap|»).
+
+ If an exception is [=thrown=] the let |box| fallback to the [=flow layout=] and abort all
+ these steps.
+
+ 12. If |value| is a promise:
+
+ - Then:
+
+ 1. Let |intrinsicSizesValue| be the result of [=run a work queue=] given |value|, and
+ |context|'s [=work queue=].
+
+ If [=run a work queue=] returns failure, let the |box| fallback to the [=flow
+ layout=] and abort all these steps.
+
+ - Otherwise:
+
+ 1. Let |intrinsicSizesValue| be |value|.
+
+ 13. Let |intrinsicSizes| be the result of [=converting=] |intrinsicSizesValue| to a
+ {{IntrinsicSizesResultOptions}}. If an exception is [=thrown=], let |box| fallback to the
+ [=flow layout=] and abort all these steps.
+
+ 14. Set the [=intrinsic sizes=] of |box|:
+
+ - Let |intrinsicSizes|'s {{IntrinsicSizesResultOptions/minContentSize}} be the [=min-content
+ size=] of |box|.
+
+ - Let |intrinsicSizes|'s {{IntrinsicSizesResultOptions/maxContentSize}} be the [=max-content
+ size=] of |box|.
+
+
+### Generating Fragments ### {#generating-fragments}
+
+The [=generate a fragment=] algorithm defines how a user agent is to generate a [=box's=]
+[=fragment=] for an author defined layout.
+
+Note: The [=generate a fragment=] algorithm allows for user agents to cache an arbitrary number of
+ previous invocations to reuse.
+
+
+When the user agent wants to generate a fragment of a [=layout API formatting context=]
+for a given |box|, |childBoxes|, |internalLayoutConstraints|, and an optional |internalBreakToken|
+it must run the following steps:
+
+ 1. Let |layoutFunction| be the ''layout()'' for the [=computed value=] of <> for
+ |box|.
+
+ 2. Let |name| be the first argument of the |layoutFunction|.
+
+ 3. Let |documentDefinition| be the result of [=get a document layout definition=] given |name|.
+
+ If [=get a document layout definition=] returned failure, or if |documentDefinition| is
+ "invalid", then let |box| fallback to the [=flow layout=] and abort all these
+ steps.
+
+ 4. Let |workletGlobalScope| be a {{LayoutWorkletGlobalScope}} from the list of [=worklet's
+ WorkletGlobalScopes=] from the layout {{Worklet}}, following the rules defined in
+ [[#global-scope-selection]].
+
+ The user agent may also [=create a WorkletGlobalScope=] at this time, given the
+ layout {{Worklet}}.
+
+ 5. Run [=invoke a layout callback=] given |name|, |box|, |childBoxes|,
+ |internalLayoutConstraints|, |internalBreakToken|, and |workletGlobalScope| optionally [=in
+ parallel=].
+
+ Note: If the user agent runs [=invoke a layout callback=] on a thread [=in parallel=], it
+ should select a layout worklet global scope which can be used on that thread.
+
+
+
+When the user agent wants to invoke a layout callback given |name|, |box|, |childBoxes|,
+|internalLayoutConstraints|, |internalBreakToken|, and |workletGlobalScope|, it must run the
+following steps:
+
+ 1. Let |definition| be the result of [=get a layout definition=] given |name|, and
+ |workletGlobalScope|.
+
+ If [=get a layout definition=] returned failure, let the |box| fallback to the [=flow
+ layout=] and abort all these steps.
+
+ 2. Let |layoutInstance| be the result of [=get a layout class instance=] given |box|,
+ |definition|, |workletGlobalScope|.
+
+ If [=get a layout class instance=] returned failure, let the |box| fallback to the [=flow
+ layout=] and abort all these steps.
+
+ 3. Let |context| be the result of [=create a layout API context=] given "layout".
+
+ 4. Let |sizingMode| be |definition|'s layout options'
+ {{LayoutOptions/sizing}} property.
+
+ 5. Let |inputProperties| be |definition|'s input properties.
+
+ 6. Let |children| be a new [=list=].
+
+ 7. For each |childBox| in |childBoxes| perform the following substeps:
+
+ 1. Let |layoutChild| be the result of [=get a layout child=] given |workletGlobalScope|,
+ |name|, |childBox|, and |context|'s [=unique id=].
+
+ 2. Append |layoutChild| to |children|.
+
+ 8. Let |edges| be a new {{LayoutEdges}} populated with the [=computed value=] for all the [=box
+ model edges=] for |box|.
+
+ 9. Let |layoutConstraints| be the result of [=create a layout constraints object=] given
+ |internalLayoutConstraints|, |box|, and |sizingMode|.
+
+ 10. Let |styleMap| be the result of [=get a style map=] given |box|, and |inputProperties|.
+
+ 11. Let |breakToken| be a new {{BreakToken}} populated with the appropriate information from
+ |internalBreakToken|.
+
+ If |internalBreakToken| is null, let |breakToken| be null.
+
+ 12. At this stage the user agent may re-use a [=fragment=] from a previous invocation if
+ |children|, |styleMap|, |layoutConstraints|, |breakToken| are equivalent to that previous
+ invocation. If so let the fragment output be that cached fragment and abort all these steps.
+
+ 13. Let |layoutFunction| be |definition|'s [=layout function=].
+
+ 14. Let |value| be the result of [=Invoke=](|layoutFunction|, |layoutInstance|, «|children|,
+ |edges|, |layoutConstraints|, |styleMap|, |breakToken|»).
+
+ If an exception is [=thrown=] the let |box| fallback to the [=flow layout=] and abort all
+ these steps.
+
+ 15. If |value| is a promise:
+
+ - Then:
+
+ 1. Let |fragmentResultValue| be the result of [=run a work queue=] given |value|.
+
+ If [=run a work queue=] returns failure, let the |box| fallback to the [=flow
+ layout=] and abort all these steps.
+
+ - Otherwise:
+
+ 1. Let |fragmentResultValue| be |value|.
+
+ 16. If |fragmentResultValue| is a [=platform object=]:
+
+ - Then:
+
+ 1. Let |fragmentResult| be the result [=converting=] |fragmentResultValue| to a
+ {{FragmentResult}}.
+
+ If an exception is [=thrown=], let |box| fallback to the [=flow layout=] and abort all
+ these steps.
+
+ - Otherwise:
+
+ 1. Let |fragmentResultOptions| be the result of [=converting=] |fragmentResultValue| to
+ a {{FragmentResultOptions}}.
+
+ If an exception is [=thrown=], let |box| fallback to the [=flow layout=] and abort
+ all these steps.
+
+ 2. Let |fragmentResult| be the result of [=construct a fragment result=] given
+ |fragmentResultOptions|.
+
+ If an exception is [=thrown=], let |box| fallback to the [=flow layout=] and abort
+ all these steps.
+
+ 17. Return an internal representation of a [=fragment=] with:
+
+ - The [=inline size=] set to |fragmentResult|'s {{FragmentResult/[[inline size]]}}.
+
+ - The [=block size=] set to |fragmentResult|'s {{FragmentResult/[[inline size]]}}.
+
+ - The child fragments set to |fragmentResult|'s {{FragmentResult/[[child fragments]]}}.
+
+ The ordering is important as this dictates their paint order (described in
+ [[#layout-api-containers]]). Their position relative to the border box of the
+ fragment should be based off the author specified {{LayoutFragment/inlineOffset}} and
+ {{LayoutFragment/blockOffset}}.
+
+ - The [=fragmentation break=] information set to |fragmentResult|'s
+ {{FragmentResult/[[internal break token]]}}.
+
+ - Store |fragmentResult|'s {{FragmentResult/[[data]]}} with the [=fragment=].
+
+
+
+### Global Scope Selection ### {#global-scope-selection}
+
+When the user agent needs to select a {{LayoutWorkletGlobalScope}} from the layout [=worklet's
+WorkletGlobalScopes=] [=list=] it must:
+
+ - Select from at least two {{LayoutWorkletGlobalScope}}s, unless the user agent is
+ under memory constraints.
+
+ - Not re-use the same {{LayoutWorkletGlobalScope}} more than 1000 times in a row.
+
+ Note: The 1000 limit was picked as a high upper bound, this limit may improve (downwards)
+ over time.
+
+Note: These rules exist to ensure that authors do not rely on being able to store state on the
+ global object or non-regeneratable state on the class. See [[worklets-1#code-idempotency]].
+
+### Utility Algorithms ### {#utility-algorithms}
+
+The section specifies algorithms common to the [=determine the intrinsic sizes=] and [=generate
+a fragment=] algorithms.
+
+Note: [=Get a document layout definition=] returns a [=document layout definition=] from the
+ owning [=document=].
+
+
+When the user agent wants to get a document layout definition given |name|, it
+must run the following steps:
+
+ 1. Let |documentLayoutDefinitionMap| be the associated [=document's=] [=document layout
+ definitions=] map.
+
+ 2. If |documentLayoutDefinitionMap|[|name|] does not exist, return failure and
+ abort all these steps.
+
+ 3. Return the result of get |documentLayoutDefinitionMap|[|name|].
+
+
+Note: [=Get a layout definition=] returns a [=layout definition=] for a given
+ {{LayoutWorkletGlobalScope}}, it the desired definition doesn't exist it will "invalidate" the
+ [=document layout definition=], (so that the layout can't be used again), and return failure.
+
+
+When the user agent wants to get a layout definition given |name|, and
+|workletGlobalScope|, it must run the following steps:
+
+ 1. Let |layoutDefinitionMap| be |workletGlobalScope|'s [=layout definitions=] map.
+
+ 2. If |layoutDefinitionMap|[|name|] does not exist, run the following steps:
+
+ 1. [=Queue a task=] to run the following steps:
+
+ 1. Let |documentLayoutDefinitionMap| be the associated [=document's=] [=document layout
+ definition=] map.
+
+ 2. Set |documentLayoutDefinitionMap|[|name|] to "invalid".
+
+ 3. The user agent should log an error to the debugging console stating that a
+ class wasn't registered in all {{LayoutWorkletGlobalScope}}s.
+
+ 2. Return failure, and abort all these steps.
+
+ 3. Return the result of [=get=] |layoutDefinitionMap|[|name|].
+
+
+Note: [=Get a layout class instance=] returns an instance of the web developer provided class.
+ (Registered in {{registerLayout()}}). If one isn't present yet, it will create a new one. This
+ algorithm may fail, as the constructor may throw an exception.
+
+
+When the user agent wants to get a layout class instance given |box|, |definition|, and
+|workletGlobalScope|, it must run the following steps:
+
+ 1. Let |layoutClassInstanceMap| be |box|'s [=layout class instances=] map.
+
+ 2. Let |layoutInstance| be the result of [=get=] |layoutClassInstanceMap|[|workletGlobalScope|].
+ If |layoutInstance| is null, run the following steps:
+
+ 1. If the [=constructor valid flag=] on |definition| is false, then return failure and
+ abort all these steps.
+
+ 2. Let |layoutCtor| be the [=class constructor=] on |definition|.
+
+ 3. Let |layoutInstance| be the result of [=Construct=](|layoutCtor|).
+
+ If [=construct=] throws an exception, set the |definition|'s [=constructor valid flag=]
+ to false, then return failure and abort all these steps.
+
+ 4. Set |layoutClassInstanceMap|[|workletGlobalScope|] to |layoutInstance|.
+
+ 4. Return |layoutInstance|.
+
+
+
+When the user agent wants to get a style map given |box|, and |inputProperties|, it
+must run the following steps:
+
+ 1. If |box|'s [=styleMap=] is null, then:
+
+ 1. Let |styleMap| be a new {{StylePropertyMapReadOnly}} populated with only the
+ [=computed values=] for properties listed in |inputProperties| for |box|.
+
+ 2. Set |box|'s [=styleMap=] internal slot to |styleMap|.
+
+ 2. Return |box|'s {{StylePropertyMapReadOnly}} contained in the [=styleMap=] internal slot.
+
+
+
+[=Run a work queue=] is designed to allow user agents to work in both a single threaded, and
+multi-threaded environment.
+
+Note: [=Run a work queue=] processes [=layout api work task=]s enqueued with
+ {{LayoutChild/intrinsicSizes()}} and {{LayoutChild/layoutNextFragment()}}. It will continue
+ processing tasks until the promise from the web developers layout or intrinsicSizes method is
+ resolved, or the queue is empty after running the microtask queue.
+
+ The result of running the queue will either be the result of the layout or intrinsicSizes
+ method, or failure.
+
+
+When the user agent wants to run a work queue given |promise|, and |workQueue|, it
+must run the following steps:
+
+ 1. If |promise| is not a promise, return failure.
+
+ 2. [=While=] |workQueue| is not [=list/empty=], and |promise| is pending:
+
+ 1. [=list/For each=] |task| in |workQueue|:
+
+ 1. Let |layoutChild| be |task|'s [=layout api work task/layout child=].
+
+ 2. Let |box| be |layoutChild|'s [=box=] in the {{LayoutChild/[[box]]}} internal slot.
+
+ 3. Let |childPromise| be |task|'s [=layout api work task/promise=].
+
+ 2. If |task|'s [=layout api work task/task type=] is "layout",
+
+ - Then [=queue a task=], or run synchronously, the following substeps:
+
+ 1. Let |childConstraints| be |task|'s [=layout api work task/layout constraints=].
+
+ 2. Let |childBreakToken| be |task|'s [=layout api work task/child break token=].
+
+ 3. Let |targetRealm| be |layoutChild|'s [=Realm=].
+
+ 4. Let |internalFragment| be the result of the user agent producing a
+ [=fragment=] based on |box|, |childConstraints|, and |childBreakToken|.
+
+ Invoking [=translate a LayoutConstraintsOptions to internal constraints=]
+ given |childConstraints|, must be run to translate the given
+ {{LayoutConstraintsOptions}} into the internal constraints for the user
+ agent's layout engine.
+
+ 5. Let |fragment| be a new {{LayoutFragment}} with:
+
+ - {{LayoutFragment/inlineSize}} being |internalFragment|'s [=inline size=]
+ relative to the [=current layout's=] writing mode.
+
+ - {{LayoutFragment/blockSize}} being |internalFragment|'s [=block size=]
+ relative to the [=current layout's=] writing mode.
+
+ - {{LayoutFragment/inlineOffset}} initially set to 0.
+
+ - {{LayoutFragment/blockOffset}} initially set to 0.
+
+ - {{LayoutFragment/breakToken}} being a new {{ChildBreakToken}} representing
+ |internalFragment|'s internal break token, if any.
+
+ - If |internalFragment| has a |clonedData| object stored with it, let
+ {{LayoutFragment/data}} being the result of
+ [=StructuredDeserialize=](|clonedData|, |targetRealm|), otherwise null.
+
+ 6. Resolve |childPromise| with |fragment|.
+
+ - Otherwise [=queue a task=], or run synchronously, the following substeps:
+
+ 1. Let |internalIntrinsicSizes| be the result of the user agent calculating the
+ border box min/max content contribution of |box|.
+
+ 2. Let |intrinsicSizes| be a new {{IntrinsicSizes}} with:
+
+ - {{IntrinsicSizes/minContentSize}} being |internalIntrinsicSizes|'
+ border box min-content contribution, relative to the [=current
+ layout's=] writing mode.
+
+ - {{IntrinsicSizes/maxContentSize}} being |internalIntrinsicSizes|'s
+ border box max-content contribution, relative to the [=current
+ layout's=] writing mode.
+
+ 3. Resolve |childPromise| with |intrinsicSizes|.
+
+ 2. Wait (optionally [=in parallel=]) for all of the above tasks to complete.
+
+ Note: If the tasks were perform synchronously, then this step is a no-op.
+
+ 3. [=list/Empty=] |workQueue|.
+
+ 4. [=Perform a microtask checkpoint=].
+
+ 3. If |promise| isn't fulfilled (it is pending, or got rejected), return failure.
+
+ 4. Return the fulfilled value of |promise|.
+
+
+
+Examples {#examples}
+====================
+
+
+The layout algorithm below performs a block-like layout (positioning fragments sequentially in the
+block direction), while centering its children in the inline direction.
+
+
+The layout algorithm performs a flexbox-like distribution of spare space in the inline direction. It
+creates child layout constraints which specify that a child should be a fixed inline size.
+
+
+This example shows a simple layout which indents child fragments for a certain number of
+lines.
+
+This example also demonstrates using the previous {{LayoutFragment/breakToken}} of a
+{{LayoutFragment}} to produce the next fragment for the {{LayoutChild}}.
+
+It also demonstrates using the {{BreakToken}} to respect the {{LayoutConstraints}}'
+{{LayoutConstraints/blockFragmentationType}}, it resumes it layout from the previous {{BreakToken}}.
+It returns a {{FragmentResultOptions}} with a {{FragmentResultOptions/breakToken}} which is used to
+resume the layout.
+
+
+registerLayout('indent-lines', class {
+ static layoutOptions = {childDisplay: 'normal'};
+ static inputProperties = ['--indent', '--indent-lines'];
+
+ async layout(children, edges, constraints, styleMap, breakToken) {
+ // Determine our (inner) available size.
+ const availableInlineSize =
+ constraints.fixedInlineSize - edges.inline;
+ const availableBlockSize = constraints.fixedBlockSize ?
+ constraints.fixedBlockSize - edges.block : null;
+
+ // Detrermine the number of lines to indent, and the indent amount.
+ const indent = resolveLength(constraints, styleMap.get('--indent'));
+ let lines = styleMap.get('--indent-lines').value;
+
+ const childFragments = [];
+
+ let childBreakToken = null;
+ if (breakToken) {
+ childBreakToken = breakToken.childBreakTokens[0];
+
+ // Remove all the children we have already produced fragments for.
+ children.splice(0, children.indexOf(childBreakToken.child));
+ }
+
+ let blockOffset = edges.blockStart;
+ let child = children.shift();
+ while (child) {
+ const shouldIndent = lines-- > 0;
+
+ // Adjust the inline size for the indent.
+ const childAvailableInlineSize = shouldIndent ?
+ availableInlineSize - indent : availableInlineSize;
+
+ const childConstraints = {
+ availableInlineSize: childAvailableInlineSize,
+ availableBlockSize,
+ percentageInlineSize: availableInlineSize,
+ blockFragmentationType: constraints.blockFragmentationType,
+ };
+
+ const fragment = await child.layoutNextFragment(childConstraints,
+ childBreakToken);
+ childFragments.push(fragment);
+
+ // Position the fragment.
+ fragment.inlineOffset = shouldIndent ?
+ edges.inlineStart + indent : edges.inlineStart;
+ fragment.blockOffset = blockOffset;
+ blockOffset += fragment.blockSize;
+
+ // Check if we have gone over the block fragmentation limit.
+ if (constraints.blockFragmentationType != 'none' &&
+ blockOffset > constraints.blockSize) {
+ break;
+ }
+
+ if (fragment.breakToken) {
+ childBreakToken = fragment.breakToken;
+ } else {
+ // If a fragment doesn't have a break token, we move onto the
+ // next child.
+ child = children.shift();
+ childBreakToken = null;
+ }
+ }
+
+ const autoBlockSize = blockOffset + edges.blockEnd;
+
+ // Return our fragment.
+ const result = {
+ autoBlockSize,
+ childFragments: childFragments,
+ }
+
+ if (childBreakToken) {
+ result.breakToken = {
+ childBreakTokens: [childBreakToken],
+ };
+ }
+
+ return result;
+ }
+});
+
+
+
+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.
diff --git a/css-layout-api/README.md b/css-layout-api/README.md
new file mode 100644
index 00000000..93e86dde
--- /dev/null
+++ b/css-layout-api/README.md
@@ -0,0 +1 @@
+See [EXPLAINER](EXPLAINER.md).
diff --git a/css-layout-api/README_old.md b/css-layout-api/README_old.md
new file mode 100644
index 00000000..c4226ad1
--- /dev/null
+++ b/css-layout-api/README_old.md
@@ -0,0 +1,530 @@
+# CSS Layout API
+
+The CSS Layout API is designed to give authors the ability to write their own layout algorithms in
+additon to the native ones user agents ship with today.
+
+For example the user agents currently ship with
+ - Block Flow Layout
+ - Flexbox Layout
+
+With the CSS Layout API, authors could write their own layouts which implement
+ - Constraint based layouts
+ - Masonary layouts
+ - Line spacing + snapping
+
+This document aims to give a high level overview to the Layout API.
+
+### Concepts
+
+##### The `Box`
+
+A `Box` refers to a CSS box, that is a node that has some sort of style. This can refer to:
+
+ - An element with an associated style, (an element that has `display: none` for these purposes does
+ not have a style).
+
+ - The `::before` and `::after` pseudo elements with an associated style, (note for layout purposes
+ the `::first-letter`, `::first-line`, `::selection` are *not* independent boxes, they are more a
+ special kind of selector that can override style on *part* of another box).
+
+ - A `TextNode` with some style.
+
+This is effectively the DOM tree but with some extra things. One important thing to note is that a
+`Box` doesn't have any layout information, it is the _input_ to layout.
+
+For the layout API specifically a box is represented like:
+
+```webidl
+interface Box {
+ readonly attribute StylePropertyMapReadonly styleMap;
+ FragmentRequest doLayout(ConstraintSpace space, OpaqueBreakToken breakToken);
+};
+```
+
+The `styleMap` contains the required computed style for that `Box`.
+
+##### The `Fragment`
+
+A `Fragment` refers to a CSS fragment, that is it is the part of the layout result of a box. This
+could be for example:
+
+ - A whole box which has undergone layout. E.g. the result of laying out an `` tag.
+
+ - A portion of a box which has undergone layout. E.g. the result of laying out the first column of
+ a multicol layout. ``
+
+ - A portion of a `TextNode` which has undergone layout, for example the first line, or the first
+ portion of a line with the same style.
+
+ - The `::first-letter` fragment of a `TextNode`.
+
+One can think of this as the _leaf_ representation you can get out of:
+```js
+let range = document.createRange();
+range.selectNode(element);
+console.log(range.getClientRects());
+```
+
+For the layout API specifically a fragment is represented like:
+
+```webidl
+interface Fragment {
+ readonly attribute double inlineSize;
+ readonly attribute double blockSize;
+
+ attribute double inlineStart; // inlineOffset instead?
+ attribute double blockStart;
+
+ readonly attribute sequence unpositionedBoxes;
+
+ readonly attribute OpaqueBreakToken? breakToken;
+
+ readonly attribute BaselineOffset dominantBaseline;
+ readonly attribute BaselineOffset? ideographicBaseline;
+ // other baselines go here.
+};
+```
+
+One important thing to note is that you can't change the `inlineSize` or `blockSize` of a fragment
+once have received it from a child layout. The _only_ thing you can change is its position (with
+`inlineStart` or `blockStart`) relative to the parent.
+
+See below for a description of baselines.
+
+The `unpositionedBoxes` attribute is a list of `Box`es which couldn't be positioned by the child.
+The current layout can choose to layout and position these, or it can pass them up to its parent.
+
+#### The `ConstraintSpace`
+
+A `ConstraintSpace` is a 2D representation of the layout space given to a layout. A constraint space
+has:
+ - A `inlineSize` and `blockSize`. If present, these describe a fixed width in which the layout can
+ produce a `Fragment`. The layout should produce a `Fragment` which fits inside these bounds. If
+ it exceeds these bounds, the `Fragment` may be paint clipped, etc, as determined by its parent.
+
+ - A `inlineScrollOffset` and `blockScrollOffset`. If present, these describe that if the resulting
+ `Fragment` exceeds these offsets, it must call `willInlineScroll()` / `willBlockScroll()`. This
+ will result in the constraint space being updated (and also reset to its initial state?). These
+ methods will potentially change the `inlineSize` or `blockSize` to allow room for a scrollbar.
+
+ - A `inlineFragmentOffset` and `blockFragmentingOffset`. If present, these describe that if the
+ resulting `Fragment` must fragment at this particular point.
+
+ - A list of exclusions. Described more in-depth below.
+
+The `ConstraintSpace` is represented as:
+
+```webidl
+partial interface ConstraintSpace {
+ readonly attribute double? inlineSize;
+ readonly attribute double? blockSize;
+
+ readonly attribute double? inlineScrollOffset;
+ readonly attribute double? blockScrollOffset;
+
+ readonly attribute double? inlineFragmentOffset; // Is inline fragment offset needed?
+ readonly attribute double? blockFragmentOffset;
+
+ void willInlineScroll();
+ void willBlockScroll();
+};
+```
+
+This may be better represented as:
+
+```webidl
+partial interface ConstraintSpace {
+ readonly attribute ExtentConstraint inlineConstraint;
+ readonly attribute ExtentConstraint inlineConstraint;
+
+ void willInlineScroll();
+ void willBlockScroll();
+};
+
+enum ExtentConstraintType = 'fixed' | 'scroll' | 'fragment';
+
+interface ExtentConstraint {
+ readonly attribute ExtentConstraintType type;
+ readonly attribute double offset;
+};
+```
+
+| Actually this doesn't really work? As you can have an inlineSize, which also can overflow. |
+| --- |
+
+Exclusions can be added to the constraint space which children should avoid. E.g.
+
+```webidl
+partial interface ConstraintSpace {
+ void addExclusion(Fragment fragment, optional FlowEnum flow);
+ void addExclusion(Exclusion fragment, optional FlowEnum flow);
+ readonly attribute sequence exclusions;
+};
+
+dictionary Exclusion {
+ double inlineSize;
+ double blockSize;
+
+ double inlineStart;
+ double blockStart;
+
+ double inlineEnd;
+ double blockEnd;
+};
+```
+
+The author can iterate through the available space via the `layoutOpportunities()` api.
+
+```webidl
+partial interface ConstraintSpace {
+ Generator layoutOpportunities();
+};
+
+interface LayoutOpportunity {
+ readonly attribute double inlineSize;
+ readonly attribute double blockSize;
+
+ readonly attribute double inlineStart;
+ readonly attribute double blockStart;
+
+ readonly attribute double inlineEnd;
+ readonly attribute double blockEnd;
+}
+```
+
+Here is a cute little gif which shows the layout opportunities for a `ConstraintSpace` with two
+exclusions.
+
+
+
+The layoutOpportunities generator will return a series of max-rects for a given constraint space.
+These are ordered by `inlineStart`, `inlineSize` then `blockStart`.
+
+| How do we represent non-rect exclusions? Initial thought is to always jump by `1em` of author |
+| specified amount. |
+| --- |
+
+###### Advanced exclusions
+
+Not everything in CSS avoids all exclusions. For example:
+
+
+
+The green block-level element doesn't avoid the intruding floats, but its inline-level children do.
+
+Should authors be able to annotate exclusions with a tag, then just `LayoutOpportunities` based on
+those tags? For example:
+
+```webidl
+partial interface ConstraintSpace {
+ void addExclusion(Fragment exclusion, optional FlowEnum flow, optional sequence tags);
+
+ // calling layoutOpportunities(['left']), only provides layout opportunities which avoids
+ // exclusions tagged with left.
+ Generator layoutOpportunities(optional sequence tags);
+};
+```
+
+#### Breaking and `BreakToken`s
+
+TODO write about how break tokens work.
+
+#### Pseudo-elements and style overrides
+
+`::first-letter` and `::first-line` are a little bit special in terms of CSS; they aren't really
+elements just a different style applied to a fragment(s).
+
+In order to handle these do we allow override styles when performing layout on a child? For example:
+```webidl
+partial interface Box {
+ FragmentRequest doLayout(ConstraintSpace space, OpaqueBreakToken breakToken, Object overrideStyles);
+}
+```
+
+```js
+registerLayout('handle-first-line', class {
+ *layout(constraintSpace, children, styleMap, opt_breakToken) {
+ // ...
+
+ let child = children[i];
+ let fragment = yield child.doLayout(childConstraintSpace, breakToken, {
+ // This would be queried from styleMap?
+ // This would only allow computed-style values?
+ fontSize: new CSSLengthValue({px: 18}),
+ });
+
+ // ...
+ }
+});
+```
+
+TODO: These is a problem with the above example?
+
+Similarly we have a class of CSS layout algorithms which _force_ a particular style on their
+children, (flex & grid). Do we handle these in a similar way? For example:
+
+```js
+registerLayout('kinda-like-flex', class {
+ *layout(constraintSpace, children, styleMap, opt_breakToken) {
+ // ...
+
+ let child = children[i];
+ let fragment = yield child.doLayout(childConstraintSpace, breakToken, {
+ inlineSize: 180, // Only accepts numbers in px.
+ });
+
+ // ...
+
+ }
+});
+```
+
+We need something like this, needs to be here, or on the constraintSpace somehow.
+
+#### Utility functions
+
+We need a set of utility function which do things like resolve a computed-inline-size against
+another length etc. These functions will probably become clear over-time from internal
+implementations and people writing algorithms against this API but for starters we'll probably need:
+
+```webidl
+[NoInterfaceObject]
+interface LayoutUtilities {
+ // Resolves the inline-size according to an algorithm to be defined in the spec. This doesn't
+ // limit authors to having their own layout units and resolving the lengths differently. This is
+ // just a helper.
+ number resolveInlineSize(ConstraintSpace constraintSpace, StylePropertyMapReadonly styleMap);
+
+ // Resolves the size against a different length.
+ number resolveSize(CSSValue property, number size);
+
+ // Resolves the size against a different length for the minimum amount.
+ number resolveMinimumSize(CSSValue property, number size);
+}
+```
+
+This is just some basic ones, we'll need more.
+
+#### Flags!
+
+We need to indicate to the engine when we want a particular layout behaviour placed on us. For
+example if we are a:
+ - formatting context
+ - should "blockify" children (like flex, grid)
+ - magically handle abs-pos
+
+TODO there are probably others here.
+
+For example if we should establish a formatting context, implicitly this means that the
+constraintSpace we are given cannot have any pre-defined exclusions.
+
+We need to decide on the defaults here, and if we allow changing the default.
+
+A simple API proposal:
+```js
+registerLayout('weee!', class {
+ static formattingContext = false; // default is true?
+ static handleAbsPos = false; // default is true?
+ static blockifyChildren = true; // default is false?
+
+ *layout() {
+ // etc.
+ }
+});
+```
+
+#### Adding and removing children
+
+We need a callback for when child boxes are added / removed. Rendering engines today have
+optimizations in place for when this occurs; for example in grid, the user agent will place its
+children into a "Tracks" data structure for layout.
+
+TODO: add API proposal here.
+
+#### Baselines
+
+TODO: add explaination why we need a more powerful API than just offset here.
+
+### Performing Layout
+
+The Layout API is best described with a simple dummy example:
+
+```js
+registerLayout('really-basic-block', class {
+ *layout(constraintSpace, children, styleMap, opt_breakToken) {
+ let inlineSize = 0;
+ let blockSize = 0;
+ const childFragments = [];
+
+ for (let child of children) {
+ let fragment = yield child.doLayout(constraintSpace);
+
+ // Position the new fragment.
+ fragment.inlineStart = 0;
+ fragment.blockStart = blockSize;
+ blockSize += fragment.blockSize;
+
+ // Add it as an exclusion to the constraintSpace
+ constraintSpace.addExclusion(fragment, 'block-end');
+
+ // Update the running totals for our size.
+ inlineSize = Math.max(inlineSize, fragment.inlineSize);
+ childFragments.push(fragment);
+ }
+
+ return {
+ inlineSize: inlineSize,
+ blockSize: blockSize,
+ children: childFragments,
+ };
+ }
+});
+```
+
+The first thing to notice about the API is that the layout method on the class returns a generator.
+This is to allow two things:
+ 1. User agents implementing parallel layout.
+ 2. User agents implementing asynchronous layout.
+
+The generator returns a `FragmentRequest`. Inside of the authors layout funciton, this object is
+completely opaque. This is a token for the user-agent to perform layout _at some stage_ for the
+particular box it was generated for.
+
+When a `FragmentRequest` is returned from the generator, the user-agent needs to produce a
+`Fragment` for it, and return it via. the generator `next()` call.
+
+As a concrete example, the user agent could implement the logic driving the author defined layout
+as:
+
+```js
+function performLayout(constraintSpace, box) {
+ // Get the author defined layout instance.
+ const layoutInstance = getLayoutInstanceForBox(box);
+
+ // Access the generator returned by *layout();
+ const layoutGenerator = layoutInstance.layout(constraintSpace, box.children, box.styleMap);
+
+ // Loop through all of the fragment requests.
+ let fragmentRequestObj = layoutGenerator.next();
+ while (!fragmentRequestObj.done) {
+ const fragmentRequest = [];
+ const fragmentResult = [];
+
+ // Coorce fragmentRequestObj into an array.
+ if (fragmentRequestObj.value.length) {
+ fragmentRequest.push(...fragmentRequestObject.value);
+ } else {
+ fragmentRequest.push(fragmentRequestObject.value);
+ }
+
+ for (let i = 0; i < fragmentRequest.length; i++) {
+ fragmentResult.push(performLayout(fragmentRequest[i]));
+ }
+
+ // Request the next fragment.
+ fragmentRequestObj = layoutGenerator.next(
+ fragmentResult.length == 1 : fragmentResult[0] : fragmentResult);
+ }
+
+ // The last value from the generator should be the final return value.
+ const fragmentDict = fragmentRequest.value;
+ return new Fragment(fragmentDict);
+}
+```
+
+### Example layout algorithms
+
+```js
+// 'multicol' does a simple multi-column layout.
+registerLayout('multicol', class {
+ *layout(constraintSpace, children, styleMap, opt_breakToken) {
+ const inlineSize = resolveInlineSize(constraintSpace, styleMap);
+
+ // Try and decide the number of size of columns.
+ const columnCountValue = styleMap.get('column-count');
+ const columnInlineSizeValue = styleMap.get('column-width');
+
+ let columnCount = 1;
+ let columnInlineSize = inlineSize;
+
+ if (columnCountValue) {
+ columnCount = columnCountValue.value;
+ }
+
+ if (columnInlineSizeValue) {
+ columnInlineSize = resolveSize(columnInlineSizeValue, inlineSize);
+ }
+
+ if (constraintSpace.inlineScrollOffset &&
+ columnInlineSize * columnCount > constraintSpace.inlineScrollOffset) {
+ // NOTE: under this condition, we need to start again to re-resolve lengths?
+ constraintSpace.willInlineScroll();
+ return; // Or just continue here?
+ }
+
+ // Create a constraint space which is just the inlineSize of the column.
+ const colConstraintSpace = new ConstraintSpace({
+ inlineSize: columnInlineSize
+ });
+
+ // Perform layout on all the children, taking into account the children
+ // which may fragment in the inline direction.
+ const childFragments = [];
+ let childBlockSize = 0;
+ let layoutOpp;
+ for (let child of children) {
+ let breakToken;
+ do {
+ const fragment = yield child.doLayout(colConstraintSpace, breakToken);
+ breakToken = fragment.breakToken;
+
+ const gen = colConstraintSpace.layoutOpportunities();
+
+ layoutOpp = gen.next().value;
+ if (layoutOpp.inlineSize < fragment.inlineSize()) {
+ layoutOpp = gen.next().value;
+ }
+
+ fragment.inlineStart = opp.inlineStart;
+ fragment.blockStart = opp.blockStart;
+ colConstraintSpace.addExclusion(fragment, 'inline-flow');
+
+ childFragments.push(fragment);
+ } while (breakToken);
+ }
+
+ // FIXME: This might be wrong, need a helper method on constraintSpace which returns the max
+ // blockEnd of all the exclusions.
+ const childBlockSize =
+ colConstraintSpace.layoutOpportunities().next().value.blockStart;
+
+ // Next, a clever person would nicely balance the columns, we are going
+ // to do something really simple. :)
+ const columnBlockSize = Math.ceil(childBlockSize / columnCount);
+ const columnGap = resolveSize(styleMap.get('column-gap'), inlineSize);
+ let size = 0;
+ let columnNum = 0;
+ let columnEndOffset = 0;
+ for (let fragment of childFragments) {
+ if (size && fragment.blockSize + size > columnBlockSize) {
+ size = 0;
+ columnNum++;
+ columnEndOffset += size;
+ }
+
+ fragment.inlineStart += columnNum * (columnGap + columnInlineSize);
+ fragment.blockStart -= columnEndOffset;
+ size = Math.max(size, fragment.blockStart + fragment.blockSize);
+ }
+
+ const blockSize =
+ resolveBlockSize(constraintSpace, styleMap, columnBlockSize);
+
+ return {
+ inlineSize: inlineSize,
+ blockSize: blockSize,
+ fragments: childFragments,
+ };
+ }
+});
+```
diff --git a/css-layout-api/images/constraint_space_1.html b/css-layout-api/images/constraint_space_1.html
new file mode 100644
index 00000000..55f6ee7a
--- /dev/null
+++ b/css-layout-api/images/constraint_space_1.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+ 120px
+ 120px
+
+
diff --git a/css-layout-api/images/constraint_space_1.png b/css-layout-api/images/constraint_space_1.png
new file mode 100644
index 00000000..86b26d8c
Binary files /dev/null and b/css-layout-api/images/constraint_space_1.png differ
diff --git a/css-layout-api/images/constraint_space_2.html b/css-layout-api/images/constraint_space_2.html
new file mode 100644
index 00000000..a8b68074
--- /dev/null
+++ b/css-layout-api/images/constraint_space_2.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+ 120px
+ 100px
+ scrollTrigger
+
+
diff --git a/css-layout-api/images/constraint_space_2.png b/css-layout-api/images/constraint_space_2.png
new file mode 100644
index 00000000..a1a246c1
Binary files /dev/null and b/css-layout-api/images/constraint_space_2.png differ
diff --git a/css-layout-api/images/constraint_space_3.html b/css-layout-api/images/constraint_space_3.html
new file mode 100644
index 00000000..ec520c05
--- /dev/null
+++ b/css-layout-api/images/constraint_space_3.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+ 120px
+ 100px
+ scrollTrigger
+ scrollTrigger
+
+
diff --git a/css-layout-api/images/constraint_space_3.png b/css-layout-api/images/constraint_space_3.png
new file mode 100644
index 00000000..bbe29ee6
Binary files /dev/null and b/css-layout-api/images/constraint_space_3.png differ
diff --git a/css-layout-api/images/constraint_space_4.html b/css-layout-api/images/constraint_space_4.html
new file mode 100644
index 00000000..d003d4ef
--- /dev/null
+++ b/css-layout-api/images/constraint_space_4.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+ 140px
+ 100px
+ fragmentation
+
+
diff --git a/css-layout-api/images/constraint_space_4.png b/css-layout-api/images/constraint_space_4.png
new file mode 100644
index 00000000..374e201d
Binary files /dev/null and b/css-layout-api/images/constraint_space_4.png differ
diff --git a/css-layout-api/img/edges.png b/css-layout-api/img/edges.png
new file mode 100644
index 00000000..6d01d1fa
Binary files /dev/null and b/css-layout-api/img/edges.png differ
diff --git a/css-layout-api/img/layout-fragment-offsets.png b/css-layout-api/img/layout-fragment-offsets.png
new file mode 100644
index 00000000..c42a15df
Binary files /dev/null and b/css-layout-api/img/layout-fragment-offsets.png differ
diff --git a/css-paint-api/EXPLAINER.md b/css-paint-api/EXPLAINER.md
new file mode 100644
index 00000000..96a5402c
--- /dev/null
+++ b/css-paint-api/EXPLAINER.md
@@ -0,0 +1,274 @@
+CSS Paint API Explained
+=======================
+
+The CSS Paint API is being developed to improve the extensibility of CSS.
+
+Specifically this allows developers to write a paint function which allows us to draw directly into
+an elements background, border, or content.
+
+This work was motivated for a couple of reasons:
+
+### Reduction of DOM ###
+
+We noticed that developers are using an increasing amount of DOM to create visual effects. As an
+example the [<paper-button>](https://www.webcomponents.org/element/PolymerElements/paper-button/paper-button)
+uses multiple divs to achieve the "ripple" effect.
+
+Instead of using addition divs the developer could just draw directly into the background-image of
+the element instead.
+
+This means that the memory and cpu usage of the page would go down, the rendering engine doesn't
+have to keep in memory the additional DOM nodes, in addition to performing style-recalc, layout,
+painting for all these additional divs.
+
+### Efficiency Gains ###
+
+Providing a "hook" into the rendering engine allows for efficiency gains which are difficult to
+achieve with current APIs.
+
+#### Invalidation ####
+
+As the CSS paint API Invalidation is based off style changes, this check can occur in the same pass
+as the rest of the box tree. For example:
+
+```css
+my-button {
+ --property-which-invalidates-paint: no-hover;
+}
+
+my-button:hover {
+ --property-which-invalidates-paint: hover;
+}
+```
+
+To achieve the same effect with current APIs you have to rebuild the engines invalidation logic
+which is potentially less efficient.
+
+#### Painting ####
+
+Once a box has been invalidated, a rendering engine isn't required to paint it that frame. For
+example some rendering engines just paint what is visible within the "visual viewport". This means
+that only a smaller amount of work is needed to be done.
+
+A naive implementation with existing APIs may try and paint everything within the document.
+
+### Extensibility of CSS ###
+
+We believe that allowing developers to extend CSS is good for the ecosystem. As an example if a
+developer wanted an additional feature they could implement it themselves. E.g. if the developer
+wanted a new type of dashed border, they shouldn't have to wait for browsers to implement this.
+
+They should have the power to implement this themselves with the same capability as the rendering
+engine.
+
+Getting Started
+---------------
+
+First you'll need to add a module script into the paint worklet.
+
+```js
+if ('paintWorklet' in CSS) {
+ await CSS.paintWorklet.addModule('my-paint-script.js');
+ console.log('paint script installed!');
+}
+```
+
+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).
+ - May have multiple copies of the script context spawned on multiple CPU cores.
+
+As a couple of concrete example as to how the user agent may use these capabilities:
+ - When a "tab" is backgrounded the script context(s) of the paint worklet may be killed to free up memory.
+ - A multi-threaded user-engine may spawn multiple backing script contexts to perform the paint phase of the rendering engine in parallel.
+
+Painting a circle
+-----------------
+
+The global script context of the paint worklet has exactly one method exposed to developers: `registerPaint`.
+
+```js
+registerPaint('circle', class {
+ static inputProperties = ['--circle-color'];
+
+ paint(ctx, size, style) {
+ // Change the fill color.
+ const color = style.get('--circle-color');
+ ctx.fillStyle = color;
+
+ // Determine the center point and radius.
+ const x = size.width / 2;
+ const y = size.height / 2;
+ const radius = Math.min(x, y);
+
+ // Draw the circle \o/
+ ctx.beginPath();
+ ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
+ ctx.fill();
+ }
+});
+```
+
+There are a few things going on in this example so lets step through them one-by-one.
+
+The `paint` function is your callback into the browsers paint phase in the rendering engine. You are given:
+ - `ctx`, a rendering context, similar to a `CanvasRenderingContext2D`.
+ - `size`, the size of the area in which you should paint.
+ - `style`, the computed style of the element which are you currently painting.
+
+The `style` is a Typed-OM style map. It will _only_ contain the CSS properties that you listed under
+the static `inputProperties` accessor.
+
+This is to allow the engine to cache results of your paint class. For example if
+`--some-other-property` changes the user-agent knows that this doesn't affect your paint class, and
+can re-use the stored result.
+
+The only time when your paint class _must_ be called is when the element it is painting into is
+within the viewport, and the size or CSS input properties have changed.
+
+Why classes?
+------------
+
+Classes allow composition of paint handlers. As an example:
+
+```js
+class RectPainter {
+ static inputProperties = ['--rect-color'];
+
+ paint(ctx, size, style) {
+ // Change the fill color.
+ ctx.fillStyle = style.get('--circle-color');
+
+ // Draw the solid rect.
+ ctx.fillRect(0, 0, size.width, size.height);
+ }
+}
+
+class BorderRectPainter extends RectPainter {
+ static inputProperties = ['--border-color', ...super.inputProperties];
+
+ paint(ctx, size, style) {
+ super.paint(ctx, size, style);
+
+ ctx.strokeStyle = style.get('--border-color');
+ ctx.lineWidth = 4;
+
+ ctx.strokeRect(0, 0, size.width, size.height);
+ }
+}
+
+registerPaint('border-rect', BorderRectPainter);
+```
+
+Classes also give the developer a specific point in time to perform pre-initialization work. As an
+example:
+
+```js
+registerPaint('lots-of-paths', class {
+
+ constructor() {
+ this.paths = performPathPreInit();
+ }
+
+ performPathPreInit() {
+ // Lots of work here to produce list of Path2D object to be reused.
+ }
+
+ paint(ctx, size, style) {
+ ctx.stroke(this.paths[i]);
+ }
+});
+```
+
+In this example `performPathPreInit` is doing CPU intensive work which should only be done once.
+Without classes this would typically be done when the script is first run, instead this is performed
+when the class instance is first created (which may be much later in time).
+
+Drawing Images
+--------------
+
+The API works in conjunction with the [CSS Properties and Values](https://drafts.css-houdini.org/css-properties-values-api/)
+proposal and the [CSS Typed OM](https://drafts.css-houdini.org/css-typed-om/) proposal.
+
+Using the Properties and Values `registerProperty` method, developers can create a custom CSS
+property with the `` type. E.g.
+
+```js
+registerProperty({
+ name: '--profile-image',
+ syntax: ''
+});
+```
+
+This tells the rendering engine to treat the CSS property `--profile-image` as an image; and as a
+result the style engine will parse and begin loading that image.
+
+You can then directly draw this image from within your paint method:
+
+```js
+registerPaint('avatar', class {
+ static inputProperties = ['--profile-image'];
+
+ paint(ctx, size, styleMap) {
+ // Determine the center point and radius.
+ const x = size.width / 2;
+ const y = size.height / 2;
+ const radius = Math.min(x, y);
+
+ ctx.save();
+ // Set up a round clipping path for the profile image.
+ ctx.beginPath();
+ ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
+ ctx.clip();
+
+ // Draw the image inside the round clip.
+ ctx.drawImage(styleMap.get('--profile-image'));
+ ctx.restore();
+
+ // Draw a badge or something on top of the image.
+ drawBadge(ctx);
+ }
+});
+```
+
+The above example would be used in CSS by:
+```css
+.avatar-img {
+ background: paint(avatar);
+ --profile-image: url("profile-image.png");
+}
+```
+
+Paint Arguments
+---------------
+
+It is also possible with this API to have additional arguments to the `paint()` function, for
+example:
+
+```js
+registerPaint('circle-args', class {
+ static inputArguments = [''];
+
+ paint(ctx, size, _, args) {
+ const color = args[0].cssText;
+ ctx.fillStyle = color;
+
+ const x = size.width / 2;
+ const y = size.height / 2;
+ const radius = Math.min(x, y);
+
+ ctx.beginPath();
+ ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
+ ctx.fill();
+ }
+});
+```
+
+```js
+my-element {
+ background:
+ paint(circle-args, red) center/50% no-repeat,
+ paint(cirlce-args, blue);
+}
+```
diff --git a/css-paint-api/Overview.bs b/css-paint-api/Overview.bs
index a6c5de4c..f7570b54 100644
--- a/css-paint-api/Overview.bs
+++ b/css-paint-api/Overview.bs
@@ -1,320 +1,1022 @@
Title: CSS Painting API Level 1
-Status: DREAM
+Status: ED
Group: houdini
ED: https://drafts.css-houdini.org/css-paint-api-1/
+TR: http://www.w3.org/TR/css-paint-api-1/
+Previous Version: https://www.w3.org/TR/2018/WD-css-paint-api-1-20180410/
+Previous Version: https://www.w3.org/TR/2016/WD-css-paint-api-1-20160607/
Shortname: css-paint-api
Level: 1
-Abstract:
-Editor: Shane Stephens, shanestephens@google.com
-Editor: Ian Kilpatrick, ikilpatrick@chromium.org
-Editor: Dean Jackson, dino@apple.com
+Abstract:
+ An API for allowing web developers to define a custom CSS <> with javascript, which will
+ respond to style and size changes.
+ See EXPLAINER.
+Former Editor: Shane Stephens, shanestephens@google.com, w3cid 47691
+Editor: Ian Kilpatrick, ikilpatrick@chromium.org, w3cid 73001
+Editor: Dean Jackson, dino@apple.com, w3cid 42080
+Ignored Terms: PaintWorklet
+
+
-spec:css-break-3; type:dfn; text:fragment
+spec:infra; type:dfn; text:list
+spec:webidl; type:dfn; text:converting
+spec:html; type:dfn;
+ text:set bitmap dimensions;
+ text:reset the rendering context to its default state
Introduction {#intro}
=====================
-The paint stage of CSS is responsible for painting the background, content and highlight of an element based on that element's geometry (as generated by the layout stage) and computed style.
-
-This specification describes an API which allows developers to paint a part of an element in response to geometry / computed style changes.
+The paint stage of CSS is responsible for painting the background, content and highlight of a
+box based on that box's size (as generated by the layout stage) and computed style.
-Note: In a future version of the spec, support may be added for defining the clip, global alpha, filter on a portion of an element (for example on the background layers).
+This specification describes an API which allows developers to paint a part of a box in
+response to size / computed style changes with an additional <> function.
-Paint Invalidation {#paint-invalidation}
-========================================
+Note: In a future version of the spec, support could be added for defining the clip, global alpha,
+ filter on a portion of a box (for example on the background layers).
-A document has an associated paint name to input properties map. Initially it is empty and is populated when {{registerPaint(name, paintCtor)}} is called.
+Paint Worklet {#paint-worklet}
+==============================
-Each <> function for a fragment has an associated paint valid flag. It may be either paint-valid or paint-invalid. It is initially set to paint-invalid.
+The {{paintWorklet}} attribute allows access to the {{Worklet}} responsible for all the classes
+which are related to painting.
-When the geometry (as determined by layout) of a fragment changes, each <> function's paint valid flag should be set to paint-invalid.
+The {{paintWorklet}}'s [=worklet global scope type=] is {{PaintWorkletGlobalScope}}.
-When the computed style for an element changes, the user agent must run the following steps:
- 1. For each <> function on the element, perform the following substeps:
- 1. Let name be the first argument of the <> function.
- 2. Let inputProperties be the result of performing a lookup in paint name to input properties map with |name| as the key.
- 3. For each property in |inputProperties|, if the |property|'s computed value has changed, set the paint valid flag on the |fragment| to paint-invalid.
+The {{paintWorklet}}'s worklet destination type is "paintworklet".
-[[#drawing-an-image]] results in the paint valid flag for a <> function to be set to paint-valid.
+
-Note: In a future version of the spec, support may be added for partial invalidation.
- The user agent will be able to specify a region of the rendering context which needs to be re-painted by the paint class.
+A {{PaintWorkletGlobalScope}} is a global execution context of the {{paintWorklet}}.
-Registering Custom Paint {#registering-custom-paint}
-====================================================
+A {{PaintWorkletGlobalScope}} has a {{PaintWorkletGlobalScope/devicePixelRatio}} property which is
+identical to the Window.{{Window/devicePixelRatio}} property.
-The {{RenderWorkletGlobalScope}} has a map of name to paint instance map. Initially this map is empty; it is populated when {{registerPaint(name, paintCtor)}} is called.
+The {{PaintRenderingContext2DSettings}} contains the settings for the rendering context associated
+with the paint canvas. The {{PaintRenderingContext2DSettings}} provides a supported subset of canvas
+rendering context 2D settings. In the future, it may be extended to support color management in
+paint canvas.
+
-When the registerPaint(name, paintCtor) method is called, the user agent must run the following steps:
- 1. If the |name| is not a valid <>, throw a NotSupportedError and abort all these steps.
- 2. If the |name| exists as a key in the name to paint instance map, throw a NotSupportedError and abort all these steps.
- 3. If the result of IsConstructor(argument=|paintCtor|) is false, throw a NotSupportedError and abort all these steps.
- 4. Let prototype be the result of Get(O=|paintCtor|, P="prototype").
- 5. If the result of IsCallable(argument=Get(O=|prototype|, P="paint")) is false, throw a NotSupportedError and abort all these steps.
- 6. If the result of HasProperty(O=|prototype|, P="overflow") and IsCallable(argument=Get(O=|prototype|, P="overflow")) is false, throw a NotSupportedError and abort all these steps.
- 7. Let inputProperties be the result of Get(O=|paintCtor|, P="inputProperties").
- 8. If the result of IsArray(argument=|inputProperties|) is false, throw a NotSupportedError and abort all these steps.
- 9. Add the key-value pair (|name| - |inputProperties|) to the paint name to input properties map of the associated document.
- 10. Let paintInstance be the result of Construct(|paintCtor|).
- 11. Add the key-value pair (|name| - |paintInstance|) to the name to paint instance map of the {{RenderWorkletGlobalScope}}.
+Concepts {#concepts}
+====================
+
+A paint definition is a [=struct=] which describes the information needed by the
+{{PaintWorkletGlobalScope}} about the author defined <> (which can be referenced by the
+<> function). It consists of:
+
+ - class constructor which is the class [=constructor=].
+
+ - paint function which is the paint [=Function=]
+ [=callback function=] type.
+
+ - constructor valid flag.
+
+ - input properties which is a [=list=] of
+ DOMStrings.
+
+ - A PaintRenderingContext2DSettings object.
+
+A document paint definition is a [=struct=] which describes the information
+needed by the [=document=] about the author defined <> function (which can be referenced
+by the paint function). It consists of:
+
+ - A input properties which is a [=list=] of
+ DOMStrings.
+
+ - A input argument syntaxes which is a [=list=] of
+ [=syntax definitions=].
+
+ - A PaintRenderingContext2DSettings object.
+
+Registering Custom Paint {#registering-custom-paint}
+====================================================
+
+The [=document=] has a [=map=] of document paint definitions. Initially
+this map is empty; it is populated when {{registerPaint(name, paintCtor)}} is called.
+
+A {{PaintWorkletGlobalScope}} has a [=map=] of paint definitions. Initially this map
+is empty; it is populated when {{registerPaint(name, paintCtor)}} is called.
+
+A {{PaintWorkletGlobalScope}} has a [=map=] of paint class instances. Initially this
+map is empty; it is populated when [=draw a paint image=] is invoked by the user agent.
+
+Instances of paint classes in the [=paint class instances=] map may be disposed and removed from
+the map by the user agent at any time. This may be done when a <> function no longer is
+used, or the user agent needs to reclaim memory.
+
+
+When the registerPaint(|name|, |paintCtor|) method is
+called, the user agent must run the following steps:
+ 1. If the |name| is an empty string, [=throw=] a [=TypeError=] and abort all these steps.
+
+ 2. Let |paintDefinitionMap| be {{PaintWorkletGlobalScope}}'s [=paint definitions=] map.
+
+ 3. If |paintDefinitionMap|[|name|] [=map/exists=] [=throw=] a
+ "{{InvalidModificationError}}" {{DOMException}} and abort all these steps.
+
+ 4. Let |inputProperties| be an empty sequence<DOMString>.
+
+ 5. Let |inputPropertiesIterable| be the result of [=Get=](|paintCtor|, "inputProperties").
+
+ 6. If |inputPropertiesIterable| is not undefined, then set |inputProperties| to the result of
+ [=converting=] |inputPropertiesIterable| to a sequence<DOMString>. If an
+ exception is [=thrown=], rethrow the exception and abort all these steps.
+
+ 7. Filter |inputProperties| so that it only contains [=supported CSS properties=] and
+ [=custom properties=].
+
+ Note: The list of CSS properties provided by the input properties getter can either be custom or
+ native CSS properties.
+
+ Note: The list of CSS properties may contain shorthands.
+
+ Note: In order for a paint image class to be forwards compatible, the list of CSS properties can
+ also contains currently invalid properties for the user agent. For example
+ margin-bikeshed-property.
+
+ 8. Let |inputArguments| be an empty sequence<DOMString>.
+
+ 9. Let |inputArgumentsIterable| be the result of [=Get=](|paintCtor|, "inputArguments").
+
+ 10. If |inputArgumentsIterable| is not undefined, then set |inputArguments| to the result of
+ [=converting=] |inputArgumentsIterable| to a sequence<DOMString>. If an
+ exception is thrown, rethrow the exception and abort all these steps.
-Note: The list of CSS properties provided by the input properties getter can either be custom or native CSS properties.
+ 11. Let |inputArgumentSyntaxes| be an [=list/empty=] [=list=].
-Note: The list of input properties should only be looked up once, the class doesn't have the opportunity to dynamically change its input properties.
+ 12. [=list/For each=] |item| in |inputArguments| perform the following substeps:
-Note: In a future version of the spec, the author may be able to set an option to receive a different type of RenderingContext.
- In particular the author may want a WebGL rendering context to render 3D effects.
- There are complexities in setting up a WebGL rendering context to take the {{Geometry}} and {{StylePropertyMap}} as inputs.
+ 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=].
-Issue(w3c/css-houdini-drafts#31): Allow author to specify the intrinsic size.
+ 2. [=list/Append=] |parsedSyntax| to |inputArgumentSyntaxes|.
-When the unregisterPaint(name) method is called, the user agent must run the following steps:
- 1. Remove the key-value pair associated with the |name| key in the paint name to input properties map of the associated document.
- 2. Remove the key-value pair associated with the |name| key in the name to paint instance map of the {{RenderWorkletGlobalScope}}.
+ 13. Let |contextOptionsValue| be the result of [=Get=](|paintCtor|, "contextOptions").
+
+ 14. Let |paintRenderingContext2DSettings| be the result of [=converting=]
+ |contextOptionsValue| to a {{PaintRenderingContext2DSettings}}.
+ If an exception is [=thrown=], rethrow the exception and abort all these steps.
+
+ Note: Setting paintRenderingContext2DSettings.alpha is false allows user agents
+ to anti-alias text in addition to performing "visibility" optimizations, e.g. not
+ painting an image behind the paint image as the paint image is opaque.
+
+ 15. If the result of [=IsConstructor=](|paintCtor|) is false, [=throw=] a [=TypeError=]
+ and abort all these steps.
+
+ 16. Let |prototype| be the result of [=Get=](|paintCtor|, "prototype").
+
+ 17. If the result of [=Type=](|prototype|) is not Object, [=throw=] a [=TypeError=] and
+ abort all these steps.
+
+ 18. Let |paintValue| be the result of [=Get=](|prototype|, "paint").
+
+ 19. Let |paint| be the result of [=converting=] |paintValue| to the [=Function=]
+ [=callback function=] type. Rethrow any exceptions from the conversion.
+
+ 20. Let |definition| be a new [=paint definition=] with:
+
+ - [=paint definition/class constructor=] being |paintCtor|.
+
+ - [=paint function=] being |paint|.
+
+ - [=paint definition/constructor valid flag=] being true.
+
+ - [=paint definition/input properties=] being |inputProperties|.
+
+ - [=paint definition/PaintRenderingContext2DSettings object=] being |paintRenderingContext2DSettings|.
+
+ 21. [=map/Set=] |paintDefinitionMap|[|name|] to |definition|.
+
+ 22. [=Queue a task=] to run the following steps:
+
+ 1. Let |documentPaintDefinitionMap| be the associated [=document's=] [=document paint
+ definitions=] [=map=].
+
+ 2. Let |documentDefinition| be a new [=document paint definition=] with:
+
+ - [=document paint definition/input properties=] being |inputProperties|.
+
+ - [=document paint definition/input argument syntaxes=] being
+ |inputArgumentSyntaxes|.
+
+ - [=document paint definition/PaintRenderingContext2DSettings object=] being |paintRenderingContext2DSettings|.
+
+ 3. If |documentPaintDefinitionMap|[|name|] [=map/exists=], run the following steps:
+
+ 1. Let |existingDocumentDefinition| be the result of [=map/get=]
+ |documentPaintDefinitionMap|[|name|].
+
+ 2. If |existingDocumentDefinition| is "invalid", abort all these steps.
+
+ 3. If |existingDocumentDefinition| and |documentDefinition| are not equivalent, (that is
+ [=document paint definition/input properties=], input argument syntaxes, and PaintRenderingContext2DSettings object are different), then:
+
+ [=map/Set=] |documentPaintDefinitionMap|[|name|] to "invalid".
+
+ Log an error to the debugging console stating that the same class was registered
+ with different inputProperties, inputArguments, or
+ paintRenderingContext2DSettings.
+
+ 4. Otherwise, [=map/set=] |documentPaintDefinitionMap|[|name|] to
+ |documentDefinition|.
+
+Note: The list of input properties should only be looked up once, the class doesn't have the
+ opportunity to dynamically change its input properties.
+
+Note: In a future version of the spec, the author could have the ability to receive a different type
+ of RenderingContext. In particular the author may want a WebGL rendering context to render 3D
+ effects. There are complexities in setting up a WebGL rendering context to take the
+ {{PaintSize}} and {{StylePropertyMap}} as inputs.
+
-Issue(w3c/css-houdini-drafts#33): What to do about cursor.
+For the 'cursor' property, the <> function should be treated as an [=invalid image=] and
+fallback to the next supported <>.
-Issue: How do we do things like conic-gradient? I.e. paint functions which accept arguments as inputs?
+At [=computed value=] time the <> function does not need to match the grammar
+registered by {{registerPaint()}}. Instead this will result in an [=invalid image=] when the
+parsing occurs inside [=draw a paint image=].
The 2D rendering context {#2d-rendering-context}
================================================
-[Exposed=Worklet]
+[Exposed=PaintWorklet]
interface PaintRenderingContext2D {
};
-PaintRenderingContext2D implements CanvasState;
-PaintRenderingContext2D implements CanvasTransform;
-PaintRenderingContext2D implements CanvasCompositing;
-PaintRenderingContext2D implements CanvasImageSmoothing;
-PaintRenderingContext2D implements CanvasFillStrokeStyles;
-PaintRenderingContext2D implements CanvasShadowStyles;
-PaintRenderingContext2D implements CanvasRect;
-PaintRenderingContext2D implements CanvasDrawPath;
-PaintRenderingContext2D implements CanvasText;
-PaintRenderingContext2D implements CanvasDrawImage;
-PaintRenderingContext2D implements CanvasPathDrawingStyles;
-PaintRenderingContext2D implements CanvasTextDrawingStyles;
-PaintRenderingContext2D implements CanvasPath;
+PaintRenderingContext2D includes CanvasState;
+PaintRenderingContext2D includes CanvasTransform;
+PaintRenderingContext2D includes CanvasCompositing;
+PaintRenderingContext2D includes CanvasImageSmoothing;
+PaintRenderingContext2D includes CanvasFillStrokeStyles;
+PaintRenderingContext2D includes CanvasShadowStyles;
+PaintRenderingContext2D includes CanvasRect;
+PaintRenderingContext2D includes CanvasDrawPath;
+PaintRenderingContext2D includes CanvasDrawImage;
+PaintRenderingContext2D includes CanvasPathDrawingStyles;
+PaintRenderingContext2D includes CanvasPath;
Note: The {{PaintRenderingContext2D}} implements a subset of the {{CanvasRenderingContext2D}} API.
- Specifically it doesn't implement the {{CanvasHitRegion}}, {{CanvasImageData}} or
- {{CanvasUserInterface}} APIs.
-
-A {{PaintRenderingContext2D}} object has a scratch bitmap. This is initialised when the
-object is created. The size of the scratch bitmap is the size of the fragment it is rendering
-plus the size specified by the overflow method.
-
-The logical origin (0,0) is not necessarily placed at the origin of the scratch bitmap. If
-the fragment which is being painted has an associated overflow, the logical origin is placed at
-(overflow-left,overflow-top) of the scratch bitmap.
-
-Issue: Add image explaining origin vs. logical origin.
-
-The size of the scratch bitmap does not necessarily represent the size of the actual bitmap
+ Specifically it doesn't implement the {{CanvasImageData}}, {{CanvasUserInterface}},
+ {{CanvasText}}, or {{CanvasTextDrawingStyles}} APIs.
+
+A {{PaintRenderingContext2D}} object has a output bitmap.
+This is initialised when the object is created.
+The size of the [=PaintRenderingContext2D/output bitmap=] is the [=concrete object size=]
+of the object it is rendering to.
+
+A {{PaintRenderingContext2D}} object also has an alpha flag,
+which can be set to true or false.
+Initially, when the context is created,
+its alpha flag must be set to true.
+When a {{PaintRenderingContext2D}} object has its alpha flag set to false,
+then its alpha channel must be fixed to 1.0 (fully opaque) for all pixels,
+and attempts to change the alpha component of any pixel must be silently ignored.
+
+The size of the [=PaintRenderingContext2D/output bitmap=] does not necessarily represent the size of the actual bitmap
that the user agent will use internally or during rendering. For example, if the visual viewport is
zoomed the user agent may internally use bitmaps which correspond to the number of device pixels in
the coordinate space, so that the resulting rendering is of high quality.
Additionally the user agent may record the sequence of drawing operations which have been applied to
-the scratch bitmap such that the user agent can subsequently draw onto a device bitmap at the
-correct resolution. This also allows user agents to re-use the same output of the scratch
-bitmap repeatably while the visual viewport is being zoomed for example.
+the [=PaintRenderingContext2D/output bitmap=] such that the user agent can subsequently draw onto a device bitmap at the
+correct resolution. This also allows user agents to re-use the same output of the [=PaintRenderingContext2D/output bitmap=] repeatably while the visual viewport is being zoomed for example.
+
+Whenever "currentColor" is used as a color in the {{PaintRenderingContext2D}} API, it
+is treated as opaque black.
+
+
+ The code below will produce a solid black rectange.
+
-When the user agent is to create a {{PaintRenderingContext2D}} object for a given
-|width|, |height| and |overflowOffset| it must run the following steps:
+
+When the user agent is to create a PaintRenderingContext2D object for a given |width|,
+|height|, and |paintRenderingContext2DSettings|, it must run the following steps:
1. Create a new {{PaintRenderingContext2D}}.
- 2. Set bitmap dimensions for the context's scratch bitmap to |width| and |height|.
- 3. Set the logical origin of the scratch bitmap to |overflowOffset|.
+ 2. [=Set bitmap dimensions=] for the context's [=PaintRenderingContext2D/output bitmap=] to the rounded values of |width| and |height|.
+ 3. Set the {{PaintRenderingContext2D}}'s [=PaintRenderingContext2D/alpha=] flag to |paintRenderingContext2DSettings|'s {{alpha}}.
4. Return the new {{PaintRenderingContext2D}}.
+Note: The initial state of the rendering context is set inside the [=set bitmap dimensions=]
+ algorithm, as it invokes [=reset the rendering context to its default state=] and clears the
+ [=PaintRenderingContext2D/output bitmap=].
+
+
+Drawing a CSSImageValue {#drawing-a-cssimagevalue}
+--------------------------------------------------
+
+The {{CanvasImageSource}} typedef is extended to also include the {{CSSImageValue}} type to be used
+as an image source.
+
+For interfaces which use the {{CanvasDrawImage}} mixin:
+ - When a {{CanvasImageSource}} object represents an {{CSSImageValue}}, the result of invoking
+ the value's underlying image algorithm must be used as the source image for the purposes of
+ {{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}
====================================
+If a <> function image for a [=box=] is within the visual viewport, the user agent
+must display an image output from an invocation of the [=draw a paint image=] algorithm.
+
+Note: The user agent doesn't have to run [=draw a paint image=] each frame for a <>
+ function within the visual viewport. It can cache results, (potentially using additional
+ invalidation steps) to display the correct image output.
+
+Note: The user agent can optionally defer drawing images which are outside the visual viewport.
+
+
+ If an author updates a style inside a requestAnimationFrame, e.g.
+
+ And the element is inside the visual viewport, the user agent is required to
+ [=draw a paint image=] and display the result for the current frame.
+
+
+The [=draw a paint image=] function is invoked by the user agent during the [=object size
+negotiation=] algorithm which is responsible for rendering an <>, with
+|snappedConcreteObjectSize| defined as follows. Let |concreteObjectSize| be the [=concrete object
+size=] of the [=box=]. The |snappedConcreteObjectSize| is usually the same as the
+|concreteObjectSize|. However, the user agent may adjust the size such that it paints to pixel
+boundaries. If it does, the user agent should adjust the |snappedConcreteObjectSize| by the
+proportional change from its original size such that the <> function can adjust the drawing
+accordingly.
+
+For the purposes of the [=object size negotiation=] algorithm, the paint image has no
+[=intrinsic dimensions=].
+
+Note: In a future version of the spec, the author could have the ability to specify the [=intrinsic
+ dimensions=] of the paint image. This will probably be exposed as a callback allowing the
+ author to define static [=intrinsic dimensions=] or dynamically updating the [=intrinsic
+ dimensions=] based on computed style and size changes.
+
+The {{PaintSize}} object represents the size of the image that the author should draw. This is
+the |snappedConcreteObjectSize| given by the user agent.
+
+Note: See [[css-images-3#object-sizing-examples]] for examples on how the [=concrete object
+ size=] is calculated.
+
+The [=draw a paint image=] function may be speculatively invoked by the user agent at any point,
+with any |snappedConcreteObjectSize|. The resulting image is not displayed.
+
+Note: User agents may use any heuristic to speculate a possible future value for
+ |snappedConcreteObjectSize|, for example speculating that the size remains unchanged.
+
+Note: Although the image is not displayed, it may still be cached, and subsequent invocations of
+ <> may use the cached image.
+
-If a <> function for a fragment is paint-invalid and the fragment is within the visual viewport,
-then user agent mustdraw an image for the current frame.
+
+When the user agent wants to draw a paint image of a <> function for a |box|
+into its appropriate stacking level (as defined by the property the CSS property its associated
+with), given |snappedConcreteObjectSize| it must run the following steps:
+ 1. Let |paintFunction| be the <> function on the |box| which the user agent wants to
+ draw.
+
+ 2. Let |name| be the first argument of the |paintFunction|.
+
+ 3. Let |documentPaintDefinitionMap| be the associated [=document's=] [=document paint
+ definitions=] map.
+
+ 4. If |documentPaintDefinitionMap|[|name|] does not [=map/exist=], let the image output
+ be an [=invalid image=] and abort all these steps.
+
+ 5. Let |documentDefinition| be the result of [=map/get=]
+ |documentPaintDefinitionMap|[|name|].
+
+ 6. If |documentDefinition| is "invalid", let the image output be an [=invalid
+ image=] and abort all these steps.
+
+ 7. Let |inputArgumentSyntaxes| be |documentDefinition|'s input argument syntaxes.
+
+ 8. Let |inputArguments| be the [=list=] of all the |paintFunction| arguments after
+ the "paint name" argument.
+
+ 9. If |inputArguments| do not match the registered grammar given by |inputArgumentSyntaxes|, let
+ the image output be an [=invalid image=] and abort all these steps.
+
+
+
+ example-1 produces an [=invalid image=] as "red" does not
+ match the registered grammar.
+
+ example-2 produces an [=invalid image=] as there are too many function
+ arguments.
+
+
+ 10. Let |workletGlobalScope| be a {{PaintWorkletGlobalScope}} from the list of [=worklet's
+ WorkletGlobalScopes=] from the paint {{Worklet}}, following the rules defined in
+ [[#global-scope-selection]].
+
+ The user agent may also [=create a WorkletGlobalScope=] at this time, given the
+ paint {{Worklet}}.
+
+ 11. Run [=invoke a paint callback=] given |name|, |inputArguments|, |snappedConcreteObjectSize|,
+ |workletGlobalScope| optionally [=in parallel=].
+
+ Note: If the user agent runs [=invoke a paint callback=] on a thread [=in parallel=],
+ it should select a paint worklet global scope which can be used on that thread.
+
+
+
+When the user agent wants to invoke a paint callback given |name|, |inputArguments|,
+|snappedConcreteObjectSize|, and |workletGlobalScope|, it must run the following steps:
+
+ 1. Let |paintDefinitionMap| be |workletGlobalScope|'s [=paint definitions=] map.
+
+ 2. If |paintDefinitionMap|[|name|] does not [=map/exist=], run the following steps:
+
+ 1. [=Queue a task=] to run the following steps:
-Note: The user agent may choose to draw an image for <> functions not within the visual viewport.
+ 1. Let |documentPaintDefinitionMap| be the associated [=document=]'s [=document
+ paint definitions=] map.
-When the user agent wants to draw an image of a <> for a fragment into its appropriate stacking level (as defined by the property the CSS property it's associated with) it must run the following steps:
- 1. If the paint valid flag for the <> function on the |fragment| is paint-valid the user agent may use the drawn image from the previous invocation.
- If so it can abort all these steps.
+ 2. [=map/Set=] |documentPaintDefinitionMap|[|name|] to "invalid".
- Note: The user agent for implementation reasons may also continue with all these steps in this case. It can do this every frame, or multiple times per frame.
+ 3. The user agent should log an error to the debugging console stating that a
+ class wasn't registered in all {{PaintWorkletGlobalScope}}s.
- 2. Let name be the first argument of the <> function.
+ 2. Let the image output be an [=invalid image=] and abort all these steps.
- 3. If no paint class was registered with |name|, the resulting image output will be an invalid image and the user agent must abort all these steps.
+ Note: This handles the case where there could be a paint worklet global scope which didn't
+ receive the {{registerPaint(name, paintCtor)}} for |name| (however another global scope
+ did). A paint callback which is invoked on the other global scope could succeed, but
+ wont succeed on a subsequent frame when [=draw a paint image=] is called.
- 4. Let inputProperties be the result of looking up |name| on the associated document's paint name to input properties map.
+ 3. Let |definition| be the result of [=get=] |paintDefinitionMap|[|name|].
- 5. Let styleMap be a new {{StylePropertyMap}} populated with only the computed value's for properties listed in |inputProperties|.
+ 4. Let |paintClassInstanceMap| be |workletGlobalScope|'s [=paint class instances=] map.
- 6. Let overflow be the result invoke a method on a class inside a Worklet given "overflow" as the methodPropertyKey and [|styleMap|] as the arguments with the following options:
- - To create a worklet global scope the user agent will:
+ 5. Let |paintInstance| be the result of [=get=] |paintClassInstanceMap|[|name]|. If
+ |paintInstance| is null, run the following steps:
- Return a new {{RenderWorkletGlobalScope}}.
- - To lookup a class instance on a worklet global scope given a |workletGlobalScope|, the user agent will:
+ 1. If the [=paint definition/constructor valid flag=] on |definition| is false, let the image output be an
+ [=invalid image=] and abort all these steps.
- Return the result of looking up |name| on the |workletGlobalScope|'s name to paint instance map.
+ 2. Let |paintCtor| be the [=paint definition/class constructor=] on |definition|.
- Note: User agents may have to compute overflow before entering their paint phase in order to determine which fragments to paint (overflow changes what could be seen on the output device).
- User agents may opt into running the steps up to this point, to determine overflow, then continuing later to determine the drawn image for the fragments which need painting.
+ 3. Let |paintInstance| be the result of [=Construct=](|paintCtor|).
- 7. Let renderingContext be the result of create a {{PaintRenderingContext2D}} object given:
- - "overflowOffset" - The logical offset for the scratch bitmap specified by |overflow|.
- - "width" - The width of the |fragment| plus the additional width specified by |overflow|.
- - "height" - The height of the |fragment| plus the additional height specified by |overflow|.
+ If [=construct=] throws an exception,
+ set the |definition|'s [=paint definition/constructor valid flag=] to false,
+ let the image output be an [=invalid image=] and abort all these
+ steps.
- Note: The |renderingContext| must not be re-used between invocations of paint. Implicitly this means that there is no stored data, or state on the |renderingContext| between invocations.
- For example you can't setup a clip on the context, and expect the same clip to be applied next time the paint method is called.
+ 4. [=map/Set=] |paintClassInstanceMap|[|name|] to |paintInstance|.
- Issue: TODO add note we should specify the output of overflow.
+ 6. Let |inputProperties| be |definition|'s [=paint definition/input properties=].
- 8. Let geometry be a new {{Geometry}} initialized to the width and height of the |fragment|.
+ 7. Let |styleMap| be a new {{StylePropertyMapReadOnly}} populated with only the
+ [=computed value=]'s for properties listed in |inputProperties|.
- Issue(w3c/css-houdini-drafts#23): Decide geometry information should be in level 1.
+ 8. Let |renderingContext| be the result of [=create a PaintRenderingContext2D object=] given:
+ - "width" - The width given by |snappedConcreteObjectSize|.
+ - "height" - The height given by |snappedConcreteObjectSize|.
+ - "paintRenderingContext2DSettings" - The
+ [=paint definition/PaintRenderingContext2DSettings object=] given by |definition|.
- 9. To produce the image output, invoke a method on a class inside a Worklet given "paint" as the methodPropertyKey and [|renderingContext|, |geometry|, |styleMap|] as the arguments with the following options:
- - To create a worklet global scope the user agent will:
+ Note: The |renderingContext| is not be re-used between invocations of paint. Implicitly this
+ means that there is no stored data, or state on the |renderingContext| between
+ invocations. For example you can't setup a clip on the context, and expect the same clip
+ to be applied next time the paint method is called.
- Return a new {{RenderWorkletGlobalScope}}.
- - To lookup a class instance on a worklet global scope given a |workletGlobalScope|, the user agent will:
+ Note: Implicitly this also means that |renderingContext| is effectively "neutered" after a
+ paint method is complete. The author code may hold a reference to |renderingContext| and
+ invoke methods on it, but this will have no effect on the current image, or subsequent
+ images.
- Return the result of looking up |name| on the |workletGlobalScope|'s name to paint instance map.
+ 9. Let |paintSize| be a new {{PaintSize}} initialized to the width and height defined by
+ |snappedConcreteObjectSize|.
- If an exception is thrown the resulting image output will be an invalid image.
+ 10. At this stage the user agent may re-use an image from a previous invocation if |paintSize|,
+ |styleMap|, |inputArguments| are equivalent to that previous invocation. If so let the image
+ output be that cached image and abort all these steps.
- Otherwise the image output should be produced from the |renderingContext| given to the paint method.
+
+ In the example below, both div-1 and div-2 have paint
+ functions which have equivalent javascript arguments. A user-agent can cache the result
+ of one invocation and use it for both elements.
- Note: User agents should provide tooling within their debugging tools to show authors a partial output of the image, if an exception is thrown.
+
- 10. Set the paint valid flag for the <> function on the |fragment| to paint-valid.
+
+ <style>
+ .div-1 {
+ width: 50px;
+ height: 50px;
+ background-image: paint(simple);
+ }
+ .div-2 {
+ width: 100px;
+ height: 100px;
-Note: The user agent should consider long running paint functions similar to long running script in the main execution context.
- For example, they should show a "unresponsive script" dialog or similar.
- In addition user agents should provide tooling within their debugging tools to show authors how expensive their paint classes are.
+ background-size: 50% 50%;
+ background-image: paint(simple);
+ }
+ </style>
+ <div class=div-1></div>
+ <div class=div-2></div>
+ <script>
+ CSS.paintWorklet.addModule('paint.js');
+ </script>
+
+
+
+ 11. Let |paintFunctionCallback| be |definition|'s [=paint function=].
+
+ 12. [=Invoke=] |paintFunctionCallback| with arguments «|renderingContext|, |paintSize|,
+ |styleMap|, |inputArguments|», and with |paintInstance| as the [=callback this value=].
+
+ If |paintFunctionCallback| does not complete within an acceptable time (as determined by the
+ user agent, i.e. it is a "long running script") the user agent may terminate the
+ script, let the image output be an [=invalid image=], and abort all these steps.
+
+ Note: User agents could provide tooling within their debugging tools to show authors how
+ expensive their paint classes are. User agents could also how an "unresponsive script"
+ dialog in this case if appropriate.
+
+ 13. The image output is to be produced from the |renderingContext| given to the method.
+
+ If an exception is [=thrown=] the let the image output be an [=invalid image=].
+
+Note: The contents of the resulting image are not designed to be accessible. Authors can communicate
+ any useful information through the standard accessibility APIs.
+
-Note: The contents of the image are not designed to be accessible. Authors should communicate any useful information through the standard accessibility APIs.
+Global Scope Selection {#global-scope-selection}
+------------------------------------------------
-Issue(w3c/css-houdini-drafts#24): Determine how to side-load images or other data.
+When the user agent needs to select a {{PaintWorkletGlobalScope}} from the paint [=worklet's
+WorkletGlobalScopes=] [=list=] it must:
-Issue: What information should we provide for read-modify-write use-cases?
- Are read-modify-write use-cases important for v1?
- For example, if you are sliding the previous paint output out?
- For providing the previous paint output we should provide an ImageBitmap if you ask.
+ - Select from at least two {{PaintWorkletGlobalScope}}s, unless the user agent is under
+ memory constraints.
-Issue: Describe what happens if a callback doesn't hit a frame timing boundary. I.e. just renders a transparent image instead?
+ - Not re-use the same {{PaintWorkletGlobalScope}} more than 1000 times in a row.
+
+ Note: The 1000 limit was picked as a high upper bound, this limit may improve (downwards)
+ over time.
+
+Note: These rules exist to ensure that authors do not rely on being able to store state on the
+ global object or non-regeneratable state on the class. See [[worklets-1#code-idempotency]].
Examples {#examples}
====================
-Example 1: A colored circle. {#example-1}
------------------------------------------
+Example 1: Colored Circle {#example-1}
+--------------------------------------
+
+The example below makes use of the fact that <> functions are able to be animated. E.g.
+when the textarea is focused in the example below, the --circle-color property will
+transition from deepskyblue to purple.
+
+This ability isn't limited to just transitions, it also applies to CSS animations, and the Web
+Animations API.
+
+
-// Inside RenderWorkletGlobalScope.
+// circle.js
registerPaint('circle', class {
- static get inputProperties() { return ['--circle-color']; }
+ static get inputProperties() { return ['--circle-color']; }
+ paint(ctx, geom, properties) {
+ // Change the fill color.
+ const color = properties.get('--circle-color');
+ ctx.fillStyle = color.cssText;
+
+ // Determine the center point and radius.
+ const x = geom.width / 2;
+ const y = geom.height / 2;
+ const radius = Math.min(x, y);
+
+ // Draw the circle \o/
+ ctx.beginPath();
+ ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
+ ctx.fill();
+ }
+});
+
+
+Example 2: Image Placeholder {#example-2}
+-----------------------------------------
+
+It is possible for an author to use paint to draw a placeholder image while an image is being
+loaded.
+
+
+// heading-color.js
+registerPaint('heading-color', class {
+ static get inputProperties() { return []; }
+ paint(ctx, geom, properties) {
+ // Select a color based on the width and height of the image.
+ const width = geom.width;
+ const height = geom.height;
+ const color = colorArray[(width * height) % colorArray.length];
+
+ // Draw just a solid image.
+ ctx.fillStyle = color;
+ ctx.fillRect(0, 0, width, height);
+ }
+});
+
+
+Example 5: Drawing outside an element's area {#example-5}
+---------------------------------------------------------
+
+It is possible to draw outside an element's area by using the 'border-image' property.
+
+
+
diff --git a/css-paint-api/issues-list-2018-04-10.txt b/css-paint-api/issues-list-2018-04-10.txt
new file mode 100644
index 00000000..41cef9ac
--- /dev/null
+++ b/css-paint-api/issues-list-2018-04-10.txt
@@ -0,0 +1,38 @@
+Draft: https://www.w3.org/TR/2018/WD-css-paint-api-1-20180410/
+Title: CSS Paint API Level 1
+
+----
+Issue 1.
+Summary: Filter out unsupported properties from inputProperties.
+From: Darren Shen
+Comment: https://github.com/w3c/css-houdini-drafts/issues/523
+Response: https://github.com/w3c/css-houdini-drafts/issues/523#issuecomment-351219067
+Changes: https://github.com/w3c/css-houdini-drafts/commit/50fa9b8bedde46462113d756d6895701d3d743d0
+Closed: Accepted
+Resolved: Editor discretion
+----
+Issue 2.
+Summary: Allow inputArguments define optional arguments.
+From: zheeeng
+Comment: https://github.com/w3c/css-houdini-drafts/issues/763
+Response: https://github.com/w3c/css-houdini-drafts/issues/763#issuecomment-401653404
+Closed: Deferred
+----
+Issue 3.
+Summary: Need to convert paint function to WebIDL Function type.
+From: Shiino Yuki
+Comment: https://github.com/w3c/css-houdini-drafts/issues/743
+Response: https://github.com/w3c/css-houdini-drafts/issues/743#issuecomment-379738324
+Changes: https://github.com/w3c/css-houdini-drafts/commit/f9f174dca09f5149c3df558d1c8e74517df19f5e
+Closed: Accepted
+Resolved: https://lists.w3.org/Archives/Public/public-houdini/2018Apr/0002.html
+----
+Issue 4.
+Summary: Use WebIDL callback interface inside of registerPaint.
+From: Shiino Yuki
+Comment: https://github.com/w3c/css-houdini-drafts/issues/743
+Response: https://github.com/w3c/css-houdini-drafts/issues/743#issuecomment-379738324
+Closed: Rejected
+Resolved: https://lists.w3.org/Archives/Public/public-houdini/2018Apr/0002.html
+Verified: https://github.com/w3c/css-houdini-drafts/issues/743#issuecomment-379646250
+----
diff --git a/css-parser-api/Overview.bs b/css-parser-api/Overview.bs
index f72ce478..82f9a00d 100644
--- a/css-parser-api/Overview.bs
+++ b/css-parser-api/Overview.bs
@@ -1,13 +1,19 @@
-Title: CSS Parser API Level 1
-Status: DREAM
-Group: houdini
-ED: https://drafts.css-houdini.org/css-parser-api-1/
+Title: CSS Parser API
Shortname: css-parser-api
Level: 1
-Abstract:
-Editor: Tab Atkins, jackalmage@gmail.com
-Editor: Shane Stephens, shanestephens@google.com
-Editor: Daniel Glazman, danie.glazman@disruptiveinnovations.com
-Editor: Brian Kardell, bkardell@gmail.com
+Status: UD
+Group: HOUDINI
+URL: https://drafts.css-houdini.org/css-parser-api/
+Editor: Tab Atkins-Bittner, Google, http://xanthir.com/contact/, w3cid 42199
+Abstract: An API exposing the CSS parser more directly,
+Abstract: for parsing arbitrary CSS-like languages into a mildly typed representation.
+
+Introduction {#intro}
+=====================
+
+This spec is intentionally left blank,
+as it is currently being developed in the WICG
+at https://github.com/wicg/css-parser-api/
+(live spec).
diff --git a/css-parser-api/readme.md b/css-parser-api/readme.md
new file mode 100644
index 00000000..e8a82d7f
--- /dev/null
+++ b/css-parser-api/readme.md
@@ -0,0 +1,5 @@
+CSS Parser Explainer
+====================
+
+This specification is not currently being worked on in the Houdini TF.
+Instead, it will be worked on in a [WICG repository](https://github.com/WICG/CSS-Parser-API).
diff --git a/css-properties-values-api/Overview.bs b/css-properties-values-api/Overview.bs
index e3f4d3aa..1caace06 100644
--- a/css-properties-values-api/Overview.bs
+++ b/css-properties-values-api/Overview.bs
@@ -1,17 +1,20 @@
Title: CSS Properties and Values API Level 1
-Status: DREAM
+Status: ED
Group: houdini
ED: https://drafts.css-houdini.org/css-properties-values-api-1/
+TR: https://www.w3.org/TR/css-properties-values-api-1/
+Previous Version: https://www.w3.org/TR/2017/WD-css-properties-values-api-1-20171109/
+Previous Version: http://www.w3.org/TR/2016/WD-css-properties-values-api-1-20160607/
Shortname: css-properties-values-api
Level: 1
-Abstract: This CSS module defines an API for registering new CSS properties. Properties registered using this API are provided with a parse syntax that defines a type, inheritance behaviour, and a default value.
-Editor: Tab Atkins, jackalmage@gmail.com
-Editor: Shane Stephens, shanestephens@google.com
-Editor: Daniel Glazman, daniel.glazman@disruptive-innovations.com
-Editor: Alan Stearns, stearns@adobe.com
-Editor: Elliot Sprehn, esprehn@chromium.org
-Editor: Greg Whitworth, gwhit@microsoft.com
+Abstract: This CSS module defines an API for registering new CSS properties. Properties registered using this API are provided with a parse syntax that defines a type, inheritance behaviour, and an initial value.
+Editor: Tab Atkins-Bittner, Google, http://xanthir.com/contact/, w3cid 42199
+Former Editor: Shane Stephens, shanestephens@google.com, w3cid 47691
+Editor: Daniel Glazman, daniel.glazman@disruptive-innovations.com, w3cid 13329
+Editor: Alan Stearns, stearns@adobe.com, w3cid 46659
+Former Editor: Elliot Sprehn, esprehn@chromium.org
+Editor: Greg Whitworth, gwhit@microsoft.com, w3cid 69511
Ignored Terms: boolean, Animatable, Map, Context, isolated worker, SyntaxError,
Ignored Terms: InvalidModificationError, NotFoundError, StylePropertyMapReadOnly,
Ignored Terms: worklet global scope
@@ -20,18 +23,29 @@ Ignored Terms: construct, name map of inputs
Ignored Vars: arguments, methodPropertyKey, inputStyleMap, workletGlobalScope
Ignored Terms: WorkletGlobalContext
Repository: w3c/css-houdini-drafts
-At Risk: the apply hook ()
+Markup Shorthands: css on, markdown on
+spec:css-color-4; type:property; text:color
+spec:css-syntax-3; type:dfn;
+ text:input stream
+ text:starts with an identifier
+ text:consume a name
+spec:css-transforms-1; type:type; text:
+spec:css-values-4;
+ type:value;
+ text:ex
+ text:cap
+ type:dfn
+ text:identifier
+spec:cssom-1; type:interface; text:CSS
+spec:dom;
+ type:interface; text:Document
+ type:dfn; text:shadow tree
+spec:infra; type:dfn;
+ text:string
+ text:list
Introduction {#intro}
@@ -47,116 +61,801 @@ 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. This
-specification also provides an additional javascript-mediated means via which custom
-properties can modify the computed value of native properties.
+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.
-Accordingly, it is recommended not to use the computed value modification facilities of this
-specification directly for layout, but only to impact cascading in a non-native manner.
-[[css-variables]] defines a new <> function that can be used to
-insert the values of custom properties into other CSS property values. Where
-possible, this mechanism should be preferred above the computed value modification
-facilities of this specification.
-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
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 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}
+------------------------------------------------
+
+
+
+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}
+------------------------------------------------
+
+
+
+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:
-The {{PropertyDescriptor}} dictionary {#the-propertydescriptor-dictionary}
+Additional, the {{Document}} object gains a new \[[registeredPropertySet]] private slot,
+which is a set of records that describe registered custom properties.
+
+The {{registerProperty()}} Function {#the-registerproperty-function}
+--------------------------------------------------------------------
+
+The registerProperty(PropertyDefinition definition) method
+registers a custom property according to the configuration options provided in
+definition.
+When it is called,
+it executes the register a custom property algorithm,
+passing the options in its definition argument
+as arguments of the same names.
+
+
+ To register a custom property
+ with |name| being a string,
+ and optionally
+ |syntax| being a string,
+ |inherits| being a boolean,
+ and |initialValue| being a string,
+ execute these steps:
+
+ 1. Let |property set|
+ be the value of the
+ current global object's
+ associated Document's
+ {{[[registeredPropertySet]]}} slot.
+
+ 2. If |name| is not a [=custom property name string=],
+ throw a {{SyntaxError}}
+ and exit this algorithm.
+
+ If |property set|
+ already contains an entry with |name| as its property name
+ (compared codepoint-wise),
+ throw an {{InvalidModificationError}}
+ and exit this algorithm.
+
+ 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 |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,
+ as defined in [[!css-variables]].
+ Skip to the next step of this algorithm.
+
+ Otherwise,
+ if |syntax definition| is the universal syntax definition,
+ [=CSS/parse=] |initialValue| as a <>.
+ If this fails,
+ throw a {{SyntaxError}}
+ and exit this algorithm.
+ Otherwise,
+ let |parsed initial value| be the parsed result.
+ Skip to the next step of this algorithm.
+
+ Otherwise, if |initialValue| is not present,
+ throw a {{SyntaxError}}
+ and exit this algorithm.
+
+ Otherwise,
+ [=CSS/parse=] {{PropertyDefinition/initialValue}}
+ according to |syntax definition|.
+ If this fails,
+ throw a {{SyntaxError}}
+ and exit this algorithm.
+
+ Otherwise, let |parsed initial value| be the parsed result.
+ If |parsed initial value| is not computationally independent,
+ throw a {{SyntaxError}}
+ and exit this algorithm.
+
+ 5. Set |inherit flag| to the value of |inherits|.
+
+ 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|.
+ [=set/Append=] |registered property|
+ to |property set|.
+
+
+A property value is computationally independent
+if it can be converted into a computed value
+using only the value of the property on the element,
+and "global" information that cannot be changed by CSS.
+
+
+ For example, ''5px'' is computationally independent,
+ as converting it into a computed value doesn't change it at all.
+ Similarly, ''1in'' is computationally independent,
+ as converting it into a computed value
+ relies only on the "global knowledge" that ''1in'' is ''96px'',
+ which can't be altered or adjusted by anything in CSS.
+
+ On the other hand, ''3em'' is not computationally independent,
+ because it relies on the value of 'font-size' on the element
+ (or the element's parent).
+ Neither is a value with a ''var()'' function,
+ because it relies on the value of a custom property.
+
+
+When a custom property is registered with a given type,
+the process via which specified values for that property are turned into computed values
+is defined fully by the type selected,
+as described in [[#calculation-of-computed-values]].
+
+ Note: A way to unregister properties may be added in the future.
+
+Registering a custom property must not affect the [=cascade=] in any way.
+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=]
+violates the registered syntax,
+however,
+the property becomes [=invalid at computed-value time=]
+(and thus resets to the registered initial value).
+
+
+ By default, all custom property declarations that can be parsed as a sequence of tokens
+ are valid. Hence, the result of this stylesheet:
+
+
+
+ is to set the 'color' property of elements of class "thing" to ''inherit''.
+ The second '--my-color' declaration overrides the first at parse time (both are valid),
+ and the ''var()'' reference in the 'color' property is found to be invalid at computed-value time
+ (because ''url("not-a-color")'' is not a color).
+ At this stage of the CSS pipeline (computation time),
+ the only available fallback is the initial value of the property,
+ which in the case of color is ''inherit''.
+ Although there was a valid usable value (green),
+ this was removed during parsing because it was superseded by the URL.
+
+ If we call:
+
+
+
+ the parsing doesn't significantly change,
+ regardless of whether the registration occurs before or after the stylesheet above.
+ The only difference is that it's the '--my-color' property that becomes [=invalid at computed-value time=] instead
+ and gets set to its initial value of ''black'';
+ then 'color' is validly set to ''black'',
+ rather than being [=invalid at computed-value time=]
+ and becoming ''inherit''.
+
+
+The {{PropertyDefinition}} Dictionary {#the-propertydefinition-dictionary}
--------------------------------------------------------------------------
-A PropertyDescriptor dictionary represents author-specified configuration
-options for a custom property. {{PropertyDescriptor}} dictionaries contain the
+A PropertyDefinition dictionary represents author-specified configuration
+options for a custom property. {{PropertyDefinition}} dictionaries contain the
following members:
-: name
+: name
:: The name of the custom property being defined.
-: syntax
+: syntax
:: A string representing how this custom property is parsed.
-: inherits
+: inherits
:: True if this custom property should inherit down the DOM tree; False otherwise.
-: initialValue
+: initialValue
:: The initial value of this custom property.
-The {{registerProperty()}} function {#the-registerproperty-function}
---------------------------------------------------------------------
-
-The registerProperty(PropertyDescriptor descriptor) method
-registers a custom property according the to configuration options provided in
-descriptor.
-
-Attempting to register properties with a {{PropertyDescriptor/name}} that doesn't
-correspond to the <> production must cause {{registerProperty()}}
-to throw a {{SyntaxError}}.
-
-The list of types supported in the {{PropertyDescriptor/syntax}} member are listed
-in . Currently, only simple
-type references are supported. Attempting to register properties with a
-{{PropertyDescriptor/syntax}} that is not supported must cause {{registerProperty()}}
-to throw a {{SyntaxError}}.
-
-Note: for example, the syntax string could be "<length>" or "<number>".
-Note: in future levels we anticipate supporting more sophisticated parse strings, e.g.
-"<length> || <number>"
+
-Attempting to call {{registerProperty()}} with an {{PropertyDescriptor/initialValue}} that is
-not parseable using the provided {{PropertyDescriptor/syntax}} must cause it to
-throw a {{SyntaxError}}.
+Syntax Strings {#syntax-strings}
+================================
-When a custom property is registered with a given type, the process via which specified
-values for that property are turned into computed values is defined
-fully by the type selected, as described in
-.
+A syntax string describes the value types accepted by a registered
+custom property. Syntax strings consists of
+[=syntax component names=], that are
+optionally [[#multipliers|multiplied]] and [[#combinator|combined]].
-Note: As defined by [[css3-animations]] and [[css3-transitions]], it is possible to
-specify animations and transitions that reference custom properties.
+A syntax string can be parsed into a syntax definition, which is either:
-When referenced by animations and transitions, custom properties will interpolate
-in a manner defined by their types.
-If the start and end of an interpolation have matching types, then they
-will interpolate as specified in [[!css3-animations]] or the corresponding property.
-Otherwise, the interpolation falls back to the default 50% flip described in
-[[!css3-animations]].
+ 1. A list of syntax components, each of which accept the value types
+ specified in [[#supported-names]], or
+ 2. The universal syntax definition ('*'), which accepts any valid token
+ stream.
-If {{registerProperty()}} is called with a descriptor name that matches an already registered property,
-then an {{InvalidModificationError}} is thrown and the re-registration fails.
+Note: Regardless of the syntax specified, all custom properties accept
+CSS-wide keywords, and process these values
+appropriately.
-Properties can be unregistered using
-unregisterProperty(DOMString name).
-If this function is called with a name that doesn't match an existing property
-then a {{NotFoundError}} is thrown.
+
+ For example, the following are all valid syntax strings.
+
+ : "<length>"
+ :: accepts length values
+ : "<length> | <percentage>"
+ :: accepts lengths, percentages, percentage calc expressions, and length calc
+ expressions, but not calc expressions containing a combination of length
+ and percentage values.
+ : "<length-percentage>"
+ :: accepts all values that "<length> | <percentage>" would
+ accept, as well as calc expressions containing a combination of both length
+ and percentage values.
+ : "big | bigger | BIGGER"
+ :: accepts the ident big, or the ident bigger, or
+ the ident BIGGER.
+ : "<length>+"
+ :: accepts a space-separated list of length values.
+ : "*"
+ :: accepts any valid token stream
+
-Successful calls to both {{registerProperty()}} and {{unregisterProperty()}}
-trigger a reparse of the specified value of the newly registered or unregistered
-property, followed by an invalidation of the computed style of all {{document}}s
-created on the in-scope {{Window}}.
+Note: The internal grammar of syntax strings is a subset of
+[[css-values-3#value-defs|the CSS Value Definition Syntax]]. Future levels of this specification are expected
+to expand the complexity of the allowed grammar, allowing custom properties
+that more closely resemble the full breadth of what CSS properties allow.
-Issue(63): Phrasing? How do I write this correctly?
+The remainder of this chapter describes the internal grammar of the syntax
+strings.
-Supported syntax strings {#supported-syntax-strings}
-----------------------------------------------------
+Supported Names {#supported-names}
+----------------------------------
-The following syntax strings are supported:
+This section defines the supported syntax component names, and the
+corresponding types accepted by the resulting syntax component.
: "<length>"
:: Any valid <> value
@@ -166,328 +865,437 @@ The following syntax strings are supported:
:: Any valid <> value
: "<length-percentage>"
:: Any valid <> or <> value, any valid <>
- expression combining <> and <> components.
+ 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 <
+ To determine whether two {{CSSNumericValue}}s |value1| and |value2|
+ are equal numeric values,
+ perform the following steps:
-
+ 1. If |value1| and |value2| are not members of the same interface,
+ return `false`.
-{{PositionValue}} objects represent values for properties that take <>
-productions, for example 'background-position'.
+ 2. If |value1| and |value2| are both {{CSSUnitValue}}s,
+ return `true` if they have equal {{CSSUnitValue/unit}} and {{CSSUnitValue/value}} internal slots,
+ or `false` otherwise.
-The x attribute contains the position offset
-from the left edge of the container, expressed as a length.
+ 3. If |value1| and |value2| are both
+ {{CSSMathSum}}s, {{CSSMathProduct}}s, {{CSSMathMin}}s, or {{CSSMathMax}}s:
-The y attribute contains the position offset
-from the top edge of the container, expressed as a length.
+ 1. If |value1|’s {{CSSMathSum/values}} and |value2|s {{CSSMathSum/values}} internal slots
+ have different [=list/sizes=],
+ return `false`.
-Note that <> productions accept a complicated combination of keywords
-and values. When specified as such in a stylesheet or via the untyped CSSOM,
-the cssString attribute will contain the specified
-string. However, this string is normalized as two Lengths into the x and y values of the
-{{StyleValue}} object.
+ 2. If any [=list/item=] in |value1|'s {{CSSMathSum/values}} internal slot
+ is not an [=equal numeric value=]
+ to the [=list/item=] in |value2|’s {{CSSMathSum/values}} internal slot
+ at the same index,
+ return `false`.
-New {{PositionValue}} objects can only be constructed via pairs of lengths, and
-will only return the direct serialization of these lengths in the
-cssString attribute.
+ 3. Return `true`.
-
+ 4. Assert: |value1| and |value2| are both {{CSSMathNegate}}s or {{CSSMathInvert}}s.
-For example, the following style sheet:
+ 5. Return whether |value1|’s {{CSSMathNegate/value}}
+ and |value2|’s {{CSSMathNegate/value}}
+ are [=equal numeric values=].
+
-
-.example {
- background-position: center bottom 10px;
-}
-
+
+ The to(|unit|) method converts an existing {{CSSNumericValue}} |this|
+ into another one with the specified |unit|,
+ if possible.
+ When called, it must perform the following steps:
-Will produce the following behavior:
+ 1. Let |type| be the result of [=creating a type=] from |unit|.
+ If |type| is failure,
+ [=throw=] a {{SyntaxError}}.
-
-// "center bottom 10px"
-document.querySelector('.example').styleMap.get('background-position').cssString;
+ 2. Let |sum| be the result of [=creating a sum value=] from |this|.
+ If |sum| is failure,
+ [=throw=] a {{TypeError}}.
-// 50% - as a SimpleLength
-document.querySelector('.example').styleMap.get('background-position').x;
+ 3. If |sum| has more than one [=list/item=],
+ [=throw=] a {{TypeError}}.
+ Otherwise, let |item| be the result of [=create a CSSUnitValue from a sum value item|creating a CSSUnitValue=]
+ from the sole [=list/item=] in |sum|,
+ then [=convert a CSSUnitValue|converting=] it to |unit|.
+ If |item| is failure,
+ [=throw=] a {{TypeError}}.
-// calc(100% - 10px) - as a CalcLength
-document.querySelector('.example').styleMap.get('background-position').y;
-
+ 4. Return |item|.
+
+
+
+ When asked to create a CSSUnitValue from a sum value item |item|,
+ perform the following steps:
+
+ 1. If |item| has more than one [=map/entry=] in its [=sum value/unit map=],
+ return failure.
+
+ 2. If |item| has no [=map/entries=] in its [=sum value/unit map=],
+ return a new {{CSSUnitValue}}
+ whose {{CSSUnitValue/unit}} internal slot
+ is set to "number",
+ and whose {{CSSUnitValue/value}} internal slot
+ is set to |item|’s [=sum value/value=].
+
+ 3. Otherwise, |item| has a single [=map/entry=] in its [=sum value/unit map=].
+ If that [=map/entry’s=] [=map/value=] is anything other than `1`,
+ return failure.
+ 4. Otherwise, return a new {{CSSUnitValue}}
+ whose {{CSSUnitValue/unit}} internal slot
+ is set to that [=map/entry’s=] [=map/key=],
+ and whose {{CSSUnitValue/value}} internal slot
+ is set to |item|’s [=sum value/value=].
-Mapping of properties to accepted types
-=======================================
-This section provides a table of which types of {{StyleValue}} a given property can accept.
-Note that most, but not all properties take {{KeywordValue}}.
-Shorthand properties and values are not supported.
-
-
-
-Issue: Spec up ColorValue, URIValue, ShapeValue, StringValue, CounterValue, TimeValue, PercentageValue, FrequencyValue, VoiceValue, CustomIdentValue, TransitionTimingFunctionValue. What is attr() in other specs?
-
-Issue: Do we want to make a generic PairValue and QuadValue, or have more specific classes for background-size, border-image-repeat, border-radius-*, border-image-outset, border-image-width?
-
-Issue: BorderImageSliceValue represents [ | ]{1,4} && fill?
-
-Issue: What do we do for play-during, which takes [ mix || repeat ]?
-
-Issue: Do we want a pair type for things like quotes which take []
-
-Issue: Do we need to say that LengthValues must be positive e.g. for border-widths here?
-
-Issue: Do we want a font value class? How about a FontWeightValue (for 100, 200 etc) class?
-
-Issue: Add more CSS3 properties. This table currently only contains CSS2.1 properties and CSS3 properties alphabetically to border-radius.
-
-{{StyleValue}} normalization {#stylevalue-normalization}
-========================================================
-
-Issue: write me
+
+ The toSum(...|units|) method converts an existing {{CSSNumericValue}} |this|
+ into a {{CSSMathSum}} of only {{CSSUnitValue}}s with the specified units,
+ if possible.
+ (It's like {{CSSNumericValue/to()}},
+ but allows the result to have multiple units in it.)
+ If called without any units,
+ it just simplifies |this| into a minimal sum of {{CSSUnitValue}}s.
+
+ When called, it must perform the following steps:
+
+ 1. [=list/For each=] |unit| in |units|,
+ if the result of [=creating a type=] from |unit| is failure,
+ [=throw=] a {{SyntaxError}}.
+
+ 2. Let |sum| be the result of [=creating a sum value=] from |this|.
+ If |sum| is failure,
+ [=throw=] a {{TypeError}}.
+
+ 3. Let |values| be the result of [=create a CSSUnitValue from a sum value item|creating a CSSUnitValue=]
+ [=list/for each=] [=list/item=] in |sum|.
+ If any [=list/item=] of |values| is failure,
+ [=throw=] a {{TypeError}}.
+
+ 4. If |units| is [=list/empty=],
+ sort |values| in [=code point=] order according to the {{CSSUnitValue/unit}} internal slot of its [=list/items=],
+ then return a new {{CSSMathSum}} object
+ whose {{CSSMathSum/values}} internal slot is set to |values|.
+
+ 5. Otherwise,
+ let |result| initially be an empty [=list=].
+ [=list/For each=] |unit| in |units|:
+
+ 1. Let |temp| initially be a new {{CSSUnitValue}}
+ whose {{CSSUnitValue/unit}} internal slot
+ is set to |unit|
+ and whose {{CSSUnitValue/value}} internal slot
+ is set to `0`.
+
+ 2. [=list/For each=] |value| in |values|:
+
+ 1. Let |value unit| be |value|’s {{CSSUnitValue/unit}} internal slot.
+
+ 2. If |value unit| is a [=compatible unit=] with |unit|,
+ then:
+
+ 1. [=convert a CSSUnitValue|Convert=] |value| to |unit|.
+ 2. Increment |temp|’s {{CSSUnitValue/value}} internal slot
+ by the value of |value|’s {{CSSUnitValue/value}} internal slot.
+ 3. [=list/Remove=] |value| from |values|.
+
+ 3. [=list/Append=] |temp| to |result|.
+
+ 6. If |values| is not [=list/empty=],
+ [=throw=] a {{TypeError}}.
+ |this| had units that you didn't ask for.
+
+ 7. Return a new {{CSSMathSum}} object
+ whose {{CSSMathSum/values}} internal slot
+ is set to |result|.
+
+
+
+ The type() method
+ returns a representation of the [=type=] of |this|.
+
+ When called, it must perform the following steps:
+
+ 1. Let |result| be a new {{CSSNumericType}}.
+
+ 2. For each |baseType| → |power| in the [=type=] of |this|,
+ 1. If |power| is not 0,
+ set |result|[|baseType|] to |power|.
+
+ 3. If the [=percent hint=] of |this| is not null,
+ 1. Set {{CSSNumericType/percentHint}} to the [=percent hint=] of |this|.
+
+ 4. Return |result|.
+
+
+
+ A sum value
+ 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 value,
+ which is a number,
+ and a unit map,
+ which is a [=ordered map|map=] of units (strings) to powers (integers).
+
+
+ Here are a few examples of CSS values,
+ and their equivalent [=sum values=]:
+
+ * ''1px'' becomes `«(1, «["px" → 1]»)»`
+ * ''calc(1px + 1in)'' becomes `«(97, «["px" → 1]»)»`
+ (because ''in'' and ''px'' are [=compatible units=],
+ and ''px'' is the [=canonical unit=] for them)
+ * ''calc(1px + 2em)'' becomes `«(1, «["px" → 1]»), (2, «["em" → 1]»)»`
+ * ''calc(1px + 2%)'' becomes `«(1, «["px" → 1]»), (2, «["percent" → 1]»)»`
+ (percentages are allowed to add to other units,
+ but aren't resolved into another unit,
+ like they are in a [=type=])
+ * ''calc(1px * 2em)'' becomes `«(2, «["em" → 1, "px" → 1]»)»`
+ * ''calc(1px + 1deg)'' can't be represented as a [=sum value=]
+ because it's an invalid computation
+ * ''calc(1px * 2deg)'' becomes `«(2, «["deg" → 1, "px" → 1]»)»`
+
+
+ To create a sum value from a {{CSSNumericValue}} |this|,
+ the steps differ based on |this|’s class:
+
+
+ : {{CSSUnitValue}}
+ ::
+
+ 1. Let |unit| be the value of |this|’s {{CSSUnitValue/unit}} internal slot,
+ and |value| be the value of |this|’s {{CSSUnitValue/value}} internal slot.
+ 2. If |unit| is a member of a set of [=compatible units=],
+ and is not the set's [=canonical unit=],
+ multiply |value| by the conversion ratio between |unit| and the [=canonical unit=],
+ and change |unit| to the [=canonical unit=].
+ 3. If |unit| is `"number"`,
+ return «(|value|, «[ ]»)».
+ 3. Otherwise, return «(|value|, «[|unit| → 1]»)».
+
+
+ : {{CSSMathSum}}
+ ::
+
+ 1. Let |values| initially be an empty [=list=].
+
+ 2. [=list/For each=] |item| in this’s {{CSSMathSum/values}} internal slot:
+
+ 1. Let |value| be the result of [=creating a sum value=] from |item|.
+ If |value| is failure,
+ return failure.
+
+ 2. [=list/For each=] |subvalue| of |value|:
+
+ 1. If |values| already contains an [=list/item=]
+ with the same [=sum value/unit map=] as |subvalue|,
+ increment that [=list/item=]’s [=sum value/value=]
+ by the [=sum value/value=] of |subvalue|.
+
+ 2. Otherwise, [=list/append=] |subvalue| to |values|.
+
+ 3. [=create a type from a unit map|Create a type=]
+ from the [=sum value/unit map=]
+ of each [=list/item=] of |values|,
+ and [=add=] all the types together.
+ If the result is failure,
+ return failure.
+
+ 4. Return |values|.
+
+
+ : {{CSSMathNegate}}
+ ::
+
+ 1. Let |values| be the result of [=creating a sum value=]
+ from this’s {{CSSMathNegate/value}} internal slot.
+
+ 2. If |values| is failure,
+ return failure.
+
+ 3. Negate the [=sum value/value=] of each [=list/item=] of |values|.
+
+ 4. Return |values|.
+
+
+ : {{CSSMathProduct}}
+ ::
+
+ 1. Let |values| initially be the [=sum value=] «(1, «[ ]»)».
+ (I.e. what you'd get from ''1''.)
+
+ 2. [=list/For each=] |item| in this’s {{CSSMathProduct/values}} internal slot:
+
+ 1. Let |new values| be the result of [=creating a sum value=] from |item|.
+ Let |temp| initially be an empty [=list=].
+
+ 2. If |new values| is failure,
+ return failure.
+
+ 3. [=list/For each=] |item1| in |values|:
+
+ 1. [=list/For each=] |item2| in |new values|:
+
+ 1. Let |item| be a [=tuple=] with its [=sum value/value=]
+ set to the product of the [=sum value/values=] of |item1| and |item2|,
+ and its [=sum value/unit map=]
+ set to the [=product of two unit maps|product=] of the [=sum value/unit maps=] of |item1| and |item2|,
+ with all [=map/entries=] with a zero value removed.
+
+ 2. Append |item| to |temp|.
+
+ 4. Set |values| to |temp|.
+
+ 3. Return |values|.
+
+
+ : {{CSSMathInvert}}
+ ::
+
+ 1. Let |values| be the result of [=creating a sum value=]
+ from this’s {{CSSMathInvert/value}} internal slot.
+
+ 2. If |values| is failure,
+ return failure.
+
+ 3. If the length of [=values=] is more than one,
+ return failure.
+
+ 3. Invert (find the reciprocal of) the [=sum value/value=] of the [=list/item=] in |values|,
+ and negate the [=map/value=] of each [=map/entry=] in its [=sum value/unit map=].
+
+ 4. Return |values|.
+
+
+ : {{CSSMathMin}}
+ ::
+
+ 1. Let |args| be the result of [=creating a sum value=]
+ [=list/for each=] [=list/item=] in this’s {{CSSMathMin/values}} internal slot.
+
+ 2. If any [=list/item=] of |args| is failure,
+ or has a length greater than one,
+ return failure.
+
+ 3. If not all of the [=sum value/unit maps=] among the [=list/items=] of |args| are identical,
+ return failure.
+
+ 4. Return the [=list/item=] of |args| whose sole [=list/item=] has the smallest [=sum value/value=].
+
+
+ : {{CSSMathMax}}
+ ::
+
+ 1. Let |args| be the result of [=creating a sum value=]
+ [=list/for each=] [=list/item=] in this’s {{CSSMathMax/values}} internal slot.
+
+ 2. If any [=list/item=] of |args| is failure,
+ or has a length greater than one,
+ return failure.
+
+ 3. If not all of the [=sum value/unit maps=] among the [=list/items=] of |args| are identical,
+ return failure.
+
+ 4. Return the [=list/item=] of |args| whose sole [=list/item=] has the largest [=sum value/value=].
+
+
+
+
+
+ To create a type from a unit map |unit map|:
+
+ 1. Let |types| be an initially empty [=list=].
+
+ 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|.
+
+ 3. Return the result of [=CSSNumericValue/multiplying=] all the [=list/items=] of |types|.
+
+
+
+ The product of two unit maps |units1| and |units2|
+ is the result given by the following steps:
+
+ 1. Let |result| be a copy of |units1|.
+
+ 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|.
+
+ 3. Return |result|.
+
+
+The {{CSSNumericValue/parse()}} method allows a {{CSSNumericValue}}
+to be constructed directly from a string containing CSS.
+Note that this is a static method,
+existing directly on the {{CSSNumericValue}} interface object,
+rather than on {{CSSNumericValue}} instances.
+
+
+ The parse(|cssText|) method,
+ when called,
+ must perform the following steps:
+
+ 1. [=Parse a component value=] from |cssText|
+ and let |result| be the result.
+ If |result| is a syntax error,
+ [=throw=] a {{SyntaxError}}
+ and abort this algorithm.
+
+ 2. If |result| is not a <>, <>, <>,
+ or a [=math function=],
+ [=throw=] a {{SyntaxError}}
+ and abort this algorithm.
+
+ 3. [=Reify a numeric value=] |result|,
+ and return the result.
+
+
+
+
+### Numeric Value Typing ### {#numeric-typing}
+
+Each {{CSSNumericValue}} has an associated type,
+which is a [=ordered map|map=] of [=base types=] to integers,
+and an associated [=percent hint=].
+The base types are
+"length",
+"angle",
+"time",
+"frequency",
+"resolution",
+"flex",
+and "percent".
+The ordering of a [=type=]’s entries always matches this [=base type=] ordering.
+The percent hint
+is either null or a [=base type=] other than "percent".
+
+Note: As new unit types are added to CSS,
+they'll be added to this list of [=base types=],
+and to the CSS [=math functions=].
+
+
+ To create a type from a string |unit|,
+ follow the appropriate branch of the following:
+
+
+ : |unit| is "number"
+ :: Return «[ ]» (empty map)
+ : |unit| is "percent"
+ :: Return «[ "percent" → 1 ]»
+ : |unit| is a <> unit
+ :: Return «[ "length" → 1 ]»
+ : |unit| is an <> unit
+ :: Return «[ "angle" → 1 ]»
+ : |unit| is a <> unit
+ :: Return «[ "time" → 1 ]»
+ : |unit| is a <> unit
+ :: Return «[ "frequency" → 1 ]»
+ : |unit| is a <> unit
+ :: Return «[ "resolution" → 1 ]»
+ : |unit| is a <> unit
+ :: Return «[ "flex" → 1 ]»
+ : anything else
+ :: Return failure.
+
+
+ In all cases, the associated [=percent hint=] is null.
+
+
+
+ To add two types |type1| and |type2|,
+ perform the following steps:
+
+ 1. Replace |type1| with a fresh copy of |type1|,
+ and |type2| with a fresh copy of |type2|.
+ Let |finalType| be a new [=type=]
+ with an initially empty [=ordered map=]
+ and an initially null [=percent hint=].
+
+ 2.
+
+ : If both |type1| and |type2| have non-null [=percent hints=]
+ with different values
+ :: The types can't be added.
+ Return failure.
+
+ : If |type1| has a non-null [=percent hint=] |hint| and |type2| doesn't
+ :: [=Apply the percent hint=] |hint| to |type2|.
+
+ Vice versa if |type2| has a non-null [=percent hint=] and |type1| doesn't.
+
+ : Otherwise
+ :: Continue to the next step.
+
+
+
+ 3.
+
+ : If all the [=map/entries=] of |type1| with non-zero values
+ are [=map/contained=] in |type2| with the same value,
+ and vice-versa
+ :: Copy all of |type1|’s [=map/entries=] to |finalType|,
+ and then copy all of |type2|’s [=map/entries=] to |finalType|
+ that |finalType| doesn't already [=map/contain=].
+ Set |finalType|’s [=percent hint=] to |type1|’s [=percent hint=].
+ Return |finalType|.
+
+ : If |type1| and/or |type2| [=map/contain=] "percent" with a non-zero value,
+ and |type1| and/or |type2| [=map/contain=] a key *other than* "percent" with a non-zero value
+ :: For each [=base type=] other than "percent" |hint|:
+
+ 1. Provisionally [=apply the percent hint=] |hint| to both |type1| and |type2|.
+
+ 2. If, afterwards, all the [=map/entries=] of |type1| with non-zero values
+ are [=map/contained=] in |type2| with the same value,
+ and vice versa,
+ then copy all of |type1|’s [=map/entries=] to |finalType|,
+ and then copy all of |type2|’s [=map/entries=] to |finalType|
+ that |finalType| doesn't already [=map/contain=].
+ Set |finalType|’s [=percent hint=] to |hint|.
+ Return |finalType|.
+
+ 3. Otherwise, revert |type1| and |type2| to their state at the start of this loop.
+
+ If the loop finishes without returning |finalType|,
+ then the types can't be added.
+ Return failure.
+
+ Note: You can shortcut this in some cases
+ by just checking the sum of all the [=map/values=]
+ of |type1| vs |type2|.
+ If the sums are different,
+ the types can't be added.
+
+ : Otherwise
+ :: The types can't be added.
+ Return failure.
+
+
+
+
+ To apply the percent hint |hint| to a |type|,
+ perform the following steps:
+
+ 1. If |type| doesn't [=map/contain=] |hint|, set |type|[|hint|] to 0.
+ 2. If |type| [=map/contains=] "percent", add |type|["percent"] to |type|[|hint|],
+ then set |type|["percent"] to 0.
+ 4. Set |type|’s [=percent hint=] to |hint|.
+
+
+
+ To multiply two types |type1| and |type2|,
+ perform the following steps:
+
+ 1. Replace |type1| with a fresh copy of |type1|,
+ and |type2| with a fresh copy of |type2|.
+ Let |finalType| be a new [=type=]
+ with an initially empty [=ordered map=]
+ and an initially null [=percent hint=].
+
+ 2. If both |type1| and |type2| have non-null [=percent hints=]
+ with different values,
+ the types can't be multiplied.
+ Return failure.
+
+ 3. If |type1| has a non-null [=percent hint=] |hint| and |type2| doesn't,
+ [=apply the percent hint=] |hint| to |type2|.
+
+ Vice versa if |type2| has a non-null [=percent hint=] and |type1| doesn't.
+
+ 4. Copy all of |type1|’s [=map/entries=] to |finalType|,
+ then [=map/for each=] |baseType| → |power| of |type2|:
+
+ 1. If |finalType|[|baseType|] [=map/exists=],
+ increment its value by |power|.
+ 2. Otherwise, set |finalType|[|baseType|] to |power|.
+
+ Set |finalType|’s [=percent hint=] to |type1|’s [=percent hint=].
+
+ 5. Return |finalType|.
+
+
+
+ To invert a type |type|,
+ perform the following steps:
+
+ 1. Let |result| be a new [=type=]
+ with an initially empty [=ordered map=]
+ and an initially null [=percent hint=]
+ 1. [=map/For each=] |unit| → |exponent| of |type|,
+ set |result|[|unit|] to (-1 * |exponent|).
+ 3. Return |result|.
+
+
+A [=type=] is said to match a CSS production in some circumstances:
+
+* A [=type=] matches <>
+ if its only non-zero [=map/entry=] is «[ "length" → 1 ]»
+ and its [=percent hint=] is null.
+ Similarly for <>, <>, <>, <>, and <>.
+* A [=type=] matches <>
+ if its only non-zero [=map/entry=] is «[ "percent" → 1 ]».
+* A [=type=] matches <>
+ if its only non-zero [=map/entry=] is either «[ "length" → 1 ]» or «[ "percent" → 1 ]»
+ Same for <>, <>, etc.
+* A [=type=] matches <>
+ if it has no non-zero [=map/entries=]
+ and its [=percent hint=] is null.
+* A [=type=] matches <>
+ if it has no non-zero [=map/entries=],
+ or its only non-zero [=map/entry=] is «[ "percent" → 1 ]».
+
+Many specifications use ''[ <> | <> ]''
+instead of ''<>'' in their grammar,
+and specify in prose that the <> and <> can be combined.
+For the purposes of [=CSSNumericValue/matching=],
+these cases should be treated as <>.
+Similarly for <>, etc.
+
+Note: [=Types=] form a semi-group under both addition
+and a monoid under multiplication
+(with the multiplicative identity being «[ ]» with a null [=percent hint=]),
+meaning that they're associative and commutative.
+Thus the spec can, for example,
+[=add=] an unbounded number of types together unambiguously,
+rather than having to manually [=add=] them pair-wise.
+
+
+
+
+
+### Value + Unit: {{CSSUnitValue}} objects ### {#simple-numeric}
+
+Numeric values that can be expressed as a single unit
+(or a naked number or percentage)
+are represented as {{CSSUnitValue}}s.
+
+
+ For example, the value ''5px'' in a stylesheet
+ will be represented by a {{CSSUnitValue}}
+ with its `value` attribute set to `5`
+ and its `unit` attribute set to `"px"`.
+
+ Similarly, the value ''10'' in a stylesheet
+ will be represented by a {{CSSUnitValue}}
+ with its `value` attribute set to `10`
+ and its `unit` attribute set to `"number"`.
+
+ The CSSUnitValue(|value|, |unit|) constructor must,
+ when called,
+ perform the following steps:
+
+ 1. If [=creating a type=] from |unit| returns failure,
+ [=throw=] a {{TypeError}}
+ and abort this algorithm.
+
+ 2. Return a new {{CSSUnitValue}}
+ with its {{CSSUnitValue/value}} internal slot set to |value|
+ and its {{CSSUnitValue/unit}} set to |unit|.
+
+
+
+ The [=type=] of a {{CSSUnitValue}}
+ is the result of [=creating a type=] from its {{CSSUnitValue/unit}} internal slot.
+
+
+
+ To create a CSSUnitValue from a pair (|num|, |unit|),
+ return a new {{CSSUnitValue}} object
+ with its {{CSSUnitValue/value}} internal slot
+ set to |num|,
+ and its {{CSSUnitValue/unit}} internal slot
+ set to |unit|.
+
+
+ For example, [=create a CSSUnitValue from a pair|creating a CSSUnitValue=] from `(5, "px")`
+ creates an object equivalent to
+ new CSSUnitValue(5, "px").
+
+
+ Note: This is a spec-internal algorithm,
+ meant simply to make it easier to create unit values in algorithms when needed.
+
+
+
+ To convert a CSSUnitValue |this| to a unit |unit|,
+ perform the following steps:
+
+ 1. Let |old unit| be the value of |this|’s {{CSSUnitValue/unit}} internal slot,
+ and |old value| be the value of |this|’s {{CSSUnitValue/value}} internal slot.
+
+ 2. If |old unit| and |unit| are not [=compatible units=],
+ return failure.
+
+ 3. Return a new {{CSSUnitValue}}
+ whose {{CSSUnitValue/unit}} internal slot
+ is set to |unit|,
+ and whose {{CSSUnitValue/value}} internal slot
+ is set to |old value| multiplied by
+ the conversation ratio between |old unit| and |unit|.
+
+
+
+
+
+
+### Complex Numeric Values: {{CSSMathValue}} objects ### {#complex-numeric}
+
+Numeric values that are more complicated than a single value+unit
+are represented by a tree of {{CSSMathValue}} subclasses,
+eventually terminating in {{CSSUnitValue}} objects at the leaf nodes.
+The ''calc()'', ''min()'', and ''max()'' functions in CSS
+are represented in this way.
+
+
+ For example,
+ the CSS value ''calc(1em + 5px)''
+ will be represented by a {{CSSMathSum}}
+ like CSSMathSum(CSS.em(1), CSS.px(5)).
+
+ A more complex expression,
+ like ''calc(1em + 5px * 2)'',
+ will be represented by a nested structure
+ like CSSMathSum(CSS.em(1), CSSMathProduct(CSS.px(5), 2)).
+
+
+ Note: These are all instances of the {{CSSMathOperator}} enum.
+
+
+
+ The CSSMathSum(...|args|) constructor must,
+ when called,
+ perform the following steps:
+
+ 1. Replace each [=list/item=] of |args|
+ with the result of [=rectifying a numberish value=] for the [=list/item=].
+
+ 2. If |args| [=list/is empty=],
+ [=throw=] a {{SyntaxError}}.
+
+ 3. Let |type| be the result of [=adding=] the [=types=] of all the [=list/items=] of |args|.
+ If |type| is failure,
+ [=throw=] a {{TypeError}}.
+
+ 4. Return a new {{CSSMathSum}}
+ whose {{CSSMathSum/values}} internal slot
+ is set to |args|.
+
+ The CSSMathMin(...|args|)
+ and CSSMathMax(...|args|) constructors
+ are defined identically to the above,
+ except that in the last step
+ they return a new {{CSSMathMin}} or {{CSSMathMax}} object,
+ respectively.
+
+ The CSSMathProduct(...|args|) constructor
+ is defined identically to the above,
+ except that in step 3 it [=CSSNumericValue/multiplies=] the types instead of [=adding=],
+ and in the last step
+ it returns a {{CSSMathProduct}}.
+
+
+
+ The CSSMathClamp(|min|, |val|, |max|) constructor must,
+ when called,
+ perform the following steps:
+
+ 1. Replace |min|, |val|, and |max|
+ with the result of [=rectifying a numberish value=] for each.
+
+ 2. Let |type| be the result of [=adding=] the [=types=] of |min|, |val|, and |max|.
+ If |type| is failure,
+ [=throw=] a {{TypeError}}.
+
+ 3. Return a new {{CSSMathClamp}}
+ whose {{CSSMathClamp/min}}, {{CSSMathClamp/val}}, and {{CSSMathClamp/max}} internal slots
+ are set to |min|, |val|, and |max|, respectively.
+
+
+
+ The CSSMathNegate(|arg|) constructor must,
+ when called,
+ perform the following steps:
+
+ 1. Replace |arg|
+ with the result of [=rectifying a numberish value=] for |arg|.
+
+ 2. Return a new {{CSSMathNegate}}
+ whose {{CSSMathNegate/value}} internal slot
+ is set to |arg|.
+
+ The CSSMathInvert(|arg|) constructor
+ is defined identically to the above,
+ except that in the last step
+ it returns a new {{CSSMathInvert}} object.
+
+
+
+ The [=type=] of a CSSMathValue
+ depends on its class:
+
+
+ : {{CSSMathSum}}
+ : {{CSSMathMin}}
+ : {{CSSMathMax}}
+ :: The [=type=] is the result of [=adding=] the [=types=]
+ of each of the [=list/items=] in its {{CSSMathSum/values}} internal slot.
+
+ : {{CSSMathClamp}}
+ :: The [=type=] is the result of [=adding=] the [=types=]
+ of the {{CSSMathClamp/min}}, {{CSSMathClamp/val}}, and {{CSSMathClamp/max}} internal slots.
+
+ : {{CSSMathProduct}}
+ :: The [=type=] is the result of [=CSSNumericValue/multiplying=] the [=types=]
+ of each of the [=list/items=] in its {{CSSMathProduct/values}} internal slot.
+
+ : {{CSSMathNegate}}
+ :: The [=type=] is the same as the [=type=] of its {{CSSMathNegate/value}} internal slot.
+
+ : {{CSSMathInvert}}
+ :: The [=type=] is the same as the [=type=] of its {{CSSMathInvert/value}} internal slot,
+ but with all [=map/values=] negated.
+
+ All of the above methods must,
+ when called with a double |value|,
+ return a new {{CSSUnitValue}}
+ whose {{CSSUnitValue/value}} internal slot
+ is set to |value|
+ and whose {{CSSUnitValue/unit}} internal slot
+ is set to the name of the method as defined here.
+
+ Note: The unit used does not depend on the *current* name of the function,
+ if it's stored in another variable;
+ `let foo = CSS.px; let val = foo(5);` does not return a `{value: 5, unit: "foo"}` {{CSSUnitValue}}.
+ The above talk about names is just a shorthand
+ to avoid defining the unit individually for all ~20 functions.
+
+
+
+
+
+{{CSSTransformValue}} objects {#transformvalue-objects}
+-------------------------------------------------------
+
+{{CSSTransformValue}} objects represent <> values,
+used by the 'transform' property.
+They "contain" one or more {{CSSTransformComponent}}s,
+which represent individual <> values.
+
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
+interface CSSTransformValue : CSSStyleValue {
+ constructor(sequence transforms);
+ iterable;
+ readonly attribute unsigned long length;
+ getter CSSTransformComponent (unsigned long index);
+ setter CSSTransformComponent (unsigned long index, CSSTransformComponent val);
+
+ readonly attribute boolean is2D;
+ DOMMatrix toMatrix();
+};
+
+
+A {{CSSTransformValue}}’s [=values to iterate over=]
+is a [=list=] of {{CSSTransformComponent}}s.
+
+
+ The CSSTransformValue(|transforms|) constructor must,
+ when called,
+ perform the following steps:
+
+ 1. If |transforms| [=list/is empty=],
+ [=throw=] a {{TypeError}}.
+
+ 2. Return a new {{CSSTransformValue}} whose [=values to iterate over=] is |transforms|.
+
+
+
+ The is2D attribute of a {{CSSTransformValue}} |this| must,
+ on getting,
+ return `true` if,
+ [=list/for each=] |func| in |this|’s [=values to iterate over=],
+ the |func|’s {{CSSTransformComponent/is2D}} attribute would return `true`;
+ otherwise,
+ the attribute returns `false`.
+
+
+
+ The toMatrix() method of a {{CSSTransformValue}} |this| must,
+ when called,
+ perform the following steps:
+
+ 1. Let |matrix| be a new {{DOMMatrix}},
+ initialized to the identity matrix,
+ with its {{DOMMatrixReadOnly/is2D}} internal slot set to `true`.
+
+ 2. [=list/For each=] |func| in |this|’s [=values to iterate over=]:
+
+ 1. Let |funcMatrix| be the {{DOMMatrix}} returned by
+ calling {{CSSTransformComponent/toMatrix()}} on |func|.
+ 2. Set |matrix| to the result of multiplying |matrix|
+ and the matrix represented by |funcMatrix|.
+
+ 3. Return |matrix|.
+
+
+The length attribute indicates how many transform components are contained within the {{CSSTransformValue}}.
+
+They have a \[[values]] internal slot,
+which is a [=list=] of {{CSSTransformComponent}} objects.
+This list is the object's [=values to iterate over=].
+
+
+ The [=supported property indices|supported property indexes=]
+ of a {{CSSTransformValue}} |this|
+ are the integers greater than or equal to 0,
+ and less than the [=list/size=] of |this|’s {{CSSTransformValue/[[values]]}} internal slot.
+
+ To [=determine the value of an indexed property=]
+ of a {{CSSTransformValue}} |this|
+ and an index |n|,
+ let |values| be |this|’s {{CSSTransformValue/[[values]]}} internal slot,
+ and return |values|[|n|].
+
+ To [=set the value of an existing indexed property=]
+ of a {{CSSTransformValue}} |this|,
+ an index |n|,
+ and a value |new value|,
+ let |values| be |this|’s {{CSSTransformValue/[[values]]}} internal slot,
+ and set |values|[|n|] to |new value|.
+
+ To [=set the value of a new indexed property=]
+ of a {{CSSTransformValue}} |this|,
+ an index |n|,
+ and a value |new value|,
+ let |values| be |this|’s {{CSSTransformValue/[[values]]}} internal slot.
+ If |n| is not equal to the [=list/size=] of |values|,
+ [=throw=] a {{RangeError}}.
+ Otherwise,
+ [=list/append=] |new value| to |values|.
+
+ The is2D attribute
+ indicates whether the transform is 2D or 3D.
+ When it's `true`,
+ the attributes of the transform that are relevant to 3D transforms
+ (such as the {{CSSTranslate/z|CSSTranslate.z}} attribute)
+ simply have no effect on the transform they represent.
+
+ Note: This affects the serialization of the object,
+ and concepts such as the object's "equivalent 4x4 matrix".
+
+
+
+ {{CSSTransformComponent/is2D}} Design Considerations
+
+ For legacy reasons,
+ 2D and 3D transforms are distinct,
+ even if they have identical effects;
+ a ''translateZ(0px)'' has observable effects on a page,
+ even tho it's defined to be an identity transform,
+ as the UA activates some 3D-based optimizations for the element.
+
+ There were several possible ways to reflect this--
+ nullable 3D-related attributes,
+ separate 2D and 3D interfaces,
+ etc--
+ but we chose the current design
+ (an author-flippable switch that dictates the behavior)
+ because it allows authors to,
+ in most circumstances,
+ operate on transforms without having to care whether they're 2D or 3D,
+ but also prevents "accidentally" flipping a 2D transform into becoming 3D.
+
+
+
+ The toMatrix() method of a {{CSSTransformComponent}} |this| must,
+ when called,
+ perform the following steps:
+
+ 1. Let |matrix| be a new {{DOMMatrix}} object,
+ initialized to |this|’s equivalent 4x4 transform matrix,
+ as defined in [[css-transforms-1#mathematical-description]],
+ and with its {{DOMMatrixReadOnly/is2D}} internal slot
+ set to the same value as |this|'s {{CSSTransformComponent/is2D}} internal slot.
+
+ Note: Recall that the {{CSSTransformComponent/is2D}} flag
+ affects what transform,
+ and thus what equivalent matrix,
+ a {{CSSTransformComponent}} represents.
+
+ As the entries of such a matrix are defined relative to the ''px'' unit,
+ if any <>s in |this| involved in generating the matrix
+ are not [=compatible units=] with ''px''
+ (such as [=relative lengths=] or [=percentages=]),
+ [=throw=] a {{TypeError}}.
+
+ 2. Return |matrix|.
+
+
+
+
+ The CSSTranslate(|x|, |y|, |z|) constructor must,
+ when invoked,
+ perform the following steps:
+
+ 1. If |x| or |y| don't [=CSSNumericValue/match=] <>,
+ [=throw=] a {{TypeError}}.
+
+ 2. If |z| was passed, but doesn't [=CSSNumericValue/match=] <>,
+ [=throw=] a {{TypeError}}.
+
+ 3. Let |this| be a new {{CSSTranslate}} object,
+ with its {{CSSTranslate/x}} and {{CSSTranslate/y}} internal slots
+ set to |x| and |y|.
+
+ 4. If |z| was passed,
+ set |this|’s {{CSSTranslate/z}} internal slot
+ to |z|,
+ and set |this|’s {{CSSTransformComponent/is2D}} internal slot
+ to `false`.
+
+ 4. If |z| was not passed,
+ set |this|’s {{CSSTranslate/z}} internal slot
+ to [=create a CSSUnitValue from a pair|new unit value=] of `(0, "px")`,
+ and set |this|’s {{CSSTransformComponent/is2D}} internal slot
+ to `true`.
+
+ 6. Return |this|.
+
+
+
+ The CSSRotate(|angle|) constructor must,
+ when invoked,
+ perform the following steps:
+
+ 1. If |angle| doesn't [=CSSNumericValue/match=] <>,
+ [=throw=] a {{TypeError}}.
+
+ 2. Return a new {{CSSRotate}}
+ with its {{CSSRotate/angle}} internal slot
+ set to |angle|,
+ its {{CSSRotate/x}} and {{CSSRotate/y}} internal slots
+ set to [=create a CSSUnitValue from a pair|new unit values=] of `(0, "number")`,
+ its {{CSSRotate/z}} internal slot
+ set to a [=create a CSSUnitValue from a pair|new unit value=] of `(1, "number")`,
+ and its {{CSSTransformComponent/is2D}} internal slot set to `true`.
+
+
+
+ The CSSRotate(|x|, |y|, |z|, |angle|) constructor must,
+ when invoked,
+ perform the following steps:
+
+ 1. If |angle| doesn't [=CSSNumericValue/match=] <>,
+ [=throw=] a {{TypeError}}.
+
+ 2. Let |x|, |y|, and |z|
+ be replaced by the result of [=rectifying a numberish value=].
+
+ 3. If |x|, |y|, or |z|
+ don't [=CSSNumericValue/match=] <>,
+ [=throw=] a {{TypeError}}.
+
+ 4. Return a new {{CSSRotate}}
+ with its {{CSSRotate/angle}} internal slot
+ set to |angle|,
+ its {{CSSRotate/x}}, {{CSSRotate/y}}, {{CSSRotate/z}} internal slots set to |x|, |y|, and |z|,
+ and its {{CSSTransformComponent/is2D}} internal slot set to `false`.
+
+
+
+ The x, y, and z attributes must,
+ on setting to a new value |val|,
+ [=rectify a numberish value=] from |val|
+ and set the corresponding internal slot to the result of that.
+
+
+
+ The CSSScale(|x|, |y|, |z|) constructor must,
+ when invoked,
+ perform the following steps:
+
+ 1. Let |x|, |y|, and |z| (if passed)
+ be replaced by the result of [=rectifying a numberish value=].
+
+ 2. If |x|, |y|, or |z| (if passed)
+ don't [=CSSNumericValue/match=] <>,
+ [=throw=] a {{TypeError}}.
+
+ 3. Let |this| be a new {{CSSScale}} object,
+ with its {{CSSScale/x}} and {{CSSScale/y}} internal slots
+ set to |x| and |y|.
+
+ 4. If |z| was passed,
+ set |this|’s {{CSSScale/z}} internal slot to |z|,
+ and set |this|’s {{CSSTransformComponent/is2D}} internal slot to `false`.
+
+ 5. If |z| was not passed,
+ set |this|’s {{CSSScale/z}} internal slot
+ to a [=create a CSSUnitValue from a pair|new unit value=] of `(1, "number")`,
+ and set |this|’s {{CSSTransformComponent/is2D}} internal slot to `true`.
+
+ 6. Return |this|.
+
+
+
+ The x, y, and z attributes must,
+ on setting to a new value |val|,
+ [=rectify a numberish value=] from |val|
+ and set the corresponding internal slot to the result of that.
+
+
+
+ The CSSSkew(|ax|, |ay|) constructor must,
+ when invoked,
+ perform the following steps:
+
+ 1. If |ax| or |ay| do not [=CSSNumericValue/match=] <>,
+ [=throw=] a {{TypeError}}.
+
+ 2. Return a new {{CSSSkew}} object
+ with its {{CSSSkew/ax}} and {{CSSSkew/ay}} internal slots
+ set to |ax| and |ay|,
+ and its {{CSSTransformComponent/is2D}} internal slot set to `true`.
+
+
+
+ The CSSSkewX(|ax|) constructor must,
+ when invoked,
+ perform the following steps:
+
+ 1. If |ax| does not [=CSSNumericValue/match=] <>,
+ [=throw=] a {{TypeError}}.
+
+ 2. Return a new {{CSSSkewX}} object
+ with its {{CSSSkewX/ax}} internal slot
+ set to |ax|,
+ and its {{CSSTransformComponent/is2D}} internal slot set to `true`.
+
+
+
+ The CSSSkewY(|ay|) constructor must,
+ when invoked,
+ perform the following steps:
+
+ 1. If |ay| does not [=CSSNumericValue/match=] <>,
+ [=throw=] a {{TypeError}}.
+
+ 2. Return a new {{CSSSkewY}} object
+ with its {{CSSSkewY/ay}} internal slot
+ set to |ay|,
+ and its {{CSSTransformComponent/is2D}} internal slot set to `true`.
+
+
+
+ The is2D attribute
+ of a {{CSSSkew}}, {{CSSSkewX}}, or {{CSSSkewY}} object must,
+ on setting,
+ do nothing.
+
+ Note: ''skew()'', ''skewX()'', and ''skewY()'' functions always represent 2D transforms.
+
+
+
+ The CSSPerspective(|length|) constructor must,
+ when invoked,
+ perform the following steps:
+
+ 1. If |length| does not [=CSSNumericValue/match=] <>,
+ [=throw=] a {{TypeError}}.
+
+ 2. Return a new {{CSSPerspective}} object
+ with its {{CSSPerspective/length}} internal slot
+ set to |length|,
+ and its {{CSSTransformComponent/is2D}} internal slot set to `false`.
+
+
+
+ The is2D attribute of a {{CSSPerspective}} object must,
+ on setting,
+ do nothing.
+
+ Note: ''perspective()'' functions always represent 3D transforms.
+
+
+
+ The CSSMatrixComponent(|matrix|, |options|) constructor must,
+ when invoked,
+ perform the following steps:
+
+ 1. Let |this| be a new {{CSSMatrixComponent}} object
+ with its {{CSSMatrixComponent/matrix}} internal slot
+ set to |matrix|.
+
+ 2. If |options| was passed
+ and has a {{CSSMatrixComponentOptions/is2D}} field,
+ set |this|’s {{CSSTransformValue/is2D}} internal slot
+ to the value of that field.
+
+ 3. Otherwise,
+ set |this|’s {{CSSTransformValue/is2D}} internal slot
+ to the value of |matrix|’s {{DOMMatrixReadOnly/is2D}} internal slot.
+
+ 4. Return |this|.
+
+
+
+ Each {{CSSTransformComponent}} can correspond to
+ one of a number of underlying transform functions.
+ For example, a {{CSSTranslate}}
+ with an x value of ''10px''
+ and y & z values of ''0px'' could represent any of the following:
+
+ * translate(10px)
+ * translate(10px, 0)
+ * translateX(10px)
+ * translate3d(10px, 0, 0)
+
+ When stringified, however,
+ it will always print out either ''translate(10px, 0px)'' or ''translate3d(10px, 0px, 0px)'',
+ depending on whether its {{CSSTransformComponent/is2D}} internal slot
+ is `true` or `false`,
+ respectively.
+
+
+{{CSSImageValue}} objects represent values for properties that take <> productions,
+for example 'background-image', 'list-style-image', and 'border-image-source'.
+
+Note: This object is intentionally opaque,
+and exposes no details of what kind of image it contains,
+or any aspect of the image.
+This is because having *something* to represent images is necessary for Custom Paint,
+but there are sufficient complexities in getting URL-handling and loading specified firmly
+that it's not realistically possible to specify in the timeline of this specification.
+This will be expanded on in future levels.
+
+If a {{CSSImageValue}} object represents an <> that involves a URL
+(such as ''url()'' or ''image()''),
+the handling of such values is identical to how CSS currently handles them.
+In particular, resolving relative URLs or fragment URLs
+has the same behavior as in normal CSS.
+
+
+ For example, relative URLs are resolved against the URL of the stylesheet they're within
+ (or the document's URL, if they're specified in a <{style}> element or <{html-global/style}> attribute).
+ This resolution doesn't happen eagerly at parse-time,
+ but at some currently-unspecified point during value computation.
+
+ Thus, if an element's style is set to ''background-image: url(foo);'',
+ and that specified value is extracted via the Typed OM
+ and then set on an element in a different document,
+ both the source and destination elements
+ will resolve the URL differently,
+ as they provide different base URLs.
+
+ On the other hand,
+ if the extracted value was a [=computed value=]
+ (from {{computedStyleMap()}}),
+ then it would already be resolved to an absolute URL,
+ and thus would act identically no matter where you later set it to.
+ (Unless it was a fragment URL,
+ which CSS treats differently and never fully resolves,
+ so it always resolves against the current document.)
+
+
+
+
+
+{{CSSStyleValue}} Reification {#reify-stylevalue}
+===========================================================
+
+This section describes how Typed OM objects are constructed from [=internal representations=],
+a process called reification.
+
+Some general principles apply to all [=reification=],
+and so aren't stated in each individual instance:
+
+* 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 [=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.
+
+Property-specific Rules {#reify-property}
+-----------------------------------------
+
+The following list defines the [=reification=] behavior for every single property in CSS,
+for both specified and computed values.
+
+
+: unregistered [=custom properties=]
+:: For both specified and computed values,
+ [=reify a list of component values=] from the value,
+ and return the result.
+
+: registered [=custom properties=]
+:: Reified as described by [[css-properties-values-api-1#css-style-value-reification]].
+
+: align-content
+::
+
+: align-items
+:: For both specified and computed values:
+
+ 1. If the value is normal or stretch,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. If the value is baseline or first baseline,,
+ [=reify an identifier=] "baseline"
+ and return the result.
+
+ 3. If the value is a <> with no <>,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 4. Otherwise, reify as a {{CSSStyleValue}}
+ and return the result.
+
+: align-self
+:: For both specified and computed values:
+
+ 1. If the value is auto, normal, or stretch,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. If the value is baseline or first baseline,,
+ [=reify an identifier=] "baseline"
+ and return the result.
+
+ 3. If the value is a <> with no <>,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 4. Otherwise, reify as a {{CSSStyleValue}}
+ and return the result.
+
+: alignment-baseline
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: all
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: animation-composition
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: appearance
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: azimuth
+::
+ : For specified values:
+ ::
+ 1. If the value is an <>,
+ [=reify a numeric value=] from the value
+ and return the result.
+
+ 2. If the value is a single keyword,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 3. Otherwise,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+ : For computed values:
+ ::
+ [=Reify a numeric value=] from the angle
+ and return the result.
+
+: backdrop-filter
+:: For both specified and computed values:
+
+ 1. If the value is none,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: backface-visibility
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: background
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: background-attachment
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: background-blend-mode
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: background-clip
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: background-color
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: background-image
+:: For both specified and computed values:
+
+ 1. If the value is none,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. If the value is a ''url()'' function,
+ [=reify a url=] from the value
+ and return the result.
+
+ 3. Otherwise,
+ [=reify an image=] from the value
+ and return the result.
+
+: background-image-transform
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: background-position
+:: For both specified and computed values,
+ [=reify a position=] from the value
+ and return the result.
+
+: background-repeat
+:: For both specified and computed values:
+
+ 1. If the value is a single keyword,
+ or the same keyword repeated twice,
+ [=reify an identifier=] from the keyword
+ and return the result.
+
+ 2. If the value is repeat no-repeat,
+ [=reify an identifier=] "repeat-x"
+ and return the result.
+
+ 3. If the value is no-repeat repeat,
+ [=reify an identifier=] "repeat-y"
+ and return the result.
+
+ 4. Otherwise,
+ reify to a {{CSSStyleValue}}
+ and return the result.
+
+: baseline-shift
+:: For both specified and computed values:
+
+ 1. If the value is sub or super,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise,
+ [=reify a numeric value=] from the value
+ and return the result.
+
+: block-size
+:: Same as for 'width'
+
+: block-step
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: block-step-align
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: block-step-insert
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: block-step-round
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: block-step-size
+:: For both specified and computed values:
+
+ 1. If the value is none,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise,
+ [=reify a numeric value=] from the value
+ and return the result.
+
+: bookmark-label
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: bookmark-level
+:: For both specified and computed values:
+
+ 1. If the value is none,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise,
+ [=reify a numeric value=] from the value
+ and return the result.
+
+: bookmark-state
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: border
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: border-block
+:: Same as 'border-block-start'
+
+: border-block-color
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: border-block-end
+:: Same as 'border-block-start'
+
+: border-block-end-color
+:: Same as 'border-top-color'
+
+: border-block-end-style
+:: Same as 'border-top-style'
+
+: border-block-end-width
+:: Same as 'border-top-width'
+
+: border-block-start
+:: Same as 'border-top'
+
+: border-block-start-color
+:: Same as 'border-top-color'
+
+: border-block-start-style
+:: Same as 'border-top-style'
+
+: border-block-start-width
+:: Same as 'border-top-width'
+
+: border-block-style
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: border-block-width
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: border-bottom
+:: Same as 'border-top'
+
+: border-bottom-color
+:: Same as 'border-top-color'
+
+: border-bottom-style
+:: Same as 'border-top-style'
+
+: border-bottom-width
+:: Same as 'border-top-width'
+
+: border-boundary
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: border-collapse
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: border-color
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: border-image-transform
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: border-inline
+::
+
+: border-inline-color
+::
+
+: border-inline-end
+::
+
+: border-inline-end-color
+::
+
+: border-inline-end-style
+::
+
+: border-inline-end-width
+::
+
+: border-inline-start
+::
+
+: border-inline-start-color
+::
+
+: border-inline-start-style
+::
+
+: border-inline-start-width
+::
+
+: border-inline-style
+::
+
+: border-inline-width
+::
+
+: border-left
+:: Same as 'border-top'
+
+: border-left-color
+:: Same as 'border-top-color'
+
+: border-left-style
+:: Same as 'border-top-style'
+
+: border-left-width
+:: Same as 'border-top-width'
+
+: border-radius
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: border-right
+:: Same as 'border-top'
+
+: border-right-color
+:: Same as 'border-top-color'
+
+: border-right-style
+:: Same as 'border-top-style'
+
+: border-right-width
+:: Same as 'border-top-width'
+
+: border-spacing
+::
+
+: border-style
+::
+
+: border-top
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: border-top-color
+:: For both specified and computed values:
+
+ 1. If the value is currentcolor,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, reify as a {{CSSStyleValue}}
+ and return the result.
+
+: border-top-style
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: border-top-width
+:: For both specified and computed values:
+
+ 1. If the value is a <>,
+ [=reify a numeric value=] from the value
+ and return the result.
+ 2. Otherwise, [=reify an identifier=] from the value
+ and return the result.
+
+: border-width
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: bottom
+:: For both specified and computed values:
+
+ 1. If the value is auto,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, [=reify a numeric value=] from the value
+ and return the result.
+
+: box-decoration-break
+::
+
+: box-sizing
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: box-snap
+::
+
+: break-after
+::
+
+: break-before
+::
+
+: break-inside
+::
+
+: caption-side
+::
+
+: caret
+::
+
+: caret-color
+:: For both specified and computed values:
+
+ 1. If the value is currentcolor,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, reify as a {{CSSStyleValue}}
+ and return the result.
+
+: caret-shape
+::
+
+: clear
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: clip
+::
+
+: clip-path
+::
+
+: clip-rule
+::
+
+: color
+:: For both specified and computed values:
+
+ 1. If the value is currentcolor,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, reify as a {{CSSStyleValue}}
+ and return the result.
+
+: color-adjust
+::
+
+: color-interpolation
+::
+
+: color-rendering
+::
+
+: column-gap
+::
+
+: column-span
+::
+
+: contain
+::
+
+: content
+::
+
+: continue
+::
+
+: copy-into
+::
+
+: counter-increment
+::
+
+: counter-reset
+::
+
+: counter-set
+::
+
+: cue
+::
+
+: cue-after
+::
+
+: cue-before
+::
+
+: cursor
+::
+
+: cx
+::
+
+: cy
+::
+
+: d
+::
+
+: direction
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: display
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: dominant-baseline
+::
+
+: elevation
+::
+
+: empty-cells
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: fill
+::
+
+: fill-break
+::
+
+: fill-color
+::
+
+: fill-image
+::
+
+: fill-opacity
+::
+
+: fill-origin
+::
+
+: fill-position
+::
+
+: fill-repeat
+::
+
+: fill-rule
+::
+
+: fill-size
+::
+
+: 'filter-margin-top, filter-margin-right, filter-margin-bottom, filter-margin-left'
+::
+
+: flex
+::
+
+: flex-basis
+::
+
+: flex-direction
+::
+
+: flex-flow
+::
+
+: flex-grow
+::
+
+: flex-shrink
+::
+
+: flex-wrap
+::
+
+: float
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: float-defer
+::
+
+: font
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: font-family
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: font-language-override
+:: For both specified and computed values:
+
+ 1. If the value is normal,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, reify as a {{CSSStyleValue}}
+ and return the result.
+
+: font-max-size
+:: For both specified and computed values:
+
+ 1. If the value is an <>, <> or infinity,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, [=reify a numeric value=] from the value
+ and return the result.
+
+: font-min-size
+:: Same as 'font-size'
+
+: font-optical-sizing
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: font-palette
+:: For both specified and computed values:
+
+ 1. If the value is normal, light or dark,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, reify as a {{CSSStyleValue}}
+ and return the result.
+
+: font-presentation
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: font-size
+:: For both specified and computed values:
+
+ 1. If the value is an <> or <>,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, [=reify a numeric value=] from the value
+ and return the result.
+
+: font-size-adjust
+:: For both specified and computed values:
+
+ 1. If the value is none,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, [=reify a numeric value=] from the value
+ and return the result.
+
+: font-stretch
+:: For both specified and computed values:
+
+ 1. If the value is a <>,
+ [=reify a numeric value=] from the value
+ and return the result.
+
+ 2. Otherwise, [=reify an identifier=] from the value
+ and return the result.
+
+: font-style
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: font-synthesis
+:: For both specified and computed values:
+
+ 1. If the value is none, weight, style or small-caps,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, reify as a {{CSSStyleValue}}
+ and return the result.
+
+: font-variant
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: font-variant-alternates
+:: For both specified and computed values:
+
+ 1. If the value is none or historical-forms,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, reify as a {{CSSStyleValue}}
+ and return the result.
+
+: font-variant-emoji
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: font-variation-settings
+:: For both specified and computed values:
+
+ 1. If the value is normal,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, reify as a {{CSSStyleValue}}
+ and return the result.
+
+: font-weight
+:: For both specified and computed values:
+
+ 1. If the value is a <>,
+ [=reify a numeric value=] from the value
+ and return the result.
+
+ 2. Otherwise, [=reify an identifier=] from the value
+
+: gap
+::
+
+: globalcompositeoperation
+::
+
+: glyph-orientation-vertical
+::
+
+: grid
+::
+
+: grid-area
+::
+
+: grid-auto-columns
+::
+
+: grid-auto-flow
+::
+
+: grid-auto-rows
+::
+
+: grid-column
+::
+
+: grid-column-end
+::
+
+: grid-column-gap
+::
+
+: grid-column-start
+::
+
+: grid-gap
+::
+
+: grid-row
+::
+
+: grid-row-end
+::
+
+: grid-row-gap
+::
+
+: grid-row-start
+::
+
+: grid-template
+::
+
+: grid-template-areas
+::
+
+: grid-template-columns
+::
+
+: grid-template-rows
+::
+
+: height
+:: For both specified and computed values:
+
+ 1. If the value is auto,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. If the value is a <> or <>,
+ [=reify a numeric value=] from the value
+ and return the result.
+
+: image-rendering
+::
+
+: image-resolution
+::
+
+: initial-letter
+::
+
+: initial-letter-align
+::
+
+: initial-letter-wrap
+::
+
+: inline-size
+::
+
+: inset
+::
+
+: inset-block
+::
+
+: inset-block-end
+::
+
+: inset-block-start
+::
+
+: inset-inline
+::
+
+: inset-inline-end
+::
+
+: inset-inline-start
+::
+
+: isolation
+::
+
+: justify-content
+::
+
+: justify-items
+::
+
+: justify-self
+::
+
+: left
+:: For both specified and computed values:
+
+ 1. If the value is auto,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, [=reify a numeric value=] from the value
+ and return the result.
+
+: letter-spacing
+::
+
+: line-grid
+::
+
+: line-height
+:: For both specified and computed values:
+
+ 1. If the value is normal,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, [=reify a numeric value=] from the value
+ and return the result.
+
+: line-height-step
+::
+
+: line-snap
+::
+
+: list-style
+::
+
+: list-style-image
+:: For both specified and computed values:
+
+ 1. If the value is none,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. If the value is a ''url()'' function,
+ [=reify a url=] from the value
+ and return the result.
+
+ 3. Otherwise, [=reify an image=] from the value
+ and return the result.
+
+: list-style-position
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: list-style-type
+::
+
+: margin
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: margin-block
+::
+
+: margin-block-end
+::
+
+: margin-block-start
+::
+
+: margin-bottom
+:: Same as 'margin-top'
+
+: margin-inline
+::
+
+: margin-inline-end
+::
+
+: margin-inline-start
+::
+
+: margin-left
+:: Same as 'margin-top'
+
+: margin-right
+:: Same as 'margin-top'
+
+: margin-top
+:: For both specified and computed values:
+
+ 1. If the value is auto,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, [=reify a numeric value=] from the value
+ and return the result.
+
+: marker
+::
+
+: marker-end
+::
+
+: marker-mid
+::
+
+: marker-side
+::
+
+: marker-start
+::
+
+: mask
+::
+
+: mask-border
+::
+
+: mask-border-mode
+::
+
+: mask-border-outset
+::
+
+: mask-border-repeat
+::
+
+: mask-border-slice
+::
+
+: mask-border-source
+::
+
+: mask-border-width
+::
+
+: mask-clip
+::
+
+: mask-composite
+::
+
+: mask-image
+:: For both specified and computed values:
+
+ 1. If the value is none,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, [=reify an image=] from the value
+ and return the result.
+
+: mask-mode
+::
+
+: mask-origin
+::
+
+: mask-position
+::
+
+: mask-repeat
+::
+
+: mask-size
+::
+
+: mask-type
+::
+
+: max-block-size
+::
+
+: max-height
+::
+
+: max-inline-size
+::
+
+: max-lines
+::
+
+: max-width
+::
+
+: min-block-size
+::
+
+: min-height
+::
+
+: min-inline-size
+::
+
+: min-width
+::
+
+: mix-blend-mode
+::
+
+: nav-down
+::
+
+: nav-left
+::
+
+: nav-right
+::
+
+: nav-up
+::
+
+: object-fit
+::
+
+: offset
+::
+
+: offset-after
+::
+
+: offset-anchor
+::
+
+: offset-before
+::
+
+: offset-distance
+::
+
+: offset-end
+::
+
+: offset-path
+::
+
+: offset-position
+::
+
+: offset-rotate
+::
+
+: offset-start
+::
+
+: opacity
+:: For both specified and computed values,
+ [=reify a numeric value=] from the value
+ and return the result.
+
+: order
+::
+
+: orphans
+::
+
+: outline
+::
+
+: outline-color
+:: For both specified and computed values:
+
+ 1. If the value is currentcolor,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, reify as a {{CSSStyleValue}}
+ and return the result.
+
+: outline-offset
+::
+
+: outline-style
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: outline-width
+::
+
+: overflow
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: overflow-anchor
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: overflow-x
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: overflow-y
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: padding
+:: For both specified and computed values,
+ reify as a {{CSSStyleValue}}
+ and return the result.
+
+: padding-block
+::
+
+: padding-block-end
+::
+
+: padding-block-start
+::
+
+: padding-bottom
+:: Same as 'padding-top'
+
+: padding-inline
+::
+
+: padding-inline-end
+::
+
+: padding-inline-start
+::
+
+: padding-left
+:: Same as 'padding-top'
+
+: padding-right
+:: Same as 'padding-top'
+
+: padding-top
+:: For both specified and computed values,
+ [=reify a numeric value=] from the value
+ and return the result.
+
+: page
+::
+
+: page-break-after
+::
+
+: page-break-before
+::
+
+: page-break-inside
+::
+
+: paint-order
+::
+
+: pause
+::
+
+: pause-after
+::
+
+: pause-before
+::
+
+: perspective
+::
+
+: perspective-origin
+::
+
+: pitch
+::
+
+: pitch-range
+::
+
+: place-content
+::
+
+: place-items
+::
+
+: place-self
+::
+
+: play-during
+::
+
+: pointer-events
+::
+
+: position
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: presentation-level
+::
+
+: quotes
+::
+
+: r
+::
+
+: region-fragment
+::
+
+: resize
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: rest
+::
+
+: rest-after
+::
+
+: rest-before
+::
+
+: richness
+::
+
+: right
+:: For both specified and computed values:
+
+ 1. If the value is auto,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, [=reify a numeric value=] from the value
+ and return the result.
+
+: rotate
+::
+
+: row-gap
+::
+
+: ruby-align
+::
+
+: ruby-merge
+::
+
+: ruby-position
+::
+
+: rx
+::
+
+: ry
+::
+
+: scale
+::
+
+: scroll-behavior
+::
+
+: scroll-margin
+::
+
+: scroll-margin-block
+::
+
+: scroll-margin-block-end
+::
+
+: scroll-margin-block-start
+::
+
+: scroll-margin-bottom
+::
+
+: scroll-margin-inline
+::
+
+: scroll-margin-inline-end
+::
+
+: scroll-margin-inline-start
+::
+
+: scroll-margin-left
+::
+
+: scroll-margin-right
+::
+
+: scroll-margin-top
+::
+
+: scroll-padding
+::
+
+: scroll-padding-block
+::
+
+: scroll-padding-block-end
+::
+
+: scroll-padding-block-start
+::
+
+: scroll-padding-bottom
+::
+
+: scroll-padding-inline
+::
+
+: scroll-padding-inline-end
+::
+
+: scroll-padding-inline-start
+::
+
+: scroll-padding-left
+::
+
+: scroll-padding-right
+::
+
+: scroll-padding-top
+::
+
+: scroll-snap-align
+::
+
+: scroll-snap-stop
+::
+
+: scroll-snap-type
+::
+
+: scrollbar-3dlight-color
+::
+
+: scrollbar-arrow-color
+::
+
+: scrollbar-base-color
+::
+
+: scrollbar-darkshadow-color
+::
+
+: scrollbar-face-color
+::
+
+: scrollbar-gutter
+::
+
+: scrollbar-highlight-color
+::
+
+: scrollbar-shadow-color
+::
+
+: scrollbar-track-color
+::
+
+: shape-inside
+::
+
+: shape-margin
+::
+
+: shape-padding
+::
+
+: shape-rendering
+::
+
+: shape-subtract
+::
+
+: size
+::
+
+: solid-color
+::
+
+: solid-opacity
+::
+
+: speak
+::
+
+: speak-as
+::
+
+: speak-header
+::
+
+: speak-numeral
+::
+
+: speak-punctuation
+::
+
+: speech-rate
+::
+
+: stop-color
+::
+
+: stop-opacity
+::
+
+: stress
+::
+
+: stroke
+::
+
+: stroke-align
+::
+
+: stroke-break
+::
+
+: stroke-color
+::
+
+: stroke-dash-corner
+::
+
+: stroke-dash-justify
+::
+
+: stroke-dasharray
+::
+
+: stroke-dashoffset
+::
+
+: stroke-image
+::
+
+: stroke-linecap
+::
+
+: stroke-linejoin
+::
+
+: stroke-miterlimit
+::
+
+: stroke-opacity
+::
+
+: stroke-origin
+::
+
+: stroke-position
+::
+
+: stroke-repeat
+::
+
+: stroke-size
+::
+
+: stroke-width
+::
+
+: table-layout
+::
+
+: text-align
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: text-anchor
+::
+
+: text-combine-upright
+::
+
+: text-decoration
+::
+
+: text-decoration-fill
+::
+
+: text-decoration-skip
+::
+
+: text-decoration-skip-ink
+::
+
+: text-decoration-stroke
+::
+
+: text-decoration-width
+::
+
+: text-emphasis-skip
+::
+
+: text-indent
+::
+
+: text-orientation
+::
+
+: text-overflow
+::
+
+: text-rendering
+::
+
+: text-size-adjust
+::
+
+: text-transform
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: text-underline-offset
+::
+
+: top
+:: For both specified and computed values:
+
+ 1. If the value is auto,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, [=reify a numeric value=] from the value
+ and return the result.
+
+: transform-style
+::
+
+: transition
+::
+
+: transition-delay
+::
+
+: transition-duration
+::
+
+: transition-property
+::
+
+: transition-timing-function
+::
+
+: translate
+::
+
+: unicode-bidi
+::
+
+: user-select
+::
+
+: vector-effect
+::
+
+: vertical-align
+:: For both specified and computed values:
+
+ 1. If the value is baseline,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. Otherwise, [=reify a numeric value=] from the value
+ and return the result.
+
+: visibility
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: voice-balance
+::
+
+: voice-duration
+::
+
+: voice-family
+::
+
+: voice-pitch
+::
+
+: voice-range
+::
+
+: voice-rate
+::
+
+: voice-stress
+::
+
+: voice-volume
+::
+
+: volume
+::
+
+: white-space
+:: For both specified and computed values,
+ [=reify an identifier=] from the value
+ and return the result.
+
+: widows
+::
+
+: width
+:: For both specified and computed values:
+
+ 1. If the value is auto,
+ [=reify an identifier=] from the value
+ and return the result.
+
+ 2. If the value is a <> or <>,
+ [=reify a numeric value=] from the value
+ and return the result.
+
+: will-change
+::
+
+: word-spacing
+::
+
+: writing-mode
+::
+
+: x
+::
+
+: y
+::
+
+: z-index
+::
+
+
+
+
+Unrepresentable Values {#reify-failure}
+---------------------------------------
+
+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=] for a particular property,
+ensuring that it can be used as a value for that property,
+and nothing else.
+
+
+ To reify as a CSSStyleValue
+ a |value|
+ for a |property|:
+
+ 1. Return a new {{CSSStyleValue}} object
+ representing |value|
+ whose {{[[associatedProperty]]}} internal slot
+ is set to |property|.
+
+
+
+Raw CSS tokens: properties with ''var()'' references {#reify-tokens}
+--------------------------------------------------
+
+Regardless of what the property's grammar is otherwise,
+a property value with an un-substituted ''var()'' reference
+is represented as a [=list=] of [=component values=],
+which becomes a {{CSSUnparsedValue}} in the Typed OM.
+
+
+ To reify a list of component values
+ from a |list|:
+
+ 1. Replace all ''var()'' references in |list|
+ with {{CSSVariableReferenceValue}} objects,
+ as described in [[#reify-var]].
+ 2. Replace each remaining maximal subsequence of [=component values=] in |list|
+ with a single string of their concatenated serializations.
+ 3. Return a new {{CSSUnparsedValue}}
+ whose {{CSSUnparsedValue/[[tokens]]}} slot is set to |list|.
+
+
+
+The string "calc(42px + var(--foo, 15em) + var(--bar, var(--far) + 15px))"
+is converted into a {{CSSUnparsedValue}} that contains a sequence with:
+
+ * the string "calc(42px + "
+ * a {{CSSVariableReferenceValue}} with:
+ * {{CSSVariableReferenceValue/variable}} "--foo"
+ * {{CSSVariableReferenceValue/fallback}} a
+ {{CSSUnparsedValue}} with a single-valued sequence containing " 15em"
+ * the string " + "
+ * a {{CSSVariableReferenceValue}} with:
+ * {{CSSVariableReferenceValue/variable}} "--bar"
+ * {{CSSVariableReferenceValue/fallback}} a {{CSSUnparsedValue}}
+ with a sequence containing:
+ * the string " "
+ * a {{CSSVariableReferenceValue}} with
+ * {{CSSVariableReferenceValue/variable}} "--far"
+ * {{CSSVariableReferenceValue/fallback}} null
+ * the string " + 15px"
+ * the string ")"
+
+
+''var()'' References {#reify-var}
+-------------------------------------
+
+''var()'' references become {{CSSVariableReferenceValue}}s in the Typed OM.
+
+
+ To reify a ''var()'' reference |var|:
+
+ 1. Let |object| be a new {{CSSVariableReferenceValue}}.
+
+ 2. Set |object|’s {{CSSVariableReferenceValue/variable}} internal slot
+ to the serialization of the <> providing the variable name.
+
+ 3. If |var| has a fallback value,
+ set |object|’s {{CSSVariableReferenceValue/fallback}} internal slot
+ to the result of [=reify a list of component values|reifying the fallback's component values=].
+ Otherwise,
+ set it to `null`.
+
+ 4. Return |object|.
+
+
+[=Identifier=] Values {#reify-ident}
+-----------------------------------
+
+CSS [=identifiers=] become {{CSSKeywordValue}}s in the Typed OM.
+
+
+ To reify an [=identifier=] |ident|:
+
+ 1. Return a new {{CSSKeywordValue}}
+ with its {{CSSKeywordValue/value}} internal slot
+ set to the serialization of |ident|.
+
+
+<>, <>, and <> values {#reify-numeric}
+-------------------------------------------------------------------------
+
+CSS <>, <>, and <> values become {{CSSNumericValue}}s in the Typed OM.
+
+
+ To reify a numeric value |num|:
+
+ 1. If |num| is a [=math function=],
+ [=reify a math expression=] from |num|
+ and return the result.
+
+ 2. If |num| is the unitless value ''0'' and |num| is a <>,
+ return a new {{CSSUnitValue}}
+ with its {{CSSUnitValue/value}} internal slot
+ set to 0,
+ and its {{CSSUnitValue/unit}} internal slot
+ set to "px".
+
+ 3. Return a new {{CSSUnitValue}}
+ with its {{CSSUnitValue/value}} internal slot
+ set to the numeric value of |num|,
+ and its {{CSSUnitValue/unit}} internal slot
+ set to "number" if |num| is a <>,
+ "percent" if |num| is a <>,
+ and |num|’s unit if |num| is a <>.
+
+ If the value being [=reified=] is a computed value,
+ the unit used must be the appropriate [=canonical unit=] for the value's type,
+ with the numeric value scaled accordingly.
+
+
+ For example, if an element has `style="width: 1in;"`,
+ `el.attributeStyleMap.get('width')` will return `CSS.in(1)`,
+ but `el.computedStyleMap.get('width')` will return `CSS.px(96)`,
+ as ''px'' is the [=canonical unit=] for absolute lengths.
+
+
+
+
+ To reify a math expression |num|:
+
+ 1. If |num| is a ''min()'' or ''max()'' expression:
+ 1. Let |values| be the result of [=reify a math expression|reifying=]
+ the arguments to the expression,
+ treating each argument as if it were the contents of a ''calc()'' expression.
+
+ 2. Return a new {{CSSMathMin}} or {{CSSMathMax}} object, respectively,
+ with its {{CSSMathMin/values}} internal slot
+ set to |values|.
+
+ 2. Assert: Otherwise, |num| is a ''calc()''.
+
+ 3. Turn |num|’s argument
+ into an expression tree
+ using standard PEMDAS precedence rules,
+ with the following exceptions/clarification:
+
+ * Treat subtraction as instead being addition,
+ with the RHS argument instead wrapped in a special "negate" node.
+ * Treat division as instead being multiplication,
+ with the RHS argument instead wrapped in a special "invert" node.
+ * Addition and multiplication are N-ary;
+ each node can have any number of arguments.
+ * If an expression has only a single value in it,
+ and no operation,
+ treat it as an addition node with the single argument.
+
+ 4. Recursively transform the expression tree into objects,
+ as follows:
+
+
+ : addition node
+ :: becomes a new {{CSSMathSum}} object,
+ with its {{CSSMathSum/values}} internal slot
+ set to its list of arguments
+ : multiplication node
+ :: becomes a new {{CSSMathProduct}} object,
+ with its {{CSSMathProduct/values}} internal slot
+ set to its list of arguments
+ : negate node
+ :: becomes a new {{CSSMathNegate}} object,
+ with its {{CSSMathNegate/value}} internal slot
+ set to its argument
+ : invert node
+ :: becomes a new {{CSSMathInvert}} object,
+ with its {{CSSMathInvert/value}} internal slot
+ set to its argument
+ : leaf node
+ :: [=reified=] as appropriate
+
+
+
+ For example, ''calc(1px - 2 * 3em)''
+ produces the structure:
+
+
+
+ Note: The value computation process may transform different units into identical ones,
+ simplifying the resulting expression.
+ For example, ''calc(1px + 2em)'' as a specified value
+ results in a CSSMathSum(CSS.px(1), CSS.em(2)),
+ but as a computed value will give CSS.px(33) or similar
+ (depending on the value of an ''em'' in that context).
+
+
+
+<> and <> values {#reify-transformvalue}
+------------------------------------------------------------------------------------
+
+CSS <> values become {{CSSTransformValue}}s in the Typed OM,
+while CSS <> values become {{CSSTransformComponent}}s.
+
+
+ To reify a <> |list|:
+
+ 1. Return a new {{CSSTransformValue}}
+ whose [=values to iterate over=]
+ are the result of mapping the [=reify a <transform-function>=] algorithm
+ over |list|.
+
+
+
+ To reify a <> |func|,
+ perform the appropriate set of steps below,
+ based on |func|:
+
+
+ : ''matrix()''
+ : ''matrix3d()''
+ ::
+ 1. Return a new {{CSSMatrixComponent}} object,
+ whose {{CSSMatrixComponent/matrix}} internal slot
+ is set to a 4x4 matrix representing the same information
+ as |func|,
+ and whose {{CSSTransformComponent/is2D}} internal slot
+ is `true` if |func| is ''matrix()'',
+ and `false` otherwise.
+
+ : ''translate()''
+ : ''translateX()''
+ : ''translateY()''
+ : ''translate3d()''
+ : ''translateZ()''
+ ::
+ 1. Return a new {{CSSTranslate}} object,
+ whose {{CSSTranslate/x}}, {{CSSTranslate/y}}, and {{CSSTranslate/z}} internal slots
+ are set to the [=reify a numeric value|reification=] of the specified x/y/z offsets,
+ or the [=reify a numeric value|reification=] of ''0px'' if not specified in |func|,
+ and whose {{CSSTransformComponent/is2D}} internal slot
+ is `true` if |func| is ''translate()'', ''translateX()'', or ''translateY()'',
+ and `false` otherwise.
+
+ : ''scale()''
+ : ''scaleX()''
+ : ''scaleY()''
+ : ''scale3d()''
+ : ''scaleZ()''
+ ::
+ 1. Return a new {{CSSScale}} object,
+ whose {{CSSScale/x}}, {{CSSScale/y}}, and {{CSSScale/z}} internal slots
+ are set to the specified x/y/z scales,
+ or to ''1'' if not specified in |func|
+ and whose {{CSSTransformComponent/is2D}} internal slot
+ is `true` if |func| is ''scale()'', ''scaleX()'', or ''scaleY()'',
+ and `false` otherwise.
+
+ : ''rotate()''
+ : ''rotate3d()''
+ : ''rotateX()''
+ : ''rotateY()''
+ : ''rotateZ()''
+ ::
+ 1. Return a new {{CSSRotate}} object,
+ whose {{CSSRotate/angle}} internal slot
+ is set to the [=reify a numeric value|reification=] of the specified angle,
+ and whose {{CSSRotate/x}}, {{CSSRotate/y}}, and {{CSSRotate/z}} internal slots
+ are set to the specified rotation axis coordinates,
+ or the implicit axis coordinates if not specified in |func|
+ and whose {{CSSTransformComponent/is2D}} internal slot
+ is `true` if |func| is ''rotate()'',
+ and `false` otherwise.
+
+ : ''skew()''
+ ::
+ 1. Return a new {{CSSSkew}} object,
+ whose {{CSSSkew/ax}} and {{CSSSkew/ay}} internal slots
+ are set to the [=reify a numeric value|reification=] of the specified x and y angles,
+ or the [=reify a numeric value|reification=] of ''0deg'' if not specified in |func|,
+ and whose {{CSSTransformComponent/is2D}} internal slot
+ is `true`.
+
+ : ''skewX()''
+ ::
+ 1. Return a new {{CSSSkewX}} object,
+ whose {{CSSSkewX/ax}} internal slot
+ is set to the [=reify a numeric value|reification=] of the specified x angle,
+ or the [=reify a numeric value|reification=] of ''0deg'' if not specified in |func|,
+ and whose {{CSSTransformComponent/is2D}} internal slot
+ is `true`.
+
+ : ''skewY()''
+ ::
+ 1. Return a new {{CSSSkewY}} object,
+ whose {{CSSSkewY/ay}} internal slot
+ is set to the [=reify a numeric value|reification=] of the specified y angle,
+ or the [=reify a numeric value|reification=] of ''0deg'' if not specified in |func|,
+ and whose {{CSSTransformComponent/is2D}} internal slot
+ is `true`.
+
+ : ''perspective()''
+ ::
+ 1. Return a new {{CSSPerspective}} object,
+ whose {{CSSPerspective/length}} internal slot
+ is set to the [=reify a numeric value|reification=] of the specified length
+ and whose {{CSSTransformComponent/is2D}} internal slot
+ is `false`.
+
+
+
+
+<> (and subtype) Values {#reify-url}
+--------------------------------------------------------------
+
+Resource references are reified by determining whether the reference is invalid
+(in which case {{CSSResourceValue/state}} is set to ''error'') or
+requires network data (in which case {{CSSResourceValue/state}}
+is set to ''loading''). If data is not required and the reference is valid then
+{{CSSResourceValue/state}} is set to ''loaded''.
+
+If {{CSSResourceValue/state}} is set to ''loading'' then the image
+reference is reevaluated once the pending data becomes available, according to the
+same rules referenced above.
+
+Reification can not fail for {{CSSResourceValue}} objects.
+
+
+The string 'url(''bike.png'')' is converted into a {{CSSURLImageValue}} with {{CSSResourceValue/state}} set to ''unloaded'' and the {{CSSURLImageValue/url}} set to ''https://drafts.css-houdini.org/css-typed-om/bike.png''. The {{CSSImageValue/intrinsicWidth}}, {{CSSImageValue/intrinsicHeight}} and {{CSSImageValue/intrinsicRatio}} are all set to null.
+
+
+
+
+
+
+
+{{CSSStyleValue}} Serialization {#stylevalue-serialization}
+===========================================================
+
+The way that a {{CSSStyleValue}} serializes is dependent on how the value was constructed.
+
+: if the value was constructed from a USVString
+:: the serialization is the USVString from which the value was constructed.
+: otherwise, if the value was constructed using an IDL constructor
+:: the serialization is specified in the sections below.
+: otherwise, if the value was extracted from the CSSOM
+:: the serialization is specified in [[#cssom-serialization]] below.
+
+
+ To serialize a {{CSSUnparsedValue}} |this|:
+
+ 1. Let |s| initially be the empty [=string=].
+
+ 2. [=list/For each=] |item| in |this|’s {{CSSUnparsedValue/[[tokens]]}} internal slot:
+ 1. If |item| is a {{USVString}},
+ append it to |s|.
+ 2. Otherwise, |item| is a {{CSSVariableReferenceValue}}.
+ Serialize it,
+ then append the result to |s|.
+
+ 3. Return |s|.
+
+
+
+ To serialize a {{CSSVariableReferenceValue}} |this|:
+
+ 1. Let |s| initally be "var(".
+
+ 2. Append |this|’s {{CSSVariableReferenceValue/variable}} internal slot to |s|.
+
+ 3. If |this|’s {{CSSVariableReferenceValue/fallback}} internal slot is not `null`,
+ append ", " to |s|,
+ then serialize the {{CSSVariableReferenceValue/fallback}} internal slot
+ and append it to |s|.
+
+ 4. Append ")" to |s|
+ and return |s|.
+
+ To serialize a {{CSSUnitValue}} |this|:
+
+ 1. Let |value| and |unit| be
+ |this|‘s {{CSSUnitValue/value}} and {{CSSUnitValue/unit}} internal slots.
+
+ 2. Set |s| to the result of serializing a <> from |value|,
+ per [[cssom#serializing-css-values]].
+
+ 3. If |unit| is "number",
+ return |s|.
+
+ 4. Otherwise, if |unit| is "percent",
+ append "%" to |s|,
+ then return |s|.
+
+ 5. Otherwise, append |unit| to |s|,
+ then return |s|.
+
+ To serialize a {{CSSMathValue}} |this|,
+ with additional flags |nested|, a boolean (defaulting to false if unspecified),
+ and |paren-less|, a boolean (defaulting to false if unspecified):
+
+ 1. Let |s| initially be the empty [=string=].
+
+ 2. If |this| is a {{CSSMathMin}} or {{CSSMathMax}}:
+ 1. Append "min(" or "max(" to |s|, as appropriate.
+
+ 2. [=list/For each=] |arg| in |this|’s {{CSSMathMin/values}} internal slot,
+ serialize |arg| with |nested| and |paren-less| both true,
+ and append the result to |s|,
+ appending a ", " between successive values.
+
+ 3. Append ")" to |s|
+ and return |s|.
+
+ 3. Otherwise, if |this| is a {{CSSMathSum}}:
+ 1. If |paren-less| is true,
+ continue to the next step;
+ otherwise, if |nested| is true,
+ append "(" to |s|;
+ otherwise, append "calc(" to |s|.
+
+ 2. Serialize the first [=list/item=] in |this|’s {{CSSMathSum/values}} internal slot
+ with |nested| set to true,
+ and append the result to |s|.
+
+ 3. [=list/For each=] |arg| in |this|’s {{CSSMathSum/values}} internal slot beyond the first:
+ 1. If |arg| is a {{CSSMathNegate}},
+ append " - " to |s|,
+ then serialize |arg|'s {{CSSMathNegate/value}} internal slot
+ with |nested| set to true,
+ and append the result to |s|.
+ 2. Otherwise, append " + " to |s|,
+ then serialize |arg|
+ with |nested| set to true,
+ and append the result to |s|.
+
+ 4. If |paren-less| is false,
+ append ")" to |s|,
+
+ 5. Return |s|.
+
+ 4. Otherwise, if |this| is a {{CSSMathNegate}}:
+ 1. If |paren-less| is true,
+ continue to the next step;
+ otherwise, if |nested| is true,
+ append "(" to |s|;
+ otherwise, append "calc(" to |s|.
+
+ 2. Append "-" to |s|.
+
+ 3. Serialize |this|’s {{CSSMathNegate/value}} internal slot
+ with |nested| set to true,
+ and append the result to |s|.
+
+ 4. If |paren-less| is false,
+ append ")" to |s|,
+
+ 5. Return |s|.
+
+ 5. Otherwise, if |this| is a {{CSSMathProduct}}:
+ 1. If |paren-less| is true,
+ continue to the next step;
+ otherwise, if |nested| is true,
+ append "(" to |s|;
+ otherwise, append "calc(" to |s|.
+
+ 2. Serialize the first [=list/item=] in |this|’s {{CSSMathProduct/values}} internal slot
+ with |nested| set to true,
+ and append the result to |s|.
+
+ 3. [=list/For each=] |arg| in |this|’s {{CSSMathProduct/values}} internal slot beyond the first:
+ 1. If |arg| is a {{CSSMathInvert}},
+ append " / " to |s|,
+ then serialize |arg|'s {{CSSMathInvert/value}} internal slot
+ with |nested| set to true,
+ and append the result to |s|.
+ 2. Otherwise, append " * " to |s|,
+ then serialize |arg|
+ with |nested| set to true,
+ and append the result to |s|.
+
+ 4. If |paren-less| is false,
+ append ")" to |s|,
+
+ 5. Return |s|.
+
+ 6. Otherwise, if |this| is a {{CSSMathInvert}}:
+ 1. If |paren-less| is true,
+ continue to the next step;
+ otherwise, if |nested| is true,
+ append "(" to |s|;
+ otherwise, append "calc(" to |s|.
+
+ 2. Append "1 / " to |s|.
+
+ 3. Serialize |this|’s {{CSSMathInvert/value}} internal slot
+ with |nested| set to true,
+ and append the result to |s|.
+
+ 4. If |paren-less| is false,
+ append ")" to |s|,
+
+ 5. Return |s|.
+
+
+{{CSSTransformValue}} and {{CSSTransformComponent}} Serialization {#transformvalue-serialization}
+-------------------------------------------------------------------------------------------------
+
+
+ To serialize a {{CSSTransformValue}} |this|:
+
+ 1. Return the result of serializing each [=list/item=]
+ in |this|’s [=values to iterate over=],
+ then concatenating them separated by " ".
+
+
+
+ To serialize a {{CSSTranslate}} |this|:
+
+ 1. Let |s| initially be the empty [=string=].
+
+ 2. If |this|’s {{CSSTransformValue/is2D}} internal slot is `false`:
+ 1. Append "translate3d(" to |s|.
+
+ 2. Serialize |this|’s {{CSSTranslate/x}} internal slot,
+ and append it to |s|.
+
+ 3. Append ", " to |s|.
+
+ 4. Serialize |this|’s {{CSSTranslate/y}} internal slot,
+ and append it to |s|.
+
+ 5. Append ", " to |s|.
+
+ 6. Serialize |this|’s {{CSSTranslate/z}} internal slot,
+ and append it to |s|.
+
+ 7. Append ")" to |s|,
+ and return |s|.
+
+ 3. Otherwise:
+ 1. Append "translate(" to |s|.
+
+ 2. Serialize |this|’s {{CSSTranslate/x}} internal slot,
+ and append it to |s|.
+
+ 3. Append ", " to |s|.
+
+ 4. Serialize |this|’s {{CSSTranslate/y}} internal slot,
+ and append it to |s|.
+
+ 5. Append ")" to |s|,
+ and return |s|.
+
+
+
+ To serialize a {{CSSRotate}} |this|:
+
+ 1. Let |s| initially be the empty [=string=].
+
+ 2. If |this|’s {{CSSTransformValue/is2D}} internal slot is `false`:
+ 1. Append "rotate3d(" to |s|.
+
+ 2. Serialize |this|’s {{CSSRotate/x}} internal slot,
+ and append it to |s|.
+
+ 3. Append ", " to |s|.
+
+ 4. Serialize |this|’s {{CSSRotate/y}} internal slot,
+ and append it to |s|.
+
+ 5. Append ", " to |s|.
+
+ 6. Serialize |this|’s {{CSSRotate/z}} internal slot,
+ and append it to |s|.
+
+ 7. Append "," to |s|.
+
+ 8. Serialize |this|’s {{CSSRotate/angle}} internal slot,
+ and append it to |s|.
+
+ 9. Append ")" to |s|,
+ and return |s|.
+
+ 3. Otherwise:
+ 1. Append "rotate(" to |s|.
+
+ 2. Serialize |this|’s {{CSSRotate/angle}} internal slot,
+ and append it to |s|.
+
+ 3. Append ")" to |s|,
+ and return |s|.
+
+
+
+ To serialize a {{CSSScale}} |this|:
+
+ 1. Let |s| initially be the empty [=string=].
+
+ 2. If |this|’s {{CSSTransformValue/is2D}} internal slot is `false`:
+ 1. Append "scale3d(" to |s|.
+
+ 2. Serialize |this|’s {{CSSScale/x}} internal slot,
+ and append it to |s|.
+
+ 3. Append ", " to |s|.
+
+ 4. Serialize |this|’s {{CSSScale/y}} internal slot,
+ and append it to |s|.
+
+ 5. Append ", " to |s|.
+
+ 6. Serialize |this|’s {{CSSScale/z}} internal slot,
+ and append it to |s|.
+
+ 7. Append ")" to |s|,
+ and return |s|.
+
+ 3. Otherwise:
+ 1. Append "scale(" to |s|.
+
+ 2. Serialize |this|’s {{CSSScale/x}} internal slot,
+ and append it to |s|.
+
+ 3. If |this|’s {{CSSScale/x}} and {{CSSScale/y}} internal slots
+ are [=equal numeric values=],
+ append ")" to |s|
+ and return |s|.
+
+ 4. Otherwise, append ", " to |s|.
+
+ 5. Serialize |this|’s {{CSSScale/y}} internal slot,
+ and append it to |s|.
+
+ 6. Append ")" to |s|,
+ and return |s|.
+
+
+
+ To serialize a {{CSSSkew}} |this|:
+
+ 1. Let |s| initially be "skew(".
+
+ 2. Serialize |this|’s {{CSSSkew/ax}} internal slot,
+ and append it to |s|.
+
+ 3. If |this|’s {{CSSSkew/ay}} internal slot is a {{CSSUnitValue}}
+ with a {{CSSUnitValue/value}} of `0`,
+ then append ")" to |s|
+ and return |s|.
+
+ 4. Otherwise, append ", " to |s|.
+
+ 5. Serialize |this|’s {{CSSSkew/ay}} internal slot,
+ and append it to |s|.
+
+ 6. Append ")" to |s|,
+ and return |s|.
+
+
+
+ To serialize a {{CSSSkewX}} |this|:
+
+ 1. Let |s| initially be "skewX(".
+
+ 2. Serialize |this|’s {{CSSSkewX/ax}} internal slot,
+ and append it to |s|.
+
+ 3. Append ")" to |s|,
+ and return |s|.
+
+
+
+ To serialize a {{CSSSkewY}} |this|:
+
+ 1. Let |s| initially be "skewY(".
+
+ 2. Serialize |this|’s {{CSSSkewY/ay}} internal slot,
+ and append it to |s|.
+
+ 3. Append ")" to |s|,
+ and return |s|.
+
+
+
+ To serialize a {{CSSPerspective}} |this|:
+
+ 1. Let |s| initially be "perspective(".
+
+ 2. Serialize |this|’s {{CSSPerspective/length}} internal slot,
+ and append it to |s|.
+
+ 3. Append ")" to |s|,
+ and return |s|.
+
+
+
+ To serialize a {{CSSMatrixComponent}} |this|:
+
+ 1. Return the [=DOMMatrixReadOnly/stringification behavior|serialization=]
+ of |this|’s {{CSSMatrixComponent/matrix}} internal slot.
+
+
+
+{{CSSURLImageValue}} Serialization {#urlimagevalue-serialization}
+-----------------------------------------------------------
+
+{{CSSURLImageValue}} objects are serialized to the string given by
+'url("' + {{CSSURLImageValue/url}} + '")'.
+
+Serialization from CSSOM Values {#cssom-serialization}
+------------------------------------------------------
+
+{{CSSStyleValue}} objects produced by the user agent from values in the CSSOM,
+rather than directly constructed by the author,
+are serialized according to the following rules,
+depending on the property they came from:
+
+: 'background-color'
+::
+ 1. If the value is the ''currentcolor'' keyword,
+ return "currentcolor".
+ 2. Otherwise, return the result of serializing the <> value.
+
+: 'border-color'
+::
+ 1. If the value is the ''currentcolor'' keyword,
+ return "currentcolor".
+ 2. Otherwise, return the result of serializing the <> value.
+
+: 'border-image'
+::
+ 1. Let |values| initially be the empty [=list=].
+ 2. If 'border-image-source' is not ''border-image-source/none'',
+ serialize 'border-image-source' and append it to |values|.
+ 3. If 'border-image-slice' does not specify ''100%'' for all sides and omits the ''border-image-slice/fill'' keyword,
+ serialize 'border-image-slice' and append it to |values|.
+ 4. If 'border-image-width' does not specify ''1'' for all sides,
+ append "/ " (U+002F FORWARD SLASH followed by U+0020 SPACE)
+ to the result of serializing 'border-image-width' and append it to |values|.
+ 5. If 'border-image-outset' does not specify ''0'' for all sides:
+ 1. If the previous 'border-image-width' step did not append anything to |values|,
+ let |prefix| be "// "
+ (two U+002F FORWARD SLASH characters followed by U+0020 SPACE);
+ otherwise let |prefix| be "/ "
+ (U+002F FORWARD SLASH followed by U+0020 SPACE)
+ 2. Append |prefix|
+ to the result of serializing 'border-image-outset'
+ and append it to |values|.
+ 6. If 'border-image-repeat' is not ''border-image-repeat/stretch'' in both axises,
+ serialize 'border-image-repeat' and append it to |values|.
+ 7. If |values| is [=list/empty=],
+ append "none" to |values|.
+ 8. Return the result of concatenating all the items in |values|,
+ separated by " " (U+0020 SPACE).
+
+: 'bottom'
+::
+ 1. If the value is the ''auto'' keyword,
+ return "auto".
+ 2. If the value is of type <>,
+ return the result of serializing the <> value.
+ 3. Otherwise, return the result of serializing the <> value.
+
+: 'color'
+::
+ 1. If the value is the ''currentcolor'' keyword,
+ return "currentcolor".
+ 2. Otherwise, return the result of serializing the <> value.
+
+: 'left'
+::
+ 1. If the value is the ''auto'' keyword,
+ return "auto".
+ 2. If the value is of type <>,
+ return the result of serializing the <> value.
+ 3. Otherwise, return the result of serializing the <> value.
+
+: 'opacity'
+::
+ 1. If the value is of type <>,
+ return the result of serializing the <> value.
+ 2. Otherwise, return the result of serializing the <> value.
+
+: 'right'
+::
+ 1. If the value is the ''auto'' keyword,
+ return "auto".
+ 2. If the value is of type <>,
+ return the result of serializing the <> value.
+ 3. Otherwise, return the result of serializing the <> value.
+
+: 'top'
+::
+ 1. If the value is the ''auto'' keyword,
+ return "auto".
+ 2. If the value is of type <>,
+ return the result of serializing the <> value.
+ 3. Otherwise, return the result of serializing the <> value.
+
+
+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.
diff --git a/css-typed-om/README.md b/css-typed-om/README.md
new file mode 100644
index 00000000..af829a3b
--- /dev/null
+++ b/css-typed-om/README.md
@@ -0,0 +1,111 @@
+# CSS Typed OM Explained
+
+CSS Typed OM is an extensible API for the CSSOM that reduces the burden of manipulating a CSS property's value via string manipulation. It does so by exposing CSS values as typed JavaScript objects rather than strings.
+
+## Motivation
+
+The benefits of CSS Typed OM include:
+* Allows the performant manipulation of values assigned to CSS properties.
+* Being able to write more maintainable code:
+ * Your code is easier to understand,
+ * Easier to write more generic code.
+
+### Examples
+
+### Getting a property's value
+
+In CSSOM land:
+We get specified and computed values for CSS properties of an element in CSSOM via the `.style` attribute on an element and the `getComputedStyle()` object respectively.
+
+In Typed OM:
+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 CSSUnitValue
+```
+
+Similarly to get the computed value of a CSS property we would do the following:
+```
+element.computedStyleMap().get('height'); // returns a CSSUnitValue
+```
+
+### Changing a property's value
+
+To understand the power of Typed OM take a look at the example in CSSOM below:
+
+```
+let heightValue = target.style.height.slice(0,-2);
+heightValue++;
+target.style.height = heightValue + 'px';
+```
+
+In the above example, we are manipulating the `height` CSS property by:
+* extract the numeric portion of a string,
+* performing mathematical operation,
+* converting a number to a string,
+* appending a unit to the resulting string.
+
+The CSS parser will then parse the final string and convert it back to a number for the CSS engine.
+
+With Typed OM you can manipulate typed Javascript objects thereby eliminating the CSS Parser altogether. The above example will now read as follows:
+
+```
+let heightValue = element.attributeStyleMap.get('height');
+heightValue.value++;
+target.attributeStyleMap.set('height', heightValue);
+```
+
+In the above code, `heightValue` gets assigned a `CSSUnitValue` with a value set to the current value of the height and the unit set to 'px'. You can then modify the unit as you would do an integer and not have to incur the cost of manipulating a string.
+
+## Spec references
+
+### What is a CSSStyleValue?
+
+[CSSStyleValue](https://drafts.css-houdini.org/css-typed-om-1/#cssstylevalue) is the base class through which all CSS values are expressed. For a detailed list of what CSSStyleValue subclasses each CSS property maps to please see [here](PLACEHOLDER). Values that aren't supported yet will also be considered as CSSStyleValue objects.
+
+### What are the different CSSStyleValue subclasses?
+
+#### CSSKeywordValue
+
+[CSSKeywordValue](https://drafts.css-houdini.org/css-typed-om-1/#csskeywordvalue) objects represent CSS keywords and other identifiers. An example would be:
+
+```
+element.attributeStyleMap.set("display", new CSSKeywordValue("none")));
+```
+
+#### CSSNumericValue
+
+[CSSNumericValue](https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue) objects represent CSS values that are numeric in nature.
+
+The class can be broken down into two subclasses:
+* CSSUnitValues represent simple numbers with units - `CSSUnitValue(10, 'px')` for example is the typed representation of `10px`
+* CSSMathValues represent more complex expressions - `CSSMathSum(CSS.em(1), CSS.px(5))` for example is the typed representation of `calc(1em + 5px)`
+
+#### CSSTransformValue
+
+[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.
+
+#### 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)
+
+### What about Custom Properties?
+
+Unregistered custom properties are represented by [CSSUnparsedValues](https://drafts.css-houdini.org/css-typed-om-1/#cssunparsedvalue) in the Typed OM API. `var()` references are represented using [CSSVariableReferenceValues](https://drafts.css-houdini.org/css-typed-om-1/#cssvariablereferencevalue).
+
+## Future Capabilities
+
+The current specification doesn't have support for the following, however support for the following is under consideration for Typed OM Level 2:
+* Colors,
+* URLs (that aren't images),
+* Shapes,
+* Strings,
+* Counters,
+* etc.
+
+## Go ahead - try it!
+
+Currently you can try out CSS Typed OM in Chromium based browsers (it is behind a flag though.)
+
+Do let us know if you are having trouble with Typed OM or are missing capabilities by filing an issue on [GitHub](https://github.com/w3c/css-houdini-drafts/issues/new).
diff --git a/csslogo.ico b/csslogo.ico
new file mode 100644
index 00000000..e92b8786
Binary files /dev/null and b/csslogo.ico differ
diff --git a/default.css b/default.css
new file mode 100644
index 00000000..5b23c22b
--- /dev/null
+++ b/default.css
@@ -0,0 +1,157 @@
+/*
+ * Style sheet for the CSS3 specification,
+ * to be used in addition to https://www.w3.org/StyleSheets/TR/W3C-{WD,PR,REC}
+ */
+
+@import "https://www.w3.org/StyleSheets/TR/2016/base.css";
+
+/** Example Code **************************************************************/
+
+ div.html, div.xml,
+ pre.html, pre.xml {
+ padding: 0.5em;
+ margin: 1em 0;
+ position: relative;
+ clear: both;
+ color: #600;
+ }
+ pre.example,
+ pre.html,
+ pre.xml {
+ padding-top: 1.5em;
+ }
+
+ pre.illegal, .illegal pre
+ pre.deprecated, .deprecated pre {
+ color: red;
+ }
+
+/** Inline markup fragments ***************************************************/
+
+ code.html, code.xml {
+ color: #660000;
+ }
+
+/******************************************************************************/
+/* Images */
+/******************************************************************************/
+
+ pre.ascii-art {
+ display: table; /* shrinkwrap */
+ margin: 1em auto;
+ }
+
+ /* Displaying the output of text layout, particularly when
+ line-breaking is being invoked, and thus having a
+ visible width is good. */
+ pre.output {
+ margin: 1em;
+ border: solid thin silver;
+ padding: 0.25em;
+ display: table;
+ }
+
+/******************************************************************************/
+/* Tables */
+/******************************************************************************/
+
+/* XXX: Remove these once all specs are bikeshedded or Bert's processor generates .def/.index classes */
+
+/** Property/Descriptor Definition Tables *************************************/
+
+ table.propdef, table.propdef-extra,
+ table.descdef, table.definition-table {
+ page-break-inside: avoid;
+ width: 100%;
+ margin: 1.2em 0;
+ border-left: 0.5em solid #8CCBF2;
+ padding: 0.5em 1em;
+ background: #DEF;
+ border-spacing: 0;
+ }
+
+ table.propdef td, table.propdef-extra td,
+ table.descdef td, table.definition-table td,
+ table.propdef th, table.propdef-extra th,
+ table.descdef th, table.definition-table th {
+ padding: 0.5em;
+ vertical-align: baseline;
+ border-bottom: 1px solid #bbd7e9;
+ }
+ table.propdef > tbody > tr:last-child th,
+ table.propdef-extra > tbody > tr:last-child th,
+ table.descdef > tbody > tr:last-child th,
+ table.definition-table > tbody > tr:last-child th,
+ table.propdef > tbody > tr:last-child td,
+ table.propdef-extra > tbody > tr:last-child td,
+ table.descdef > tbody > tr:last-child td,
+ table.definition-table > tbody > tr:last-child td {
+ border-bottom: 0;
+ }
+
+ table.propdef th,
+ table.propdef-extra th,
+ table.descdef th,
+ table.definition-table th {
+ font-style: italic;
+ font-weight: normal;
+ width: 8.3em;
+ padding-left: 1em;
+ }
+
+ /* For when values are extra-complex and need formatting for readability */
+ table td.pre {
+ white-space: pre-wrap;
+ }
+
+ /* A footnote at the bottom of a propdef */
+ table.propdef td.footnote,
+ table.propdef-extra td.footnote,
+ table.descdef td.footnote,
+ table.definition-table td.footnote {
+ padding-top: 0.6em;
+ }
+ table.propdef td.footnote::before,
+ table.propdef-extra td.footnote::before,
+ table.descdef td.footnote::before,
+ table.definition-table td.footnote::before {
+ content: " ";
+ display: block;
+ height: 0.6em;
+ width: 4em;
+ border-top: thin solid;
+ }
+
+/** Profile Tables ************************************************************/
+ /* table of required features in a CSS profile */
+
+ table.features th {
+ background: #00589f;
+ color: #fff;
+ text-align: left;
+ padding: 0.2em 0.2em 0.2em 0.5em;
+ }
+ table.features td {
+ vertical-align: top;
+ border-bottom: 1px solid #ccc;
+ padding: 0.3em 0.3em 0.3em 0.7em;
+ }
+
+/** At-risk List **************************************************************/
+ /* Style for At Risk features (intended as editorial aid, not intended for publishing) */
+
+ .atrisk::before {
+ float: right;
+ margin-top: -0.25em;
+ padding: 0.5em 1em 0.5em 0;
+ text-indent: -0.9em;
+ border: 1px solid;
+ content: '\25C0 Not yet widely implemented';
+ white-space: pre;
+ font-size: small;
+ background-color: white;
+ color: gray;
+ text-align: center;
+ }
+
+ .toc .atrisk::before { content:none }
diff --git a/font-metrics-api/Overview.bs b/font-metrics-api/Overview.bs
index 3ea61999..4c607ddd 100644
--- a/font-metrics-api/Overview.bs
+++ b/font-metrics-api/Overview.bs
@@ -6,7 +6,207 @@ ED: https://drafts.css-houdini.org/font-metrics-api-1/
Shortname: font-metrics-api
Level: 1
Abstract:
-Editor: Stephen Zilles, szilles@adobe.com
-Editor: Alan Stearns, stearns@adobe.com
-Editor: Chris Lilley, chris@w3.org
+Editor: Emil A Eklund, eae@google.com, w3cid 93298
+Editor: Alan Stearns, stearns@adobe.com, w3cid 46659
+
+
+
+Introduction {#intro}
+=====================
+
+The API exposed by this specification is designed to provide basic font metrics
+for both in-document and out-of-document content.
+
+Note: In a future version of this spec support may be added for exposing
+information about individual runs of text, including information about
+directionality, script, and character properties.
+
+Measure API {#measure-api}
+============================================
+
+
+
+Two methods are provided for measuring text, one for in-document measurements
+and another for out-of-document measurements. Both return a {{FontMetrics}}
+object.
+
+{{Document/measureElement()}} takes an {{Element}} and returns a {{FontMetrics}}
+object. If the {{Element}} is not in the document or isn't rendered an empty
+{{FontMetrics}} object will be returned.
+
+{{Document/measureText()}} takes a {{DOMString}} and a
+{{StylePropertyMapReadOnly}}, returning a {{FontMetrics}} object. Unless a font
+is specified as a part of the styleMap the user agents default will be used.
+
+Note: The only styles that apply to the {{Document/measureText()}} method are
+those that are passed in as a part of the styleMap. Document styles do not apply.
+
+{{FontMetrics}} object {#fontmetrics-definition}
+----------------------------
+
+
+
+The {{FontMetrics}} object has the following attributes:
+
+{{FontMetrics/width}}
+The advance width of the line box, in CSS pixels.
+
+{{FontMetrics/advances}}
+List of advances for each codepoint in the given text relative to the preceding
+codepoint, in CSS pixels. Where a glyph is composed of a sequence of codepoints
+the advance for the all but the first codepoint in the sequence will be zero.
+
+{{FontMetrics/boundingBoxLeft}}
+The distance parallel to the {{FontMetrics/dominantBaseline}} from the alignment
+point given by the text-align property to the left side of the bounding
+rectangle of the given text, in CSS pixels; positive numbers indicating a
+distance going left from the given alignment point.
+
+Note: The sum of this value and {{FontMetrics/boundingBoxRight}} can be wider
+than the {{FontMetrics/width}}, in particular with slanted fonts where
+characters overhang their advance width.
+
+{{FontMetrics/boundingBoxRight}}
+The distance parallel to the {{FontMetrics/dominantBaseline}} from the alignment
+point given by the text-align property to the right side of the bounding
+rectangle of the given text, in CSS pixels. Positive numbers indicating a
+distance going right from the given alignment point.
+
+{{FontMetrics/height}}
+The distance between the highest top and the lowest bottom of the em squares in
+the line box, in CSS pixels.
+
+{{FontMetrics/emHeightAscent}}
+The distance from the {{FontMetrics/dominantBaseline}} to the highest top of the
+em squares in the line box, in CSS pixels.
+Positive numbers indicating that the {{FontMetrics/dominantBaseline}} is below
+the top of that em square (so this value will usually be positive).
+Zero if the {{FontMetrics/dominantBaseline}} is the top of that em square.
+Half the font size if the {{FontMetrics/dominantBaseline}} is the middle of that
+em square.
+
+{{FontMetrics/emHeightDescent}}
+The distance from the {{FontMetrics/dominantBaseline}} to the lowest bottom of
+the em squares in the line box, in CSS pixels.
+Positive numbers indicating that the {{FontMetrics/dominantBaseline}} is below
+the bottom of that em square (so this value will usually be negative).
+Zero if the {{FontMetrics/dominantBaseline}} is the bottom of that em square.
+
+{{FontMetrics/boundingBoxAscent}}
+The distance from the {{FontMetrics/dominantBaseline}} to the top of the
+bounding rectangle of the given text, in CSS pixels; positive numbers indicating
+a distance going up from the {{FontMetrics/dominantBaseline}}.
+
+Note: This number can vary greatly based on the input text, even if the first
+font specified covers all the characters in the input.
+
+{{FontMetrics/boundingBoxDescent}}
+The distance from the {{FontMetrics/dominantBaseline}} to the bottom of the
+bounding rectangle of the given text, in CSS pixels; positive numbers indicating
+a distance going down from the {{FontMetrics/dominantBaseline}}.
+
+{{FontMetrics/fontBoundingBoxAscent}}
+The distance from the {{FontMetrics/dominantBaseline}} to the top of the highest
+bounding rectangle of all the fonts used to render the text, in CSS pixels;
+positive numbers indicating a distance going up from the
+{{FontMetrics/dominantBaseline}}.
+
+Note: This value and {{FontMetrics/fontBoundingBoxDescent}} are useful when
+metrics independent of the actual text being measured are desired as the values
+will be consistent regardless of the text as long as the same fonts are being
+used.
+The {{FontMetrics/boundingBoxAscent}} attribute (and its corresponding attribute
+for the descent) are useful when metrics specific to the given text are desired.
+
+{{FontMetrics/fontBoundingBoxDescent}}
+The distance from the {{FontMetrics/dominantBaseline}} to the bottom of the
+lowest bounding rectangle of all the fonts used to render the text, in
+CSS pixels; positive numbers indicating a distance going down from the
+{{FontMetrics/dominantBaseline}}.
+
+{{FontMetrics/dominantBaseline}}
+Reference to the dominant {{Baseline}} for the given text in the list of
+{{FontMetrics/baselines}}.
+
+{{FontMetrics/baselines}}
+List of all {{Baseline}}s for the given text.
+
+
+{{Baseline}} object {#baseline-definition}
+----------------------------
+
+
+
+Each {{Baseline}} object represents a baseline of the measured text and has the
+following attributes:
+
+{{Baseline/name}}
+Name of the baseline in question.
+
+{{Baseline/value}}
+Distance from the {{FontMetrics/dominantBaseline}}, in CSS pixels.
+Positive numbers indicating a distance going down from the
+{{FontMetrics/dominantBaseline}}.
+
+
+{{Font}} object {#font-definition}
+----------------------------
+
+
+[Exposed=Window]
+interface Font {
+ readonly attribute DOMString name;
+ readonly attribute unsigned long glyphsRendered;
+};
+
+
+Each {{Font}} object represents a font that was used for at least one glyph in
+the measured text. It contains the following fields:
+
+{{Font/name}}
+Font family name.
+
+{{Font/glyphsRendered}}
+Number of glyphs used from the specific font. If multiple fonts are required to
+render the specified text this attribute will indicate how many glyphs where
+used from each font.
+
+Note: Indicates the number of glyphs which may be lower than the number of
+codepoints.
diff --git a/font-metrics-api/README.md b/font-metrics-api/README.md
new file mode 100644
index 00000000..24f72b58
--- /dev/null
+++ b/font-metrics-api/README.md
@@ -0,0 +1,59 @@
+Font Metrics API
+================
+
+# Motivation
+
+Box tree/view object stuff will be great as an underpinning for custom layout, particularly when it's extended to cover line boxes. But the proposals discussed so far lack some information that's needed for layout that depends on font metrics. It's not sufficient to know where the line box is being laid out. You also need to know where the glyphs inside that line box are being drawn.
+
+There are also some parts of layout that are typographically-intensive, like line breaking. Allowing script to substitute its own line breaking would be an excellent extensibility point that's often asked for.
+
+# Examples:
+
+The align-items:baseline feature in flex layout
+* needs to know where the first baseline of each flex item lands
+
+Initial letter
+* needs baseline and cap height from the initial letter, and a baseline measurement for the in-flow content
+
+Baseline grid
+* needs a baseline for each line box in the element
+
+Math layout
+* needs much much more than the above, including individual glyph advances and bounds.
+
+Line breaking
+* needs access to font data, all of the style inputs for text, and layout information (available line lengths, intrusions like floats, etc.)
+
+# Font data versus typographic measurement
+
+I (Alan) initially thought that exposing the actual font data could be a good first step, but conversations I've had have convinced me that there's a better stepping-stone to use. Given raw font metrics, one has to reverse-engineer how the browser will use those metrics in order to determine where baselines, etc. will actually be drawn. Direct access to all of the font information was not sufficient for dropcap.js, because browsers use the font information in slightly different ways - we still had to sample the rendered text in each browser to determine where glyphs were placed.
+
+Instead, we could expose how these font metrics are used by the browser, giving access to typographic layout results. If and when we have view objects it will make sense to make these results available at several levels - down to the glyph level (for advances, bounds, etc.), for runs of text within a line box (aggregating some glyph information and/or pulling information from the first glyph), for line boxes (aggregating and/or first run information), for fragments (aggregating and/or first line box) and for elements (aggregating and/or first fragment). So even without the underlying view objects it may make sense to start with attributes on higher-level objects, informed by the underlying layout structure. For example, a possible 'firstBaselinePosition' attribute of an Element might give a pixel measurement for the baseline of the first glyph of the first line box of the first fragment, measured from the top of the fragment.
+
+There's another argument that people want direct access to font data to enable their own text layout routines to draw on a canvas or invoke in a custom paint routine. That's a fine use case, but I think it's probably a lower priority than allowing measurement of text laid out by the browser. Math layout will require actual font data, but how that data is used will depend on the layout measures above. So we should eventually get to the point of exposing raw font data, but either after or at the same time as exposing at least some of the layout results.
+
+There is also an argument that exposing information on higher-level constructs can result in a dead-end, since the underlying complexities of font selection, shaping, line-breaking, et.al. might not be taken into account in either aggregating or choosing what to sample for something as high-level as an element (or even a line box). That's certainly a risk, but if that risk is considered I think it will be possible to come up with a small set of useful high-level APIs without having to wait for agreement on how to standardize lower-level text layout constructs.
+
+# Four kinds of Font Data
+
+We discussed four different areas that seemed to qualify as 'font metrics' during the F2F in Sydney, in January 2016. These are:
+
+1. What font(s) are being used? This is complicated because multiple fonts can be used per paragraph, per line,
+ per word, and even per glyph. The fonts should be exposed in the form of handles with complete font information, and
+ (for web fonts) a handle to the raw font data. dbaron & eae are going to own this area and propose an API.
+
+1. Browsers make use of some font information for layout (including multiple different baselines). In order to do this,
+ sometimes baseline information must be synthesized from the available data. However, this information is a small
+ subset of all possible data that could conceivably be synthesized about fonts. Area 2 pertains solely to information
+ that is already available in fonts, or that must be synthesized by browsers in order to successfully layout text.
+ This information should be exposed by browsers on a font-by-font basis. stevez & astearns are going to own this
+ area and propose an API.
+
+1. Fonts carry more metrics than browsers use. Often these metrics are buggy or incorrect. Nevertheless, access to the
+ raw metrics is useful. While we recognize that this is an area of interest, we decided to defer work on this for now,
+ given that the raw data is already going to be accessible via area 1, and that there are existing JS parsers for multiple
+ kinds of font.
+
+1. Font geometry and metrics that browsers don't already used could conceivably be synthesized by browsers. However, in order
+ to do so, we'd need to standardize the algorithms used to perform these syntheses. As a result, we decided to defer working
+ on this area for now.
diff --git a/images/exclusions_1.png b/images/exclusions_1.png
new file mode 100644
index 00000000..b7e56442
Binary files /dev/null and b/images/exclusions_1.png differ
diff --git a/images/layout_opp.gif b/images/layout_opp.gif
new file mode 100644
index 00000000..15dcd6a3
Binary files /dev/null and b/images/layout_opp.gif differ
diff --git a/scroll-customization-api/ScrollIntent.png b/scroll-customization-api/ScrollIntent.png
new file mode 100644
index 00000000..ae959ee8
Binary files /dev/null and b/scroll-customization-api/ScrollIntent.png differ
diff --git a/css-scroll-api/UseCases.md b/scroll-customization-api/UseCases.md
similarity index 72%
rename from css-scroll-api/UseCases.md
rename to scroll-customization-api/UseCases.md
index 4cf4a1f9..32e5da88 100644
--- a/css-scroll-api/UseCases.md
+++ b/scroll-customization-api/UseCases.md
@@ -1,21 +1,17 @@
+The [Composited Scrolling](https://github.com/w3c/css-houdini-drafts/blob/master/composited-scrolling-and-animation/UseCases.md) use cases include effects that respond to and are synchronized with scroll position. Below are additional use cases which require more control over exactly how scrolling behaves.
Hidey bars
----
-- typically have a different effect depending on the direction of scroll, and depending on whether they are attached to the top or bottom of their scroll area
-- e.g. scrolling down the bar moves with the container, and disappears outside the bounds
+- Similar to a [Scroll header](https://github.com/w3c/css-houdini-drafts/blob/master/composited-scrolling-and-animation/UseCases.md#scroll-header)
+- Typically have a different effect depending on the direction of scroll, and depending on whether they are attached to the top or bottom of their scroll area
+ - e.g. scrolling down the bar moves with the container, and disappears outside the bounds
- scrolling up, the bar animates in with a timed animation. It's position is relative to its container at this point.
- When a scroll operation ends (eg. fingers lifted from touchpad), may animate to either fully shown or fully hidden
- Advanced: content may “hide” into a smaller version of itself, that is sticky positioned (e.g. iPhone Safari URL bar)
-
-Scroll header
-----
-- A header at the top of the document
-- When approaching scrollTop=0 header smoothly animates into a more substantial one. Eg. images, opacity, text size animate with scroll position.
-- May also only be shown when scrolling down (like hidey bars)
+- Advanced: may resize the contents after showing / hiding (eg. so that contents scrollbar stops below the header, and elements can be reliably position:fixed to the top of the contents).
- Examples:
- - The top bar on [Twitter user pages](https://twitter.com/LEGO_Group)
- - Polymer's [core-scroll-header-panel](http://polymer.github.io/core-scroll-header-panel/components/core-scroll-header-panel/demos/demo9.html) and [paper-scroll-header-panel](https://elements.polymer-project.org/elements/paper-scroll-header-panel?view=demo:demo/index.html)
+ - Top controls in mobile browsers like Chrome for Android and Mobile Safari
Rubber banding
----
@@ -23,7 +19,13 @@ Rubber banding
- after that point, scroll output is a fraction of the input (input * factor -> output)
- upon release, animate back to limit
- also should work with a momentum deceleration (if the user has flicked far enough to hit the limits)
-Rubber banding
+
+Snap points
+----
+- Scrolling as normal during the normal scroll phase
+- But when the scroll ends (eg. after finger lift), animates to a well-defined boundary
+- Example
+ - [CSS Snap points](https://drafts.csswg.org/css-snappoints/), and earlier [IE/Edge implementation](https://msdn.microsoft.com/en-us/library/windows/apps/hh466031.aspx)
Pull to refresh
----
@@ -31,6 +33,7 @@ This is one of the more tricky effects.
- Implemented with a rubber band effect
- Allows drawing into the background of the container, with fixed position, giving the illusion of it being in the negative margin of the scrolling object
+ - Often the effect is coupled to the scroll position (eg. a circle that rotates in proportion to the scroll position). There's a wide variety of different effects here.
- Has a threshold in the rubber band, at which point the refresh "commits". This typically triggers another animation, where the content stays where it was (its rubber-band limit) and a progress spinner is drawn in the margin. Do we consider this as if the scroll has ended and the content has moved down?
- Once the page has the refreshed data, it adds it to the DOM (causing another scroll). Or if there wasn't any data, the content animates back to the top of the container.
- Can transition into and out-of the overscroll effect without lifting the finger
@@ -46,17 +49,6 @@ This means the effect has a number of inputs and outputs.
input: the scroll value, the limit of the rubber band
output: the final scroll position, control over the refresh drawing animation, the ability to move the content by a certain amount, the ability to cancel the effect
-Parallax
------
-- the position of elements on the page is related to the scroll position of their container (or maybe another container)
-- not a direct link between scroll offset and position. Rather it is some factor, possibly with damping or a curve.
-- postion is the most common output, but it could also be opacity or a filter effect such as blur (or really any rendering property)
-
-Video sync
------
-- Video whose time point is determined as a function of the scroll offset.
-- Must be synced perfectly with scrolling, eg. scrolling down one pixel may advance the video by one frame, and that video frame may move everything up one pixel to counter the scroll. To the user it must appear as if the content didn't move with the scroll.
-
Custom scrollers
-----
- Rather than just translate the content on scroll, do something flashier like a 3D transform
@@ -69,20 +61,13 @@ Re-targeting scrolling
- A scroll occurring over one point in the document is redirected to cause scrolling somewhere else.
- Eg. GMail does this in conversation view - scroll on the right hand side (which isn't itself scrollable) and they scroll the conversation. They do this by listening for wheel events, but this doesn't work for touch.
-Linked scrollers
------
-- Multiple elements are to be scrolled together but possibly at different rates
-- Like parallax except not overlapping, use input may occur on any of the linked elements
-- One example [here](http://stackoverflow.com/questions/19786080/how-to-synchronize-scroll-between-two-elements-with-different-height). [AV club](http://www.avclub.com/review/weeknd-navigates-trippy-perception-and-pop-reality-224412) also appears to do this.
-- Artificial example [here](http://fiddle.jshell.net/kunknown/VVaEq/2/show/)
-
Disable scroll chaining
-----
- Ability for an element to prevent scrolling from propagating up to an ancestor
- Eg. Facebook chat widget (or any position:fixed overlay window)
- position: fixed container with an overflow:scroll div inside
- When the scroller has scrollTop=0, user attempting to scroll up on top of the widget should NOT cause the document to scroll
-- IE/Edge have [-ms-scroll-chaining](https://msdn.microsoft.com/en-us/library/windows/apps/hh466007.aspx) for this
+- Similar to IE/Edge's [-ms-scroll-chaining](https://msdn.microsoft.com/en-us/library/windows/apps/hh466007.aspx) except that effects only the chaining that occurs during a scroll gesture. The use case here may want to disable chaining even at the start of a new scroll gesture.
Custom scroll limit
-----
diff --git a/scroll-customization-api/explainer.md b/scroll-customization-api/explainer.md
new file mode 100644
index 00000000..6f673809
--- /dev/null
+++ b/scroll-customization-api/explainer.md
@@ -0,0 +1,47 @@
+# Scroll customization
+
+Many native mobile applications have [UI effects that interact with scrolling](https://github.com/w3c/css-houdini-drafts/blob/master/scroll-customization-api/UseCases.md), for example snapping the scroll position to image boundaries in an image carousell. To do this web developers must [resort](http://cubiq.org/iscroll-5) to re-implementing all of scrolling on top of raw input events. There are four major drawbacks with such an approach:
+- Threaded scrolling is disabled, so any main thread work >16ms will lead to dropped frames.
+- Accessibility is often broken, as the browser (and any assistive technologies) have no idea that scrolling is occurring.
+- Such components cannot compose properly with other scrollers (native or JS), for example for proper scroll chaining.
+- It is very difficult or impossible to re-implement browser scrolling behavior exactly (on every platform), so such scrolling ends up not feeling/looking right to the user (eg. fling physics).
+
+The fundamental problem here is that scrolling on the web is a big "magical" black-box. In other UI frameworks scrolling is usually implemented as a library (with extensibility points) on top of primitives. The goal of the Houdini Scroll Customization proposal is to break open this black box to make scrolling on the web [extensible](https://extensiblewebmanifesto.org/).
+
+## Supporting Threaded scrolling
+
+Modern browsers implement scrolling off of the main JavaScript thread to ensure that scrolling can be serviced reliably at 60fps. We want to allow arbitrary scroll effects to be written using JavaScript that runs every scroll frame. This proposal builds on [Compositor Worker](https://github.com/w3c/css-houdini-drafts/blob/master/composited-scrolling-and-animation/Explainer.md) to permit customizations that typically run in-sync with scrolling.
+
+In the future we also want high performance applications to be able to opt-out of threaded scrolling and use a variant of these APIs from the main JavaScript execution context. The details of how to permit such customization without risking poor performance are complex and controversial and so are not yet covered as part of this proposal. But regardless of the thread in which it's executing, the mental model for scroll customization is the same.
+
+## ScrollIntent
+
+In order to explain scrolling, we need to introduce an abstraction that sits above raw input (eg. `wheel` and `touchmove` events) but below the effect that scrolling has (changing `scrollTop` and generation of `scroll` events). `ScrollIntent` represents the user's desire to scroll by a specific number of pixels (as well as additional details about the scroll such has the phase and velocity). A `Element` has a new method called `applyScroll` which takes a `ScrollIntent` and applies it, which by default just means updating `scrollTop` and `scrollLeft`.
+
+
+
+## Customizing `applyScroll`
+
+The default `applyScroll` behavior can be overridden to do something competely different whenever the user attempts to scroll the element. For example, to support rotating a 3d carousell using all the native scroll physics and composition, you might use code like this (hypothetical syntax):
+
+Main Thread
+```JavaScript
+var carousel_proxy = new CompositorProxy(carousel, [transform]);
+var worker = new CompositorWorker('carousel.js');
+worker.postMessage(carousel_proxy);
+```
+
+On the CompositorWorker
+```JavaScript
+onmessage = function(e) {
+ var carousel = e.data;
+ carousel.applyScroll = function(scrollIntent) {
+ this.transform.rotateX += scrollIntent.deltaX;
+ scrollIntent.consumeDelta(scrollIntent.deltaX, 0);
+ };
+};
+```
+
+## More examples
+
+TODO
diff --git a/w3c.json b/w3c.json
new file mode 100644
index 00000000..64df0a8b
--- /dev/null
+++ b/w3c.json
@@ -0,0 +1,8 @@
+{
+ "group": 32061,
+ "contacts": [
+ "svgeesus"
+ ],
+ "repo-type": "rec-track",
+ "policy": "open"
+}
\ No newline at end of file
diff --git a/worklets/Overview.bs b/worklets/Overview.bs
index 1dd7de2a..5601ca4f 100644
--- a/worklets/Overview.bs
+++ b/worklets/Overview.bs
@@ -1,54 +1,61 @@
Title: Worklets Level 1
-Status: DREAM
+Status: ED
Group: houdini
ED: https://drafts.css-houdini.org/worklets/
+TR: http://www.w3.org/TR/worklets-1/
+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.
-Editor: Ian Kilpatrick, ikilpatrick@chromium.org
+Editor: Ian Kilpatrick, ikilpatrick@chromium.org, w3cid 73001
+
+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
urlPrefix: https://html.spec.whatwg.org/multipage/workers.html; type: dfn;
- text: web workers
urlPrefix: #dom-workerglobalscope-;
text: self
urlPrefix: https://html.spec.whatwg.org/multipage/webappapis.html; type: dfn;
- text: api base url
- text: api url character encoding
- text: browsing context
- text: code entry-point
- text: creation url
text: document environment
- text: entry settings object
- text: environment settings object
- text: event loop
- text: fetch a module script tree
- text: global object
- text: https state
- text: incumbent settings object
+ text: event loop processing model
text: microtask queue
- text: module script
- text: realm execution context
- text: responsible browsing context
- text: responsible document
- text: responsible event loop
- text: run a module script
- text: script execution environment
text: task queues
-urlPrefix: https://html.spec.whatwg.org/multipage/infrastructure.html; type: dfn;
- text: cors setting attribute
- text: in parallel
- text: javascript global environment
- urlPrefix: #js-;
- text: syntaxerror;
- url: resolve-a-url; text: resolve;
-urlPrefix: https://www.w3.org/2001/tag/doc/promises-guide; type: dfn;
- text: a new promise
+ text: discarded; url: a-browsing-context-is-discarded
+urlPrefix: https://w3c.github.io/webappsec-csp/#; type: dfn;
+ text: initialize a global object's CSP list; url: initialize-global-object-csp
urlPrefix: http://www.ecma-international.org/ecma-262/6.0/#sec-; type: dfn;
text: Construct
text: InitializeHostDefinedRealm
@@ -85,8 +92,9 @@ Worklets are similar to web workers however they:
parallelism.
- Are not event API based. Instead classes are registered on the global scope, whose methods are to
be invoked by the user agent.
- - Have a reduced API surface on the javascript global environment (global scope).
- - Have a lifetime tied to running a method or set of methods on a class.
+ - Have a reduced API surface on the global scope.
+ - Have a lifetime for the global scope which is defined by subsequent specifications or user
+ agents. They aren't tied to the lifetime of the document.
As worklets have a relatively high overhead, they should be used sparingly. Due to this worklets are
expected to be shared between separate scripts. This is similar to the document environment.
@@ -94,28 +102,49 @@ expected to be shared between separate scripts. This is similar to the docume
Code Idempotency {#code-idempotency}
------------------------------------
-This section is not normative.
-
-Multiple instances of {{WorkletGlobalScope}} can be created for each {{Worklet}} that they belong
-to. User agents may choose to do this in order to parallelize work over multiple threads, or to move
-work between threads as required.
+Some specifications which use worklets ([[css-paint-api-1]]), allow user agents to parallelize work
+over multiple threads, or to move work between threads as required.
-Additionally different user agents may invoke a method on a class in a different order to other user
-agents.
+In these specifications user agents might invoke methods on a class in a different order to other
+user agents.
As a result of this, to prevent this compatibility risk between user agents, authors who register
-classes on the global scope should make their code idempotent. That is, a method or set of methods
-on a class should produce the same output given a particular input.
+classes on the global scope using these APIs, should make their code idempotent. That is, a method
+or set of methods on a class should produce the same output given a particular input.
+
+The following techniques are used in order to encourage authors to write code in an idempotent way:
-The following techniques should be used in order to encourage authors to write code in an idempotent
-way:
- No reference to the global object, e.g. self on a {{DedicatedWorkerGlobalScope}}.
+
- Code is loaded as a module script which resulting in the code being executed in strict
mode code without a shared this. This prevents two different module scripts sharing
- state be referencing shared objects on the global scope.
- - User agents may choose to always have at least two {{WorkletGlobalScope}}s per {{Worklet}} and
- randomly assign a method or set of methods on a class to a particular global scope.
- - User agents may create and destroy {{WorkletGlobalScope}}s at any time.
+ state by referencing shared objects on the global scope.
+
+ - These specifications must require user agents to always have at least two {{WorkletGlobalScope}}s
+ per {{Worklet}} and randomly assign a method or set of methods on a class to a particular
+ global scope. These specifications can provide an opt-out under memory constraints.
+
+ - User agents can create and destroy {{WorkletGlobalScope}}s at any time for these specifications.
+
+Speculative Evaluation {#speculative-evaluation}
+------------------------------------------------
+
+Some specifications which use worklets ([[css-paint-api-1]]) may invoke methods on a class based on
+the state of the user agent. To increase the concurrency between threads, a user agent may invoke a
+method speculatively, based on potential future states.
+
+In these specifications user agents might invoke methods on a class at any time, and with any
+arguments, not just ones corresponding to the current state of the user agent. The results of such
+speculative evaluations are not displayed immediately, but may be cached for use if the user agent
+state matches the speculated state. This may increase the concurrency between the user agent and
+worklet threads.
+
+As a result of this, to prevent this compatibility risk between user agents, authors who register
+classes on the global scope using these APIs, should make their code stateless. That is, the only
+effect of invoking a method should be its result, not any side-effects such as updating mutable
+state.
+
+The same techniques which encourage code idempotence also encourage authors to write stateless code.
Infrastructure {#infrastructure}
================================
@@ -127,13 +156,27 @@ The {{WorkletGlobalScope}} object provides a worklet global scope whi
global execution context of a {{Worklet}}.
-A {{WorkletGlobalScope}} has an associated environment settings object
-settings object.
+Each {{WorkletGlobalScope}} has an assocated owner
+document.
+It is initially null and set inside the create a WorkletGlobalScope algorithm.
+
+Whenever a {{Document}} object is discarded, each {{WorkletGlobalScope}} whose owner
+document is that {{Document}} object, should clear its owner document.
+
+Each {{WorkletGlobalScope}} has an associated environment settings object.
+
+Each {{WorkletGlobalScope}} has an associated module map. It is a
+module map, initially empty.
+
+Each {{WorkletGlobalScope}} has a worklet global scope execution environment. This
+execution environment may be parallel (i.e. it may be on a separate thread, process, or other
+equivalent construct), or it may live on the same thread or process as the {{Worklet}} object it
+belongs to. Which thread or process it lives on is decided by the user agent.
Note:
The {{WorkletGlobalScope}} has a limited global scope when compared to a
@@ -141,209 +184,367 @@ Note:
{{WorkletGlobalScope}} with registerAClass methods which
will allow authors to register classes for the user agent create and invoke methods on.
+When asked to report an exception, do nothing instead, or optionally report the exception to
+a developer console.
+
+Issue(whatwg/html#2611): HTML's report an exception needs updating to work with
+ non-EventTarget global objects.
+
### The event loop ### {#the-event-loop}
Each {{WorkletGlobalScope}} object has a distinct event loop. This event loop has no
-associated browsing context, and only its microtask queue is used (all other task
-queues are not used). The event loop is created by the create a
+associated browsing context. The event loop is created by the create a
WorkletGlobalScope algorithm.
+The event loop is run on the worklet global scope execution environment defined above.
+
+It is expected that only tasks associated {{Worklet/addModule()}}, the user agent invoking author
+defined callbacks, and microtasks will use this event loop.
+
+Note:
+ Even through the event loop processing model specifies that it loops continually,
+ practically implementations aren't expected to do this. The microtask queue is emptied
+ while invoking callback functions provided by the author.
+
### Creating a WorkletGlobalScope ### {#creating-a-workletglobalscope}
-When a user agent is to create a WorkletGlobalScope, for a given |workletGlobalScopeType|
-and |worklet|, it must run the following steps:
+
+When a user agent is to create a WorkletGlobalScope, given |workletGlobalScopeType|,
+|moduleResponsesMap|, and |outsideSettings|, it must run the following steps:
+
+ 1. Let |agent| be the result of [=obtain a worklet agent|obtaining a worklet agent=] given
+ |outsideSettings|. This agent corresponds to the a worklet global scope execution
+ environment. Run the rest of these steps in that context.
- 1. Call the JavaScript InitializeHostDefinedRealm 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 set up a worklet environment settings object
+ given |realmExecutionContext|, and |outsideSettings|.
- - Do not obtain any source texts for scripts or modules.
+ 4. Set |workletGlobalScope|'s owner document to |outsideSettings|'s responsible
+ document.
- 2. Let |settingsObject| be the result of set up a worklet environment settings object
- with |realmExecutionContext|.
+ 5. Invoke the initialize a global object's CSP list algorithm given |workletGlobalScope|.
- 2. Associate the |settingsObject| with |workletGlobalScope|.
+ 6. For each |entry| in the given |moduleResponsesMap| (in insertion order), run the following
+ substeps:
+
+ 1. Let |moduleURLRecord| be |entry|'s key.
- 3. For each |resolvedModuleURL| in the given |worklet|'s worklet's resolved module URLs,
- run the following substeps:
- 1. Let |script| be the result of fetch a module script tree given
- |resolvedModuleURL|, "anonymous" for the CORS setting attribute, and
- |settingsObject|.
+ 2. Let |script| be the result of fetch a worklet script given |moduleURLRecord|,
+ |moduleResponsesMap|, |outsideSettings|, and |insideSettings| when
+ it asynchronously completes.
+
+ 3. Run a module script given |script|.
- Issue: Decide if "use-credientials" should be valid for worklets.
+ Note: Fetch a worklet script won't actually perform a network request as it will hit
+ the worklet's module responses map. It also won't have a parsing error as at this
+ point it should have successfully been parsed by another worklet global scope. I.e.
+ |script| should never be null here.
- 2. Run a module script given |script|.
+ 6. Run the responsible event loop specified by |insideSettings|.
+
### Script settings for worklets ### {#script-settings-for-worklets}
-When a user agent is to set up a worklet environment settings object, given a
-|executionContext|, it must run the following steps:
- 1. Let |inheritedResponsibleBrowsingContext| be the responsible browsing context
- specified by the incumbent settings object.
+
Issue: Merge this with https://html.spec.whatwg.org/multipage/workers.html#set-up-a-worker-environment-settings-object
Worklet {#worklet-section}
--------------------------
-The {{Worklet}} object provides the capability to import module scripts into its associated
+The {{Worklet}} object provides the capability to add module scripts into its associated
{{WorkletGlobalScope}}s. The user agent can then create classes registered on the
{{WorkletGlobalScope}}s and invoke their methods.
-A {{Worklet}} has a list of the worklet's WorkletGlobalScopes. Initially this list is empty;
-it is populated when the user agent chooses to create its {{WorkletGlobalScope}}.
+A {{Worklet}} has a worklet global scope type. This is used for creating new
+{{WorkletGlobalScope}} and the type must inherit from {{WorkletGlobalScope}}.
-A {{Worklet}} has a list of the worklet's resolved module URLs. Initially this list is empty; it is
-populated when module scripts resolved.
+Note: As an example the worklet global scope type might be a {{PaintWorkletGlobalScope}}.
-When the import(moduleURL) method is called on a {{Worklet}} object,
-the user agent must run the following steps:
- 1. Let |promise| be a new promise.
+A {{Worklet}} has a list of the worklet's WorkletGlobalScopes. Initially this list
+is empty; it is populated when the user agent chooses to create its {{WorkletGlobalScope}}.
- 2. Run the following steps in parallel:
- 1. If there are no arguments, return without doing anything. Abort these steps.
+A {{Worklet}} has a worklet destination type. This is used for setting the destination requests from fetch a module worker script graph.
- 2. Let |resolvedModuleURL| be the result of resolving the |moduleURL| relative to the
- API base URL specified by the entry settings object when the method was
- invoked.
+A {{Worklet}} has a module responses map. This is a ordered map of module URLs to values
+that are a fetch responses. The map's entries are ordered based on their insertion order.
+Access to this map should be thread-safe.
- 3. If this fails, reject |promise| with a SyntaxError exception and abort these steps.
+The module responses map exists to ensure that {{WorkletGlobalScope}}s created at different
+times contain the same set of script source text and have the same behaviour. The creation of
+additional {{WorkletGlobalScope}}s should be transparent to the author.
- 4. Add |resolvedModuleURL| to the list of worklet's resolved module URLs.
+
+ Practically user agents aren't expected to implement the following algorithm using a
+ thread-safe map. Instead when {{Worklet/addModule()}} is called user agents can fetch the module
+ graph on the main thread, and send the fetched sources (the data contained in the module
+ responses map) to each thread which has a {{WorkletGlobalScope}}.
- 5. For each {{WorkletGlobalScope}} in the worklet's WorkletGlobalScopes, run these
- substeps:
- 1. Let |settings| be the {{WorkletGlobalScope}}'s associated
- environment settings object.
+ If the user agent wishes to create a new {{WorkletGlobalScope}} it can simply sent the list of
+ all fetched sources from the main thread to the thread which owns the {{WorkletGlobalScope}}.
+
- 2. Let |script| be the result of fetch a module script tree given
- |resolvedModuleURL|, "anonymous" for the CORS setting attribute, and |settings|.
+A pending tasks struct is a struct consisting of:
+ - A counter.
+This is used by the algorithms below.
- Issue: Decide if "use-credientials" should be valid for worklets.
+
+When the addModule(|moduleURL|, |options|) method is called on a
+{{Worklet}} object, the user agent must run the following steps:
+ 1. Let |promise| be a new promise.
- 3. Run a module script given |script|.
+ 2. Let |worklet| be this {{Worklet}}.
- 6. If all the steps above succeeded (in particular, if all of the scripts parsed and
- loaded into the global scopes), resolve |promise|
- Otherwise, reject |promise|.
+ 3. Let |outsideSettings| be the relevant settings object of this.
- 3. Return |promise|.
+ 4. Let |moduleURLRecord| be the result of parsing the |moduleURL| argument relative to
+ |outsideSettings|.
-Issue(w3c/css-houdini-drafts#51): What should happen when a script throws an exception while executing for the first time?
+ 5. If |moduleURLRecord| is failure, then reject promise with a "{{SyntaxError}}"
+ {{DOMException}} and return |promise|.
-Issue(w3c/css-houdini-drafts#47): Need ability to load code into {{WorkletGlobalScope}} declaratively.
+ 6. Return |promise|, and then continue running this algorithm in parallel.
-Issue: TODO write initialization for Worklet.
+ 7. Let |credentialOptions| be the {{WorkletOptions/credentials}} member of |options|.
-Lifetime of the Worklet {#lifetime-of-the-worklet}
---------------------------------------------------
+ 8. Let |moduleResponsesMap| be |worklet|'s module responses map.
-The lifetime of a {{Worklet}} is tied to the object it belongs to, for example the {{Window}}.
+ 9. Let |workletGlobalScopeType| be |worklet|'s worklet global scope type.
-The lifetime of a {{WorkletGlobalScope}} is tied to the execution lifetime of a method or set of
-methods on a class, not to the lifetime of the {{Worklet}} object.
+ 10. Let |destination| be |worklet|'s worklet destination type.
-The user agent may terminate a {{WorkletGlobalScope}} at any time it has no callbacks to
-handle or detects abnormal operation such as infinite loops and callbacks exceeding imposed time
-limits.
+ 11. If the worklet's WorkletGlobalScopes is empty, run the following steps:
-Issue(w3c/css-houdini-drafts#53): Worth adding dispose to classes to allow clean-up?
+ 1. Create a WorkletGlobalScope given |workletGlobalScopeType|, |moduleResponsesMap|,
+ and |outsideSettings|.
-Processing Model {#processing-model}
-------------------------------------
+ 2. Add the {{WorkletGlobalScope}} to worklet's WorkletGlobalScopes.
-Issue: Split this up.
+ Depending on the type of worklet the user agent may create additional
+ {{WorkletGlobalScope}}s at this time.
-The invoke a method on a class inside a Worklet has two hooks for algorithm steps that may be customized by any callers:
- - lookup a class instance on a worklet global scope
+ Note: Specifically the [[css-paint-api-1]] allows for multiple global scopes, while the
+ [[webaudio]] API does not.
-When a user agent is to invoke a method on a class inside a Worklet given a
-|methodPropertyKey|, |workletGlobalScopeType|, some |arguments|, it must run the following steps:
+ Wait for this step to complete before continuing.
- 1. Let |workletGlobalScope| be a {{WorkletGlobalScope}} from the list of the worklet's {{WorkletGlobalScope}}s.
+ 12. Let |pendingTaskStruct| be a new pending tasks struct with counter initialized to the length of worklet's
+ WorkletGlobalScopes.
- If none exist or a new {{WorkletGlobalScope}} is required, the user agent must run the following substeps:
- 1. Let |workletGlobalScope| be the result of Create a WorkletGlobalScope, given |workletGlobalScopeType| and |worklet|.
- 2. Add |workletGlobalScope| to the |worklet|'s list of the worklet's {{WorkletGlobalScope}}s.
+ 13. For each |workletGlobalScope| in the worklet's WorkletGlobalScopes, queue a
+ task on the |workletGlobalScope| to fetch and invoke a worklet script given
+ |workletGlobalScope|, |moduleURLRecord|, |destination|, |moduleResponsesMap|,
+ |credentialOptions|, |outsideSettings|, |pendingTaskStruct|, and |promise|.
- 2. Let |classInstance| be the result of lookup a class instance on a worklet global scope given |workletGlobalScope|.
- 3. Let |result| be the result of Invoke(O=|classInstance|, P=|methodPropertyKey|, Arguments=|arguments|).
- 4. Return |result|.
+ Note: The rejecting and resolving of the |promise| occurs within the fetch and invoke a
+ worklet script algorithm.
+
-Note: It is up to the user agent to select a appropriate {{WorkletGlobalScope}} to invoke the method in.
+
+When the user agent is to fetch and invoke a worklet script given |workletGlobalScope|,
+|moduleURLRecord|, |destination|, |moduleResponsesMap|, |credentialOptions|, |outsideSettings|,
+|pendingTaskStruct|, and |promise|, the user agent must run the following steps:
-Rendering Worklet {#rendering-worklet}
---------------------------------------
+ Note: This algorithm is to be run within the worklet global scope execution environment.
-The {{renderWorklet}} attribute allows access to the {{Worklet}} responsible for all the classes
-which are related to rendering.
+ 1. Let |insideSettings| be the |workletGlobalScope|'s associated environment settings
+ object.
-
+ 2. Let |script| by the result of fetch a worklet script given |moduleURLRecord|,
+ |destination|, |moduleResponsesMap|, |credentialOptions|, |outsideSettings|, and
+ |insideSettings| when it asynchronously completes.
-The {{RenderWorkletGlobalScope}} is the global execution context of the {{renderWorklet}}.
+ 3. If |script| is null, then queue a task on |outsideSettings|'s responsible event
+ loop to run these steps:
-
+ 1. If |pendingTaskStruct|'s counter is not -1, then
+ run these steps:
+
+ 1. Set |pendingTaskStruct|'s counter to -1.
+
+ 2. Reject |promise| with an "{{AbortError}}" {{DOMException}}.
+
+ 4. If |script|'s error to rethrow is not null, then queue a task on
+ |outsideSettings|'s responsible event loop given |script|'s error to rethrow
+ to run these steps:
+
+ 1. If |pendingTaskStruct|'s counter is not -1, then
+ run these steps:
+
+ 1. Set |pendingTaskStruct|'s counter to -1.
+
+ 2. Reject |promise| with error to rethrow.
+
+ 5. Run a module script given |script|.
+
+ 6. Queue a task on |outsideSettings|'s responsible event loop to run these steps:
+
+ 1. If |pendingTaskStruct|'s counter is not -1, then
+ run these steps:
+
+ 1. Decrement |pendingTaskStruct|'s counter by
+ 1.
+
+ 2. If |pendingTaskStruct|'s counter is 0, then
+ resolve |promise|.
+
+
+
+When the user agent is to fetch a worklet script given |moduleURLRecord|, |destination|,
+|moduleResponsesMap|, |credentialOptions|, |outsideSettings|, and |insideSettings|, the user agent
+must run the following steps:
+
+Note: This algorithm is to be run within the worklet global scope execution environment.
+
+1. Fetch a module worker script graph given |moduleURLRecord|, |outsideSettings|,
+ |destination|, |credentialOptions|, and |insideSettings|.
+
+ To perform the fetch given |request|, perform the following steps:
+
+ 1. Let |cache| be the |moduleResponsesMap|.
+
+ 2. Let |url| be |request|'s url.
+
+ 3. If |cache| contains an entry with key |url| whose value is "fetching", wait until that
+ entry's value changes, then proceed to the next step.
+
+ 4. If |cache| contains an entry with key |url|, asynchronously complete this algorithm with
+ that entry's value, and abort these steps.
+
+ 5. Create an entry in |cache| with key |url| and value "fetching".
+
+ 6. Fetch |request|.
+
+ 7. Let |response| be the result of fetch when it asynchronously completes.
+
+ 8. Set the value of the entry in |cache| whose key is |url| to |response|, and
+ asynchronously complete this algorithm with |response|.
+
+2. Return the result of fetch a module worker script graph when it asynchronously completes.
+
+Note: Specifically, if a script fails to parse or fails to load over the network, it will reject the
+ promise. If the script throws an error while first evaluating the promise it will resolve as a
+ classes may have been registered correctly.
+
+
+ When an author adds code into a {{Worklet}} the code may run against multiple
+ {{WorkletGlobalScope}}s, for example:
+
+ // script.js
+ console.log('Hello from a WorkletGlobalScope!');
+
+
+ Behind the scenes the user agent may load the script.js
+ into 4 global scopes, in which case the debugging tools for the user agent would print:
+
+ [paintWorklet#1] Hello from a WorkletGlobalScope!
+ [paintWorklet#4] Hello from a WorkletGlobalScope!
+ [paintWorklet#2] Hello from a WorkletGlobalScope!
+ [paintWorklet#3] Hello from a WorkletGlobalScope!
+
+
+ If the user agent decided to kill and restart a {{WorkletGlobalScope}} number 3 in this example,
+ it would print [paintWorklet#3] Hello from a
+ WorkletGlobalScope! again in the debugging tools when this occurs.
+
+
+
+Issue(w3c/css-houdini-drafts#47): Need ability to load code into a {{WorkletGlobalScope}}
+ declaratively.
+
+Lifetime of the Worklet {#lifetime-of-the-worklet}
+--------------------------------------------------
+
+The lifetime of a {{Worklet}} is tied to the object it belongs to, for example the {{Window}}.
+
+The lifetime of a {{WorkletGlobalScope}} should be defined by subsequent specifications which
+inherit from {{WorkletGlobalScope}}.
+
+Subsequent specifications may define that a {{WorkletGlobalScope}} can be terminated at any
+time particularly if there are no pending operations, or detects abnormal operation such as infinite
+loops and callbacks exceeding imposed time limits.
+
+Security Considerations {#security-considerations}
+==================================================
+
+Issue(w3c/css-houdini-drafts#92): Need to decide if to allow worklets for unsecure context, etc.
Examples {#examples}
====================
@@ -352,33 +553,34 @@ Examples {#examples}
For these examples we'll use a fake worklet on window.
-
-When the registerAnArbitaryClass(type, classConstructor) method is called,
-the user agent will add the classConstructor of type to the map of registered class constructors.
+ Each {{FakeWorkletGlobalScope}} has a map of the registered class constructors map.
+ When the
+ registerAnArbitaryClass(|type|, |classConstructor|) method is called, the user agent will add
+ the |classConstructor| of |type| to the map of registered class constructors map.
+
Loading scripts into a worklet. {#example-single}
-------------------------------------------------
-window.fakeWorklet1.import('script1.js');
-window.fakeWorklet1.import('script2.js');
+window.fakeWorklet1.addModule('script1.js');
+window.fakeWorklet1.addModule('script2.js');
// Assuming no other calls to fakeWorklet1 valid script loading orderings are:
// 1. 'script1.js', 'script2.js'
@@ -390,8 +592,8 @@ Loading scripts into multiple worklets. {#example-multiple}
Promise.all([
- window.fakeWorklet1.import('script1.js'),
- window.fakeWorklet2.import('script2.js')
+ window.fakeWorklet1.addModule('script1.js'),
+ window.fakeWorklet2.addModule('script2.js')
]).then(function() {
// Both scripts now have loaded code, can do a task which relies on this.
});
@@ -402,16 +604,27 @@ Create a registered class and invoke a method. {#example-class}
// Inside FakeWorkletGlobalScope
-registerAnArbitaryClass('foo', class FooClass {
+registerAnArbitaryClass('key', class FooClass {
process(arg) {
return !arg;
}
});
-When the user agent wants to invoke process on an instance of class with key foo it will:
- 1. Call invoke a method on a class inside a Worklet given process as the
- |methodPropertyKey|, FakeWorkletGlobalScope as |workletGlobalScopeType| and some |arguments| with the following options:
- - To lookup a class instance on a worklet global scope given a |workletGlobalScope|, the user agent will:
- 1. Let |classConstructor| be the result of looking up foo on the |workletGlobalScope|'s map of registered class constructors.
- 2. Return the result of Construct(|classConstructor|).
+As an example, if the user agent wants to invoke "process" on a new class instance, the user agent
+could follow the following steps:
+ 1. Let |workletGlobalScope| be a {{FakeWorkletGlobalScope}} from the list of worklet's
+ WorkletGlobalScopes from the fake {{Worklet}}.
+
+ The user agent may also create a WorkletGlobalScope given the fake
+ {{Worklet}} and use that.
+
+ 2. Let |classCtor| be the result of performing a lookup in registered class constructors
+ map with "key" as the key.
+
+ 3. Let |classInstance| be the result of Construct(|classCtor|).
+
+ 4. Let |result| be the result of Invoke(O=|classInstance|, P="process",
+ Arguments=["true"]).
+
+ 5. Return |result|.