Commit aaa7f0cb authored by Fergal Daly's avatar Fergal Daly Committed by Commit Bot

CSS: Implement paying attention to partmap= attribute.

Add PartNames which represents a set of names and handles applying
part name maps.

Plumb this into StyleResolver to handle part name forwarding while
ascending through shadow hosts.

Add chrome-only layout tests since the spec is not final.

Bug: 805271
Change-Id: Iddd79aafee6ce79fd3fefc7e31dc81f083b880de
Reviewed-on: https://chromium-review.googlesource.com/997317
Commit-Queue: Fergal Daly <fergal@chromium.org>
Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Cr-Commit-Position: refs/heads/master@{#553988}
parent 1cc61059
TODO(crbug/805271): Move these tests to wpt/css/css-shadow-parts. They are
testing an implementation that differs from the draft spec, so they are chromium
only for now.
The contents of support/ is also forked temporarily.
<!DOCTYPE html>
<html>
<head>
<title>CSS Shadow Parts - Double forward</title>
<meta href="mailto:fergal@chromium.org" rel="author" title="Fergal Daly">
<link href="http://www.google.com/" rel="author" title="Google">
<link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="support/shadow-helper.js"></script>
</head>
<body>
<style>#c-e-outer::part(part-forwarded2) { color: green; }</style>
<script>installCustomElement("custom-element-inner", "custom-element-inner-template");</script>
<template id="custom-element-inner-template">
<style>span { color: red; }</style>
<span id="green_part" part="partp">This text</span>
</template>
<script>installCustomElement("custom-element-middle", "custom-element-middle-template");</script>
<template id="custom-element-middle-template"><custom-element-inner id="c-e-inner"></custom-element-inner></template>
<script>installCustomElement("custom-element-outer", "custom-element-outer-template");</script>
<template id="custom-element-outer-template"><custom-element-middle id="c-e-middle" partmap="partp part-forwarded1"></custom-element-middle></template>
The following text should be green:
<custom-element-outer id="c-e-outer" partmap="part-forwarded1 part-forwarded2"></custom-element-outer>
<script type="text/javascript">
"use strict";
const colorGreen = "rgb(0, 128, 0)";
test(function() {
var el = getElementByShadowIds(document, ["c-e-outer", "c-e-middle", "c-e-inner", "green_part"]);
assert_equals(window.getComputedStyle(el).color, colorGreen);
}, "Part in inner host is forwarded through the middle host for styling by document style sheet");
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>CSS Shadow Parts - Precedence part vs part</title>
<meta href="mailto:fergal@chromium.org" rel="author" title="Fergal Daly">
<link href="http://www.google.com/" rel="author" title="Google">
<link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="support/shadow-helper.js"></script>
</head>
<body>
<style>#c-e-outer::part(part-forwarded) { color: green; }</style>
<script>installCustomElement("custom-element-inner", "custom-element-inner-template");</script>
<template id="custom-element-inner-template">
<style>span { color: blue; }</style>
<span id="green_part" part="partp">This text</span>
</template>
<script>installCustomElement("custom-element-outer", "custom-element-outer-template");</script>
<template id="custom-element-outer-template">
<style>#c-e-inner::part(partp) { color: red; }</style>
<custom-element-inner id="c-e-inner"></custom-element-inner>
</template>
The following text should be green:
<custom-element-outer id="c-e-outer" partmap="partp part-forwarded"></custom-element-outer>
<script type="text/javascript">
"use strict";
const colorGreen = "rgb(0, 128, 0)";
test(function() {
var el = getElementByShadowIds(document, ["c-e-outer", "c-e-inner", "green_part"]);
assert_equals(window.getComputedStyle(el).color, colorGreen);
}, "Style from document overrides style from outer CE");
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>CSS Shadow Parts - Simple forward shorthand</title>
<meta href="mailto:fergal@chromium.org" rel="author" title="Fergal Daly">
<link href="http://www.google.com/" rel="author" title="Google">
<link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="support/shadow-helper.js"></script>
</head>
<body>
<style>#c-e-outer::part(partp) { color: green; }</style>
<script>installCustomElement("custom-element-inner", "custom-element-inner-template");</script>
<template id="custom-element-inner-template">
<style>span { color: red; }</style>
<span id="green_part" part="partp">This text</span>
</template>
<script>installCustomElement("custom-element-outer", "custom-element-outer-template");</script>
<template id="custom-element-outer-template"><custom-element-inner id="c-e-inner"></custom-element-inner></template>
The following text should be green:
<custom-element-outer id="c-e-outer" partmap="partp"></custom-element-outer>
<script type="text/javascript">
"use strict";
const colorGreen = "rgb(0, 128, 0)";
test(function() {
var el = getElementByShadowIds(document, ["c-e-outer", "c-e-inner", "green_part"]);
assert_equals(window.getComputedStyle(el).color, colorGreen);
}, "Part in inner host is forwarded, under the same name, for styling by document style sheet");
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>CSS Shadow Parts - Simple forward</title>
<meta href="mailto:fergal@chromium.org" rel="author" title="Fergal Daly">
<link href="http://www.google.com/" rel="author" title="Google">
<link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="support/shadow-helper.js"></script>
</head>
<body>
<style>#c-e-outer::part(part-forwarded) { color: green; }</style>
<script>installCustomElement("custom-element-inner", "custom-element-inner-template");</script>
<template id="custom-element-inner-template">
<style>span { color: red; }</style>
<span id="green_part" part="partp">This text</span>
</template>
<script>installCustomElement("custom-element-outer", "custom-element-outer-template");</script>
<template id="custom-element-outer-template"><custom-element-inner id="c-e-inner"></custom-element-inner></template>
The following text should be green:
<custom-element-outer id="c-e-outer" partmap="partp part-forwarded"></custom-element-outer>
<script type="text/javascript">
"use strict";
const colorGreen = "rgb(0, 128, 0)";
test(function() {
var el = getElementByShadowIds(document, ["c-e-outer", "c-e-inner", "green_part"]);
assert_equals(window.getComputedStyle(el).color, colorGreen);
}, "Part in inner host is forwarded for styling by document style sheet");
</script>
</body>
</html>
// Takes a root element and a list of ids of shadow host elements. Each id refers to a shadow host
// inside the previous id's shadow tree.
function getElementByShadowIds(root, ids) {
for (var i = 0; ;i++) {
var host = root.getElementById(ids[i]);
if (host == null) {
throw "No element found: i=" + i + " id=" + ids[i] + ". Root was " + root;
}
if (i == ids.length - 1) {
return host;
}
root = host.shadowRoot;
if (root == null) {
throw "No shadowRoot found: i=" + i + " id=" + ids[i] + ". Host was " + host;
}
}
}
// Installs a mininal custom element based on this template.
function installCustomElement(element_name, template_id) {
ceClass = class extends HTMLElement {
constructor() {
super();
var template = document.getElementById(template_id).content;
this.attachShadow({mode: 'open'}).appendChild(template.cloneNode(true));
}
};
window.customElements.define(element_name, ceClass);
}
......@@ -379,6 +379,8 @@ blink_core_sources("css") {
"parser/sizes_attribute_parser.h",
"parser/sizes_calc_parser.cc",
"parser/sizes_calc_parser.h",
"part_names.cc",
"part_names.h",
"properties/computed_style_utils.cc",
"properties/computed_style_utils.h",
"properties/css_parsing_utils.cc",
......
......@@ -116,7 +116,8 @@ template <typename RuleDataListType>
void ElementRuleCollector::CollectMatchingRulesForList(
const RuleDataListType* rules,
CascadeOrder cascade_order,
const MatchRequest& match_request) {
const MatchRequest& match_request,
PartNames* part_names) {
if (!rules)
return;
......@@ -126,6 +127,7 @@ void ElementRuleCollector::CollectMatchingRulesForList(
init.element_style = style_.get();
init.scrollbar = pseudo_style_request_.scrollbar;
init.scrollbar_part = pseudo_style_request_.scrollbar_part;
init.part_names = part_names;
SelectorChecker checker(init);
SelectorChecker::SelectorCheckingContext context(
context_.GetElement(), SelectorChecker::kVisitedMatchEnabled);
......@@ -249,11 +251,12 @@ void ElementRuleCollector::CollectMatchingShadowHostRules(
void ElementRuleCollector::CollectMatchingPartPseudoRules(
const MatchRequest& match_request,
PartNames& part_names,
CascadeOrder cascade_order) {
if (!RuntimeEnabledFeatures::CSSPartPseudoElementEnabled())
return;
CollectMatchingRulesForList(match_request.rule_set->PartPseudoRules(),
cascade_order, match_request);
cascade_order, match_request, &part_names);
}
template <class CSSRuleCollection>
......
......@@ -37,6 +37,7 @@ namespace blink {
class CSSStyleSheet;
class CSSRuleList;
class PartNames;
class RuleData;
class SelectorFilter;
class StaticCSSRuleList;
......@@ -133,6 +134,7 @@ class ElementRuleCollector {
void CollectMatchingShadowHostRules(const MatchRequest&,
CascadeOrder = kIgnoreCascadeOrder);
void CollectMatchingPartPseudoRules(const MatchRequest&,
PartNames&,
CascadeOrder = kIgnoreCascadeOrder);
void SortAndTransferMatchedRules();
void ClearMatchedRules();
......@@ -157,7 +159,8 @@ class ElementRuleCollector {
template <typename RuleDataListType>
void CollectMatchingRulesForList(const RuleDataListType*,
CascadeOrder,
const MatchRequest&);
const MatchRequest&,
PartNames* = nullptr);
void DidMatchRule(const RuleData&,
const SelectorChecker::MatchResult&,
......
// Copyright 2018 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/part_names.h"
#include "third_party/blink/renderer/core/dom/names_map.h"
#include "third_party/blink/renderer/core/dom/space_split_string.h"
namespace blink {
namespace {
// Adds the names to the set.
static void AddToSet(const SpaceSplitString& strings,
HashSet<AtomicString>* set) {
for (size_t i = 0; i < strings.size(); i++) {
set->insert(strings[i]);
}
}
} // namespace
PartNames::PartNames(const SpaceSplitString& names) {
AddToSet(names, &names_);
}
void PartNames::ApplyMap(const NamesMap& names_map) {
// TODO(crbug/805271): Don't apply mappings immediately, instead, queue them
// up and only apply them if Contains is actually called.
HashSet<AtomicString> new_names;
for (const AtomicString& name : names_) {
if (base::Optional<SpaceSplitString> mapped_names = names_map.Get(name))
AddToSet(mapped_names.value(), &new_names);
}
std::swap(names_, new_names);
}
bool PartNames::Contains(const AtomicString& name) {
return names_.Contains(name);
}
size_t PartNames::size() {
return names_.size();
}
} // namespace blink
// Copyright 2018 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PART_NAMES_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PART_NAMES_H_
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
class NamesMap;
class SpaceSplitString;
// Represents a set of part names.
class PartNames {
public:
PartNames();
explicit PartNames(const SpaceSplitString& names);
// Updates the set, using names_map to map all of the current names to a new
// set of names. Looks up each name in names_map and takes the union of all
// of the values (which are sets of names).
void ApplyMap(const NamesMap& names_map);
// Returns true of name is included in the set.
bool Contains(const AtomicString& name);
// Returns the number of part names in the set.
size_t size();
private:
HashSet<AtomicString> names_;
DISALLOW_COPY_AND_ASSIGN(PartNames);
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PART_NAMES_H_
......@@ -33,6 +33,7 @@
#include "third_party/blink/renderer/core/css/css_style_sheet.h"
#include "third_party/blink/renderer/core/css/font_face.h"
#include "third_party/blink/renderer/core/css/page_rule_collector.h"
#include "third_party/blink/renderer/core/css/part_names.h"
#include "third_party/blink/renderer/core/css/resolver/match_request.h"
#include "third_party/blink/renderer/core/css/rule_feature_set.h"
#include "third_party/blink/renderer/core/css/style_change_reason.h"
......@@ -267,6 +268,7 @@ void ScopedStyleResolver::CollectMatchingTreeBoundaryCrossingRules(
void ScopedStyleResolver::CollectMatchingPartPseudoRules(
ElementRuleCollector& collector,
PartNames& part_names,
CascadeOrder cascade_order) {
if (!RuntimeEnabledFeatures::CSSPartPseudoElementEnabled())
return;
......@@ -276,7 +278,8 @@ void ScopedStyleResolver::CollectMatchingPartPseudoRules(
DCHECK(sheet->ownerNode());
MatchRequest match_request(&sheet->Contents()->GetRuleSet(),
&scope_->RootNode(), sheet, sheet_index++);
collector.CollectMatchingPartPseudoRules(match_request, cascade_order);
collector.CollectMatchingPartPseudoRules(match_request, part_names,
cascade_order);
}
}
......
......@@ -40,6 +40,7 @@
namespace blink {
class PageRuleCollector;
class PartNames;
class StyleSheetContents;
// ScopedStyleResolver collects the style sheets that occur within a TreeScope
......@@ -71,6 +72,7 @@ class ScopedStyleResolver final
ElementRuleCollector&,
CascadeOrder = kIgnoreCascadeOrder);
void CollectMatchingPartPseudoRules(ElementRuleCollector&,
PartNames& part_names,
CascadeOrder = kIgnoreCascadeOrder);
void MatchPageRules(PageRuleCollector&);
void CollectFeaturesTo(RuleFeatureSet&,
......
......@@ -58,6 +58,7 @@
#include "third_party/blink/renderer/core/css/font_face.h"
#include "third_party/blink/renderer/core/css/media_query_evaluator.h"
#include "third_party/blink/renderer/core/css/page_rule_collector.h"
#include "third_party/blink/renderer/core/css/part_names.h"
#include "third_party/blink/renderer/core/css/properties/css_property.h"
#include "third_party/blink/renderer/core/css/resolver/animated_style_builder.h"
#include "third_party/blink/renderer/core/css/resolver/css_variable_resolver.h"
......@@ -76,6 +77,7 @@
#include "third_party/blink/renderer/core/dom/first_letter_pseudo_element.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/dom/space_split_string.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
......@@ -91,7 +93,10 @@
#include "third_party/blink/renderer/core/style_property_shorthand.h"
#include "third_party/blink/renderer/core/svg/svg_element.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
namespace blink {
......@@ -255,24 +260,39 @@ void StyleResolver::MatchPseudoPartRules(const Element& element,
if (!RuntimeEnabledFeatures::CSSPartPseudoElementEnabled())
return;
if (!element.HasPartName())
const SpaceSplitString* part_names = element.PartNames();
if (!part_names)
return;
TreeScope& tree_scope = element.GetTreeScope();
if (tree_scope == GetDocument().GetTreeScope())
PartNames current_names(*part_names);
// ::part selectors in the shadow host's scope and above can match this
// element.
Element* host = element.OwnerShadowHost();
if (!host)
return;
TreeScope* parent_tree_scope = &tree_scope;
// TODO(b/805271): We can terminate early if we pass through a host with no
// exported parts. Requires implementing partmap.
while ((parent_tree_scope = parent_tree_scope->ParentTreeScope())) {
if (ScopedStyleResolver* resolver =
parent_tree_scope->GetScopedStyleResolver()) {
while (current_names.size()) {
TreeScope& tree_scope = host->GetTreeScope();
if (ScopedStyleResolver* resolver = tree_scope.GetScopedStyleResolver()) {
collector.ClearMatchedRules();
resolver->CollectMatchingPartPseudoRules(collector);
resolver->CollectMatchingPartPseudoRules(collector, current_names);
collector.SortAndTransferMatchedRules();
collector.FinishAddingAuthorRulesForTreeScope();
}
// We have reached the top-level document.
if (!(host = host->OwnerShadowHost()))
return;
// After the direct host of the element, if the host doesn't forward any
// parts using partmap= then the element is unreachable from any scope above
// and we can stop.
const NamesMap* part_map = host->PartNamesMap();
if (!part_map)
return;
current_names.ApplyMap(*part_map);
}
}
......
......@@ -30,6 +30,7 @@
#include "third_party/blink/renderer/core/css/selector_checker.h"
#include "third_party/blink/renderer/core/css/css_selector_list.h"
#include "third_party/blink/renderer/core/css/part_names.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"
......@@ -1148,15 +1149,8 @@ bool SelectorChecker::CheckPseudoElement(const SelectorCheckingContext& context,
case CSSSelector::kPseudoPart:
if (!RuntimeEnabledFeatures::CSSPartPseudoElementEnabled())
return false;
if (const SpaceSplitString* part_names = element.PartNames()) {
if (part_names->Contains(selector.Argument())) {
// TODO(crbug/805271): Until partmap is implemented, only consider
// styling parts from scope directly containing the shadow host.
Element* host = element.OwnerShadowHost();
return host && host->GetTreeScope() == context.scope->GetTreeScope();
}
}
return false;
DCHECK(part_names_);
return part_names_->Contains(selector.Argument());
case CSSSelector::kPseudoPlaceholder:
if (ShadowRoot* root = element.ContainingShadowRoot()) {
return root->IsUserAgent() &&
......
......@@ -42,6 +42,7 @@ class ContainerNode;
class Element;
class LayoutScrollbar;
class ComputedStyle;
class PartNames;
class SelectorChecker {
STACK_ALLOCATED();
......@@ -84,6 +85,7 @@ class SelectorChecker {
ComputedStyle* element_style = nullptr;
Member<LayoutScrollbar> scrollbar = nullptr;
ScrollbarPart scrollbar_part = kNoPart;
PartNames* part_names = nullptr;
};
explicit SelectorChecker(const Init& init)
......@@ -91,7 +93,8 @@ class SelectorChecker {
is_ua_rule_(init.is_ua_rule),
element_style_(init.element_style),
scrollbar_(init.scrollbar),
scrollbar_part_(init.scrollbar_part) {}
scrollbar_part_(init.scrollbar_part),
part_names_(init.part_names) {}
// Wraps the current element and a CSSSelector and stores some other state of
// the selector matching process.
......@@ -202,6 +205,7 @@ class SelectorChecker {
ComputedStyle* element_style_;
Member<LayoutScrollbar> scrollbar_;
ScrollbarPart scrollbar_part_;
PartNames* part_names_;
DISALLOW_COPY_AND_ASSIGN(SelectorChecker);
};
......
......@@ -13,6 +13,7 @@
#include "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
......
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