Skip to content

Commit

Permalink
[@container] Evaluate style() queries for custom properties
Browse files Browse the repository at this point in the history
The currently failing tests are due to issue 1220144.

Bug: 1302630
Change-Id: I0278b50fa3cd168b77f2bfa70deb08a0237693dc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3805335
Reviewed-by: Anders Hartvoll Ruud <andruud@chromium.org>
Commit-Queue: Rune Lillesveen <futhark@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1033407}
  • Loading branch information
Rune Lillesveen authored and Chromium LUCI CQ committed Aug 10, 2022
1 parent 1b0b506 commit 1e417ce
Show file tree
Hide file tree
Showing 13 changed files with 252 additions and 17 deletions.
2 changes: 2 additions & 0 deletions third_party/blink/renderer/core/css/container_query.cc
Expand Up @@ -20,6 +20,8 @@ ContainerSelector::ContainerSelector(AtomicString name,
physical_axes_ |= kPhysicalAxisHorizontal;
if (feature_flags & MediaQueryExpNode::kFeatureHeight)
physical_axes_ |= kPhysicalAxisVertical;
if (feature_flags & MediaQueryExpNode::kFeatureStyle)
has_style_query_ = true;
}

unsigned ContainerSelector::Type(WritingMode writing_mode) const {
Expand Down
8 changes: 8 additions & 0 deletions third_party/blink/renderer/core/css/container_query.h
Expand Up @@ -34,10 +34,18 @@ class CORE_EXPORT ContainerSelector {
// for this selector to match.
unsigned Type(WritingMode) const;

bool SelectsSizeContainers() const {
return physical_axes_ != kPhysicalAxisNone ||
logical_axes_ != kLogicalAxisNone;
}

bool SelectsStyleContainers() const { return has_style_query_; }

private:
AtomicString name_;
PhysicalAxes physical_axes_{kPhysicalAxisNone};
LogicalAxes logical_axes_{kLogicalAxisNone};
bool has_style_query_{false};
};

class CORE_EXPORT ContainerQuery final
Expand Down
29 changes: 22 additions & 7 deletions third_party/blink/renderer/core/css/container_query_evaluator.cc
Expand Up @@ -52,13 +52,13 @@ bool Matches(const ComputedStyle& style,

// static
Element* ContainerQueryEvaluator::FindContainer(
Element* context_element,
Element* starting_element,
const ContainerSelector& container_selector) {
// TODO(crbug.com/1213888): Cache results.
for (Element* element = context_element; element;
for (Element* element = starting_element; element;
element = element->ParentOrShadowHostElement()) {
if (const ComputedStyle* style = element->GetComputedStyle()) {
if (style->IsContainerForSizeContainerQueries() &&
if (style->StyleType() == kPseudoIdNone &&
Matches(*style, container_selector)) {
return element;
}
Expand All @@ -68,15 +68,30 @@ Element* ContainerQueryEvaluator::FindContainer(
return nullptr;
}

bool ContainerQueryEvaluator::EvalAndAdd(const StyleRecalcContext& context,
bool ContainerQueryEvaluator::EvalAndAdd(const Element& matching_element,
const StyleRecalcContext& context,
const ContainerQuery& query,
MatchResult& match_result) {
Element* container = FindContainer(context.container, query.Selector());
const ContainerSelector& selector = query.Selector();
Element* starting_element =
selector.SelectsSizeContainers()
? context.container
: matching_element.ParentOrShadowHostElement();
Element* container = FindContainer(starting_element, selector);
if (!container)
return false;
ContainerQueryEvaluator* evaluator = container->GetContainerQueryEvaluator();
if (!evaluator)
return false;
if (!evaluator) {
if (selector.SelectsSizeContainers() ||
!selector.SelectsStyleContainers()) {
return false;
}
evaluator = &container->EnsureContainerQueryEvaluator();
evaluator->SetData(container->GetDocument(), *container, PhysicalSize(),
kPhysicalAxisNone);
}
// TODO(crbug.com/1302630): style() queries should not compare with
// context.container.
Change change = (context.container == container)
? Change::kNearestContainer
: Change::kDescendantContainers;
Expand Down
Expand Up @@ -27,11 +27,12 @@ class ContainerSelector;
class CORE_EXPORT ContainerQueryEvaluator final
: public GarbageCollected<ContainerQueryEvaluator> {
public:
// Look for a container query container in the inclusive ancestor
// chain of `context_element`.
static Element* FindContainer(Element* context_element,
// Look for a container query container in the shadow-including inclusive
// ancestor chain of 'starting_element'.
static Element* FindContainer(Element* starting_element,
const ContainerSelector&);
static bool EvalAndAdd(const StyleRecalcContext&,
static bool EvalAndAdd(const Element& matching_element,
const StyleRecalcContext&,
const ContainerQuery&,
MatchResult&);

Expand Down
Expand Up @@ -20,6 +20,7 @@
#include "third_party/blink/renderer/core/dom/dom_token_list.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/dom/parent_node.h"
#include "third_party/blink/renderer/core/execution_context/security_context.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/html/html_div_element.h"
Expand Down Expand Up @@ -626,4 +627,42 @@ TEST_F(ContainerQueryEvaluatorTest, CustomPropertyStyleQuery) {
EXPECT_TRUE(Eval("style(--my-prop: 10px )", "--my-prop", "10px "));
}

TEST_F(ContainerQueryEvaluatorTest, FindContainer) {
SetBodyInnerHTML(R"HTML(
<div style="container-name:outer;container-type:size">
<div style="container-name:outer">
<div style="container-type: size">
<div>
<div></div>
</div>
</div>
</div>
</div>
)HTML");

UpdateAllLifecyclePhasesForTest();

Element* outer_size = ParentNode::firstElementChild(*GetDocument().body());
Element* outer = ParentNode::firstElementChild(*outer_size);
Element* inner_size = ParentNode::firstElementChild(*outer);
Element* inner = ParentNode::firstElementChild(*inner_size);

EXPECT_EQ(ContainerQueryEvaluator::FindContainer(
inner, ParseContainer("style(--foo: bar)")->Selector()),
inner);
EXPECT_EQ(
ContainerQueryEvaluator::FindContainer(
inner,
ParseContainer("(width > 100px) and style(--foo: bar)")->Selector()),
inner_size);
EXPECT_EQ(ContainerQueryEvaluator::FindContainer(
inner, ParseContainer("outer style(--foo: bar)")->Selector()),
outer);
EXPECT_EQ(
ContainerQueryEvaluator::FindContainer(
inner, ParseContainer("outer (width > 100px) and style(--foo: bar)")
->Selector()),
outer_size);
}

} // namespace blink
8 changes: 5 additions & 3 deletions third_party/blink/renderer/core/css/element_rule_collector.cc
Expand Up @@ -81,13 +81,14 @@ unsigned LinkMatchTypeFromInsideLink(EInsideLink inside_link) {
}

bool EvaluateAndAddContainerQueries(
const Element& matching_element,
const ContainerQuery& container_query,
const StyleRecalcContext& style_recalc_context,
MatchResult& result) {
for (const ContainerQuery* current = &container_query; current;
current = current->Parent()) {
if (!ContainerQueryEvaluator::EvalAndAdd(style_recalc_context, *current,
result)) {
if (!ContainerQueryEvaluator::EvalAndAdd(
matching_element, style_recalc_context, *current, result)) {
return false;
}
}
Expand Down Expand Up @@ -415,7 +416,8 @@ void ElementRuleCollector::CollectMatchingRulesForListInternal(
// elements when they depend on the originating element.
if (pseudo_style_request_.pseudo_id != kPseudoIdNone ||
result.dynamic_pseudo == kPseudoIdNone) {
if (!EvaluateAndAddContainerQueries(*container_query,
if (!EvaluateAndAddContainerQueries(context_.GetElement(),
*container_query,
style_recalc_context_, result_)) {
if (AffectsAnimations(rule_data))
result_.SetConditionallyAffectsAnimations();
Expand Down
8 changes: 8 additions & 0 deletions third_party/blink/renderer/core/css/media_query_exp.cc
Expand Up @@ -694,6 +694,14 @@ void MediaQueryFunctionExpNode::SerializeTo(StringBuilder& builder) const {
builder.Append(")");
}

MediaQueryExpNode::FeatureFlags MediaQueryFunctionExpNode::CollectFeatureFlags()
const {
FeatureFlags flags = MediaQueryUnaryExpNode::CollectFeatureFlags();
if (name_ == AtomicString("style"))
flags |= kFeatureStyle;
return flags;
}

void MediaQueryNotExpNode::SerializeTo(StringBuilder& builder) const {
builder.Append("not ");
Operand().SerializeTo(builder);
Expand Down
2 changes: 2 additions & 0 deletions third_party/blink/renderer/core/css/media_query_exp.h
Expand Up @@ -316,6 +316,7 @@ class CORE_EXPORT MediaQueryExpNode
kFeatureHeight = 1 << 3,
kFeatureInlineSize = 1 << 4,
kFeatureBlockSize = 1 << 5,
kFeatureStyle = 1 << 6,
};

using FeatureFlags = unsigned;
Expand Down Expand Up @@ -398,6 +399,7 @@ class CORE_EXPORT MediaQueryFunctionExpNode : public MediaQueryUnaryExpNode {

Type GetType() const override { return Type::kFunction; }
void SerializeTo(StringBuilder&) const override;
FeatureFlags CollectFeatureFlags() const override;

private:
AtomicString name_;
Expand Down
Expand Up @@ -1583,9 +1583,9 @@ StyleResolver::CascadedValuesForElement(Element* element, PseudoId pseudo_id) {
Element* StyleResolver::FindContainerForElement(
Element* element,
const ContainerSelector& container_selector) {
auto context = StyleRecalcContext::FromAncestors(*element);
return ContainerQueryEvaluator::FindContainer(context.container,
container_selector);
DCHECK(element);
return ContainerQueryEvaluator::FindContainer(
element->ParentOrShadowHostElement(), container_selector);
}

RuleIndexList* StyleResolver::PseudoCSSRulesForElement(
Expand Down
10 changes: 10 additions & 0 deletions third_party/blink/renderer/core/dom/element.cc
Expand Up @@ -6569,6 +6569,16 @@ ContainerQueryEvaluator* Element::GetContainerQueryEvaluator() const {
return nullptr;
}

ContainerQueryEvaluator& Element::EnsureContainerQueryEvaluator() {
ContainerQueryData& data = EnsureElementRareData().EnsureContainerQueryData();
ContainerQueryEvaluator* evaluator = data.GetContainerQueryEvaluator();
if (!evaluator) {
evaluator = MakeGarbageCollected<ContainerQueryEvaluator>();
data.SetContainerQueryEvaluator(evaluator);
}
return *evaluator;
}

bool Element::SkippedContainerStyleRecalc() const {
if (!RuntimeEnabledFeatures::CSSContainerSkipStyleRecalcEnabled())
return false;
Expand Down
1 change: 1 addition & 0 deletions third_party/blink/renderer/core/dom/element.h
Expand Up @@ -1138,6 +1138,7 @@ class CORE_EXPORT Element : public ContainerNode, public Animatable {

ContainerQueryData* GetContainerQueryData() const;
ContainerQueryEvaluator* GetContainerQueryEvaluator() const;
ContainerQueryEvaluator& EnsureContainerQueryEvaluator();
bool SkippedContainerStyleRecalc() const;

virtual void SetActive(bool active);
Expand Down
@@ -0,0 +1,96 @@
<!doctype html>
<title>CSS Container Queries Test: custom property style queries</title>
<link rel="help" href="https://drafts.csswg.org/css-contain-3/#style-container">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/cq-testcommon.js"></script>
<style>
#outer {
container-name: outer;
--inner: false;
--outer: true;
--inner-no-space:false;
--outer-no-space:true;
--inner-space-after:false ;
--outer-space-after:true ;
}
#inner {
--inner: true;
--outer: false;
--inner-no-space:true;
--outer-no-space:false;
--inner-space-after:true ;
--outer-space-after:false ;
}
</style>
<div id="outer">
<div id="inner">
<div id="target"></div>
</div>
</div>
<script>
function test_evaluation(query, expected) {
test((t) => {
let style_node = document.createElement('style');
t.add_cleanup(() => {
style_node.remove();
});
style_node.innerText = `@container ${query} { #target { --applied:true; } }`;

assert_equals(getComputedStyle(target).getPropertyValue('--applied'), '');
document.head.append(style_node);
assert_equals(getComputedStyle(target).getPropertyValue('--applied'), expected ? 'true' : '');
}, `${query}`);
}

setup(() => assert_implements_container_queries());

test_evaluation('style(--inner: true)', true);
test_evaluation('style(--inner:true)', true);
test_evaluation('style(--inner:true )', true);
test_evaluation('style(--inner: true )', true);
test_evaluation('style(--inner-no-space: true)', true);
test_evaluation('style(--inner-no-space:true)', true);
test_evaluation('style(--inner-no-space:true )', true);
test_evaluation('style(--inner-no-space: true )', true);
test_evaluation('style(--inner-space-after: true)', true);
test_evaluation('style(--inner-space-after:true)', true);
test_evaluation('style(--inner-space-after:true )', true);
test_evaluation('style(--inner-space-after: true )', true);
test_evaluation('style(--outer: true)', false);
test_evaluation('style(--outer:true)', false);
test_evaluation('style(--outer:true )', false);
test_evaluation('style(--outer: true )', false);
test_evaluation('style(--outer-no-space: true)', false);
test_evaluation('style(--outer-no-space:true)', false);
test_evaluation('style(--outer-no-space:true )', false);
test_evaluation('style(--outer-no-space: true )', false);
test_evaluation('style(--outer-space-after: true)', false);
test_evaluation('style(--outer-space-after:true)', false);
test_evaluation('style(--outer-space-after:true )', false);
test_evaluation('style(--outer-space-after: true )', false);
test_evaluation('outer style(--inner: true)', false);
test_evaluation('outer style(--inner:true)', false);
test_evaluation('outer style(--inner:true )', false);
test_evaluation('outer style(--inner: true )', false);
test_evaluation('outer style(--inner-no-space: true)', false);
test_evaluation('outer style(--inner-no-space:true)', false);
test_evaluation('outer style(--inner-no-space:true )', false);
test_evaluation('outer style(--inner-no-space: true )', false);
test_evaluation('outer style(--inner-space-after: true)', false);
test_evaluation('outer style(--inner-space-after:true)', false);
test_evaluation('outer style(--inner-space-after:true )', false);
test_evaluation('outer style(--inner-space-after: true )', false);
test_evaluation('outer style(--outer: true)', true);
test_evaluation('outer style(--outer:true)', true);
test_evaluation('outer style(--outer:true )', true);
test_evaluation('outer style(--outer: true )', true);
test_evaluation('outer style(--outer-no-space: true)', true);
test_evaluation('outer style(--outer-no-space:true)', true);
test_evaluation('outer style(--outer-no-space:true )', true);
test_evaluation('outer style(--outer-no-space: true )', true);
test_evaluation('outer style(--outer-space-after: true)', true);
test_evaluation('outer style(--outer-space-after:true)', true);
test_evaluation('outer style(--outer-space-after:true )', true);
test_evaluation('outer style(--outer-space-after: true )', true);
</script>
@@ -0,0 +1,51 @@
This is a testharness.js-based test.
FAIL style(--inner: true) assert_equals: expected "true" but got ""
FAIL style(--inner:true) assert_equals: expected "true" but got ""
FAIL style(--inner:true ) assert_equals: expected "true" but got ""
FAIL style(--inner: true ) assert_equals: expected "true" but got ""
PASS style(--inner-no-space: true)
PASS style(--inner-no-space:true)
FAIL style(--inner-no-space:true ) assert_equals: expected "true" but got ""
FAIL style(--inner-no-space: true ) assert_equals: expected "true" but got ""
FAIL style(--inner-space-after: true) assert_equals: expected "true" but got ""
FAIL style(--inner-space-after:true) assert_equals: expected "true" but got ""
PASS style(--inner-space-after:true )
PASS style(--inner-space-after: true )
PASS style(--outer: true)
PASS style(--outer:true)
PASS style(--outer:true )
PASS style(--outer: true )
PASS style(--outer-no-space: true)
PASS style(--outer-no-space:true)
PASS style(--outer-no-space:true )
PASS style(--outer-no-space: true )
PASS style(--outer-space-after: true)
PASS style(--outer-space-after:true)
PASS style(--outer-space-after:true )
PASS style(--outer-space-after: true )
PASS outer style(--inner: true)
PASS outer style(--inner:true)
PASS outer style(--inner:true )
PASS outer style(--inner: true )
PASS outer style(--inner-no-space: true)
PASS outer style(--inner-no-space:true)
PASS outer style(--inner-no-space:true )
PASS outer style(--inner-no-space: true )
PASS outer style(--inner-space-after: true)
PASS outer style(--inner-space-after:true)
PASS outer style(--inner-space-after:true )
PASS outer style(--inner-space-after: true )
FAIL outer style(--outer: true) assert_equals: expected "true" but got ""
FAIL outer style(--outer:true) assert_equals: expected "true" but got ""
FAIL outer style(--outer:true ) assert_equals: expected "true" but got ""
FAIL outer style(--outer: true ) assert_equals: expected "true" but got ""
PASS outer style(--outer-no-space: true)
PASS outer style(--outer-no-space:true)
FAIL outer style(--outer-no-space:true ) assert_equals: expected "true" but got ""
FAIL outer style(--outer-no-space: true ) assert_equals: expected "true" but got ""
FAIL outer style(--outer-space-after: true) assert_equals: expected "true" but got ""
FAIL outer style(--outer-space-after:true) assert_equals: expected "true" but got ""
PASS outer style(--outer-space-after:true )
PASS outer style(--outer-space-after: true )
Harness: the test ran to completion.

0 comments on commit 1e417ce

Please sign in to comment.