/* * This source file is part of RmlUi, the HTML/CSS Interface Middleware * * For the latest information, see http://github.com/mikke89/RmlUi * * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd * Copyright (c) 2019 The RmlUi Team, and contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include "StyleSheetNode.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/Profiling.h" #include "StyleSheetFactory.h" #include "StyleSheetNodeSelector.h" #include namespace Rml { StyleSheetNode::StyleSheetNode() { CalculateAndSetSpecificity(); } StyleSheetNode::StyleSheetNode(StyleSheetNode* parent, const String& tag, const String& id, const StringList& classes, const StringList& pseudo_classes, const StructuralSelectorList& structural_selectors, bool child_combinator) : parent(parent), tag(tag), id(id), class_names(classes), pseudo_class_names(pseudo_classes), structural_selectors(structural_selectors), child_combinator(child_combinator) { CalculateAndSetSpecificity(); } StyleSheetNode::StyleSheetNode(StyleSheetNode* parent, String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes, StructuralSelectorList&& structural_selectors, bool child_combinator) : parent(parent), tag(std::move(tag)), id(std::move(id)), class_names(std::move(classes)), pseudo_class_names(std::move(pseudo_classes)), structural_selectors(std::move(structural_selectors)), child_combinator(child_combinator) { CalculateAndSetSpecificity(); } StyleSheetNode* StyleSheetNode::GetOrCreateChildNode(const StyleSheetNode& other) { // See if we match the target child for (const auto& child : children) { if (child->EqualRequirements(other.tag, other.id, other.class_names, other.pseudo_class_names, other.structural_selectors, other.child_combinator)) return child.get(); } // We don't, so create a new child auto child = MakeUnique(this, other.tag, other.id, other.class_names, other.pseudo_class_names, other.structural_selectors, other.child_combinator); StyleSheetNode* result = child.get(); children.push_back(std::move(child)); return result; } StyleSheetNode* StyleSheetNode::GetOrCreateChildNode(String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes, StructuralSelectorList&& structural_pseudo_classes, bool child_combinator) { // See if we match an existing child for (const auto& child : children) { if (child->EqualRequirements(tag, id, classes, pseudo_classes, structural_pseudo_classes, child_combinator)) return child.get(); } // We don't, so create a new child auto child = MakeUnique(this, std::move(tag), std::move(id), std::move(classes), std::move(pseudo_classes), std::move(structural_pseudo_classes), child_combinator); StyleSheetNode* result = child.get(); children.push_back(std::move(child)); return result; } // Merges an entire tree hierarchy into our hierarchy. void StyleSheetNode::MergeHierarchy(StyleSheetNode* node, int specificity_offset) { RMLUI_ZoneScoped; // Merge the other node's properties into ours. properties.Merge(node->properties, specificity_offset); for (const auto& other_child : node->children) { StyleSheetNode* local_node = GetOrCreateChildNode(*other_child); local_node->MergeHierarchy(other_child.get(), specificity_offset); } } UniquePtr StyleSheetNode::DeepCopy(StyleSheetNode* in_parent) const { RMLUI_ZoneScoped; auto node = MakeUnique(in_parent, tag, id, class_names, pseudo_class_names, structural_selectors, child_combinator); node->properties = properties; node->children.resize(children.size()); for (size_t i = 0; i < children.size(); i++) { node->children[i] = children[i]->DeepCopy(node.get()); } return node; } // Builds up a style sheet's index recursively. void StyleSheetNode::BuildIndex(StyleSheet::NodeIndex& styled_node_index) const { // If this has properties defined, then we insert it into the styled node index. if(properties.GetNumProperties() > 0) { // The keys of the node index is a hashed combination of tag and id. These are used for fast lookup of applicable nodes. size_t node_hash = StyleSheet::NodeHash(tag, id); StyleSheet::NodeList& nodes = styled_node_index[node_hash]; auto it = std::find(nodes.begin(), nodes.end(), this); if(it == nodes.end()) nodes.push_back(this); } for (auto& child : children) { child->BuildIndex(styled_node_index); } } bool StyleSheetNode::SetStructurallyVolatileRecursive(bool ancestor_is_structural_pseudo_class) { // If any ancestor or descendant is a structural pseudo class, then we are structurally volatile. bool self_is_structural_pseudo_class = (!structural_selectors.empty()); // Check our children for structural pseudo-classes. bool descendant_is_structural_pseudo_class = false; for (auto& child : children) { if (child->SetStructurallyVolatileRecursive(self_is_structural_pseudo_class || ancestor_is_structural_pseudo_class)) descendant_is_structural_pseudo_class = true; } is_structurally_volatile = (self_is_structural_pseudo_class || ancestor_is_structural_pseudo_class || descendant_is_structural_pseudo_class); return (self_is_structural_pseudo_class || descendant_is_structural_pseudo_class); } bool StyleSheetNode::EqualRequirements(const String& _tag, const String& _id, const StringList& _class_names, const StringList& _pseudo_class_names, const StructuralSelectorList& _structural_selectors, bool _child_combinator) const { if (tag != _tag) return false; if (id != _id) return false; if (class_names != _class_names) return false; if (pseudo_class_names != _pseudo_class_names) return false; if (structural_selectors != _structural_selectors) return false; if (child_combinator != _child_combinator) return false; return true; } // Returns the specificity of this node. int StyleSheetNode::GetSpecificity() const { return specificity; } // Imports properties from a single rule definition (ie, with a shared specificity) into the node's // properties. void StyleSheetNode::ImportProperties(const PropertyDictionary& _properties, int rule_specificity) { properties.Import(_properties, specificity + rule_specificity); } // Returns the node's default properties. const PropertyDictionary& StyleSheetNode::GetProperties() const { return properties; } inline bool StyleSheetNode::Match(const Element* element) const { if (!tag.empty() && tag != element->GetTagName()) return false; if (!id.empty() && id != element->GetId()) return false; if (!MatchClassPseudoClass(element)) return false; if (!MatchStructuralSelector(element)) return false; return true; } inline bool StyleSheetNode::MatchClassPseudoClass(const Element* element) const { for (auto& name : class_names) { if (!element->IsClassSet(name)) return false; } for (auto& name : pseudo_class_names) { if (!element->IsPseudoClassSet(name)) return false; } return true; } inline bool StyleSheetNode::MatchStructuralSelector(const Element* element) const { for (auto& node_selector : structural_selectors) { if (!node_selector.selector->IsApplicable(element, node_selector.a, node_selector.b)) return false; } return true; } // Returns true if this node is applicable to the given element, given its IDs, classes and heritage. bool StyleSheetNode::IsApplicable(const Element* const in_element, bool skip_id_tag) const { // Determine whether the element matches the current node and its entire lineage. The entire hierarchy of // the element's document will be considered during the match as necessary. if (skip_id_tag) { // Id and tag have already been checked, only check class and pseudo class. if (!MatchClassPseudoClass(in_element)) return false; } else { // Id and tag have not already been matched, match everything. if (!Match(in_element)) return false; } const Element* element = in_element; // Walk up through all our parent nodes, each one of them must be matched by some ancestor element. for(const StyleSheetNode* node = parent; node && node->parent; node = node->parent) { // Try a match on every element ancestor. If it succeeds, we continue on to the next node. for(element = element->GetParentNode(); element; element = element->GetParentNode()) { if (node->Match(element)) break; // If we have a child combinator on the node, we must match this first ancestor. else if (node->child_combinator) return false; } // We have run out of element ancestors before we matched every node. Bail out. if (!element) return false; } // Finally, check the structural selector requirements last as they can be quite slow. if (!MatchStructuralSelector(in_element)) return false; return true; } bool StyleSheetNode::IsStructurallyVolatile() const { return is_structurally_volatile; } void StyleSheetNode::CalculateAndSetSpecificity() { // Calculate the specificity of just this node; tags are worth 10,000, IDs 1,000,000 and other specifiers (classes // and pseudo-classes) 100,000. specificity = 0; if (!tag.empty()) specificity += 10'000; if (!id.empty()) specificity += 1'000'000; specificity += 100'000*(int)class_names.size(); specificity += 100'000*(int)pseudo_class_names.size(); specificity += 100'000*(int)structural_selectors.size(); // Add our parent's specificity onto ours. if (parent) specificity += parent->specificity; } } // namespace Rml