Commit 68f4208d authored by Vladimir Levin's avatar Vladimir Levin Committed by Commit Bot

SubtreeVisibility: turn off script focus activation + tests.

This patch adds the ability for us to keep auto elements unlocked
when the subtree has focus.

The activation flag is replaced by an array of flags, and a function
uses those flags to determine whether we should lock or unlock.

This also adds a bunch of tests for focus().

Note that the place the focus/display lock updates happen needs
to before the scroll, and NotifyFocusedElementChanged is too
late in the pipeline for this.

Change-Id: I251dcca44275322d4ba71b7de6f8fb83eb1fe81c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2112956
Commit-Queue: vmpstr <vmpstr@chromium.org>
Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#753243}
parent ffd922ba
......@@ -90,6 +90,7 @@ void RecordActivationReason(Document* document,
DisplayLockContext::DisplayLockContext(Element* element)
: element_(element), document_(&element_->GetDocument()) {
document_->AddDisplayLockContext(this);
DetermineIfSubtreeHasFocus();
}
void DisplayLockContext::SetRequestedState(ESubtreeVisibility state) {
......@@ -124,6 +125,10 @@ void DisplayLockContext::SetRequestedState(ESubtreeVisibility state) {
!needs_deferred_not_intersecting_signal_);
needs_deferred_not_intersecting_signal_ = false;
UpdateLifecycleNotificationRegistration();
// Note that we call this here since the |state_| change is a render affecting
// state, but is tracked independently.
NotifyRenderAffectingStateChanged();
}
void DisplayLockContext::AdjustElementStyle(ComputedStyle* style) const {
......@@ -138,23 +143,13 @@ void DisplayLockContext::AdjustElementStyle(ComputedStyle* style) const {
style->SetContain(contain);
}
bool DisplayLockContext::RequestLock(uint16_t activation_mask) {
void DisplayLockContext::RequestLock(uint16_t activation_mask) {
UpdateActivationMask(activation_mask);
if (IsLocked())
return true;
if (IsActivated())
return false;
Lock();
return true;
SetRenderAffectingState(RenderAffectingState::kLockRequested, true);
}
void DisplayLockContext::RequestUnlock() {
ResetActivation();
if (IsLocked())
Unlock();
SetRenderAffectingState(RenderAffectingState::kLockRequested, false);
}
void DisplayLockContext::UpdateActivationMask(uint16_t activatable_mask) {
......@@ -167,8 +162,6 @@ void DisplayLockContext::UpdateActivationMask(uint16_t activatable_mask) {
all_activation_is_blocked);
activatable_mask_ = activatable_mask;
// Since activation mask has changed, reset all activation.
ResetActivation();
}
void DisplayLockContext::UpdateDocumentBookkeeping(
......@@ -212,10 +205,18 @@ void DisplayLockContext::UpdateActivationObservationIfNeeded() {
return;
is_observed_ = should_observe;
if (should_observe)
if (should_observe) {
document_->RegisterDisplayLockActivationObservation(element_);
else
} else {
document_->UnregisterDisplayLockActivationObservation(element_);
// If we're not listening to viewport intersections, then we can assume
// we're not intersecting:
// 1. We might not be connected, in which case we're not intersecting.
// 2. We might not be in 'auto' mode. which means that this doesn't affect
// anything consequential but acts as a reset should we switch back to
// the 'auto' mode.
SetRenderAffectingState(RenderAffectingState::kIntersectsViewport, false);
}
}
bool DisplayLockContext::NeedsLifecycleNotifications() const {
......@@ -379,28 +380,14 @@ void DisplayLockContext::CommitForActivationWithSignal(
RecordActivationReason(document_, reason);
// TODO(vmpstr): This should eventually only unlock on viewport intersection,
// but each reason needs to be tested and considered, so this is being done in
// parts.
if (reason == DisplayLockActivationReason::kScrollIntoView)
// TODO(vmpstr): This should eventually never unlock, but each reason needs to
// be tested and considered, so this is being done in parts.
if (reason == DisplayLockActivationReason::kScrollIntoView ||
reason == DisplayLockActivationReason::kScriptFocus ||
reason == DisplayLockActivationReason::kUserFocus)
return;
Unlock();
is_activated_ = true;
}
bool DisplayLockContext::IsActivated() const {
return is_activated_;
}
void DisplayLockContext::ResetActivation() {
is_activated_ = false;
if (RuntimeEnabledFeatures::CSSSubtreeVisibilityActivationEventEnabled()) {
// We're no longer activated, so if the signal didn't run yet, we should
// cancel it.
weak_factory_.InvalidateWeakPtrs();
}
}
void DisplayLockContext::NotifyIsIntersectingViewport() {
......@@ -408,13 +395,7 @@ void DisplayLockContext::NotifyIsIntersectingViewport() {
// subtree and we don't need to lock as a result.
needs_deferred_not_intersecting_signal_ = false;
UpdateLifecycleNotificationRegistration();
if (!IsLocked())
return;
DCHECK(IsActivatable(DisplayLockActivationReason::kViewportIntersection));
CommitForActivationWithSignal(
element_, DisplayLockActivationReason::kViewportIntersection);
SetRenderAffectingState(RenderAffectingState::kIntersectsViewport, true);
}
void DisplayLockContext::NotifyIsNotIntersectingViewport() {
......@@ -441,9 +422,7 @@ void DisplayLockContext::NotifyIsNotIntersectingViewport() {
needs_deferred_not_intersecting_signal_ = true;
} else {
needs_deferred_not_intersecting_signal_ = false;
DCHECK(IsActivated());
ResetActivation();
Lock();
SetRenderAffectingState(RenderAffectingState::kIntersectsViewport, false);
}
UpdateLifecycleNotificationRegistration();
}
......@@ -763,6 +742,8 @@ void DisplayLockContext::DidMoveToNewDocument(Document& old_document) {
document_->IncrementDisplayLockBlockingAllActivation();
}
}
DetermineIfSubtreeHasFocus();
}
void DisplayLockContext::WillStartLifecycleUpdate(const LocalFrameView& view) {
......@@ -799,6 +780,7 @@ void DisplayLockContext::ElementDisconnected() {
void DisplayLockContext::ElementConnected() {
UpdateActivationObservationIfNeeded();
DetermineIfSubtreeHasFocus();
}
void DisplayLockContext::ScheduleAnimation() {
......@@ -883,6 +865,67 @@ bool DisplayLockContext::ConnectedToView() const {
return element_ && document_ && element_->isConnected() && document_->View();
}
void DisplayLockContext::NotifySubtreeLostFocus() {
SetRenderAffectingState(RenderAffectingState::kSubtreeHasFocus, false);
}
void DisplayLockContext::NotifySubtreeGainedFocus() {
SetRenderAffectingState(RenderAffectingState::kSubtreeHasFocus, true);
}
void DisplayLockContext::DetermineIfSubtreeHasFocus() {
if (!ConnectedToView()) {
SetRenderAffectingState(RenderAffectingState::kSubtreeHasFocus, false);
return;
}
bool subtree_has_focus = false;
// Iterate up the ancestor chain from the currently focused element. If at any
// time we find our element, then our subtree is focused.
for (auto* focused = document_->FocusedElement(); focused;
focused = FlatTreeTraversal::ParentElement(*focused)) {
if (focused == element_.Get()) {
subtree_has_focus = true;
break;
}
}
SetRenderAffectingState(RenderAffectingState::kSubtreeHasFocus,
subtree_has_focus);
}
void DisplayLockContext::SetRenderAffectingState(RenderAffectingState state,
bool new_flag) {
render_affecting_state_[static_cast<int>(state)] = new_flag;
NotifyRenderAffectingStateChanged();
}
void DisplayLockContext::NotifyRenderAffectingStateChanged() {
auto state = [this](RenderAffectingState state) {
return render_affecting_state_[static_cast<int>(state)];
};
// Check that we're visible if and only if lock has not been requested.
DCHECK(state_ == ESubtreeVisibility::kVisible ||
state(RenderAffectingState::kLockRequested));
DCHECK(state_ != ESubtreeVisibility::kVisible ||
!state(RenderAffectingState::kLockRequested));
// We should be locked if the lock has been requested (the above DCHECKs
// verify that this means that we are not 'visible'), and any of the
// following is true:
// - We are not in 'auto' mode (meaning 'hidden') or
// - We are in 'auto' mode and nothing blocks locking: viewport is
// not intersecting, and subtree doesn't have focus.
bool should_be_locked = state(RenderAffectingState::kLockRequested) &&
(state_ != ESubtreeVisibility::kAuto ||
(!state(RenderAffectingState::kIntersectsViewport) &&
!state(RenderAffectingState::kSubtreeHasFocus)));
if (should_be_locked && !IsLocked())
Lock();
else if (!should_be_locked && IsLocked())
Unlock();
}
void DisplayLockContext::Trace(Visitor* visitor) {
visitor->Trace(element_);
visitor->Trace(document_);
......
......@@ -187,6 +187,9 @@ class CORE_EXPORT DisplayLockContext final
void ElementDisconnected();
void ElementConnected();
void NotifySubtreeLostFocus();
void NotifySubtreeGainedFocus();
void SetNeedsPrePaintSubtreeWalk(
bool needs_effective_allowed_touch_action_update) {
needs_effective_allowed_touch_action_update_ =
......@@ -218,15 +221,11 @@ class CORE_EXPORT DisplayLockContext final
// Request that this context be locked. Called when style determines that the
// subtree rooted at this element should be skipped, unless things like
// viewport intersection prevent it from doing so.
bool RequestLock(uint16_t activation_mask);
void RequestLock(uint16_t activation_mask);
// Request that this context be unlocked. Called when style determines that
// the subtree rooted at this element should be rendered.
void RequestUnlock();
// Returns true if this lock has been activated and the activation has not yet
// been cleared.
bool IsActivated() const;
// Records the locked context counts on the document as well as context that
// block all activation.
void UpdateDocumentBookkeeping(bool was_locked,
......@@ -304,6 +303,10 @@ class CORE_EXPORT DisplayLockContext final
// Unlocks the context.
void Unlock();
// Determines if the subtree has focus. This is a linear walk from the focused
// element to its root element.
void DetermineIfSubtreeHasFocus();
WeakMember<Element> element_;
WeakMember<Document> document_;
ESubtreeVisibility state_ = ESubtreeVisibility::kVisible;
......@@ -343,9 +346,6 @@ class CORE_EXPORT DisplayLockContext final
uint16_t activatable_mask_ =
static_cast<uint16_t>(DisplayLockActivationReason::kAny);
// State that tracks whether we've been activated.
bool is_activated_ = false;
// Is set to true if we are registered for lifecycle notifications.
bool is_registered_for_lifecycle_notifications_ = false;
......@@ -358,6 +358,18 @@ class CORE_EXPORT DisplayLockContext final
// Lock has been requested.
bool is_locked_ = false;
enum class RenderAffectingState : int {
kLockRequested,
kIntersectsViewport,
kSubtreeHasFocus,
kNumRenderAffectingStates
};
void SetRenderAffectingState(RenderAffectingState state, bool flag);
void NotifyRenderAffectingStateChanged();
bool render_affecting_state_[static_cast<int>(
RenderAffectingState::kNumRenderAffectingStates)] = {false};
// TODO(vmpstr): This is only needed while we're still sending activation
// events.
base::WeakPtrFactory<DisplayLockContext> weak_factory_{this};
......
......@@ -142,7 +142,7 @@ class DisplayLockContextTest : public testing::Test,
}
void UnlockImmediate(DisplayLockContext* context) {
context->RequestUnlock();
context->SetRequestedState(ESubtreeVisibility::kVisible);
}
bool GraphicsLayerNeedsCollection(DisplayLockContext* context) const {
......@@ -1641,10 +1641,9 @@ class DisplayLockContextRenderingTest : public RenderingTest,
bool IsObservingLifecycle(DisplayLockContext* context) const {
return context->is_registered_for_lifecycle_notifications_;
}
bool IsActivated(DisplayLockContext* context) const {
return context->IsActivated();
void LockImmediate(DisplayLockContext* context) {
context->SetRequestedState(ESubtreeVisibility::kHidden);
}
void LockImmediate(DisplayLockContext* context) { context->RequestLock(0u); }
};
TEST_F(DisplayLockContextRenderingTest, FrameDocumentRemovedWhileAcquire) {
......@@ -1812,7 +1811,6 @@ TEST_F(DisplayLockContextRenderingTest,
// Verify lock state.
auto* inner_context = inner_element->GetDisplayLockContext();
ASSERT_TRUE(inner_context);
EXPECT_TRUE(IsActivated(inner_context));
EXPECT_FALSE(inner_context->IsLocked());
// Lock outer.
......@@ -1887,7 +1885,6 @@ TEST_F(DisplayLockContextRenderingTest,
EXPECT_FALSE(IsObservingLifecycle(inner_context));
// Also we should still be activated and unlocked.
EXPECT_TRUE(IsActivated(inner_context));
EXPECT_FALSE(inner_context->IsLocked());
// Everything should be layout clean.
......@@ -1946,7 +1943,6 @@ TEST_F(DisplayLockContextRenderingTest, NestedLockDoesHideWhenItIsOffscreen) {
// Verify lock state.
auto* inner_context = inner_element->GetDisplayLockContext();
ASSERT_TRUE(inner_context);
EXPECT_TRUE(IsActivated(inner_context));
EXPECT_FALSE(inner_context->IsLocked());
// Lock outer.
......@@ -2023,7 +2019,6 @@ TEST_F(DisplayLockContextRenderingTest, NestedLockDoesHideWhenItIsOffscreen) {
EXPECT_TRUE(IsObservingLifecycle(inner_context));
// We're unlocked for now.
EXPECT_TRUE(IsActivated(inner_context));
EXPECT_FALSE(inner_context->IsLocked());
// Everything should be layout clean.
......@@ -2041,7 +2036,6 @@ TEST_F(DisplayLockContextRenderingTest, NestedLockDoesHideWhenItIsOffscreen) {
EXPECT_FALSE(IsObservingLifecycle(inner_context));
// We're locked.
EXPECT_FALSE(IsActivated(inner_context));
EXPECT_TRUE(inner_context->IsLocked());
}
} // namespace blink
......@@ -345,4 +345,23 @@ bool DisplayLockUtilities::IsInLockedSubtreeCrossingFrames(
return false;
}
void DisplayLockUtilities::ElementLostFocus(Element* element) {
if (!RuntimeEnabledFeatures::CSSSubtreeVisibilityEnabled())
return;
for (; element; element = FlatTreeTraversal::ParentElement(*element)) {
auto* context = element->GetDisplayLockContext();
if (context)
context->NotifySubtreeLostFocus();
}
}
void DisplayLockUtilities::ElementGainedFocus(Element* element) {
if (!RuntimeEnabledFeatures::CSSSubtreeVisibilityEnabled())
return;
for (; element; element = FlatTreeTraversal::ParentElement(*element)) {
auto* context = element->GetDisplayLockContext();
if (context)
context->NotifySubtreeGainedFocus();
}
}
} // namespace blink
......@@ -76,6 +76,11 @@ class CORE_EXPORT DisplayLockUtilities {
// Returns true if the element is in a locked subtree (or is self-locked with
// no self-updates). This crosses frames while navigating the ancestor chain.
static bool IsInLockedSubtreeCrossingFrames(const Node& node);
// Called when the focused element changes. These functions update locks to
// ensure that focused element ancestors remain unlocked for 'auto' state.
static void ElementLostFocus(Element*);
static void ElementGainedFocus(Element*);
};
} // namespace blink
......
......@@ -5210,6 +5210,8 @@ bool Document::SetFocusedElement(Element* new_focused_element,
old_focused_element->SetFocused(false, params.type);
old_focused_element->SetHasFocusWithinUpToAncestor(false, ancestor);
DisplayLockUtilities::ElementLostFocus(old_focused_element);
// Dispatch the blur event and let the node do any other blur related
// activities (important for text fields)
// If page lost focus, blur event will have already been dispatched
......@@ -5263,6 +5265,7 @@ bool Document::SetFocusedElement(Element* new_focused_element,
focused_element_->SetFocused(true, params.type);
focused_element_->SetHasFocusWithinUpToAncestor(true, ancestor);
DisplayLockUtilities::ElementGainedFocus(focused_element_.Get());
// Element::setFocused for frames can dispatch events.
if (focused_element_ != new_focused_element) {
......
<!doctype HTML>
<html class="reftest-wait">
<meta charset="utf8">
<title>subtree-visibility hidden-matchable + focus</title>
<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
<link rel="help" href="https://github.com/WICG/display-locking">
<link rel="match" href="../spacer-and-container-ref.html">
<meta name="assert" content="focus does not scroll or focus element under hidden-matchable">
<script src="/common/reftest-wait.js"></script>
<style>
.spacer {
width: 150px;
height: 3000px;
background: lightblue;
}
#container {
width: 150px;
height: 150px;
background: lightblue;
}
#target {
position: relative;
top: 75px;
width: 50px;
height: 50px;
background: red;
}
.hidden {
subtree-visibility: hidden-matchable;
}
</style>
<div class=spacer></div>
<div id=container class=hidden>
<div id=target tabindex=0></div>
</div>
<script>
function runTest() {
document.getElementById("target").focus();
requestAnimationFrame(takeScreenshot);
}
window.onload = requestAnimationFrame(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
runTest();
});
});
});
</script>
</html>
......@@ -4,7 +4,6 @@
<title>Subtree Visibility: hit testing</title>
<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
<link rel="help" href="https://github.com/WICG/display-locking">
<link rel="match" href="scroll-into-view-ref.html">
<meta name="assert" content="subtree-visibility hidden prevents hit-testing in the subtree">
<script src="/resources/testharness.js"></script>
......
......@@ -4,7 +4,6 @@
<title>Subtree Visibility: hit testing (composited)</title>
<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
<link rel="help" href="https://github.com/WICG/display-locking">
<link rel="match" href="scroll-into-view-ref.html">
<meta name="assert" content="subtree-visibility hidden prevents hit-testing in the subtree">
<script src="/resources/testharness.js"></script>
......
......@@ -4,7 +4,6 @@
<title>Subtree Visibility: hit testing (composited child)</title>
<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
<link rel="help" href="https://github.com/WICG/display-locking">
<link rel="match" href="scroll-into-view-ref.html">
<meta name="assert" content="subtree-visibility hidden prevents hit-testing in the subtree">
<script src="/resources/testharness.js"></script>
......
......@@ -4,7 +4,6 @@
<title>Subtree Visibility: hit testing (composited with a composited child)</title>
<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
<link rel="help" href="https://github.com/WICG/display-locking">
<link rel="match" href="scroll-into-view-ref.html">
<meta name="assert" content="subtree-visibility hidden prevents hit-testing in the subtree">
<script src="/resources/testharness.js"></script>
......
<!doctype HTML>
<html class="reftest-wait">
<meta charset="utf8">
<title>CSS Subtree Visibility: hidden + scrollIntoView (reference)</title>
<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
<link rel="help" href="https://github.com/WICG/display-locking">
<script src="/common/reftest-wait.js"></script>
<style>
.spacer {
height: 1000px;
background: lightgreen;
}
#container {
position: relative;
width: 150px;
background: lightblue;
}
#child {
width: 150px;
height: 300px;
}
#target {
position: absolute;
bottom: 0;
width: 50px;
height: 50px;
background: red;
}
</style>
<div>top of the page</div>
<div class=spacer></div>
<div id=container>
<div id=child></div>
<div id=target tabindex=0></div>
</div>
<div class=spacer></div>
<div>bottom of the page</div>
<script>
function runReference() {
document.getElementById("target").focus();
requestAnimationFrame(takeScreenshot);
}
window.onload = requestAnimationFrame(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
runReference();
});
});
});
</script>
</html>
<!doctype HTML>
<html class="reftest-wait">
<meta charset="utf8">
<title>CSS Subtree Visibility: auto + focus</title>
<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
<link rel="help" href="https://github.com/WICG/display-locking">
<link rel="match" href="subtree-visibility-069-ref.html">
<meta name="assert" content="focus() can focus auto skipped subtree elements">
<meta name="assert" content="focus() scrolls after removing contain:size">
<script src="/common/reftest-wait.js"></script>
<style>
.spacer {
height: 1000px;
background: lightgreen;
}
#container {
width: 150px;
background: lightblue;
contain-intrinsic-size: 50px 150px;
}
#child {
width: 150px;
height: 300px;
}
#target {
position: absolute;
bottom: 0;
width: 50px;
height: 50px;
background: red;
}
.auto {
subtree-visibility: auto;
}
</style>
<div>top of the page</div>
<div class=spacer></div>
<div id=container class=auto>
<div id=child></div>
<div id=target tabindex=0></div>
</div>
<div class=spacer></div>
<div>bottom of the page</div>
<script>
function runTest() {
document.getElementById("target").focus();
requestAnimationFrame(takeScreenshot);
}
window.onload = requestAnimationFrame(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
runTest();
});
});
});
</script>
</html>
<!doctype HTML>
<html class="reftest-wait">
<meta charset="utf8">
<title>CSS Subtree Visibility: auto + focus on display:none</title>
<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
<link rel="help" href="https://github.com/WICG/display-locking">
<link rel="match" href="spacer-and-container-ref.html">
<meta name="assert" content="focus ignores display:none element in an auto subtree">
<script src="/common/reftest-wait.js"></script>
<style>
.spacer {
width: 150px;
height: 3000px;
background: lightblue;
}
#container {
width: 150px;
height: 150px;
background: lightblue;
}
#target {
display: none;
position: relative;
top: 75px;
width: 50px;
height: 50px;
background: red;
}
.auto {
subtree-visibility: auto;
}
</style>
<div class=spacer></div>
<div id=container class=auto>
<div id=target tabindex=0></div>
</div>
<script>
function runTest() {
document.getElementById("target").focus();
requestAnimationFrame(takeScreenshot);
}
window.onload = requestAnimationFrame(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
runTest();
});
});
});
</script>
</html>
<!doctype HTML>
<html class="reftest-wait">
<meta charset="utf8">
<title>CSS Subtree Visibility: auto + focus on display:contents</title>
<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
<link rel="help" href="https://github.com/WICG/display-locking">
<link rel="match" href="spacer-and-container-ref.html">
<meta name="assert" content="focus ignores display:contents element in an auto subtree">
<script src="/common/reftest-wait.js"></script>
<style>
.spacer {
width: 150px;
height: 3000px;
background: lightblue;
}
#container {
width: 150px;
height: 150px;
background: lightblue;
}
#target {
display: contents;
position: relative;
top: 75px;
width: 50px;
height: 50px;
background: red;
}
.auto {
subtree-visibility: auto;
}
</style>
<div class=spacer></div>
<div id=container class=auto>
<div id=target tabindex=0></div>
</div>
<script>
function runTest() {
document.getElementById("target").focus();
requestAnimationFrame(takeScreenshot);
}
window.onload = requestAnimationFrame(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
runTest();
});
});
});
</script>
</html>
<!doctype HTML>
<html class="reftest-wait">
<meta charset="utf8">
<title>CSS Subtree Visibility: hidden + focus</title>
<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
<link rel="help" href="https://github.com/WICG/display-locking">
<link rel="match" href="spacer-and-container-ref.html">
<meta name="assert" content="focus ignores element in a hidden subtree">
<script src="/common/reftest-wait.js"></script>
<style>
.spacer {
width: 150px;
height: 3000px;
background: lightblue;
}
#container {
width: 150px;
height: 150px;
background: lightblue;
}
#target {
position: relative;
top: 75px;
width: 50px;
height: 50px;
background: red;
}
.hidden {
subtree-visibility: hidden;
}
</style>
<div class=spacer></div>
<div id=container class=hidden>
<div id=target tabindex=0></div>
</div>
<script>
function runTest() {
document.getElementById("target").focus();
requestAnimationFrame(takeScreenshot);
}
window.onload = requestAnimationFrame(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
runTest();
});
});
});
</script>
</html>
<!doctype HTML>
<html>
<meta charset="utf8">
<title>Subtree Visibility: off-screen focus</title>
<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
<link rel="help" href="https://github.com/WICG/display-locking">
<meta name="assert" content="subtree-visibility auto element remains non-skipped when elements in its subtree have focus.">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
body, html {
padding: 0;
margin: 0;
}
.spacer {
height: 3000px;
background: lightblue;
}
#container {
background: lightgreen;
contain-intrinsic-size: 50px 100px;
subtree-visibility: auto;
}
#focusable {
width: 10px;
height: 10px;
}
</style>
<div class=spacer></div>
<div id=container>
<div id=focusable tabindex=0></div>
</div>
<div class=spacer></div>
<script>
async_test((t) => {
// Initially container should be 3000px offscreen with contained height 100px.
function step1() {
const r = container.getBoundingClientRect();
t.step(() => {
assert_equals(r.y, 3000, "step1 offset");
assert_equals(r.height, 100, "step1 height");
});
focusable.focus();
requestAnimationFrame(step2);
}
// After focusing the subtree, the container should be somewhere closer than
// 3000px (scrolled into view) and the height should be 10px, since it no
// longer has containment.
function step2() {
const r = container.getBoundingClientRect();
t.step(() => {
assert_less_than(r.y, 3000, "step2 offset");
assert_equals(r.height, 10, "step2 height");
});
document.scrollingElement.scrollTop = 0;
requestAnimationFrame(step3);
}
// After scrolling the document back to the top, the container should be back
// at 3000px but because its subtree is still focused, it should have height
// 10px.
function step3() {
const r = container.getBoundingClientRect();
t.step(() => {
assert_equals(r.y, 3000, "step3 offset");
assert_equals(r.height, 10, "step3 height");
});
requestAnimationFrame(step4);
}
// This is a repeat of step3, to ensure that this is a stable state.
function step4() {
const r = container.getBoundingClientRect();
t.step(() => {
assert_equals(r.y, 3000, "step4 offset");
assert_equals(r.height, 10, "step4 height");
});
focusable.blur();
requestAnimationFrame(step5);
}
// After blurring the focused element, we should go back to the contained
// height of 100px.
function step5() {
const r = container.getBoundingClientRect();
t.step(() => {
assert_equals(r.y, 3000, "step5 offset");
assert_equals(r.height, 100, "step5 height");
});
t.done();
}
step1();
});
</script>
</html>
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