Skip to content

Commit

Permalink
[css-paint-api] Move creating classes to "draw a paint image" time.
Browse files Browse the repository at this point in the history
General large cleanup.
  • Loading branch information
bfgeek committed Feb 1, 2016
1 parent e0b5957 commit d4a272e
Showing 1 changed file with 152 additions and 77 deletions.
229 changes: 152 additions & 77 deletions css-paint-api/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -38,39 +38,53 @@ urlPrefix: http://www.ecma-international.org/ecma-262/6.0/#sec-; type: dfn;
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.
The paint stage of CSS is responsible for painting the background, content and highlight of an
box based on that box'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.
This specification describes an API which allows developers to paint a part of an box in
response to geometry / computed style changes with an additional <<image>> function.

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).
Note: In a future version of the spec, support may be added for defining the clip, global alpha,
filter on a portion of an box (for example on the background layers).

Paint Invalidation {#paint-invalidation}
========================================

A <a>document</a> has an associated <b>paint name to input properties map</b>. Initially it is empty and is populated when {{registerPaint(name, paintCtor)}} is called.
A <a>document</a> has an associated <dfn>paint name to input properties map</dfn>. Initially it is
empty and is populated when {{registerPaint(name, paintCtor)}} is called.

Each <<paint()>> function for a fragment has an associated <b>paint valid flag</b>. It may be either <dfn>paint-valid</dfn> or <dfn>paint-invalid</dfn>. It is initially set to <a>paint-invalid</a>.
Each <<paint()>> function for a box has an associated <dfn>paint valid flag</dfn>. It may be either
<dfn>paint-valid</dfn> or <dfn>paint-invalid</dfn>. It is initially set to <a>paint-invalid</a>.

When the geometry (as determined by layout) of a <var>fragment</var> changes, each <<paint()>> function's <b>paint valid flag</b> should be set to <a>paint-invalid</a>.
When the geometry (as determined by layout) of a |box| changes, each <<paint()>> function's <a>paint
valid flag</a> should be set to <a>paint-invalid</a>.

When the computed style for an element changes, the user agent must run the following steps:
1. For each <<paint()>> function on the element, perform the following substeps:
1. Let <var>name</var> be the first argument of the <<paint()>> function.
2. Let <var>inputProperties</var> be the result of performing a lookup in <b>paint name to input properties map</b> with |name| as the key.
3. For each <var>property</var> in |inputProperties|, if the |property|'s <a>computed value</a> has changed, set the <b>paint valid flag</b> on the |fragment| to <a>paint-invalid</a>.
When the computed style for an |box| changes, the user agent must run the following steps:
1. For each <<paint()>> function on the |box|, perform the following substeps:
1. Let |paintFunction| be the current <<paint()>> function on the |box|.

[[#drawing-an-image]] results in the <b>paint valid flag</b> for a <<paint()>> function to be set to <a>paint-valid</a>.
2. Let |name| be the first argument of the <<paint()>> function.

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.
3. Let |inputProperties| be the result of lookuping up |name| on <a>paint name to input
properties map</a>.

4. For each |property| in |inputProperties|, if the |property|'s <a>computed value</a> has
changed, set the <a>paint valid flag</a> on the |paintFunction| to <a>paint-invalid</a>.

Performing <a>draw a paint image</a> results in the <a>paint valid flag</a> for a <<paint()>>
function on a box to be set to <a>paint-valid</a>.

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.

Paint Worklet {#paint-worklet}
==============================

The {{paintWorklet}} attribute allows access to the {{Worklet}} responsible for all the classes
which are related to painting.

The {{paintWorklet}}'s <b>worklet global scope type</b> is {{PaintWorkletGlobalScope}}.
The {{paintWorklet}}'s <a>worklet global scope type</a> is {{PaintWorkletGlobalScope}}.

<pre class='idl'>
partial interface Window {
Expand All @@ -88,11 +102,6 @@ interface PaintWorkletGlobalScope : WorkletGlobalScope {
};
</pre>

Registering Custom Paint {#registering-custom-paint}
====================================================

The {{PaintWorkletGlobalScope}} has a map of <b>name to paint instance map</b>. Initially this map is empty; it is populated when {{registerPaint(name, paintCtor)}} is called.

<div class='note'>
Note: This is how the class should look.
<pre class='idl'>
Expand All @@ -103,25 +112,53 @@ The {{PaintWorkletGlobalScope}} has a map of <b>name to paint instance map</b>.
</pre>
</div>

When the <dfn method for=PaintWorkletGlobalScope>registerPaint(<var>name</var>, <var>paintCtor</var>)</dfn> method is called, the user agent <em>must</em> run the following steps:
1. If the |name| is not a valid <<ident>>, <a>throw</a> a <a>NotSupportedError</a> and abort all these steps.
2. If the |name| exists as a key in the <b>name to paint instance map</b>, <a>throw</a> a <a>NotSupportedError</a> and abort all these steps.
3. If the result of <a>IsConstructor</a>(argument=|paintCtor|) is false, <a>throw</a> a <a>NotSupportedError</a> and abort all these steps.
4. Let <var>prototype</var> be the result of <a>Get</a>(O=|paintCtor|, P="prototype").
5. If the result of <a>IsCallable</a>(argument=<a>Get</a>(O=|prototype|, P="paint")) is false, <a>throw</a> a <a>NotSupportedError</a> and abort all these steps.
6. Let <var>inputProperties</var> be the result of <a>Get</a>(O=|paintCtor|, P="inputProperties").
7. If the result of <a>IsArray</a>(argument=|inputProperties|) is false, <a>throw</a> a <a>NotSupportedError</a> and abort all these steps.
8. Add the key-value pair (|name| - |inputProperties|) to the <b>paint name to input properties map</b> of the associated <a>document</a>.
9. Let <var>paintInstance</var> be the result of <a>Construct</a>(|paintCtor|).
10. Add the key-value pair (|name| - |paintInstance|) to the <b>name to paint instance map</b> of the {{PaintWorkletGlobalScope}}.
Registering Custom Paint {#registering-custom-paint}
====================================================

The {{PaintWorkletGlobalScope}} has a <dfn>paint name to constructor map</dfn>. Initially this map
is empty; it is populated when {{registerPaint(name, paintCtor)}} is called.

The {{PaintWorkletGlobalScope}} has a <dfn>paint name to instance map</dfn>. Initially this map is
empty; it is populated when <a>draw a paint image</a> is invoked by the user agent.

Instances of paint classes in the <a>paint name to instance map</a> may be disposed and removed from
the map by the user agent at any time. This may be done when a <<paint()>> function no longer is
used, or the user agent needs to reclaim memory.

When the <dfn method for=PaintWorkletGlobalScope>registerPaint(|name|, |paintCtor|)</dfn> method is
called, the user agent <em>must</em> run the following steps:
1. If the |name| is not a valid <<ident>>, <a>throw</a> a <a>NotSupportedError</a> and abort all
these steps.

2. If the |name| exists as a key in the <a>paint name to constructor map</a>, <a>throw</a> a
<a>NotSupportedError</a> and abort all these steps.

3. If the result of <a>IsConstructor</a>(argument=|paintCtor|) is false, <a>throw</a> a
<a>NotSupportedError</a> and abort all these steps.

4. Let |prototype| be the result of <a>Get</a>(O=|paintCtor|, P="prototype").

5. If the result of <a>IsCallable</a>(argument=<a>Get</a>(O=|prototype|, P="paint")) is false,
<a>throw</a> a <a>NotSupportedError</a> and abort all these steps.

6. Let |inputProperties| be the result of <a>Get</a>(O=|paintCtor|, P="inputProperties").

7. If the result of <a>IsArray</a>(argument=|inputProperties|) is false, <a>throw</a> a
<a>NotSupportedError</a> 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.
8. Add the key-value pair (|name| - |inputProperties|) to the <a>paint name to input properties
map</a> of the associated <a>document</a>.

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: The list of CSS properties provided by the input properties getter can either be custom or
native CSS properties.

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.
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 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.

Issue(w3c/css-houdini-drafts#31): Allow author to specify the intrinsic size.

Expand Down Expand Up @@ -190,6 +227,29 @@ When the user agent is to <dfn>create a PaintRenderingContext2D object</dfn> for
Drawing an image {#drawing-an-image}
====================================

If a <<paint()>> function for a fragment is <a>paint-invalid</a> and the fragment is within the
visual viewport, then user agent <em>must</em> <a>draw a paint image</a> for the current frame. The
user agent <em>may not</em> defer the <a>draw a paint image</a> operation until a subsequent frame.

The <a>draw a paint image</a> function should be invoked by the user agent during the <a>object size
negotiation</a> algorithm which is responsible for rendering an <<image>>.

Note: The user agent may choose to <a>draw a paint image</a> for <<paint()>> functions not within
the visual viewport.

<div class="example">
If an author updates a style inside a <code>requestAnimationFrame</code>, e.g.
<pre class='lang-javascript'>
requestAnimationFrame(function() {
element.styleMap.set('--custom-prop-invalidates-paint', 42);
});
</pre>
And the <code>element</code> is inside the visual viewport, the user agent <em>msut</em> <a>draw
a paint image</a> and display the result on the current frame.
</div>

The {{Geometry}} object represents the geometry of the image that the author should draw.

<pre class='idl'>
[Exposed=Worklet]
interface Geometry {
Expand All @@ -198,69 +258,84 @@ interface Geometry {
};
</pre>

If a <<paint()>> function for a fragment is <a>paint-invalid</a> and the fragment is within the visual viewport,
then user agent <em>must</em> <a>draw an image</a> for the current frame.
When the user agent wants to <dfn>draw a paint image</dfn> of a <<paint()>> function for a |box|
into its appropriate stacking level (as defined by the property the CSS property it's associated
with), given it's |concreteObjectSize| (<a>concrete object size</a>) it <em>must</em> run the
following steps:
1. Let |paintFunction| be the current <<paint()>> function on the |box|.

Note: The user agent may choose to <a>draw an image</a> for <<paint()>> functions not within the visual viewport.
2. If the <a>paint valid flag</a> for the |paintFunction| is <a>paint-valid</a> the user agent
<em>may</em> use the drawn image from the previous invocation. If so it <em>may</em> abort
all these steps and use the previously drawn image.

When the user agent wants to <dfn>draw an image</dfn> of a <<paint()>> for a <var>fragment</var> into its appropriate stacking level (as defined by the property the CSS property it's associated with) it <em>must</em> run the following steps:
1. If the <b>paint valid flag</b> for the <<paint()>> function on the |fragment| is <a>paint-valid</a> the user agent <em>may</em> use the drawn image from the previous invocation.
If so it can abort all these steps.
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.

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. Let |name| be the first argument of the |paintFunction|.

2. Let <var>name</var> be the first argument of the <<paint()>> function.
4. Let |workletGlobalScope| be a {{PaintWorkletGlobalScope}} from the list of <a>worklet's
WorkletGlobalScopes</a> from the paint {{Worklet}}.

3. If no paint class was registered with |name|, the resulting image output will be an <a>invalid image</a> and the user agent must abort all these steps.
The user agent <em>may</em> also <a>create a WorkletGlobalScope</a> given the paint
{{Worklet}} and use that.

4. Let <var>inputProperties</var> be the result of looking up |name| on the associated <a>document</a>'s <b>paint name to input properties map</b>.
Note: The user agent <em>may</em> use any policy for which {{PaintWorkletGlobalScope}} to
select or create. It may use a single {{PaintWorkletGlobalScope}} or multiple and
randomly assign between them.

5. Let <var>styleMap</var> be a new {{StylePropertyMap}} populated with <em>only</em> the <a>computed value</a>'s for properties listed in |inputProperties|.
5. If no paint constructor exists on |workletGlobalScope|'s <a>paint name to constructor map</a>
with key |name|, the resulting image output will be an <a>invalid image</a> and the user
agent must abort all these steps.

6. Let <var>renderingContext</var> be the result of <a>create a PaintRenderingContext2D object</a> given:
- "width" - The width of the |fragment|.
- "height" - The height of the |fragment|.
6. Let |paintInstance| be the result of looking up |name| on |workletGlobalScope|'s <a>paint
name to instance map</a>. If |paintInstance| is null run the following substeps:
1. Let |paintCtor| be the result of looking up |name| on <a>paint name to constructor
map</a>.

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.
2. Let |paintInstance| be the result of <a>Construct</a>(|paintCtor|).

7. Let <var>geometry</var> be a new {{Geometry}} initialized to the width and height of the |fragment|.
3. Add the key-value pair (|name| - |paintInstance|) to the <a>paint name to instance
map</a> of the |workletGlobalScope|.

8. Let |workletGlobalScope| be a {{PaintWorkletGlobalScope}} from the list of <b>worklet's
WorkletGlobalScopes</b> from the paint {{Worklet}}.
7. Let |inputProperties| be the result of looking up |name| on the associated <a>document</a>'s
<a>paint name to input properties map</a>.

The user agent <em>may</em> also <a>create a WorkletGlobalScope</a> given the paint
{{Worklet}} and use that.
8. Let |styleMap| be a new {{StylePropertyMap}} populated with <em>only</em> the <a>computed
value</a>'s for properties listed in |inputProperties|.

Note: The user agent <em>may</em> use any policy for which {{PaintWorkletGlobalScope}} to
select or create.

9. Let |classInstance| be the result of performing a lookup in <b>name to paint instance map</b>
with |name| as the key.
9. Let |renderingContext| be the result of <a>create a PaintRenderingContext2D object</a> given:
- "width" - The width given by |concreteObjectSize|.
- "height" - The height given by |concreteObjectSize|.

10. Perform <a>Invoke</a>(O=|classInstance|, P="paint", Arguments=[|renderingContext|,
|geometry|, |styleMap|).
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.

11. The image output should be produced from the |renderingContext| given to the method.
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.

If an exception is <a>thrown</a> the resulting image output will be an <a>invalid image</a>.
10. Let |geometry| be a new {{Geometry}} initialized to the width and height defined by
|concreteObjectSize|.

12. Set the <b>paint valid flag</b> for the <<paint()>> function on the |fragment| to <a>paint-valid</a>.
11. Perform <a>Invoke</a>(O=|paintInstance|, P="paint", Arguments=[|renderingContext|,
|geometry|, |styleMap|).

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.
12. The image output is to be produced from the |renderingContext| given to the method.

Note: The contents of the image are not designed to be accessible. Authors should communicate any useful information through the standard accessibility APIs.
If an exception is <a>thrown</a> the resulting image output will be an <a>invalid image</a>.

Issue(w3c/css-houdini-drafts#24): Determine how to side-load images or other data.
13. Set the <a>paint valid flag</a> for the |paintFunction| to <a>paint-valid</a>.

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.
Note: The user agent <em>should</em> consider long running paint functions similar to long running
script in the main execution context. For example, they <em>should</em> show a "unresponsive
script" dialog or similar. In addition user agents <em>should</em> provide tooling within their
debugging tools to show authors how expensive their paint classes are.

Issue: Describe what happens if a callback doesn't hit a frame timing boundary. I.e. just renders a transparent image instead?
Note: The contents of the resulting image are not designed to be accessible. Authors <em>should</em>
communicate any useful information through the standard accessibility APIs.

Examples {#examples}
====================
Expand Down

0 comments on commit d4a272e

Please sign in to comment.