Git Product home page Git Product logo

display-locking's Introduction

Important Note

The work here has all been adopted in CSSWG or WHATWG as needed. This repo is archived for the historical record.

Display locking

Note that this feature should not be confused with the Screen Wake Lock API.

Introduction

Display Locking is an umbrella term of related features designed primarily to allow developers to increase performance of their sites. In addition, some of the features in the display locking project enable site behaviors that were not previously easy to accomplish (e.g. searchability in collapsed sections).

This document provides an overview of the features under the Display Locking projects with links that provide additional information.

Features

content-visibility

Summary

content-visibility is a CSS property designed to allow developers and browsers to easily scale to large amount of content and control when rendering work happens.

Status

The feature has been accepted by the CSS Working Group, and is a part of the css-contain-2 module.

The feature is implemented in Chromium M85.

Additional information

hidden=until-found and searchable details elements.

Summary

Leveraging content-visibility, we can also support searchable hidden content. We are applying this automatically to details elements, to make the contents of a details element available to find-in-page.

We are also adding a new attribute hidden=until-found to allow developers to create hidden, but searchable, content.

Status

The searchable details element feature is available in Chromium behind the --enable-blink-features=AutoExpandDetailsElement flag.

hidden=until-found is currently being implemented.

contain-intrinsic-size

Summary

contain-intrinsic-size is a CSS property that allows the developer to specify an intrinsic size to use when size-containment is specified. This enables the developers to specify “placeholder size” on content which is meant to be sized by intrinsic sizing, but has size containment applied to it.

Status

The feature is currently implemented and shipped in Chromium M83.

Additional information

renderpriority attribute

Summary

renderpriority (placeholder name) is an HTML attribute that indicates a request from the developer to keep the element and its subtree updated with a certain priority. The User Agent is then responsible for scheduling the updates using the specified priority as a strong hint for prioritizing the work.

Status

This feature is in active development.

Disclaimer

As the proposed features evolve, several competing API shapes might be considered at the same time, the decisions on particular behaviors might not be finalized, and some documentation may be out of date.

This document was last updated on May 28, 2020.

display-locking's People

Contributors

azu avatar bgirard avatar chrishtr avatar chrislloyd avatar dbaron avatar fergald avatar j9t avatar josepharhar avatar jxck avatar marcoscaceres avatar rakina avatar tabatkins avatar tomayac avatar vmpstr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

display-locking's Issues

Handle cases when locked subtree is too large to efficiently cache

Consider a locked element which is much larger than the viewport, or far offscreen. We should
have the ability to avoid locking too much memory to store the cached rendering output for such
elements. Perhaps checkerboarding, like we do for async images or too-tall scrollable areas, suffices?

Outline different options for showing "locked for display" objects

  • Stale pixels; show previous content until unlocked
  • visibility: hidden; hide content until new one is ready
  • show an affordance
  • show an affordance if there is user input

Another option would be to separate an API to "grab pixels" from "do rendering in the background". Then the developer could programmatically "fix the pixels" of the subtree separately from disabling rendering on it.

Use case: post-rendering callback. Relation to Display Locking?

A use case not satisfied by any web API at present is a post-rendering callback. This is a callback
that happens after rendering occurs, and at which it is guaranteed that layout is clean. Therefore
it is performant to query layout-inducing DOM APIs such as offsetTop, or style-inducing ones
such as getComputedStyle().

Since there is no such API on the web, there is no way to in general write script that queries style-
or layout-inducing properties and is guaranteed not to force style or layout.

In the presence of Display Locking (DL), this concept starts to break down. In particular, style and layout are not actually clean for locked elements and their subtrees.

Therefore it seems that post-rendering callbacks should be tied to the concept of a Display Lock. Display Locks are in turn a way to model and control rendering for specific parts of a page. For the entire page, the built-in rendering event loop processing model of the browser can be modeled like this:

// This code runs before rendering of the page begins:
let rootElementLock = document.documentElement.getDisplayLock();
// This is the well-known event loop of the browser.
rootElementLock.acquire(() => {
  // commit synchronously and immediately re-acquire the lock. This models the way that
  // script writing to DOM does not cause rendering until a yield and commit begins.
  rootElementLock.commit(() => rootElementLock.acquire());
});

