blob: 60d2e5c45d2a21c8c18e5ea28bb73c13e81aa5d7 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/css/container_query_evaluator.h"
#include "third_party/blink/renderer/core/css/container_query.h"
#include "third_party/blink/renderer/core/css/css_container_rule.h"
#include "third_party/blink/renderer/core/css/css_test_helpers.h"
#include "third_party/blink/renderer/core/css/style_engine.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
namespace blink {
class ContainerQueryEvaluatorTest : public PageTestBase,
private ScopedCSSContainerQueriesForTest {
public:
ContainerQueryEvaluatorTest() : ScopedCSSContainerQueriesForTest(true) {}
ContainerQuery* ParseContainer(String query) {
String rule = "@container " + query + " {}";
auto* style_rule = DynamicTo<StyleRuleContainer>(
css_test_helpers::ParseRule(GetDocument(), rule));
if (!style_rule)
return nullptr;
return &style_rule->GetContainerQuery();
}
bool Eval(String query,
double width,
double height,
PhysicalAxes contained_axes) {
ContainerQuery* container_query = ParseContainer(query);
DCHECK(container_query);
auto* evaluator = MakeGarbageCollected<ContainerQueryEvaluator>(
PhysicalSize(LayoutUnit(width), LayoutUnit(height)), contained_axes);
return evaluator->Eval(*container_query);
}
const PhysicalAxes none{kPhysicalAxisNone};
const PhysicalAxes both{kPhysicalAxisBoth};
const PhysicalAxes horizontal{kPhysicalAxisHorizontal};
const PhysicalAxes vertical{kPhysicalAxisVertical};
};
TEST_F(ContainerQueryEvaluatorTest, ContainmentMatch) {
{
String query = "(min-width: 100px)";
EXPECT_TRUE(Eval(query, 100.0, 100.0, horizontal));
EXPECT_TRUE(Eval(query, 100.0, 100.0, both));
EXPECT_FALSE(Eval(query, 100.0, 100.0, vertical));
EXPECT_FALSE(Eval(query, 100.0, 100.0, none));
EXPECT_FALSE(Eval(query, 99.0, 100.0, horizontal));
}
{
String query = "(min-height: 100px)";
EXPECT_TRUE(Eval(query, 100.0, 100.0, vertical));
EXPECT_TRUE(Eval(query, 100.0, 100.0, both));
EXPECT_FALSE(Eval(query, 100.0, 100.0, horizontal));
EXPECT_FALSE(Eval(query, 100.0, 100.0, none));
EXPECT_FALSE(Eval(query, 100.0, 99.0, vertical));
}
{
String query = "(min-width: 100px) and (min-height: 100px)";
EXPECT_TRUE(Eval(query, 100.0, 100.0, both));
EXPECT_FALSE(Eval(query, 100.0, 100.0, vertical));
EXPECT_FALSE(Eval(query, 100.0, 100.0, horizontal));
EXPECT_FALSE(Eval(query, 100.0, 100.0, none));
EXPECT_FALSE(Eval(query, 100.0, 99.0, both));
EXPECT_FALSE(Eval(query, 99.0, 100.0, both));
}
}
TEST_F(ContainerQueryEvaluatorTest, ContainerChanged) {
PhysicalSize size_100(LayoutUnit(100), LayoutUnit(100));
PhysicalSize size_200(LayoutUnit(200), LayoutUnit(200));
ContainerQuery* container_query_100 = ParseContainer("(min-width: 100px)");
ContainerQuery* container_query_200 = ParseContainer("(min-width: 200px)");
ASSERT_TRUE(container_query_100);
ASSERT_TRUE(container_query_200);
auto* evaluator =
MakeGarbageCollected<ContainerQueryEvaluator>(size_100, horizontal);
ASSERT_TRUE(evaluator);
EXPECT_TRUE(evaluator->EvalAndAdd(*container_query_100));
EXPECT_FALSE(evaluator->EvalAndAdd(*container_query_200));
EXPECT_FALSE(evaluator->ContainerChanged(size_100, horizontal));
EXPECT_TRUE(evaluator->EvalAndAdd(*container_query_100));
EXPECT_FALSE(evaluator->EvalAndAdd(*container_query_200));
EXPECT_TRUE(evaluator->ContainerChanged(size_200, horizontal));
EXPECT_TRUE(evaluator->EvalAndAdd(*container_query_100));
EXPECT_TRUE(evaluator->EvalAndAdd(*container_query_200));
EXPECT_FALSE(evaluator->ContainerChanged(size_200, horizontal));
EXPECT_TRUE(evaluator->EvalAndAdd(*container_query_100));
EXPECT_TRUE(evaluator->EvalAndAdd(*container_query_200));
EXPECT_TRUE(evaluator->ContainerChanged(size_200, vertical));
EXPECT_FALSE(evaluator->EvalAndAdd(*container_query_100));
EXPECT_FALSE(evaluator->EvalAndAdd(*container_query_200));
}
TEST_F(ContainerQueryEvaluatorTest, SizeInvalidation) {
SetBodyInnerHTML(R"HTML(
<style>
#container {
contain: size layout;
width: 500px;
height: 500px;
}
@container (min-width: 500px) {
div { z-index:1; }
}
</style>
<div id=container>
<div id=div></div>
<div id=div></div>
<div id=div></div>
<div id=div></div>
<div id=div></div>
<div id=div></div>
</div>
)HTML");
Element* container = GetDocument().getElementById("container");
ASSERT_TRUE(container);
ASSERT_TRUE(container->GetContainerQueryEvaluator());
{
// Causes re-layout, but the size does not change
container->SetInlineStyleProperty(CSSPropertyID::kFloat, "left");
unsigned before_count = GetStyleEngine().StyleForElementCount();
UpdateAllLifecyclePhasesForTest();
unsigned after_count = GetStyleEngine().StyleForElementCount();
// Only #container should be affected. In particular, we should not
// recalc any style for <div> children of #container.
EXPECT_EQ(1u, after_count - before_count);
}
{
// The size of the container changes, but it does not matter for
// the result of the query (min-width: 500px).
container->SetInlineStyleProperty(CSSPropertyID::kWidth, "600px");
unsigned before_count = GetStyleEngine().StyleForElementCount();
UpdateAllLifecyclePhasesForTest();
unsigned after_count = GetStyleEngine().StyleForElementCount();
// Only #container should be affected. In particular, we should not
// recalc any style for <div> children of #container.
EXPECT_EQ(1u, after_count - before_count);
}
}
TEST_F(ContainerQueryEvaluatorTest, DependentQueries) {
PhysicalSize size_100(LayoutUnit(100), LayoutUnit(100));
PhysicalSize size_150(LayoutUnit(150), LayoutUnit(150));
PhysicalSize size_200(LayoutUnit(200), LayoutUnit(200));
PhysicalSize size_300(LayoutUnit(300), LayoutUnit(300));
PhysicalSize size_400(LayoutUnit(400), LayoutUnit(400));
ContainerQuery* query_min_200px = ParseContainer("(min-width: 200px)");
ContainerQuery* query_max_300px = ParseContainer("(max-width: 300px)");
ASSERT_TRUE(query_min_200px);
auto* evaluator =
MakeGarbageCollected<ContainerQueryEvaluator>(size_100, horizontal);
evaluator->EvalAndAdd(*query_min_200px);
evaluator->EvalAndAdd(*query_max_300px);
// Updating with the same size as we initially had should not invalidate
// any query results.
EXPECT_FALSE(evaluator->ContainerChanged(size_100, horizontal));
// Makes no difference for either of (min-width: 200px), (max-width: 300px):
EXPECT_FALSE(evaluator->ContainerChanged(size_150, horizontal));
// (min-width: 200px) becomes true:
EXPECT_TRUE(evaluator->ContainerChanged(size_200, horizontal));
evaluator->EvalAndAdd(*query_min_200px);
evaluator->EvalAndAdd(*query_max_300px);
EXPECT_FALSE(evaluator->ContainerChanged(size_200, horizontal));
// Makes no difference for either of (min-width: 200px), (max-width: 300px):
EXPECT_FALSE(evaluator->ContainerChanged(size_300, horizontal));
// (max-width: 300px) becomes false:
EXPECT_TRUE(evaluator->ContainerChanged(size_400, horizontal));
}
TEST_F(ContainerQueryEvaluatorTest, EvaluatorOnDetachLayoutTree) {
SetBodyInnerHTML(R"HTML(
<style>
main {
display: block;
contain: size layout;
width: 500px;
height: 500px;
}
@container (min-width: 500px) {
div { --x:test; }
}
</style>
<main id=container1>
<div></div>
</main>
<main id=container2>
<div></div>
</main>
)HTML");
Element* container1 = GetDocument().getElementById("container1");
ASSERT_TRUE(container1);
ASSERT_TRUE(container1->GetContainerQueryEvaluator());
// DetachLayoutTree with performing_reattach=false:
container1->remove();
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(container1->GetContainerQueryEvaluator())
<< "ContainerQueryEvaluator should be removed";
// DetachLayoutTree with performing_reattach=true:
Element* container2 = GetDocument().getElementById("container2");
ASSERT_TRUE(container2);
ASSERT_TRUE(container2->GetContainerQueryEvaluator());
container2->SetInlineStyleProperty(CSSPropertyID::kDisplay, "none");
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(container2->GetContainerQueryEvaluator())
<< "ContainerQueryEvaluator should persist";
}
} // namespace blink