blob: db703f4997cddd6dea51863c6fd62731dcc06faa [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/dom/rule_matching.h"
#include <algorithm>
#include <memory>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/base/unused.h"
#include "cobalt/cssom/after_pseudo_element.h"
#include "cobalt/cssom/attribute_selector.h"
#include "cobalt/cssom/before_pseudo_element.h"
#include "cobalt/cssom/cascade_precedence.h"
#include "cobalt/cssom/child_combinator.h"
#include "cobalt/cssom/class_selector.h"
#include "cobalt/cssom/combinator.h"
#include "cobalt/cssom/combinator_visitor.h"
#include "cobalt/cssom/complex_selector.h"
#include "cobalt/cssom/compound_selector.h"
#include "cobalt/cssom/css_style_rule.h"
#include "cobalt/cssom/css_style_sheet.h"
#include "cobalt/cssom/descendant_combinator.h"
#include "cobalt/cssom/empty_pseudo_class.h"
#include "cobalt/cssom/following_sibling_combinator.h"
#include "cobalt/cssom/id_selector.h"
#include "cobalt/cssom/next_sibling_combinator.h"
#include "cobalt/cssom/not_pseudo_class.h"
#include "cobalt/cssom/property_value.h"
#include "cobalt/cssom/pseudo_element.h"
#include "cobalt/cssom/selector_tree.h"
#include "cobalt/cssom/selector_visitor.h"
#include "cobalt/cssom/simple_selector.h"
#include "cobalt/cssom/type_selector.h"
#include "cobalt/cssom/universal_selector.h"
#include "cobalt/dom/attr.h"
#include "cobalt/dom/document.h"
#include "cobalt/dom/dom_token_list.h"
#include "cobalt/dom/element.h"
#include "cobalt/dom/html_element.h"
#include "cobalt/dom/html_html_element.h"
#include "cobalt/dom/named_node_map.h"
#include "cobalt/dom/node_descendants_iterator.h"
#include "cobalt/dom/node_list.h"
#include "cobalt/dom/pseudo_element.h"
#include "cobalt/math/safe_integer_conversions.h"
namespace cobalt {
namespace dom {
namespace {
using cssom::SelectorTree;
struct NodeCombinatorType {
NodeCombinatorType(const SelectorTree::Node* node,
cssom::CombinatorType combinator_type)
: node(node), combinator_type(combinator_type) {}
const SelectorTree::Node* node;
cssom::CombinatorType combinator_type;
};
//////////////////////////////////////////////////////////////////////////
// Helper functions
//////////////////////////////////////////////////////////////////////////
// Matches an element against a selector. If the element doesn't match, returns
// NULL, otherwise returns the result of advancing the pointer to the element
// according to the combinators.
Element* MatchSelectorAndElement(cssom::Selector* selector, Element* element,
bool matching_combinators);
//////////////////////////////////////////////////////////////////////////
// Combinator matcher
//////////////////////////////////////////////////////////////////////////
class CombinatorMatcher : public cssom::CombinatorVisitor {
public:
explicit CombinatorMatcher(Element* element) : element_(element) {
DCHECK(element);
}
// Child combinator describes a childhood relationship between two elements.
// https://www.w3.org/TR/selectors4/#child-combinators
void VisitChildCombinator(cssom::ChildCombinator* child_combinator) override {
element_ = MatchSelectorAndElement(child_combinator->left_selector(),
element_->parent_element(), true);
}
// Next-sibling combinator describes that the elements represented by the two
// compound selectors share the same parent in the document tree and the
// element represented by the first compound selector immediately precedes the
// element represented by the second one.
// https://www.w3.org/TR/selectors4/#adjacent-sibling-combinators
void VisitNextSiblingCombinator(
cssom::NextSiblingCombinator* next_sibling_combinator) override {
element_ =
MatchSelectorAndElement(next_sibling_combinator->left_selector(),
element_->previous_element_sibling(), true);
}
// Descendant combinator describes an element that is the descendant of
// another element in the document tree.
// https://www.w3.org/TR/selectors4/#descendant-combinators
void VisitDescendantCombinator(
cssom::DescendantCombinator* descendant_combinator) override {
do {
element_ = element_->parent_element();
Element* element = MatchSelectorAndElement(
descendant_combinator->left_selector(), element_, true);
if (element) {
element_ = element;
return;
}
} while (element_);
}
// Following-sibling combinator describes that the elements represented by the
// two compound selectors share the same parent in the document tree and the
// element represented by the first compound selector precedes (not
// necessarily immediately) the element represented by the second one.
// https://www.w3.org/TR/selectors4/#general-sibling-combinators
void VisitFollowingSiblingCombinator(
cssom::FollowingSiblingCombinator* following_sibling_combinator)
override {
do {
element_ = element_->previous_element_sibling();
Element* element = MatchSelectorAndElement(
following_sibling_combinator->left_selector(), element_, true);
if (element) {
element_ = element;
return;
}
} while (element_);
}
Element* element() { return element_; }
private:
Element* element_;
DISALLOW_COPY_AND_ASSIGN(CombinatorMatcher);
};
//////////////////////////////////////////////////////////////////////////
// Selector matcher
//////////////////////////////////////////////////////////////////////////
class SelectorMatcher : public cssom::SelectorVisitor {
public:
explicit SelectorMatcher(Element* element)
: element_(element), matching_combinators_(false) {
DCHECK(element);
}
SelectorMatcher(Element* element, bool matching_combinators)
: element_(element), matching_combinators_(matching_combinators) {
DCHECK(element);
}
// The universal selector represents the qualified name of any element type.
// https://www.w3.org/TR/selectors4/#universal-selector
void VisitUniversalSelector(
cssom::UniversalSelector* universal_selector) override {}
// A type selector represents an instance of the element type in the document
// tree.
// https://www.w3.org/TR/selectors4/#type-selector
void VisitTypeSelector(cssom::TypeSelector* type_selector) override {
if (type_selector->element_name() != element_->local_name()) {
element_ = NULL;
}
}
// An attribute selector represents an element that has an attribute that
// matches the attribute represented by the attribute selector.
// https://www.w3.org/TR/selectors4/#attribute-selector
void VisitAttributeSelector(
cssom::AttributeSelector* attribute_selector) override {
if (!element_->HasAttribute(attribute_selector->attribute_name().c_str())) {
element_ = NULL;
return;
}
switch (attribute_selector->value_match_type()) {
case cssom::AttributeSelector::kNoMatch:
return;
case cssom::AttributeSelector::kEquals:
if (element_->GetAttribute(attribute_selector->attribute_name().c_str())
.value() != attribute_selector->attribute_value()) {
element_ = NULL;
}
return;
case cssom::AttributeSelector::kIncludes:
case cssom::AttributeSelector::kDashMatch:
case cssom::AttributeSelector::kBeginsWith:
case cssom::AttributeSelector::kEndsWith:
case cssom::AttributeSelector::kContains:
NOTIMPLEMENTED();
element_ = NULL;
return;
}
}
// The class selector represents an element belonging to the class identified
// by the identifier.
// https://www.w3.org/TR/selectors4/#class-selector
void VisitClassSelector(cssom::ClassSelector* class_selector) override {
if (!element_->class_list()->ContainsValid(class_selector->class_name())) {
element_ = NULL;
}
}
// An ID selector represents an element instance that has an identifier that
// matches the identifier in the ID selector.
// https://www.w3.org/TR/selectors4/#id-selector
void VisitIdSelector(cssom::IdSelector* id_selector) override {
if (id_selector->id() != element_->id()) {
element_ = NULL;
}
}
// The :active pseudo-class applies while an element is being activated by the
// user. For example, between the times the user presses the mouse button and
// releases it. On systems with more than one mouse button, :active applies
// only to the primary or primary activation button (typically the "left"
// mouse button), and any aliases thereof.
// https://www.w3.org/TR/selectors4/#active-pseudo
void VisitActivePseudoClass(cssom::ActivePseudoClass*) override {
NOTIMPLEMENTED();
element_ = NULL;
}
// The :empty pseudo-class represents an element that has no content children.
// https://www.w3.org/TR/selectors4/#empty-pseudo
void VisitEmptyPseudoClass(cssom::EmptyPseudoClass*) override {
if (!element_->IsEmpty()) {
element_ = NULL;
}
}
// The :focus pseudo-class applies while an element has the focus (accepts
// keyboard or mouse events, or other forms of input).
// https://www.w3.org/TR/selectors4/#focus-pseudo
void VisitFocusPseudoClass(cssom::FocusPseudoClass*) override {
if (!element_->HasFocus()) {
element_ = NULL;
}
}
// The :hover pseudo-class applies while the user designates an element with a
// pointing device, but does not necessarily activate it. For example, a
// visual user agent could apply this pseudo-class when the cursor (mouse
// pointer) hovers over a box generated by the element. Interactive user
// agents that cannot detect hovering due to hardware limitations (e.g., a pen
// device that does not detect hovering) are still conforming.
// https://www.w3.org/TR/selectors4/#hover-pseudo
void VisitHoverPseudoClass(cssom::HoverPseudoClass*) override {
if (!element_->AsHTMLElement() ||
!element_->AsHTMLElement()->IsDesignated()) {
element_ = NULL;
}
}
// The negation pseudo-class, :not(), is a functional pseudo-class taking a
// selector list as an argument. It represents an element that is not
// represented by its argument.
// https://www.w3.org/TR/selectors4/#negation-pseudo
void VisitNotPseudoClass(cssom::NotPseudoClass* not_pseudo_class) override {
if (MatchSelectorAndElement(not_pseudo_class->selector(), element_, true)) {
element_ = NULL;
}
}
// Pseudo elements doesn't affect whether the selector matches or not.
// The :after pseudo-element represents a generated element.
// https://www.w3.org/TR/CSS21/generate.html#before-after-content
void VisitAfterPseudoElement(cssom::AfterPseudoElement*) override {}
// The :before pseudo-element represents a generated element.
// https://www.w3.org/TR/CSS21/generate.html#before-after-content
void VisitBeforePseudoElement(cssom::BeforePseudoElement*) override {}
// A compound selector is a chain of simple selectors that are not separated
// by a combinator.
// https://www.w3.org/TR/selectors4/#compound
void VisitCompoundSelector(
cssom::CompoundSelector* compound_selector) override {
DCHECK_GT(compound_selector->simple_selectors().size(), 0U);
// Iterate through all the simple selectors. If any of the simple selectors
// doesn't match, the compound selector doesn't match.
for (cssom::CompoundSelector::SimpleSelectors::const_iterator
selector_iterator = compound_selector->simple_selectors().begin();
selector_iterator != compound_selector->simple_selectors().end();
++selector_iterator) {
element_ =
MatchSelectorAndElement(selector_iterator->get(), element_, false);
if (!element_) {
return;
}
}
// Check combinator.
if (matching_combinators_ && compound_selector->left_combinator()) {
CombinatorMatcher combinator_matcher(element_);
compound_selector->left_combinator()->Accept(&combinator_matcher);
element_ = combinator_matcher.element();
}
}
// A complex selector is a chain of one or more compound selectors separated
// by combinators.
// https://www.w3.org/TR/selectors4/#complex
void VisitComplexSelector(cssom::ComplexSelector* complex_selector) override {
element_ = MatchSelectorAndElement(complex_selector->last_selector(),
element_, true);
}
Element* element() const { return element_; }
private:
Element* element_;
bool matching_combinators_;
DISALLOW_COPY_AND_ASSIGN(SelectorMatcher);
};
//////////////////////////////////////////////////////////////////////////
// Helper functions
//////////////////////////////////////////////////////////////////////////
Element* MatchSelectorAndElement(cssom::Selector* selector, Element* element,
bool matching_combinators) {
DCHECK(selector);
if (!element) {
return NULL;
}
SelectorMatcher selector_matcher(element, matching_combinators);
selector->Accept(&selector_matcher);
return selector_matcher.element();
}
void GatherCandidateNodesFromSelectorNodesMap(
cssom::SimpleSelectorType simple_selector_type,
cssom::CombinatorType combinator_type,
const SelectorTree::Node* parent_node,
const SelectorTree::SelectorTextToNodesMap* map, base::Token key,
SelectorTree::NodePairs* candidate_nodes) {
SelectorTree::SelectorTextToNodesMap::const_iterator it = map->find(key);
if (it != map->end()) {
const SelectorTree::SimpleSelectorNodes& nodes = it->second;
for (const auto& nodes_iterator : nodes) {
if (nodes_iterator.simple_selector_type == simple_selector_type &&
nodes_iterator.combinator_type == combinator_type) {
candidate_nodes->emplace_back(parent_node, nodes_iterator.node);
}
}
}
}
void GatherCandidateNodesFromPseudoClassNodes(
cssom::PseudoClassType pseudo_class_type,
cssom::CombinatorType combinator_type,
const SelectorTree::Node* parent_node,
const SelectorTree::PseudoClassNodes& pseudo_class_nodes,
SelectorTree::NodePairs* candidate_nodes) {
for (const auto& nodes_iterator : pseudo_class_nodes) {
if (nodes_iterator.pseudo_class_type == pseudo_class_type &&
nodes_iterator.combinator_type == combinator_type) {
candidate_nodes->emplace_back(parent_node, nodes_iterator.node);
}
}
}
bool GatherNodeModificationsAndUpdateTargetNodes(
const SelectorTree::Nodes& source_nodes, SelectorTree::Nodes* target_nodes,
SelectorTree::Nodes* added_nodes, SelectorTree::Nodes* removed_nodes) {
if (source_nodes == *target_nodes) {
return false;
}
added_nodes->clear();
removed_nodes->clear();
if (target_nodes->empty()) {
// If the previous state of the nodes (|target_nodes|) is empty, then all
// nodes in the new state (|source_nodes|) are being added.
*added_nodes = source_nodes;
} else if (source_nodes.empty()) {
// If the new state (|source_nodes|) is empty, then all nodes in the
// previous state (|target_nodes|) are being removed.
*removed_nodes = *target_nodes;
} else {
// If neither the previous state nor the new state or empty, then the lists
// must be manually searched to determine what is being added and removed.
// Remove all nodes in |source_nodes| from |target_nodes|. Any of the nodes
// that are not found in |target_nodes| are newly added nodes.
size_t start_index = 0;
for (const auto& check_node : source_nodes) {
bool node_found = false;
for (size_t index = start_index; index < target_nodes->size(); ++index) {
if ((*target_nodes)[index] == check_node) {
if (index == start_index) {
++start_index;
}
node_found = true;
(*target_nodes)[index] = NULL;
break;
}
}
if (!node_found) {
added_nodes->push_back(check_node);
}
}
// Find nodes that remain in |target_nodes|. These did not exist in
// |source_nodes| and are newly removed nodes.
for (const auto& check_node : *target_nodes) {
if (check_node != NULL) {
removed_nodes->push_back(check_node);
}
}
}
*target_nodes = source_nodes;
// Return whether or not any modifications were found.
return !added_nodes->empty() || !removed_nodes->empty();
}
// Returns true if a matching node was added.
bool GatherMatchingNodes(const SelectorTree::Nodes& nodes,
cssom::CombinatorType combinator_type,
HTMLElement* element,
SelectorTree::NodePairs* scratchpad_node_pairs) {
bool matching_node_added = false;
// Gathering Phase: Generate candidate nodes from the nodes.
SelectorTree::NodePairs* candidate_node_pairs = scratchpad_node_pairs;
candidate_node_pairs->clear();
const std::vector<base::Token>& element_class_list =
element->class_list()->GetTokens();
// Don't retrieve the element's attributes until they are needed. Retrieving
// them typically requires the creation of a NamedNodeMap.
scoped_refptr<NamedNodeMap> element_attributes;
// Iterate through all nodes.
for (const auto& node : nodes) {
if (!node->HasCombinator(combinator_type)) {
continue;
}
// Gather candidate nodes in node's children under the given combinator.
const SelectorTree::SelectorTextToNodesMap* selector_nodes_map =
node->selector_nodes_map();
if (selector_nodes_map) {
// Universal selector.
if (node->HasSimpleSelector(cssom::kUniversalSelector, combinator_type)) {
GatherCandidateNodesFromSelectorNodesMap(
cssom::kUniversalSelector, combinator_type, node,
selector_nodes_map, base::Token(), candidate_node_pairs);
}
// Type selector.
if (node->HasSimpleSelector(cssom::kTypeSelector, combinator_type)) {
GatherCandidateNodesFromSelectorNodesMap(
cssom::kTypeSelector, combinator_type, node, selector_nodes_map,
element->local_name(), candidate_node_pairs);
}
// Attribute selector.
if (node->HasSimpleSelector(cssom::kAttributeSelector, combinator_type)) {
// If the element's attributes have not been retrieved yet, then
// retrieve them now.
if (!element_attributes) {
element_attributes = element->attributes();
}
for (unsigned int index = 0; index < element_attributes->length();
++index) {
GatherCandidateNodesFromSelectorNodesMap(
cssom::kAttributeSelector, combinator_type, node,
selector_nodes_map,
base::Token(element_attributes->Item(index)->name()),
candidate_node_pairs);
}
}
// Class selector.
if (node->HasSimpleSelector(cssom::kClassSelector, combinator_type)) {
for (const auto& element_class : element_class_list) {
GatherCandidateNodesFromSelectorNodesMap(
cssom::kClassSelector, combinator_type, node, selector_nodes_map,
element_class, candidate_node_pairs);
}
}
// Id selector.
if (node->HasSimpleSelector(cssom::kIdSelector, combinator_type)) {
GatherCandidateNodesFromSelectorNodesMap(
cssom::kIdSelector, combinator_type, node, selector_nodes_map,
element->id(), candidate_node_pairs);
}
}
// Only check for specific pseudo classes when the node has as least one.
if (node->HasAnyPseudoClass()) {
// Empty pseudo class.
if (node->HasPseudoClass(cssom::kEmptyPseudoClass, combinator_type) &&
element->IsEmpty()) {
GatherCandidateNodesFromPseudoClassNodes(
cssom::kEmptyPseudoClass, combinator_type, node,
node->pseudo_class_nodes(), candidate_node_pairs);
}
// Focus pseudo class.
if (node->HasPseudoClass(cssom::kFocusPseudoClass, combinator_type) &&
element->HasFocus()) {
GatherCandidateNodesFromPseudoClassNodes(
cssom::kFocusPseudoClass, combinator_type, node,
node->pseudo_class_nodes(), candidate_node_pairs);
}
// Hover pseudo class.
if (node->HasPseudoClass(cssom::kHoverPseudoClass, combinator_type) &&
element->IsDesignated()) {
GatherCandidateNodesFromPseudoClassNodes(
cssom::kHoverPseudoClass, combinator_type, node,
node->pseudo_class_nodes(), candidate_node_pairs);
}
// Not pseudo class.
if (node->HasPseudoClass(cssom::kNotPseudoClass, combinator_type)) {
GatherCandidateNodesFromPseudoClassNodes(
cssom::kNotPseudoClass, combinator_type, node,
node->pseudo_class_nodes(), candidate_node_pairs);
}
}
}
// Verifying Phase: Check all candidate nodes and run callback for matching
// nodes.
for (const auto& candidate_node_pair : *candidate_node_pairs) {
const SelectorTree::Node* candidate_node = candidate_node_pair.second;
// Verify that this node is a match:
// 1. If the candidate node's compound selector doesn't require a visit to
// verify the match, then the act of gathering the node as a candidate
// proved the match.
// 2. Otherwise, the node requires additional verification checks, so call
// MatchSelectorAndElement().
if (!candidate_node->compound_selector()
->requires_rule_matching_verification_visit() ||
MatchSelectorAndElement(candidate_node->compound_selector(), element,
false)) {
matching_node_added = true;
element->rule_matching_state()->matching_nodes_parent_nodes.push_back(
candidate_node_pair.first);
element->rule_matching_state()->matching_nodes.push_back(candidate_node);
}
}
return matching_node_added;
}
// Returns true if a matching node was removed.
bool RemoveNodesFromMatchingNodes(
const SelectorTree::Nodes& parent_nodes_to_remove,
SelectorTree::Nodes* parent_nodes, SelectorTree::Nodes* matching_nodes) {
DCHECK_EQ(parent_nodes->size(), matching_nodes->size());
bool node_removed = false;
// Find any |parent_nodes| that are being removed. Remove these from both
// |parent_nodes| and |matching_nodes|, as these two containers are kept in
// sync.
for (const auto& parent_node_to_remove : parent_nodes_to_remove) {
for (size_t index = 0; index < parent_nodes->size();) {
if (parent_node_to_remove == (*parent_nodes)[index]) {
node_removed = true;
parent_nodes->erase(parent_nodes->begin() + index);
matching_nodes->erase(matching_nodes->begin() + index);
} else {
++index;
}
}
}
return node_removed;
}
// Returns true if the matching nodes were modified.
bool UpdateMatchingNodesFromNodeModifications(
const SelectorTree::Nodes& added_nodes,
const SelectorTree::Nodes& removed_nodes,
cssom::CombinatorType combinator_type, HTMLElement* element,
SelectorTree::NodePairs* scratchpad_node_pairs) {
// First gather the matching nodes found in |added_nodes|.
bool matching_nodes_modified = GatherMatchingNodes(
added_nodes, combinator_type, element, scratchpad_node_pairs);
// Now remove any matching nodes found in |removed_nodes|.
HTMLElement::RuleMatchingState* element_matching_state =
element->rule_matching_state();
matching_nodes_modified |= RemoveNodesFromMatchingNodes(
removed_nodes, &element_matching_state->matching_nodes_parent_nodes,
&element_matching_state->matching_nodes);
return matching_nodes_modified;
}
void UpdateRuleMatchingStateCombinatorNodes(
HTMLElement::RuleMatchingState* matching_state,
cssom::CombinatorType combinator_type) {
// Only descendant and following sibling combinators are supported.
DCHECK(combinator_type == cssom::kDescendantCombinator ||
combinator_type == cssom::kFollowingSiblingCombinator);
if (!matching_state) {
return;
}
DCHECK(matching_state->is_set);
bool& is_dirty = combinator_type == cssom::kDescendantCombinator
? matching_state->are_descendant_nodes_dirty
: matching_state->are_following_sibling_nodes_dirty;
if (!is_dirty) {
return;
}
const SelectorTree::Nodes& base_combinator_nodes =
combinator_type == cssom::kDescendantCombinator
? matching_state->parent_descendant_nodes
: matching_state->previous_sibling_following_sibling_nodes;
SelectorTree::Nodes& combinator_nodes =
combinator_type == cssom::kDescendantCombinator
? matching_state->descendant_nodes
: matching_state->following_sibling_nodes;
// First copy the base combinator nodes and then add any matching nodes that
// have the required combinator and are not already included within the list.
combinator_nodes = base_combinator_nodes;
for (const auto& node : matching_state->matching_nodes) {
if (node->HasCombinator(combinator_type) &&
std::find(base_combinator_nodes.begin(), base_combinator_nodes.end(),
node) == base_combinator_nodes.end()) {
combinator_nodes.push_back(node);
}
}
is_dirty = false;
}
// Returns true if the matching state was modified.
bool UpdateElementRuleMatchingState(HTMLElement* element) {
SelectorTree* selector_tree = element->node_document()->selector_tree();
bool sibling_matching_active = selector_tree->has_sibling_combinators();
// Retrieve the parent and previous sibling of the current element.
HTMLElement* parent = element->parent_element()
? element->parent_element()->AsHTMLElement()
: NULL;
HTMLElement* previous_sibling =
sibling_matching_active && element->previous_element_sibling()
? element->previous_element_sibling()->AsHTMLElement()
: NULL;
// Retrieve the rule matching state of this element, its parent, and its
// previous sibling.
HTMLElement::RuleMatchingState* element_matching_state =
element->rule_matching_state();
HTMLElement::RuleMatchingState* parent_matching_state =
parent ? parent->rule_matching_state() : NULL;
HTMLElement::RuleMatchingState* previous_sibling_matching_state =
previous_sibling ? previous_sibling->rule_matching_state() : NULL;
// A parent must always have valid matching rules when its child is being
// updated.
DCHECK(!parent || parent->matching_rules_valid());
// Ensure that the previous sibling has updated matching rules. This is
// necessary because Document::UpdateComputedStyleOnElementAndAncestor() does
// not update previous siblings so it is possible for an element to have its
// computed style updated while previous siblings have invalid matching rules.
if (previous_sibling) {
UpdateElementMatchingRules(previous_sibling);
DCHECK(previous_sibling->matching_rules_valid());
}
// Ensure that the required combinator nodes of the parent and previous
// sibling are up to date. These are lazily updated, so they won't be updated
// until the first child/next sibling needs them.
UpdateRuleMatchingStateCombinatorNodes(parent_matching_state,
cssom::kDescendantCombinator);
UpdateRuleMatchingStateCombinatorNodes(previous_sibling_matching_state,
cssom::kFollowingSiblingCombinator);
bool matching_nodes_modified = !element_matching_state->is_set;
// Gather modifications between the element's previous state and current state
// for each combinator type, and use them to update the element's matching
// nodes.
SelectorTree::Nodes* added_nodes = selector_tree->scratchpad_nodes_1();
SelectorTree::Nodes* removed_nodes = selector_tree->scratchpad_nodes_2();
// Child combinator
if (parent_matching_state &&
GatherNodeModificationsAndUpdateTargetNodes(
parent_matching_state->matching_nodes,
&element_matching_state->parent_matching_nodes, added_nodes,
removed_nodes)) {
matching_nodes_modified |= UpdateMatchingNodesFromNodeModifications(
*added_nodes, *removed_nodes, cssom::kChildCombinator, element,
selector_tree->scratchpad_node_pairs());
}
// Descendant combinator
const SelectorTree::Nodes& parent_descendant_nodes =
parent_matching_state ? parent_matching_state->descendant_nodes
: selector_tree->root_nodes();
if (GatherNodeModificationsAndUpdateTargetNodes(
parent_descendant_nodes,
&element_matching_state->parent_descendant_nodes, added_nodes,
removed_nodes)) {
element_matching_state->are_descendant_nodes_dirty = true;
matching_nodes_modified |= UpdateMatchingNodesFromNodeModifications(
*added_nodes, *removed_nodes, cssom::kDescendantCombinator, element,
selector_tree->scratchpad_node_pairs());
}
// Siblings only need to be checked if they're active.
if (sibling_matching_active) {
const SelectorTree::Nodes empty_nodes{};
// Next sibling combinator
const SelectorTree::Nodes& previous_sibling_matching_nodes =
previous_sibling_matching_state
? previous_sibling_matching_state->matching_nodes
: empty_nodes;
if (GatherNodeModificationsAndUpdateTargetNodes(
previous_sibling_matching_nodes,
&element_matching_state->previous_sibling_matching_nodes,
added_nodes, removed_nodes)) {
matching_nodes_modified |= UpdateMatchingNodesFromNodeModifications(
*added_nodes, *removed_nodes, cssom::kNextSiblingCombinator, element,
selector_tree->scratchpad_node_pairs());
}
// Following sibling combinator
const SelectorTree::Nodes& previous_sibling_following_sibling_nodes =
previous_sibling_matching_state
? previous_sibling_matching_state->following_sibling_nodes
: empty_nodes;
if (GatherNodeModificationsAndUpdateTargetNodes(
previous_sibling_following_sibling_nodes,
&element_matching_state->previous_sibling_following_sibling_nodes,
added_nodes, removed_nodes)) {
element_matching_state->are_following_sibling_nodes_dirty = true;
matching_nodes_modified |= UpdateMatchingNodesFromNodeModifications(
*added_nodes, *removed_nodes, cssom::kFollowingSiblingCombinator,
element, selector_tree->scratchpad_node_pairs());
}
}
element_matching_state->is_set = true;
// Matching node modifications may impact combinator nodes, so they must be
// dirtied. However, they are lazily updated the first time that they are
// needed by a child/following sibling, rather than immediately updated. This
// prevents unnecessary updates of leaf elements and last sibling elements.
if (matching_nodes_modified) {
element_matching_state->are_descendant_nodes_dirty = true;
element_matching_state->are_following_sibling_nodes_dirty = true;
}
return matching_nodes_modified;
}
void UpdateElementMatchingRulesFromRuleMatchingState(HTMLElement* element) {
Document* element_document = element->node_document();
// Store the element's and its pseudo element's old matching rules so that
// they can be compared to the new matching rules after they are generated.
cssom::RulesWithCascadePrecedence* element_old_matching_rules =
element_document->scratchpad_html_element_matching_rules();
*element_old_matching_rules = std::move(*element->matching_rules());
element->matching_rules()->clear();
for (int type = 0; type < kMaxPseudoElementType; ++type) {
PseudoElement* pseudo_element =
element->pseudo_element(PseudoElementType(type));
if (pseudo_element) {
cssom::RulesWithCascadePrecedence* old_pseudo_element_matching_rules =
element_document->scratchpad_pseudo_element_matching_rules(
PseudoElementType(type));
*old_pseudo_element_matching_rules =
std::move(*pseudo_element->matching_rules());
pseudo_element->matching_rules()->clear();
}
}
// Walk all matching nodes, generating matching rules for the element and
// its pseudo elements from them.
for (const auto& node : element->rule_matching_state()->matching_nodes) {
// Determine the matching rules that this specific node targets. If the
// node does not have a pseudo element, then this will be the element's
// matching rules; otherwise, it'll be the matching rules associated with
// the pseudo element's type.
cssom::RulesWithCascadePrecedence* target_matching_rules;
cssom::PseudoElement* pseudo_element =
node->compound_selector()->pseudo_element();
if (!pseudo_element) {
target_matching_rules = element->matching_rules();
} else {
PseudoElementType pseudo_element_type = kNotPseudoElementType;
if (pseudo_element->AsAfterPseudoElement()) {
pseudo_element_type = kAfterPseudoElementType;
} else if (pseudo_element->AsBeforePseudoElement()) {
pseudo_element_type = kBeforePseudoElementType;
} else {
NOTREACHED();
}
// Make sure the pseudo element exists under element. If not, create it
// and clear out the old matching rules since the pseudo element had no
// pre-existing matching rules.
if (!element->pseudo_element(pseudo_element_type)) {
element->SetPseudoElement(
pseudo_element_type,
base::WrapUnique(new dom::PseudoElement(element)));
cssom::RulesWithCascadePrecedence* old_pseudo_element_matching_rules =
element_document->scratchpad_pseudo_element_matching_rules(
PseudoElementType(pseudo_element_type));
old_pseudo_element_matching_rules->clear();
}
target_matching_rules =
element->pseudo_element(pseudo_element_type)->matching_rules();
}
// Add all of the node's rules to the target matching rules.
for (const auto& weak_rule : node->rules()) {
const auto rule = weak_rule.get();
if (!rule) {
continue;
}
DCHECK(rule->parent_style_sheet());
cssom::CascadePrecedence precedence(
pseudo_element ? cssom::kNormalOverride
: rule->parent_style_sheet()->origin(),
node->cumulative_specificity(),
cssom::Appearance(rule->parent_style_sheet()->index(),
rule->index()));
target_matching_rules->push_back(std::make_pair(rule, precedence));
}
}
// Check for if the newly generated matching rules are different for the
// element or its pseudo elements.
if (*element_old_matching_rules != *element->matching_rules()) {
element->OnMatchingRulesModified();
}
for (int type = 0; type < kMaxPseudoElementType; ++type) {
PseudoElement* pseudo_element =
element->pseudo_element(PseudoElementType(type));
if (pseudo_element) {
cssom::RulesWithCascadePrecedence* old_pseudo_element_matching_rules =
element_document->scratchpad_pseudo_element_matching_rules(
PseudoElementType(type));
if (*old_pseudo_element_matching_rules !=
*pseudo_element->matching_rules()) {
pseudo_element->set_computed_style_invalid();
element->OnPseudoElementMatchingRulesModified();
}
}
}
}
} // namespace
//////////////////////////////////////////////////////////////////////////
// Public APIs
//////////////////////////////////////////////////////////////////////////
void UpdateElementMatchingRules(HTMLElement* element) {
DCHECK(element);
DCHECK(element->node_document());
if (element->matching_rules_valid()) {
return;
}
// Only update the matching rules if the rule matching state is modified.
if (UpdateElementRuleMatchingState(element)) {
UpdateElementMatchingRulesFromRuleMatchingState(element);
}
element->set_matching_rules_valid();
}
scoped_refptr<Element> QuerySelector(Node* node, const std::string& selectors,
cssom::CSSParser* css_parser) {
DCHECK(css_parser);
// Generate a rule with the given selectors and no style.
scoped_refptr<cssom::CSSRule> css_rule =
css_parser->ParseRule(selectors + " {}", node->GetInlineSourceLocation());
if (!css_rule) {
return NULL;
}
scoped_refptr<cssom::CSSStyleRule> css_style_rule =
css_rule->AsCSSStyleRule();
if (!css_style_rule) {
return NULL;
}
// Iterate through the descendants of the node and find the first matching
// element if any.
NodeDescendantsIterator iterator(node);
Node* child = iterator.First();
while (child) {
if (child->IsElement()) {
scoped_refptr<Element> element = child->AsElement();
if (MatchRuleAndElement(css_style_rule, element)) {
return element;
}
}
child = iterator.Next();
}
return NULL;
}
bool MatchRuleAndElement(cssom::CSSStyleRule* rule, Element* element) {
for (cssom::Selectors::const_iterator selector_iterator =
rule->selectors().begin();
selector_iterator != rule->selectors().end(); ++selector_iterator) {
DCHECK(*selector_iterator);
if (MatchSelectorAndElement(selector_iterator->get(), element, true)) {
return true;
}
}
return false;
}
scoped_refptr<NodeList> QuerySelectorAll(Node* node,
const std::string& selectors,
cssom::CSSParser* css_parser) {
DCHECK(css_parser);
scoped_refptr<NodeList> node_list = new NodeList();
// Generate a rule with the given selectors and no style.
scoped_refptr<cssom::CSSRule> css_rule =
css_parser->ParseRule(selectors + " {}", node->GetInlineSourceLocation());
if (!css_rule) {
return node_list;
}
scoped_refptr<cssom::CSSStyleRule> css_style_rule =
css_rule->AsCSSStyleRule();
if (!css_style_rule) {
return node_list;
}
// Iterate through the descendants of the node and find the matching elements.
NodeDescendantsIterator iterator(node);
Node* child = iterator.First();
while (child) {
if (child->IsElement()) {
scoped_refptr<Element> element = child->AsElement();
if (MatchRuleAndElement(css_style_rule, element)) {
node_list->AppendNode(element);
}
}
child = iterator.Next();
}
return node_list;
}
} // namespace dom
} // namespace cobalt