diff --git a/css-layout-api/Overview.bs b/css-layout-api/Overview.bs index 6074248e..aaf5b5af 100644 --- a/css-layout-api/Overview.bs +++ b/css-layout-api/Overview.bs @@ -16,7 +16,6 @@ Former Editor: Shane Stephens, shanestephens@google.com, w3cid 47691 Editor: Robert O'Callahan, robert@ocallahan.org Editor: Rossen Atanassov, rossen.atanassov@microsoft.com, w3cid 49885 Ignored Terms: LayoutWorklet -Ignored Terms: create a workletglobalscope - - - -
-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;
-    urlPrefix: #dom-workerglobalscope-;
-        text: self
-urlPrefix: https://html.spec.whatwg.org/multipage/webappapis.html; type: dfn;
-    text: document environment
-    text: event loop processing model
-    text: microtask queue
-    text: task queues
-    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
-    text: Invoke
-    text: strict mode code
-
- -Introduction {#intro} -===================== - -Motivations {#motivations} --------------------------- - -This section is not normative. - -Allowing extension points defined in the document environment is difficult, as rendering -engines would need to abandon previously held assumptions for what could happen in the middle of a -phase. - -For example, during the layout phase the rendering engine assumes that no DOM will be modified. - -Additionally defining extension points in the document environment would restrict rendering -engines to performing work in the same thread as the document environment. (Unless rendering -engines added complex, high-overhead infrastructure to allow thread-safe APIs in addition to thread -joining guarantees). - -The worklet is designed to allow such extension points in rendering engines, while keeping -guarantees which rendering engines rely currently on. - -Worklets are similar to web workers however they: - - Are thread-agnostic. That is, they are not defined to run on a particular thread. Rendering - engines may run them wherever they choose. - - Are able to have multiple duplicate instances of the global scope created for the purpose of - 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 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. - -Code Idempotency {#code-idempotency} ------------------------------------- - -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. - -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 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: - - - 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 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} -================================ - -The Global Scope {#the-global-scope} ------------------------------------- - -The {{WorkletGlobalScope}} object provides a worklet global scope which represents the -global execution context of a {{Worklet}}. - -
-[Exposed=Worklet]
-interface WorkletGlobalScope {
-};
-
- -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 - {{DedicatedWorkerGlobalScope}}. It is expected that other specifications will extend - {{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. 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, 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. - - 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. - - 3. Let |insideSettings| be the result of set up a worklet environment settings object - given |realmExecutionContext|, and |outsideSettings|. - - 4. Set |workletGlobalScope|'s owner document to |outsideSettings|'s responsible - document. - - 5. Invoke the initialize a global object's CSP list algorithm given |workletGlobalScope|. - - 6. For each |entry| in the given |moduleResponsesMap| (in insertion order), run the following - substeps: - - 1. Let |moduleURLRecord| be |entry|'s key. - - 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|. - - 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. - - 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|, and |outsideSettings|, it must run the following steps: - 1. Let |origin| be a unique opaque origin. - - 2. Let |inheritedAPIBaseURL| be |outsideSettings|'s API base URL. - - 3. Let |inheritedReferrerPolicy| be |outsideSettings|'s referrer policy. - - 4. Let |inheritedEmbedderPolicy| be |outsideSettings|'s embedder policy. - - 5. Let |realm| be the value of |executionContext|'s Realm component. - - 6. Let |workletGlobalScope| be |realm|'s global object. - - 7. Let |settingsObject| be a new environment settings object whose algorithms are defined - as follows: - - : The realm execution context - :: Return |executionContext|. - - : The module map. - :: Return |workletGlobalScope|'s module map. - - : The responsible document - :: Not applicable. - - : The API URL character encoding - :: Return UTF-8. - - : The API base URL - :: Return |inheritedAPIBaseURL|. - - : The origin - :: Return |origin|. - - : The referrer policy - :: Return |inheritedReferrerPolicy|. - - : The embedder policy - :: Return |inheritedEmbedderPolicy|. - - 8. Set |settingsObject|'s id to a new unique opaque string, - creation URL to |inheritedAPIBaseURL|, - top-level creation URL to null, - top-level origin to |outsideSettings|'s top-level origin, - target browsing context to null, and - active service worker to null. - - 9. Set |realm|'s \[[HostDefined]] field to |settingsObject|. - - 10. Return |settingsObject|. -
- -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 add module scripts into its associated -{{WorkletGlobalScope}}s. The user agent can then create classes registered on the -{{WorkletGlobalScope}}s and invoke their methods. - -
-[Exposed=Window]
-interface Worklet {
-    [NewObject] Promise<undefined> addModule(USVString moduleURL, optional WorkletOptions options = {});
-};
-
-dictionary WorkletOptions {
-    RequestCredentials credentials = "same-origin";
-};
-
- -A {{Worklet}} has a worklet global scope type. This is used for creating new -{{WorkletGlobalScope}} and the type must inherit from {{WorkletGlobalScope}}. - -Note: As an example the worklet global scope type might be a {{PaintWorkletGlobalScope}}. - -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 destination type. This is used for setting the destination requests from fetch a module worker script graph. - -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. - -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. - -
- 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}}. - - 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}}. -
- -A pending tasks struct is a struct consisting of: - - A counter. -This is used by the algorithms below. - -
-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. - - 2. Let |worklet| be this {{Worklet}}. - - 3. Let |outsideSettings| be the relevant settings object of this. - - 4. Let |moduleURLRecord| be the result of parsing the |moduleURL| argument relative to - |outsideSettings|. - - 5. If |moduleURLRecord| is failure, then reject promise with a "{{SyntaxError}}" - {{DOMException}} and return |promise|. - - 6. Return |promise|, and then continue running this algorithm in parallel. - - 7. Let |credentialOptions| be the {{WorkletOptions/credentials}} member of |options|. - - 8. Let |moduleResponsesMap| be |worklet|'s module responses map. - - 9. Let |workletGlobalScopeType| be |worklet|'s worklet global scope type. - - 10. Let |destination| be |worklet|'s worklet destination type. - - 11. If the worklet's WorkletGlobalScopes is empty, run the following steps: - - 1. Create a WorkletGlobalScope given |workletGlobalScopeType|, |moduleResponsesMap|, - and |outsideSettings|. - - 2. Add the {{WorkletGlobalScope}} to worklet's WorkletGlobalScopes. - - Depending on the type of worklet the user agent may create additional - {{WorkletGlobalScope}}s at this time. - - Note: Specifically the [[css-paint-api-1]] allows for multiple global scopes, while the - [[webaudio]] API does not. - - Wait for this step to complete before continuing. - - 12. Let |pendingTaskStruct| be a new pending tasks struct with counter initialized to the length of worklet's - WorkletGlobalScopes. - - 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|. - - Note: The rejecting and resolving of the |promise| occurs within the fetch and invoke a - worklet script algorithm. -
- -
-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: - - Note: This algorithm is to be run within the worklet global scope execution environment. - - 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. - - 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!');
-    
- -
-        // main.js
-        await CSS.paintWorklet.addModule('script.js');
-    
- - 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} -==================== - -This section is not normative. - -For these examples we'll use a fake worklet on window. - -
-
-    partial interface Window {
-      [SameObject] readonly attribute Worklet fakeWorklet1;
-      [SameObject] readonly attribute Worklet fakeWorklet2;
-    };
-    
- -
-    [Global=(Worklet,FakeWorklet),Exposed=FakeWorklet]
-    interface FakeWorkletGlobalScope : WorkletGlobalScope {
-        void registerAnArbitaryClass(DOMString type, Function classConstructor);
-    };
-    
- - 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.addModule('script1.js');
-window.fakeWorklet1.addModule('script2.js');
-
-// Assuming no other calls to fakeWorklet1 valid script loading orderings are:
-// 1. 'script1.js', 'script2.js'
-// 2. 'script2.js', 'script1.js'
-
- -Loading scripts into multiple worklets. {#example-multiple} ------------------------------------------------------------ - -
-Promise.all([
-    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.
-});
-
- -Create a registered class and invoke a method. {#example-class} ---------------------------------------------------------------- - -
-// Inside FakeWorkletGlobalScope
-registerAnArbitaryClass('key', class FooClass {
-    process(arg) {
-        return !arg;
-    }
-});
-
- -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|. + +