Thought about this way, for a page with no additional DLs, the natural way for script to register a post-rendering callback would be to observe the commit resolution of rootElementLock. This could be achieved with a lifecycle observer for didcommit on rootElementLock (see #36):

document.documentElement..getDisplayLock().addEventListener("didcommit", () => { ... });

document interaction with IntersectionObserver

Based on playing with

https://jsfiddle.net/bpza7vy5/1/

in chromium, it seems like an when an observed element's ancestor locked or unlocked, this triggers an event. I think this is good from the perspective of the virtual scroller, although it would be nice to know from the event that this was the cause, because ideally the virtual scroller would know that it is now inside a locked subtree and there is no point in doing any work at all until that changes.

Tab order / find in page

Not sure if it's an issue or not, but would be nice to get some feedback: If an element is locked for display, but the DOM is modified in a way that the next tab order is in the locked subtree, then should it skip that element and go to the next tab order element that is unlocked?

Same question for find in page, should that find a thing that is currently locked or should it skip it?

Consider ability of the user agent to force unlock

Examples that might justify force-unlock:

a. In mode 1 (see #12), and the subtree has been locked for "too long"
b. The user wants to perform actions such as find-in-page and the subtree has been locked for "too long", where here "too long" is less than (a).
c. Detected starvation of commit due to script continuously mutating DOM during commit which prevents completion of the cooperative commit (this one would force a sync commit, not "force-unlock").

Extremely large content discussion

All browsers have a maximum size (including scroll size) for elements, Chrome and Safari are 33.5 million pixels, Firefox is 17.5 million pixels, Edge is 1.5 million pixels. This makes virtual scrolling for lists and grids larger than that awkward, the way we are approaching it (in our list component) at the moment is using a sort of parallax to say, for example, 1 scrollable pixel is 5 virtual pixels, to get a scroll height of 5 * 33.5 million pixels in Chrome/Safari. The problem with this approach is that a single turn of the mouse wheel now scrolls way more pixels than we want it to. There are other things we can do to mitigate this to some extent but it would be nice to have a native way to solve this problem.

While discussing this problem on the Chromium bug tracker, @chrishtr mentioned that display locking could potentially allow you to have a scroll bar that is not affected by this limitation. I'm having a bit of difficulty seeing how this would work with the current draft and hoping someone could shed some light.

Observation pattern for lock lifecycle

It seems useful to provide lifecycle observation events for locks. This allows code that is not directly
manipulating the lock to observe its state transitions.

beforecommit: event fired during the rendering event loop but before step 7.

This is an opportunity for script to do additional work that should happen at the same time as the commit.

didcommit: event fired after step 7 completes

This allows script to run immediately after commit but before any other event loop tasks.

Write more samples.

Need more complicate sample code. Examples:

  • Heavy layout dom with affordance
  • Nested locking
  • Locking several unrelated elements
  • Accordion view

Explicit commit

Is there any reason why this is the default.

In this example, after the callback runs the lock is automatically committed.

Might this prevent use cases where i 1. You don't pass a callback to acquireDisplayLock and 2. Don't invoke acquire For example:

const lock = element.acquireDisplayLock()
setTimeout(() => {
lock.commit() // no-op if someone else with the lock has already committed it.
}, 1000)

element.setAttribute('foo', 'bar')
element.appendChild(child)

How does the current behavior know when "all pending tasks are done".

Figure out infinite timeout.

Right now the proposal is to treat Infinity as an infinite timeout, but it also happens to treat NaN as an infinite timeout, we need to resolve if that's fine. If so, it needs to be specced as such.

How to handle cases where siblings affect layout

Thinking of things like grid, table cells, floats, or even just wrapping.

Eg:

<style>
.content {
  contain: content;
  background: yellow;
}
</style>
<div class="float">H</div>
<div class="content">ello</div>
<script>
(async function() {
  await querySelector('.content').getDisplayLock().acquire();
  document.querySelector('.float').style.float = 'left';
})();
</script>

What would happen in the above case?

http://jsbin.com/qikitoc/1/edit?html,css,output.

Locking replaced content

Replaced elements, i.e <img>, <video>, <embed>, <object> and <iframe>, paint their
replaced content atomically in the "foreground" phase of painting. If a replaced element is
locked, then I think this foreground content should not be painted, and rendering work should
be skipped.

If update() is called on a lock of a replaced element, then the work can be done asynchronously (e.g schedule image / video decodes on background threads). Blink already does image and video decoding in other threads/processes, regardless, but while locked but not updating this work can be avoided.

SVG images are drawn in their own document, and this work can be done cooperatively / on a background thread as well.

Update the docs

We need to update the docs again for the acquire/update/commit api.

Handle abort scenarios

Examples:

  1. Developer removes contain: contents value while locked.
  2. Callback code throws an exception.

In these cases we should probably synchronously commit the lock and reject the promise.

Update documentation to align with a new API shape

See #15 for discussion.

Basically, the change is from having a lock object that is explicitly committed to an "acquireDisplayLock" which runs a callback, possibly continuing the scheduling via a context, and automatically commits.

We should prevent rAF and lifecycle updates altogether if documentElement or parent are locked.

Consider the case of locking the root element of the root frame, or the root element of a frame embedded in a cross-origin parent frame:

document.documentElement.displayLock.acquire();

This should of course blank out the frame's paint and avoid rendering cost, but we can do more: we can avoid running a BeginMainFrame entirely. This avoids costs like:
(a) rAF callbacks
(b) input event processing
(c) compositor commit

because none of them can actually do anything useful if the whole document is locked.

In addition to the above benefits, they also provide a developer API to opt into the DeferBeginMainFrame behavior / DeferCommit Chrome already heuristically supports during page load. Thus an app that wants to e.g. avoid extra rendering which may slow down page load may do so.

context should have access to the locked element.

Particularly in lock-before-append mode, it seems to always be necessary to have access to the locked element, and this pattern is probably very likely:

e.acquireDisplayLock((context) => { func(context, e); });

Maybe |context| should have a lockedElement member so that one can write:

e.acquireDisplayLock(func);

And then context.lockedElement.appendChild(...); or whatever else is needed.

WDYT?

Align API shape with Web Locks?

For Web Locks API we ended up with somewhat different API shape to avoid explicit acquire/release in most cases:

await navigator.locks.request('my_resource', async lock => {
  // callback is invoked once the lock is acquired
  await do_something();
  await do_something_else();
  // lock will be released when this async callback's implicit promise resolves
});
// the awaited request call's promise resolves once the lock is released

The promise returned by the request() function coul also reject if the lock request failed, timed out, or signaling other issues (e.g. if the lock was held for too long).

We arrived at this API shape after much iteration (starting off with explicit acquire/release calls), and settled on it since it's "safe by default" (e.g. in the face of exceptions/returns, etc). The TAG went through the same iteration and ended up liking the approach.

In the display locking example, I could imagine an API shape like this:

await element.requestDisplayLock(async lock => {
  // callback invoked once lock is held
  // Modify element's subtree. The DOM is modified immediately, same as without
  // display locking. However, rendering of the element is delayed.
  element.innerHTML = ...;
  // lock is implicitly released here
});
// awaited promise resolves once lock is released and UA finishes up with follow-up steps

Just throwing this out there to consider.

Support the use-case of maximal memory efficiency

In examples such as domenic/infinite-list-study-group#8,
it is critical to be able to minimize memory of a large code file by avoiding rendering-related data structures. If these turn out to lead to memory use more than 2x or so the memory for strings of text in the code file, we would need to add functionality to allow the developer to evict rendering state for offscreen content.

Explore auto-commit patterns

One issue is when to call commit(). It must be called "when the DOM update is complete", but
it may be tricky for sites to determine when this is the case.

There may be a good way to have locks auto-commit when the current task, or sequence of
async-updated tasks "related to" the original one, complete. This has the potential to avoid the site needing to plumb state about when updates complete.

Intrinsic sizing while locked

We need to decide upon out the layout behavior of locked elements while locked, and how to supply placeholder sizes to acquire() in cases when we don't want to pay for layout of the subtree.

This document lays out possibilities, tradeoffs and use-cases.

Proposed resolution is:

MVP:

displayLock.acquire({size: [a, b]}) will lock the element and lay it out as if it has contain:size CSS with width=a, height=b in place of the border-scrollbar-padding size. acquire() will resolve immediately, without waiting for a render.

displayLock.acquire() is equivalent to displayLock.acquire({size: [0, 0]})

Post-MVP/lower priority:

displayLock.acquire({size:auto}) will delay acquisition until the next render, and set
the size to be the laid-out size of the element, as well as computing the min- and max- intrinsic sizing of the subtree

displayLock.acquire({size: none}) will acquire immediately, and not allow the presence of the locked root to influence style or layout in any way.

Support full-framerate (e.g. 60fps) animations

It should be possible to achieve full-framerate animations (i.e. ones that are cheap enough to run at that speed) using Display Locking, just as it is with requestAnimationFrame.

However, with current semantics this will result in an extra frame at the beginning of an animation, and an extra frame between animation frames.

Consider:

1  function renderFrame() {
2  ...
3  requestAnimationFrame(renderFrame);
4 }
5  requestAnimationFrame(renderFrame);

The browser attempts to run the rendering event loop at its desired framerate.
Step 6 of that loop is running requestAnimationFrame callbacks. If requestAnimationFrame is called from within such a callback, the argument to the requestAnimationFrame method is run at the next frame. Therefore this code will execute the renderFrame method at full-framerate, assuming the code and DOM update can fit within the frame deadline.

Now consider this example:

function renderFrame() {
  ...
}

function acquire() {
  acquireDisplayLock(renderFrame).then(() => requestAnimationFrame(acquire));
}

The promise resolution callback happens after all rendering work is done, but before committing to the screen. This is because the resolution callback needs to be able to re-lock the content
if desired, even after completion of all tasks, in order to support such use-cases as measuring layout.

In addition, the rendering loop must run once after calling acquireDisplayLock before calling its callback parameter, in order to set up the cached state that will be displayed while the element is locked.

Therefore the sequence of actions to go through two rounds of acquire/renderFrame is going to be:

  1. acquireDisplayLock
  2. Run rendering loop (to cache pixels for step 1)
  3. Run renderFrame callback
  4. Run rendering loop for first frame of animation (but not yet displayed!)
  5. Run promise resolution callback.
  6. Run rendering loop. This calls acquireDisplayLock in a rAF callback, then renders content actually computed in step 4. Also caches pixels from first frame.
  7. Run renderFrame callback.
  8. Run rendering loop for second frame of animation (but not yet displayed!)
  9. Run promise resolution callback.
  10. Run rendering loop to display second frame.

Note that between steps 6 and 10 there are two rendering loops. This means that an acquireDisplayLock rendering loop cannot run faster than half the framerate of the device. This is undesireable.

One way to fix this is have a way to declare a display lock to be passive, e.g. by passing a parameter indicating such. Passive display locks would not allow script to abort in in the completion callback. This would allow us to display the second frame in step 8 rather than 10, because step 9 was not allowed to re-lock.

This would allow the animation to run at full-framerate, except for one additional frame at the start of the animation.

Add nested locking example

From @progers,

Can you add a sentence about the nesting issue ("Locked element in a locked subtree.")? For example, the infinite-list-widget uses display locking, and the happy-button widget also uses display locking. I think library authors will want an answer for this.

Handle case of replacing element and subtree without interrupting animations

Display locking currently handles the case of locking a subtree and modifying it, while displaying
(or not) the old pixels for the subtree.

It does not, however, handle the case of replacing an element and its subtree, without locking that
element in the meantime. Replacing a large amount of content seems a common situation, and
continuing any interactions and animations during the computation of layout for its replacement
is useful. For example, any ongoing video, image, scroll, or script-driven animations or input operations could continue unimpeded.

This can be achieved by adding a new mode to DisplayLocking. There will now be usage modes modes:

  1. The existing mode: Subtree update
    a. Lock an element currently in the DOM
    b. Update its subtree to new state
    c. Commit and unlock element

  2. The new mode: Element and subtree update
    a. Create a new element
    b. Lock it
    c. Insert it into the DOM as a sibiling of the original element
    d. Update its style (including sizing and paint containment), and subtree to new state
    e. Commit and unlock element
    f. In unlock promise resolution, remove original element from the DOM

In mode 1, the locked element itself is not "locked for display", meaning its box decorations are displayed and updated as usual, and it participates in layout.

In mode 2, the locked element is actually locked, meaning its box decorations are not displayed and it does not participate in layout. Only in step 2f does a visual update occur in which the new element has an effect on display to the screen.

Note that step 2f will have a cost equal to the cost of re-computing layout with the original element removed from the DOM (but no other changes made). This is because step 2e cannot predict whether this will happen, and can only compute layout assuming the new element and subtree will be added to the DOM. In combination with containment isolation, however, this final layout should be fast. This is because step 2d requires sizing and paint containment to be specified.

Concern about activation and incremental search

It seems that all locked nodes that contain the search string are activated when searching. Since browser search is incremental, this has the unfortunate side effect that if:

  • You've rendered a code editor with a huge document inside of it using many chunks of hidden content for the off-screen parts
  • The user starts a text search and types, say, "e"

Most of the chunks will contain an "e", and will be activated, forcing the browser to render potentially hundreds of thousands of lines of content right away, largely defeating the point of the optimization.

Is there a solution to this yet?

Declarative version

Display locking makes sense also in a declarative mode, as an async attribute on elements. Updates to the visual display of such elements would be done asynchronously to the rest of the page, and therefore may not present atomically with other content. However, content under a single async element would be presented atomically to the screen. Async rendering would only apply to elements with appropriate containment CSS.

The attribute could be rendering={async|sync|auto}, with auto being the default; this allows browsers to potentially skip rendering heuristically for elements with containment CSS that were not on-screen, and are predicted not to be on-screen soon (e.g. scrolled far below the viewport). This is analogous to the decoding attribute for elements (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)

Decide what to do about forced layouts

If a layout-inducing property (e.g. offsetTop) is queried for a locked element, what should happen?

Options include:

  1. Throw an exception
  2. Return a bogus value
  3. Force synchronous layout

All three are straightforward to implement.

I think option 3 is probably best, based on feedback from a large site developer, who noted that it's very hard to make sure layout-inducing readbacks are never present on sites with contributions from many developers and includig third-party code, and if they are present, returning bogus values will result in bugs, and exceptions in a broken site.

Instead we should force layout, and use mechanisms like the Reporting API to inform developers of performance issues.

Synchronously Aborting an In Progress Lock

There is a difference between this API and the mechanism that we have in React and our native frameworks.

In those frameworks we have the ability to effectively synchronously "rollback" any changes that were made during the lock. Instead we apply different changes and synchronously perform a new layout. In some variants we also synchronously wait for the layout to finish or compute a new layout for both changes synchronously.

The key is that once you have started to perform an asynchronous layout, you can opt-out of the asynchronous nature at any point. For example, if you had a higher priority interaction that you wanted to perform on that tree. The main use case for this is coordination and interop though. If something else around you changes, such as resize of the container when the content in the middle requires multi-pass layout to be performed. Or if some preexisting code around the tree needs to operate synchronously and you want to coordinate displays. E.g. if you don't want to show blank space while scrolling or something like that.

For newly inserted subtrees, this is not much of an issue because we can work around this by just synchronously inserting a new root. So for React, I was only planning on using this feature for that use case.

However, ideally we could also use it for updated trees. If we acquired a display lock for a subtree to perform an update and then something around it needs to update synchronously and this thing needs to coordinate with that tree. Then there will be visible inconsistencies that immediately pop out as highly visible.

I think there are three strategies we can try to use to address this:

  • Immutable/persistent/copy-on-write DOM trees (which is what we use in the frameworks above). I don't think this is a reasonable small step for a DOM API.
  • Rollback of DOM changes. This is a much more invasive change since it'll mean a way to define which parts are rolled back and which are not. Although it could be a powerful feature.
  • Expose a synchronous commit API that can be called any time after having acquired the lock. If called after the asynchronous commit has started, this forces the layout to be completed in a synchronous way. This can be implemented by blocking on a different thread or by recomputing the layout.

I see this mostly as an escape hatch rather than a primary usage of the API.

It may seem heavy handed to block the main thread on synchronous layout but the alternative is that we're more conservative with our use of this API which means that this and a lot more layout is synchronous anyway. (The tradeoff is that visible discrepancies or not being able to use some APIs are worse than main thread jank.)

Add more event timing edge cases/discussions

From @progers:

I think some event timing questions should be answered [in the edge cases section / elsewhere], as they are not edge cases for widget authors. For example, dom mutation events and intersection observer.

What should happen when update() or commit() is called on a nested lock?

If an element e is locked, and its ancestor is also locked, what should happen if we call e.update() or e.commit()?

Option 1: reject
Option 2: update/commit all ancestor locks (this is similar / the same as not allowing nested locks, and instead delegating to an ancestor lock if it exists)
Option 3: synchronously "unlock" the current lock for future lifecycles (which amounts to removing the lock and otherwise doing nothing, if in a nested lock

Use cases:
a. Want to unlock some content that may be under another lock, and then forget about it. Don't want to accidentally leave the content unlocked if the ancestor unlocks in the future.
b. Want to update some content in order to measure its layout.
c. Want to update some content in order to pre-render it to prepare for quick final display.

Use case (b) needs to also know whether the update() or commit() or updateAndCommit() actually finished, or whether it stayed locked, in order to avoid forced layouts when reading back layout.

for commit() and updateAndCommit(), I think option 3 makes the most sense, because it solves use case (a).

For update(), option 2 would solve the most use cases, e.g. (b). But option 1 is more performant because it avoids doing work that is much larger than the display locked subtree.

None of the options solve the problem of knowing whether it's safe to read back layout though.

Dealing with CSS Animations.

We need to figure out what to do with CSS animations in a locked subtree. The current plan of record is to suspend all animations in a subtree. This would also have to include things like pausing animated GIFs.

Mode 2: when to append the element

Mode 2 (which I propose we call lock-before-append to avoid confusion) says that if we acquire the display lock before appending the element to the dom, then no pixels will be displayed for the element. They only appear after layout and whatnot have completed and the promise is resolved.

It is a bit unclear/ambiguous in this case what it means to have a display lock be acquired. Specifically, the promise that acquireDisplayLock returns resolves after the lock is committed (ie things have been laid out), so that can only happen if the element is already appended into the DOM.

So, consider this:
let e = document.createElement("div");
e.acquireDisplayLock(func1).then(func2);
func3();

So, when should "e" be placed into the DOM? It can't be func2() since that's that the completion callback and it doesn't run until we actually laid things out.

func3() also seems a bit suspect since acquireDisplayLock is meant to be asynchronous, so it looks weird although from the implementation perspective there's nothing wrong with doing it there. Ie, we already know that the lock was requested, so whether we append the element immediate after that or a bit later doesn't really matter.

func1() seems to also be an "ok" spot to do it, although it's now responsible not just for generating the content but also appending it to the DOM.

What should be the recommended guidance (I'm in the process of writing layout tests, and I'd like them to be canonical)

Clarify that updates after commit() is called but before promise resolution are sync

Consider the following code:

element.displayLock.acquire(() => {
element.displayLock.commit();
element.offsetLeft;
}

In this situation, offsetLeft left would use the committed offsetLeft of the layout of the element. OTOH:

element.displayLock.acquire(() => {
element.offsetLeft;
}

Here offsetLeft would only be based on the intrinsic sizing of the element, not its surrounding context.

Interaction with Shadow DOM

The spec will need to be explicit about which trees it's talking about and how it interacts with Shadow DOM and slot changes.

Style inheritance and layout trees follow the flat tree order, which means for this case:

<div id="host"><span slot="x"></span></div>
<script>
  const root = host.attachShadow({mode:"open"});
  root.innerHTML = "<div><span><slot name='x' /></span></div>"
  root.querySelector("span").acquireDisplayLock();
</script>

The span child of the host is also locked for style/layout/paint updates?

Or put another way, it is the flat-tree subtree that is locked?

How will slot assignment changes like removing the slot attribute of the host child above affect locking?

Dealing with layout inducing properties.

We need to figure out what happens when layout inducing properties are accessed for a locked subtree (eg any of the following https://gist.github.com/paulirish/5d52fb081b3570c81e3a ).

It's possible that we just return 0 or some value that is known to be unreliable? It would be nice to guarantee that upon commit promise resolution, these properties can in fact be read and have correct values. This would open up the possibility for display locking to be used for layout measurements without displaying contents.

Behavior of DisplayLockContext methods in corner case situations

  1. Call commit on an un-acquired lock.

=> Does not reject. Resolves after next render.

  1. Call commit on acquired lock, in the middle of update().

=> Does not reject. Upgrades to commit, finishes on next render, and commits immediately after.

  1. Call update on an un-acquired lock.

=> Equivalent to calling commit() on an un-acquired lock.

[Rationale: use case is a developer wanting
to unlock the element, and would like to use cooperative commit if possible, but if the lock is not acquired
we might as well commit, because acquire() costs the same as commit() in this case]

  1. Call to acquire() when already acquired.

=> Immediately resolves.

  1. Call to commit() after commit() but before commit is finished.

Resolves at same time as other commit().

  1. Call to update() after commit() but before commit is finished.

Equivalent to item 5.

  1. Call to acquire() after commit() but before commit is finished.

Resolves at same time as commit().

  1. Call to acquire() during commit resolution callback.

Resolves immediately.

[See https://github.com//issues/26#issuecomment-453201521]

  1. Call to update() after update() but before other update() is finished.

Resolves at same time as other update().

  1. Call to commit after update() but before other update() is finished.

Upgrades to commit and rejects other update.

[Rationale: the other update might have been measuring pre-commit layout, which is no longer possible if we upgrade to commit.]

  1. Element style changes to not be compatible with Display Locking.

=> Equivalent to calling commit()

Does selection activate locked elements?

What should activatable locks do when being selected? Either drag select or ctrl-a

Argument for "unlock": does what user expects
Argument for "unlock only on-screen content": performs well

We could unlock everything since if the user is trying to copy all of the page (formatted) text, then we need to have it all unlocked. This also means we should reject an activatable acquire if the element is selected.

/cc @chrishtr @fergald @rakina

Display artifacts on locked subtrees

Painting retained “snapshots” at the old size might not always be desirable: when layout is altered on “locking roots”, some subtrees could have unpainted areas while others would clip. This is especially evident in suggested use-cases such as “resizing multi-pane UI with complex layout within each pane” as described here.

An example for how this can be gracefully handled is with macOS's fullscreen window management, which has a similar approach to resizing 2 side-by-side half-screen windows, where everything is blurred out to avoid relayout while resizing.

Should this responsibility be solely offloaded to the consumer of this API? Or is it worth considering having the user-agent handling these situations more gracefully by default?

Examples of async rendering from non-web systems

Let's use this issue to track links to examples we can learn from outside of the web.

Android surfaceRedrawNeededAsync:

https://developer.android.com/reference/android/opengl/GLSurfaceView#surfaceRedrawNeededAsync(android.view.SurfaceHolder,%20java.lang.Runnable)

This is a new feature in Android O. When the framework calls this method, it is requesting a need to have a surface of an activity redrawn, but it can be done asynchronously in the background. The second parameter is a callback to call when rendering is complete.

This API is intended for scenarios such as the phone rotating during display of web content. Since Chrome does all rendering asynchronously, it is not able to draw to the surface immediately and must use the async version supplied here. On pre-Android O phones Chrome does not have this API, and so must either draw the old, cached frame (stretched to the new dimensions of the phone, which is probably ugly), or draw a solid color. For fullscreen video it actually draws solid black until rendering
is complete. With Android O, the framework will wait for Chrome to draw new content so it can present
atomically.

acquire() locks immediately or asynchronously

pros for locking immediately:

  • DOM manipulations after acquire act on a locked state
  • Forced (unrelated to the locked root) updates, do not lay out the changed DOM

cons for locking immediately:

  • size: auto needs to figure out the locked size, so it's unclear whether DOM changed after acquire() is considered or not.
  • naive size: auto implementation would do full layout during the regular frame and read off the size, then acquire the lock (that looks like acquire is asynchronous)

Should it be both? "acquire() is synchronous if size != auto, and asynchronous otherwise"?

/cc @chrishtr @fergald @rakina

Blanking vs locking

At blinkon, it sounded like the idea was to lock the layout, and blank the pixels. Did I understand that correctly?

I'm interested in using display-locking for an element that's transitioning, eg blurring and fading.

During this transition, something like a hover effect will cause the layer to repaint, and likely jank the animation.

I was hoping I'd be able to use display-locking to prevent this, but if it's going to blank out the pixels, it doesn't work for me.

cc @surma

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.