blob: 3022bae8fdff265bc88cdb863014c22b53983de6 [file] [log] [blame]
/*
* (C) 1999-2003 Lars Knoll (knoll@kde.org)
* (C) 2002-2003 Dirk Mueller (mueller@kde.org)
* Copyright (C) 2002, 2005, 2006, 2008, 2012 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "third_party/blink/renderer/core/css/css_style_rule.h"
#include "third_party/blink/renderer/core/css/css_grouping_rule.h"
#include "third_party/blink/renderer/core/css/css_property_value_set.h"
#include "third_party/blink/renderer/core/css/css_rule_list.h"
#include "third_party/blink/renderer/core/css/css_selector.h"
#include "third_party/blink/renderer/core/css/css_style_sheet.h"
#include "third_party/blink/renderer/core/css/cssom/declared_style_property_map.h"
#include "third_party/blink/renderer/core/css/parser/css_parser.h"
#include "third_party/blink/renderer/core/css/style_rule.h"
#include "third_party/blink/renderer/core/css/style_rule_css_style_declaration.h"
#include "third_party/blink/renderer/core/css/style_sheet_contents.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
using SelectorTextCache = HeapHashMap<WeakMember<const CSSStyleRule>, String>;
static SelectorTextCache& GetSelectorTextCache() {
DEFINE_STATIC_LOCAL(Persistent<SelectorTextCache>, cache,
(MakeGarbageCollected<SelectorTextCache>()));
return *cache;
}
CSSStyleRule::CSSStyleRule(StyleRule* style_rule,
CSSStyleSheet* parent,
wtf_size_t position_hint)
: CSSRule(parent),
style_rule_(style_rule),
style_map_(MakeGarbageCollected<DeclaredStylePropertyMap>(this)),
position_hint_(position_hint),
child_rule_cssom_wrappers_(
style_rule->ChildRules() ? style_rule->ChildRules()->size() : 0) {}
CSSStyleRule::~CSSStyleRule() = default;
CSSStyleDeclaration* CSSStyleRule::style() const {
if (!properties_cssom_wrapper_) {
properties_cssom_wrapper_ =
MakeGarbageCollected<StyleRuleCSSStyleDeclaration>(
style_rule_->MutableProperties(), const_cast<CSSStyleRule*>(this));
}
return properties_cssom_wrapper_.Get();
}
String CSSStyleRule::selectorText() const {
if (HasCachedSelectorText()) {
DCHECK(GetSelectorTextCache().Contains(this));
return GetSelectorTextCache().at(this);
}
DCHECK(!GetSelectorTextCache().Contains(this));
String text = style_rule_->SelectorsText();
GetSelectorTextCache().Set(this, text);
SetHasCachedSelectorText(true);
return text;
}
void CSSStyleRule::setSelectorText(const ExecutionContext* execution_context,
const String& selector_text) {
CSSStyleSheet::RuleMutationScope mutation_scope(this);
const auto* context = MakeGarbageCollected<CSSParserContext>(
ParserContext(execution_context->GetSecureContextMode()));
StyleSheetContents* parent_contents =
parentStyleSheet() ? parentStyleSheet()->Contents() : nullptr;
HeapVector<CSSSelector> arena;
StyleRule* parent_rule_for_nesting =
FindClosestParentStyleRuleOrNull(parentRule());
CSSNestingType nesting_type = parent_rule_for_nesting
? CSSNestingType::kNesting
: CSSNestingType::kNone;
base::span<CSSSelector> selector_vector = CSSParser::ParseSelector(
context, nesting_type, parent_rule_for_nesting, /*is_within_scope=*/false,
parent_contents, selector_text, arena);
if (selector_vector.empty()) {
return;
}
StyleRule* new_style_rule =
StyleRule::Create(selector_vector, std::move(*style_rule_));
if (parent_contents) {
position_hint_ = parent_contents->ReplaceRuleIfExists(
style_rule_, new_style_rule, position_hint_);
}
// If we have any nested rules, update their parent selector(s) to point to
// our newly created StyleRule instead of the old one.
if (new_style_rule->ChildRules()) {
for (StyleRuleBase* child_rule : *new_style_rule->ChildRules()) {
child_rule->Reparent(style_rule_, new_style_rule);
}
}
style_rule_ = new_style_rule;
if (HasCachedSelectorText()) {
GetSelectorTextCache().erase(this);
SetHasCachedSelectorText(false);
}
}
String CSSStyleRule::cssText() const {
// Referring to https://drafts.csswg.org/cssom-1/#serialize-a-css-rule:
// Step 1.
StringBuilder result;
result.Append(selectorText());
result.Append(" {");
// Step 2.
String decls = style_rule_->Properties().AsText();
// Step 3.
StringBuilder rules;
unsigned size = length();
for (unsigned i = 0; i < size; ++i) {
// Step 6.2 for rules.
rules.Append("\n ");
rules.Append(ItemInternal(i)->cssText());
}
// Step 4.
if (decls.empty() && rules.empty()) {
result.Append(" }");
return result.ReleaseString();
}
// Step 5.
if (rules.empty()) {
result.Append(' ');
result.Append(decls);
result.Append(" }");
return result.ReleaseString();
}
// Step 6.
if (!decls.empty()) {
// Step 6.2 for decls (we don't do 6.1 explicitly).
result.Append("\n ");
result.Append(decls);
}
// Step 6.2 for rules was done above.
result.Append(rules);
result.Append("\n}");
return result.ReleaseString();
}
void CSSStyleRule::Reattach(StyleRuleBase* rule) {
DCHECK(rule);
style_rule_ = To<StyleRule>(rule);
if (properties_cssom_wrapper_) {
properties_cssom_wrapper_->Reattach(style_rule_->MutableProperties());
}
for (unsigned i = 0; i < child_rule_cssom_wrappers_.size(); ++i) {
if (child_rule_cssom_wrappers_[i]) {
child_rule_cssom_wrappers_[i]->Reattach(
(*style_rule_->ChildRules())[i].Get());
}
}
}
void CSSStyleRule::Trace(Visitor* visitor) const {
visitor->Trace(style_rule_);
visitor->Trace(properties_cssom_wrapper_);
visitor->Trace(style_map_);
visitor->Trace(child_rule_cssom_wrappers_);
visitor->Trace(rule_list_cssom_wrapper_);
CSSRule::Trace(visitor);
}
unsigned CSSStyleRule::length() const {
if (style_rule_->ChildRules()) {
return style_rule_->ChildRules()->size();
} else {
return 0;
}
}
CSSRule* CSSStyleRule::Item(unsigned index, bool trigger_use_counters) const {
if (index >= length()) {
return nullptr;
}
DCHECK_EQ(child_rule_cssom_wrappers_.size(),
style_rule_->ChildRules()->size());
Member<CSSRule>& rule = child_rule_cssom_wrappers_[index];
if (!rule) {
rule = (*style_rule_->ChildRules())[index]->CreateCSSOMWrapper(
index, const_cast<CSSStyleRule*>(this), trigger_use_counters);
}
return rule.Get();
}
CSSRuleList* CSSStyleRule::cssRules() const {
if (!rule_list_cssom_wrapper_) {
rule_list_cssom_wrapper_ =
MakeGarbageCollected<LiveCSSRuleList<CSSStyleRule>>(
const_cast<CSSStyleRule*>(this));
}
return rule_list_cssom_wrapper_.Get();
}
unsigned CSSStyleRule::insertRule(const ExecutionContext* execution_context,
const String& rule_string,
unsigned index,
ExceptionState& exception_state) {
if (style_rule_->ChildRules() == nullptr) {
// Implicitly zero rules.
if (index > 0) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
"the index " + String::Number(index) +
" must be less than or equal to the length of the rule list.");
return 0;
}
style_rule_->EnsureChildRules();
}
DCHECK_EQ(child_rule_cssom_wrappers_.size(),
style_rule_->ChildRules()->size());
StyleRuleBase* new_rule = ParseRuleForInsert(
execution_context, rule_string, index, style_rule_->ChildRules()->size(),
*this, exception_state);
if (new_rule == nullptr) {
// Already raised an exception above.
return 0;
} else {
CSSStyleSheet::RuleMutationScope mutation_scope(this);
style_rule_->WrapperInsertRule(index, new_rule);
child_rule_cssom_wrappers_.insert(index, Member<CSSRule>(nullptr));
UseCountForSignalAffected();
return index;
}
}
void CSSStyleRule::deleteRule(unsigned index, ExceptionState& exception_state) {
if (style_rule_->ChildRules() == nullptr ||
index >= style_rule_->ChildRules()->size()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
"the index " + String::Number(index) +
" is greated than the length of the rule list.");
return;
}
DCHECK_EQ(child_rule_cssom_wrappers_.size(),
style_rule_->ChildRules()->size());
CSSStyleSheet::RuleMutationScope mutation_scope(this);
style_rule_->WrapperRemoveRule(index);
if (child_rule_cssom_wrappers_[index]) {
child_rule_cssom_wrappers_[index]->SetParentRule(nullptr);
}
child_rule_cssom_wrappers_.EraseAt(index);
UseCountForSignalAffected();
}
void CSSStyleRule::UseCountForSignalAffected() {
if (style_rule_->HasSignalingChildRule()) {
CountUse(WebFeature::kCSSRuleWithSignalingChildModified);
}
}
} // namespace blink