Commit 26cbade5 authored by Vladimir Levin's avatar Vladimir Levin Committed by Commit Bot

[DL] Handle forced layouts.

If the layout is forced, then we temporarily allow the processing of
the subtree. The locked element's frame rect still remains to be the same
as at the time the lock was acquired.

R=chrishtr@chromium.org, mstensho@chromium.org, futhark@chromium.org

Bug: 907613, 882663
Change-Id: I9d1f63c287a73e7a8f3cbc9cb5acc2a009ddd024
Reviewed-on: https://chromium-review.googlesource.com/c/1363957
Commit-Queue: vmpstr <vmpstr@chromium.org>
Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Reviewed-by: default avatarMorten Stenshorne <mstensho@chromium.org>
Cr-Commit-Position: refs/heads/master@{#615556}
parent fada5812
......@@ -338,21 +338,28 @@ void DisplayLockContext::NotifyConnectedMayHaveChanged() {
bool DisplayLockContext::ShouldStyle() const {
SCOPED_LOGGER(__PRETTY_FUNCTION__);
return state_ >= kCommitting;
return update_forced_ || state_ >= kCommitting;
}
void DisplayLockContext::DidStyle() {
SCOPED_LOGGER(__PRETTY_FUNCTION__);
if (state_ != kCommitting)
if (state_ != kCommitting && !update_forced_)
return;
// We must have contain: content for display locking.
// Note that we should also have content containment even if we're forcing
// this update to happen. Otherwise, proceeding with layout may cause
// unexpected behavior. By rejecting the promise, the behavior can be detected
// by script.
auto* style = GetElement()->GetComputedStyle();
if (!style || !style->ContainsContent()) {
RejectAndCleanUp();
return;
}
if (state_ != kCommitting)
return;
if (lifecycle_update_state_ <= kNeedsStyle) {
// Normally we need to do layout next, but if it's not dirty then we can
// skip ahead to pre-paint.
......@@ -365,8 +372,9 @@ void DisplayLockContext::DidStyle() {
bool DisplayLockContext::ShouldLayout() const {
SCOPED_LOGGER(__PRETTY_FUNCTION__);
return std::tie(state_, lifecycle_update_state_) >=
std::tuple<State, LifecycleUpdateState>{kCommitting, kNeedsLayout};
return update_forced_ ||
std::tie(state_, lifecycle_update_state_) >=
std::tuple<State, LifecycleUpdateState>{kCommitting, kNeedsLayout};
}
void DisplayLockContext::DidLayout() {
......@@ -380,8 +388,9 @@ void DisplayLockContext::DidLayout() {
bool DisplayLockContext::ShouldPrePaint() const {
SCOPED_LOGGER(__PRETTY_FUNCTION__);
return std::tie(state_, lifecycle_update_state_) >=
std::tuple<State, LifecycleUpdateState>{kCommitting, kNeedsPrePaint};
return update_forced_ || std::tie(state_, lifecycle_update_state_) >=
std::tuple<State, LifecycleUpdateState>{
kCommitting, kNeedsPrePaint};
}
void DisplayLockContext::DidPrePaint() {
......@@ -398,6 +407,9 @@ void DisplayLockContext::DidPrePaint() {
bool DisplayLockContext::ShouldPaint() const {
SCOPED_LOGGER(__PRETTY_FUNCTION__);
// Note that forced updates should never require us to paint, so we don't
// check |update_forced_| here. In other words, although |update_forced_|
// could be true here, we still should not paint.
return std::tie(state_, lifecycle_update_state_) >=
std::tuple<State, LifecycleUpdateState>{kCommitting, kNeedsPaint};
}
......@@ -451,6 +463,24 @@ void DisplayLockContext::NotifyPendingFrameRectScopeEnded() {
GetElement()->GetLayoutBox()->SetFrameRectForDisplayLock(*locked_frame_rect_);
}
DisplayLockContext::ScopedForcedUpdate
DisplayLockContext::GetScopedForcedUpdate() {
SCOPED_LOGGER(__PRETTY_FUNCTION__);
DCHECK(!update_forced_);
update_forced_ = true;
// Now that the update is forced, we should ensure that style and layout code
// can reach it via dirty bits.
MarkAncestorsForStyleRecalcIfNeeded();
MarkAncestorsForLayoutIfNeeded();
return ScopedForcedUpdate(this);
}
void DisplayLockContext::NotifyForcedUpdateScopeEnded() {
SCOPED_LOGGER(__PRETTY_FUNCTION__);
DCHECK(update_forced_);
update_forced_ = false;
}
void DisplayLockContext::FinishResolution() {
SCOPED_LOGGER(__PRETTY_FUNCTION__);
if (state_ == kResolving)
......@@ -547,4 +577,19 @@ DisplayLockContext::ScopedPendingFrameRect::~ScopedPendingFrameRect() {
context_->NotifyPendingFrameRectScopeEnded();
}
DisplayLockContext::ScopedForcedUpdate::ScopedForcedUpdate(
DisplayLockContext* context)
: context_(context) {}
DisplayLockContext::ScopedForcedUpdate::ScopedForcedUpdate(
ScopedForcedUpdate&& other)
: context_(other.context_) {
other.context_ = nullptr;
}
DisplayLockContext::ScopedForcedUpdate::~ScopedForcedUpdate() {
if (context_)
context_->NotifyForcedUpdateScopeEnded();
}
} // namespace blink
......@@ -59,6 +59,19 @@ class CORE_EXPORT DisplayLockContext final
UntracedMember<DisplayLockContext> context_ = nullptr;
};
class ScopedForcedUpdate {
public:
ScopedForcedUpdate(ScopedForcedUpdate&&);
~ScopedForcedUpdate();
private:
friend class DisplayLockContext;
ScopedForcedUpdate(DisplayLockContext*);
UntracedMember<DisplayLockContext> context_ = nullptr;
};
DisplayLockContext(Element*, ExecutionContext*);
~DisplayLockContext() override;
......@@ -121,6 +134,14 @@ class CORE_EXPORT DisplayLockContext final
// and the pending one is stored in the context until it is needed.
ScopedPendingFrameRect GetScopedPendingFrameRect();
// Returns a ScopedForcedUpdate object which for the duration of its lifetime
// will allow updates to happen on this element's subtree. For the element
// itself, the frame rect will still be the same as at the time the lock was
// acquired.
// Only one ScopedForcedUpdate can be retrieved from the same context at a
// time.
ScopedForcedUpdate GetScopedForcedUpdate();
private:
friend class DisplayLockContextTest;
friend class DisplayLockSuspendedHandle;
......@@ -177,6 +198,10 @@ class CORE_EXPORT DisplayLockContext final
// GetScopedPendingFrameRect() for more information.
void NotifyPendingFrameRectScopeEnded();
// When ScopedForcedUpdate is destroyed, it calls this function. See
// GetScopedForcedUpdate() for more information.
void NotifyForcedUpdateScopeEnded();
HeapVector<Member<V8DisplayLockCallback>> callbacks_;
Member<ScriptPromiseResolver> resolver_;
......@@ -205,12 +230,14 @@ class CORE_EXPORT DisplayLockContext final
Member<Element> element_;
WeakMember<Element> weak_element_handle_;
bool process_queue_task_scheduled_ = false;
unsigned suspended_count_ = 0;
State state_ = kUninitialized;
LifecycleUpdateState lifecycle_update_state_ = kNeedsStyle;
LayoutRect pending_frame_rect_;
base::Optional<LayoutRect> locked_frame_rect_;
bool process_queue_task_scheduled_ = false;
bool update_forced_ = false;
};
} // namespace blink
......
......@@ -2579,6 +2579,26 @@ void Document::EnsurePaintLocationDataValidForNode(const Node* node) {
if (!node->InActiveDocument())
return;
// If we're forcing location information to be updated, we need to ensure that
// all locked elements in the ancestor chain allow us to do the updates. When
// the scoped objects are destroyed, the locks are restored. Note that the
// frame rect of the locked elements themselves will still be the same as at
// the time the lock was acquired.
// TODO(vmpstr): This is somewhat inefficient, since we would pay the cost of
// traversing the ancestor chain even for nodes that are not in the locked
// subtree. We need to figure out if there is a supplementary structure that
// we can use to quickly identify nodes that are in the locked subtree.
Vector<DisplayLockContext::ScopedForcedUpdate> scoped_update_forced_list;
if (RuntimeEnabledFeatures::DisplayLockingEnabled()) {
for (auto* ancestor = node; ancestor;
ancestor = ancestor->ParentOrShadowHostNode()) {
if (!ancestor->IsElementNode())
continue;
if (auto* context = ToElement(ancestor)->GetDisplayLockContext())
scoped_update_forced_list.push_back(context->GetScopedForcedUpdate());
}
}
// For all nodes we must have up-to-date style and have performed layout to do
// any location-based calculation.
UpdateStyleAndLayoutIgnorePendingStylesheets();
......
<!doctype HTML>
<!--
Runs an acquireDisplayLock which constructs and measures a subtree.
This should do a forced layout.
-->
<style>
#container {
contain: content;
width: 100px;
height: 100px;
background: lightgreen;
}
.child {
width: 20px;
height: 20%;
background: cyan;
}
#spacer {
width: 150px;
height: 150px;
background: green;
}
</style>
<div id="parent"></div>
<div id="spacer"></div>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
async_test((t) => {
function createChild(id) {
let child = document.createElement("div");
child.classList = "child";
child.id = id;
return child;
}
function measure(context) {
context.lockedElement.appendChild(createChild("0"));
context.lockedElement.appendChild(createChild("1"));
context.lockedElement.appendChild(createChild("2"));
t.step(() => {
// Ensure children are laid out; this forces a layout.
assert_equals(document.getElementById("0").offsetTop, 0, "0 in measure");
assert_equals(document.getElementById("1").offsetTop, 20, "1 in measure");
assert_equals(document.getElementById("2").offsetTop, 40, "2 in measure");
// Both parent should be 0 height, since it's locked. Both parent and spacers
// should have 8 offsetTop.
assert_equals(document.getElementById("parent").offsetTop, 8, "parent in measure");
assert_equals(document.getElementById("spacer").offsetTop, 8, "spacer in measure");
});
}
function acquire() {
let container = document.createElement("div");
container.id = "container";
container.acquireDisplayLock(measure).then(() => {
// We have to do the final measurement on the next frame, since in the promise
// resolution we still can be relocked.
window.requestAnimationFrame(() => {
t.step(() => {
// Ensure children are still laid out.
assert_equals(document.getElementById("0").offsetTop, 0, "0 in completion");
assert_equals(document.getElementById("1").offsetTop, 20, "1 in completion");
assert_equals(document.getElementById("2").offsetTop, 40, "2 in completion");
// Now the parent should encompass a container, so spacer is pushed down.
assert_equals(document.getElementById("parent").offsetTop, 8, "parent in completion");
assert_equals(document.getElementById("spacer").offsetTop, 108, "spacer in completion");
});
t.done();
});
});
document.getElementById("parent").appendChild(container);
}
acquire();
}, "Measure Forced Layout");
</script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment