blob: 6fe597879f3208b5f9c40b0cb2eecdb8dd8d3ad8 [file] [log] [blame]
/*
* (C) 1999-2003 Lars Knoll (knoll@kde.org)
* Copyright (C) 2004, 2006, 2007, 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_sheet.h"
#include "third_party/blink/renderer/bindings/core/v8/media_list_or_string.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_css_style_sheet_init.h"
#include "third_party/blink/renderer/core/css/css_import_rule.h"
#include "third_party/blink/renderer/core/css/css_rule_list.h"
#include "third_party/blink/renderer/core/css/media_list.h"
#include "third_party/blink/renderer/core/css/parser/css_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_impl.h"
#include "third_party/blink/renderer/core/css/style_engine.h"
#include "third_party/blink/renderer/core/css/style_rule.h"
#include "third_party/blink/renderer/core/css/style_sheet_contents.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/frame/deprecation.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/html/html_link_element.h"
#include "third_party/blink/renderer/core/html/html_style_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/svg/svg_style_element.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
class StyleSheetCSSRuleList final : public CSSRuleList {
public:
StyleSheetCSSRuleList(CSSStyleSheet* sheet) : style_sheet_(sheet) {}
void Trace(Visitor* visitor) const override {
visitor->Trace(style_sheet_);
CSSRuleList::Trace(visitor);
}
private:
unsigned length() const override { return style_sheet_->length(); }
CSSRule* item(unsigned index) const override {
return style_sheet_->item(index);
}
CSSStyleSheet* GetStyleSheet() const override { return style_sheet_; }
Member<CSSStyleSheet> style_sheet_;
};
#if DCHECK_IS_ON()
static bool IsAcceptableCSSStyleSheetParent(const Node& parent_node) {
// Only these nodes can be parents of StyleSheets, and they need to call
// clearOwnerNode() when moved out of document. Note that destructor of
// the nodes don't call clearOwnerNode() with Oilpan.
return parent_node.IsDocumentNode() || IsA<HTMLLinkElement>(parent_node) ||
IsA<HTMLStyleElement>(parent_node) ||
IsA<SVGStyleElement>(parent_node) ||
parent_node.getNodeType() == Node::kProcessingInstructionNode;
}
#endif
// static
const Document* CSSStyleSheet::SingleOwnerDocument(
const CSSStyleSheet* style_sheet) {
if (style_sheet)
return StyleSheetContents::SingleOwnerDocument(style_sheet->Contents());
return nullptr;
}
CSSStyleSheet* CSSStyleSheet::Create(Document& document,
const CSSStyleSheetInit* options,
ExceptionState& exception_state) {
auto* parser_context = MakeGarbageCollected<CSSParserContext>(document);
if (AdTracker::IsAdScriptExecutingInDocument(&document))
parser_context->SetIsAdRelated();
// Following steps at spec draft
// https://wicg.github.io/construct-stylesheets/#dom-cssstylesheet-cssstylesheet
auto* contents = MakeGarbageCollected<StyleSheetContents>(parser_context);
CSSStyleSheet* sheet = MakeGarbageCollected<CSSStyleSheet>(contents, nullptr);
sheet->SetConstructorDocument(document);
sheet->SetTitle(options->title());
sheet->ClearOwnerNode();
sheet->ClearOwnerRule();
contents->RegisterClient(sheet);
scoped_refptr<MediaQuerySet> media_query_set;
if (options->media().IsString()) {
media_query_set = MediaQuerySet::Create(options->media().GetAsString(),
document.GetExecutionContext());
} else {
media_query_set = options->media().GetAsMediaList()->Queries()->Copy();
}
auto* media_list = MakeGarbageCollected<MediaList>(
media_query_set, const_cast<CSSStyleSheet*>(sheet));
sheet->SetMedia(media_list);
if (options->alternate())
sheet->SetAlternateFromConstructor(true);
if (options->disabled())
sheet->setDisabled(true);
return sheet;
}
CSSStyleSheet* CSSStyleSheet::CreateInline(StyleSheetContents* sheet,
Node& owner_node,
const TextPosition& start_position) {
DCHECK(sheet);
return MakeGarbageCollected<CSSStyleSheet>(sheet, owner_node, true,
start_position);
}
CSSStyleSheet* CSSStyleSheet::CreateInline(Node& owner_node,
const KURL& base_url,
const TextPosition& start_position,
const WTF::TextEncoding& encoding) {
Document& owner_node_document = owner_node.GetDocument();
auto* parser_context = MakeGarbageCollected<CSSParserContext>(
owner_node_document, owner_node_document.BaseURL(),
true /* origin_clean */,
Referrer(
owner_node_document.GetExecutionContext()
? owner_node_document.GetExecutionContext()->OutgoingReferrer()
: String(), // GetExecutionContext() only returns null in tests.
owner_node.GetDocument().GetReferrerPolicy()),
encoding);
if (AdTracker::IsAdScriptExecutingInDocument(&owner_node.GetDocument()))
parser_context->SetIsAdRelated();
auto* sheet = MakeGarbageCollected<StyleSheetContents>(parser_context,
base_url.GetString());
return MakeGarbageCollected<CSSStyleSheet>(sheet, owner_node, true,
start_position);
}
CSSStyleSheet::CSSStyleSheet(StyleSheetContents* contents,
CSSImportRule* owner_rule)
: contents_(contents),
owner_rule_(owner_rule),
start_position_(TextPosition::MinimumPosition()) {
contents_->RegisterClient(this);
}
CSSStyleSheet::CSSStyleSheet(StyleSheetContents* contents,
Node& owner_node,
bool is_inline_stylesheet,
const TextPosition& start_position)
: contents_(contents),
is_inline_stylesheet_(is_inline_stylesheet),
owner_node_(&owner_node),
start_position_(start_position) {
#if DCHECK_IS_ON()
DCHECK(IsAcceptableCSSStyleSheetParent(owner_node));
#endif
contents_->RegisterClient(this);
}
CSSStyleSheet::~CSSStyleSheet() = default;
void CSSStyleSheet::WillMutateRules() {
// If we are the only client it is safe to mutate.
if (!contents_->IsUsedFromTextCache() &&
!contents_->IsReferencedFromResource()) {
contents_->ClearRuleSet();
contents_->SetMutable();
return;
}
// Only cacheable stylesheets should have multiple clients.
DCHECK(contents_->IsCacheableForStyleElement() ||
contents_->IsCacheableForResource());
// Copy-on-write.
contents_->UnregisterClient(this);
contents_ = contents_->Copy();
contents_->RegisterClient(this);
contents_->SetMutable();
// Any existing CSSOM wrappers need to be connected to the copied child rules.
ReattachChildRuleCSSOMWrappers();
}
void CSSStyleSheet::DidMutate(Mutation mutation) {
if (mutation == Mutation::kRules) {
DCHECK(contents_->IsMutable());
DCHECK_LE(contents_->ClientSize(), 1u);
}
Document* document = OwnerDocument();
if (!document || !document->IsActive())
return;
if (!custom_element_tag_names_.IsEmpty()) {
document->GetStyleEngine().ScheduleCustomElementInvalidations(
custom_element_tag_names_);
}
bool invalidate_matched_properties_cache = false;
if (ownerNode() && ownerNode()->isConnected()) {
document->GetStyleEngine().SetNeedsActiveStyleUpdate(
ownerNode()->GetTreeScope());
invalidate_matched_properties_cache = true;
} else if (!adopted_tree_scopes_.IsEmpty()) {
for (auto tree_scope : adopted_tree_scopes_) {
// It is currently required that adopted sheets can not be moved between
// documents.
DCHECK(tree_scope->GetDocument() == document);
if (!tree_scope->RootNode().isConnected())
continue;
document->GetStyleEngine().SetNeedsActiveStyleUpdate(*tree_scope);
invalidate_matched_properties_cache = true;
}
}
if (mutation == Mutation::kRules) {
if (invalidate_matched_properties_cache)
document->GetStyleResolver().InvalidateMatchedPropertiesCache();
probe::DidMutateStyleSheet(document, this);
}
}
void CSSStyleSheet::EnableRuleAccessForInspector() {
enable_rule_access_for_inspector_ = true;
}
void CSSStyleSheet::DisableRuleAccessForInspector() {
enable_rule_access_for_inspector_ = false;
}
CSSStyleSheet::InspectorMutationScope::InspectorMutationScope(
CSSStyleSheet* sheet)
: style_sheet_(sheet) {
style_sheet_->EnableRuleAccessForInspector();
}
CSSStyleSheet::InspectorMutationScope::~InspectorMutationScope() {
style_sheet_->DisableRuleAccessForInspector();
}
void CSSStyleSheet::ReattachChildRuleCSSOMWrappers() {
for (unsigned i = 0; i < child_rule_cssom_wrappers_.size(); ++i) {
if (!child_rule_cssom_wrappers_[i])
continue;
child_rule_cssom_wrappers_[i]->Reattach(contents_->RuleAt(i));
}
}
void CSSStyleSheet::setDisabled(bool disabled) {
if (disabled == is_disabled_)
return;
is_disabled_ = disabled;
DidMutate(Mutation::kSheet);
}
void CSSStyleSheet::SetMediaQueries(
scoped_refptr<MediaQuerySet> media_queries) {
media_queries_ = std::move(media_queries);
if (media_cssom_wrapper_ && media_queries_)
media_cssom_wrapper_->Reattach(media_queries_.get());
}
bool CSSStyleSheet::MatchesMediaQueries(const MediaQueryEvaluator& evaluator) {
viewport_dependent_media_query_results_.clear();
device_dependent_media_query_results_.clear();
if (!media_queries_)
return true;
return evaluator.Eval(*media_queries_,
&viewport_dependent_media_query_results_,
&device_dependent_media_query_results_);
}
unsigned CSSStyleSheet::length() const {
return contents_->RuleCount();
}
CSSRule* CSSStyleSheet::item(unsigned index) {
unsigned rule_count = length();
if (index >= rule_count)
return nullptr;
if (child_rule_cssom_wrappers_.IsEmpty())
child_rule_cssom_wrappers_.Grow(rule_count);
DCHECK_EQ(child_rule_cssom_wrappers_.size(), rule_count);
Member<CSSRule>& css_rule = child_rule_cssom_wrappers_[index];
if (!css_rule)
css_rule = contents_->RuleAt(index)->CreateCSSOMWrapper(this);
return css_rule.Get();
}
void CSSStyleSheet::ClearOwnerNode() {
DidMutate(Mutation::kSheet);
if (owner_node_)
contents_->UnregisterClient(this);
owner_node_ = nullptr;
}
bool CSSStyleSheet::CanAccessRules() const {
return enable_rule_access_for_inspector_ || contents_->IsOriginClean();
}
CSSRuleList* CSSStyleSheet::rules(ExceptionState& exception_state) {
return cssRules(exception_state);
}
unsigned CSSStyleSheet::insertRule(const String& rule_string,
unsigned index,
ExceptionState& exception_state) {
if (!CanAccessRules()) {
exception_state.ThrowSecurityError(
"Cannot access StyleSheet to insertRule");
return 0;
}
DCHECK(child_rule_cssom_wrappers_.IsEmpty() ||
child_rule_cssom_wrappers_.size() == contents_->RuleCount());
if (index > length()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
"The index provided (" + String::Number(index) +
") is larger than the maximum index (" + String::Number(length()) +
").");
return 0;
}
const auto* context =
MakeGarbageCollected<CSSParserContext>(contents_->ParserContext(), this);
StyleRuleBase* rule =
CSSParser::ParseRule(context, contents_.Get(), rule_string);
if (!rule) {
exception_state.ThrowDOMException(
DOMExceptionCode::kSyntaxError,
"Failed to parse the rule '" + rule_string + "'.");
return 0;
}
RuleMutationScope mutation_scope(this);
if (rule->IsImportRule() && IsConstructed()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kSyntaxError,
"Can't insert @import rules into a constructed stylesheet.");
return 0;
}
bool success = contents_->WrapperInsertRule(rule, index);
if (!success) {
if (rule->IsNamespaceRule())
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Failed to insert the rule");
else
exception_state.ThrowDOMException(
DOMExceptionCode::kHierarchyRequestError,
"Failed to insert the rule.");
return 0;
}
if (!child_rule_cssom_wrappers_.IsEmpty())
child_rule_cssom_wrappers_.insert(index, Member<CSSRule>(nullptr));
return index;
}
void CSSStyleSheet::deleteRule(unsigned index,
ExceptionState& exception_state) {
if (!CanAccessRules()) {
exception_state.ThrowSecurityError(
"Cannot access StyleSheet to deleteRule");
return;
}
DCHECK(child_rule_cssom_wrappers_.IsEmpty() ||
child_rule_cssom_wrappers_.size() == contents_->RuleCount());
if (index >= length()) {
if (length()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
"The index provided (" + String::Number(index) +
") is larger than the maximum index (" +
String::Number(length() - 1) + ").");
} else {
exception_state.ThrowDOMException(DOMExceptionCode::kIndexSizeError,
"Style sheet is empty (length 0).");
}
return;
}
RuleMutationScope mutation_scope(this);
bool success = contents_->WrapperDeleteRule(index);
if (!success) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Failed to delete rule");
return;
}
if (!child_rule_cssom_wrappers_.IsEmpty()) {
if (child_rule_cssom_wrappers_[index])
child_rule_cssom_wrappers_[index]->SetParentStyleSheet(nullptr);
child_rule_cssom_wrappers_.EraseAt(index);
}
}
int CSSStyleSheet::addRule(const String& selector,
const String& style,
int index,
ExceptionState& exception_state) {
StringBuilder text;
text.Append(selector);
text.Append(" { ");
text.Append(style);
if (!style.IsEmpty())
text.Append(' ');
text.Append('}');
insertRule(text.ToString(), index, exception_state);
// As per Microsoft documentation, always return -1.
return -1;
}
int CSSStyleSheet::addRule(const String& selector,
const String& style,
ExceptionState& exception_state) {
return addRule(selector, style, length(), exception_state);
}
ScriptPromise CSSStyleSheet::replace(ScriptState* script_state,
const String& text) {
if (!IsConstructed()) {
return ScriptPromise::RejectWithDOMException(
script_state,
MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotAllowedError,
"Can't call replace on non-constructed CSSStyleSheets."));
}
SetText(text, CSSImportRules::kIgnoreWithWarning);
// We currently parse synchronously, and since @import support was removed,
// nothing else happens asynchronously. This API is left as-is, so that future
// async parsing can still be supported here.
return ScriptPromise::Cast(script_state, ToV8(this, script_state));
}
void CSSStyleSheet::replaceSync(const String& text,
ExceptionState& exception_state) {
if (!IsConstructed()) {
return exception_state.ThrowDOMException(
DOMExceptionCode::kNotAllowedError,
"Can't call replaceSync on non-constructed CSSStyleSheets.");
}
SetText(text, CSSImportRules::kIgnoreWithWarning);
}
CSSRuleList* CSSStyleSheet::cssRules(ExceptionState& exception_state) {
if (!CanAccessRules()) {
exception_state.ThrowSecurityError("Cannot access rules");
return nullptr;
}
if (!rule_list_cssom_wrapper_) {
rule_list_cssom_wrapper_ =
MakeGarbageCollected<StyleSheetCSSRuleList>(this);
}
return rule_list_cssom_wrapper_.Get();
}
String CSSStyleSheet::href() const {
return contents_->OriginalURL();
}
KURL CSSStyleSheet::BaseURL() const {
return contents_->BaseURL();
}
bool CSSStyleSheet::IsLoading() const {
return contents_->IsLoading();
}
MediaList* CSSStyleSheet::media() {
if (!media_queries_)
media_queries_ = MediaQuerySet::Create();
if (!media_cssom_wrapper_) {
media_cssom_wrapper_ = MakeGarbageCollected<MediaList>(
media_queries_.get(), const_cast<CSSStyleSheet*>(this));
}
return media_cssom_wrapper_.Get();
}
void CSSStyleSheet::SetMedia(MediaList* media_list) {
media_cssom_wrapper_ = media_list;
}
CSSStyleSheet* CSSStyleSheet::parentStyleSheet() const {
return owner_rule_ ? owner_rule_->parentStyleSheet() : nullptr;
}
Document* CSSStyleSheet::OwnerDocument() const {
if (CSSStyleSheet* parent = parentStyleSheet())
return parent->OwnerDocument();
if (IsConstructed()) {
DCHECK(!ownerNode());
return ConstructorDocument();
}
return ownerNode() ? &ownerNode()->GetDocument() : nullptr;
}
bool CSSStyleSheet::SheetLoaded() {
DCHECK(owner_node_);
SetLoadCompleted(owner_node_->SheetLoaded());
return load_completed_;
}
void CSSStyleSheet::StartLoadingDynamicSheet() {
SetLoadCompleted(false);
owner_node_->StartLoadingDynamicSheet();
}
void CSSStyleSheet::SetLoadCompleted(bool completed) {
if (completed == load_completed_)
return;
load_completed_ = completed;
if (completed)
contents_->ClientLoadCompleted(this);
else
contents_->ClientLoadStarted(this);
}
void CSSStyleSheet::SetText(const String& text, CSSImportRules import_rules) {
child_rule_cssom_wrappers_.clear();
CSSStyleSheet::RuleMutationScope mutation_scope(this);
contents_->ClearRules();
bool allow_imports = import_rules == CSSImportRules::kAllow;
if (contents_->ParseString(text, allow_imports) ==
ParseSheetResult::kHasUnallowedImportRule &&
import_rules == CSSImportRules::kIgnoreWithWarning) {
OwnerDocument()->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kJavaScript,
mojom::blink::ConsoleMessageLevel::kWarning,
"@import rules are not allowed here. See "
"https://github.com/WICG/construct-stylesheets/issues/"
"119#issuecomment-588352418."));
}
}
void CSSStyleSheet::SetAlternateFromConstructor(
bool alternate_from_constructor) {
alternate_from_constructor_ = alternate_from_constructor;
}
bool CSSStyleSheet::IsAlternate() const {
if (owner_node_) {
auto* owner_element = DynamicTo<Element>(owner_node_.Get());
return owner_element &&
owner_element->FastGetAttribute(html_names::kRelAttr)
.Contains("alternate");
}
return alternate_from_constructor_;
}
bool CSSStyleSheet::CanBeActivated(
const String& current_preferrable_name) const {
if (disabled())
return false;
if (owner_node_ && owner_node_->IsInShadowTree()) {
if (IsA<HTMLStyleElement>(owner_node_.Get()) ||
IsA<SVGStyleElement>(owner_node_.Get()))
return true;
auto* html_link_element = DynamicTo<HTMLLinkElement>(owner_node_.Get());
if (html_link_element && html_link_element->IsImport())
return !IsAlternate();
}
auto* html_link_element = DynamicTo<HTMLLinkElement>(owner_node_.Get());
if (!owner_node_ ||
owner_node_->getNodeType() == Node::kProcessingInstructionNode ||
!html_link_element || !html_link_element->IsEnabledViaScript()) {
if (!title_.IsEmpty() && title_ != current_preferrable_name)
return false;
}
if (IsAlternate() && title_.IsEmpty())
return false;
return true;
}
void CSSStyleSheet::Trace(Visitor* visitor) const {
visitor->Trace(contents_);
visitor->Trace(owner_node_);
visitor->Trace(owner_rule_);
visitor->Trace(media_cssom_wrapper_);
visitor->Trace(child_rule_cssom_wrappers_);
visitor->Trace(rule_list_cssom_wrapper_);
visitor->Trace(adopted_tree_scopes_);
visitor->Trace(constructor_document_);
StyleSheet::Trace(visitor);
}
} // namespace blink