Skip to content

Commit

Permalink
Improve the run-to-completion principle. (#536)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* Identify which LockManager.

This can be reverted once
WICG/shared-storage#212 is merged and crawled.

---------

Co-authored-by: Martin Thomson <[email protected]>
Co-authored-by: Jan-Ivar Bruaroey <[email protected]>
  • Loading branch information
3 people authored Dec 17, 2024
1 parent 6c1568b commit c63b666
Showing 1 changed file with 48 additions and 19 deletions.
67 changes: 48 additions & 19 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -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
</pre>
<pre class='ignored-specs'>
spec: css21
Expand Down Expand Up @@ -1437,34 +1438,62 @@ to have bindings in other programming languages.

<h3 id="js-rtc">Preserve run-to-completion semantics</h3>

Don't modify data accessed via JavaScript APIs
while a JavaScript <a>event loop</a> 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,
<a>queue a task</a> 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=].

<div class="example">
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.

</div>

Data can update synchronously from the result of developer action.

<div class="example">
{{ChildNode/remove()|node.remove()}} changes the DOM synchronously
and is immediately observable.
</div>

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

<h3 id="js-gc">Don't expose garbage collection</h3>

Ensure your JavaScript Web APIs don't provide a way
Expand Down

0 comments on commit c63b666

Please sign in to comment.