From c63b6665187163721159518e0d1aed12598b3d64 Mon Sep 17 00:00:00 2001 From: Jeffrey Yasskin Date: Tue, 17 Dec 2024 15:22:47 -0800 Subject: [PATCH] Improve the run-to-completion principle. (#536) * Improve the run-to-completion principle. * "While an event loop is running" wasn't the right period to limit changes. * We have a list of exceptions to this principle. * Warn against letting an attribute change in the middle of a statement. * Apply Martin's suggestions. Co-authored-by: Martin Thomson * Remove a redundant sentence of emphasis on not changing attributes in the middle of a statement. * Be more general than `while true`. Co-authored-by: Martin Thomson * Move a positive statement to the top of the principle. * Remove irrelevant and incorrect claims. * It doesn't matter whether JS is a wrapper around code in another language. * JS doesn't always get to complete: users can kill it. * Apply changes resulting from Jan-Ivar's code review Co-authored-by: Jan-Ivar Bruaroey * Identify which LockManager. This can be reverted once https://github.com/WICG/shared-storage/pull/212 is merged and crawled. --------- Co-authored-by: Martin Thomson Co-authored-by: Jan-Ivar Bruaroey --- index.bs | 67 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/index.bs b/index.bs index 747c9c2c..3dcc0462 100644 --- a/index.bs +++ b/index.bs @@ -47,6 +47,7 @@ spec:webidl type:idl; text:long type:idl; text:short type:interface; text:double +spec:web-locks; type:interface; text:LockManager
 spec: css21
@@ -1437,34 +1438,62 @@ to have bindings in other programming languages.
 
 

Preserve run-to-completion semantics

-Don't modify data accessed via JavaScript APIs -while a JavaScript event loop is running. - -A JavaScript Web API is generally a wrapper around -a feature implemented in a lower-level language, -such as C++ or Rust. -Unlike those languages, -when using JavaScript developers can expect -that once a piece of code begins executing, -it will continue executing until it has completed. +If a change to state originates outside of the JavaScript execution context, +propagate that change to JavaScript between tasks, +for example by [[html#queuing-tasks|queuing a task]], +or as part of [=update the rendering=]. +Unlike lower-level languages +such as C++ or Rust, +JavaScript has historically acted as if +only one piece of code can execute at once. Because of that, JavaScript authors take for granted that the data available to a function won’t change unexpectedly while the function is running. -So if a JavaScript Web API exposes some piece of data, -such as an object property, -the user agent must not update that data -while a JavaScript task is running. -Instead, if the underlying data changes, -queue a task to modify the exposed version of the data. +Changes that are not the result of developer action +and changes that are asynchronously delivered +should not happen in the middle of other JavaScript, +including between [=microtasks=].
-If a JavaScript task has accessed the {{NavigatorOnline/onLine|navigator.onLine}} property, -and browser's online status changes, -the property won't be updated until the next task runs. +During synchronous execution (such as a `while` loop), +and after `await`ing an already-resolved `Promise`, +developers are *unlikely* to expect things like: + +* The DOM to update as a result of the HTML parser loading new content from the network +* {{HTMLImageElement/width|img.width}} to change as a result of loading image data from the network +* Buttons of a {{Gamepad}} to change state +* {{Element/scrollTop}} to change, even if scrolling can visually occur +* A synchronous method to act differently depending on asynchronous state changes. + For example, if {{LockManager}} had synchronous methods, + their behavior would depend on concurrent calls in other windows. + +These things aren't updated by the currently running script, +so they shouldn't change during the current task. +
+Data can update synchronously from the result of developer action. + +
+{{ChildNode/remove()|node.remove()}} changes the DOM synchronously +and is immediately observable. +
+ +A few kinds of situations justify violating this rule: + +* Observing the current time, + as in {{Date/now|Date.now()}} and {{Performance/now|performance.now()}}, + although note that it's also useful to present a consistent task-wide time + as in {{AnimationTimeline/currentTime|document.timeline.currentTime}}. +* Functions meant to help developers interrupt synchronous work, + as in the case of {{IdleDeadline/timeRemaining()|IdleDeadline.timeRemaining()}}. +* States meant to protect users from surprising UI changes, + like [=transient activation=]. + Note that {{UserActivation/isActive|navigator.userActivation.isActive}} + violates [[#attributes-like-data|the guidance that recommends a method for this case]]. +

Don't expose garbage collection

Ensure your JavaScript Web APIs don't provide a way