1/*
2 * (C) 1999-2003 Lars Knoll (knoll@kde.org)
3 * Copyright (C) 2004, 2006, 2007, 2012, 2013 Apple Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21#include "config.h"
22#include "CSSStyleSheet.h"
23
24#include "CSSImportRule.h"
25#include "CSSKeyframesRule.h"
26#include "CSSParser.h"
27#include "CSSRuleList.h"
28#include "Document.h"
29#include "HTMLLinkElement.h"
30#include "HTMLStyleElement.h"
31#include "MediaList.h"
32#include "Node.h"
33#include "SVGStyleElement.h"
34#include "SecurityOrigin.h"
35#include "StyleResolver.h"
36#include "StyleRule.h"
37#include "StyleScope.h"
38#include "StyleSheetContents.h"
39#include <wtf/text/StringBuilder.h>
40
41namespace WebCore {
42
43class StyleSheetCSSRuleList final : public CSSRuleList {
44public:
45 StyleSheetCSSRuleList(CSSStyleSheet* sheet) : m_styleSheet(sheet) { }
46
47private:
48 void ref() final { m_styleSheet->ref(); }
49 void deref() final { m_styleSheet->deref(); }
50
51 unsigned length() const final { return m_styleSheet->length(); }
52 CSSRule* item(unsigned index) const final { return m_styleSheet->item(index); }
53
54 CSSStyleSheet* styleSheet() const final { return m_styleSheet; }
55
56 CSSStyleSheet* m_styleSheet;
57};
58
59#if !ASSERT_DISABLED
60static bool isAcceptableCSSStyleSheetParent(Node* parentNode)
61{
62 // Only these nodes can be parents of StyleSheets, and they need to call clearOwnerNode() when moved out of document.
63 return !parentNode
64 || parentNode->isDocumentNode()
65 || is<HTMLLinkElement>(*parentNode)
66 || is<HTMLStyleElement>(*parentNode)
67 || is<SVGStyleElement>(*parentNode)
68 || parentNode->nodeType() == Node::PROCESSING_INSTRUCTION_NODE;
69}
70#endif
71
72Ref<CSSStyleSheet> CSSStyleSheet::create(Ref<StyleSheetContents>&& sheet, CSSImportRule* ownerRule)
73{
74 return adoptRef(*new CSSStyleSheet(WTFMove(sheet), ownerRule));
75}
76
77Ref<CSSStyleSheet> CSSStyleSheet::create(Ref<StyleSheetContents>&& sheet, Node& ownerNode, const Optional<bool>& isCleanOrigin)
78{
79 return adoptRef(*new CSSStyleSheet(WTFMove(sheet), ownerNode, TextPosition(), false, isCleanOrigin));
80}
81
82Ref<CSSStyleSheet> CSSStyleSheet::createInline(Ref<StyleSheetContents>&& sheet, Element& owner, const TextPosition& startPosition)
83{
84 return adoptRef(*new CSSStyleSheet(WTFMove(sheet), owner, startPosition, true, true));
85}
86
87CSSStyleSheet::CSSStyleSheet(Ref<StyleSheetContents>&& contents, CSSImportRule* ownerRule)
88 : m_contents(WTFMove(contents))
89 , m_isInlineStylesheet(false)
90 , m_isDisabled(false)
91 , m_mutatedRules(false)
92 , m_ownerNode(0)
93 , m_ownerRule(ownerRule)
94 , m_startPosition()
95{
96 m_contents->registerClient(this);
97}
98
99CSSStyleSheet::CSSStyleSheet(Ref<StyleSheetContents>&& contents, Node& ownerNode, const TextPosition& startPosition, bool isInlineStylesheet, const Optional<bool>& isOriginClean)
100 : m_contents(WTFMove(contents))
101 , m_isInlineStylesheet(isInlineStylesheet)
102 , m_isDisabled(false)
103 , m_mutatedRules(false)
104 , m_isOriginClean(isOriginClean)
105 , m_ownerNode(&ownerNode)
106 , m_ownerRule(0)
107 , m_startPosition(startPosition)
108{
109 ASSERT(isAcceptableCSSStyleSheetParent(&ownerNode));
110 m_contents->registerClient(this);
111}
112
113CSSStyleSheet::~CSSStyleSheet()
114{
115 // For style rules outside the document, .parentStyleSheet can become null even if the style rule
116 // is still observable from JavaScript. This matches the behavior of .parentNode for nodes, but
117 // it's not ideal because it makes the CSSOM's behavior depend on the timing of garbage collection.
118 for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) {
119 if (m_childRuleCSSOMWrappers[i])
120 m_childRuleCSSOMWrappers[i]->setParentStyleSheet(0);
121 }
122 if (m_mediaCSSOMWrapper)
123 m_mediaCSSOMWrapper->clearParentStyleSheet();
124
125 m_contents->unregisterClient(this);
126}
127
128CSSStyleSheet::WhetherContentsWereClonedForMutation CSSStyleSheet::willMutateRules()
129{
130 // If we are the only client it is safe to mutate.
131 if (m_contents->hasOneClient() && !m_contents->isInMemoryCache()) {
132 m_contents->setMutable();
133 return ContentsWereNotClonedForMutation;
134 }
135 // Only cacheable stylesheets should have multiple clients.
136 ASSERT(m_contents->isCacheable());
137
138 // Copy-on-write.
139 m_contents->unregisterClient(this);
140 m_contents = m_contents->copy();
141 m_contents->registerClient(this);
142
143 m_contents->setMutable();
144
145 // Any existing CSSOM wrappers need to be connected to the copied child rules.
146 reattachChildRuleCSSOMWrappers();
147
148 return ContentsWereClonedForMutation;
149}
150
151void CSSStyleSheet::didMutateRuleFromCSSStyleDeclaration()
152{
153 ASSERT(m_contents->isMutable());
154 ASSERT(m_contents->hasOneClient());
155 didMutate();
156}
157
158void CSSStyleSheet::didMutateRules(RuleMutationType mutationType, WhetherContentsWereClonedForMutation contentsWereClonedForMutation, StyleRuleKeyframes* insertedKeyframesRule)
159{
160 ASSERT(m_contents->isMutable());
161 ASSERT(m_contents->hasOneClient());
162
163 auto* scope = styleScope();
164 if (!scope)
165 return;
166
167 if (mutationType == RuleInsertion && !contentsWereClonedForMutation && !scope->activeStyleSheetsContains(this)) {
168 if (insertedKeyframesRule) {
169 if (auto* resolver = scope->resolverIfExists())
170 resolver->addKeyframeStyle(*insertedKeyframesRule);
171 return;
172 }
173 scope->didChangeActiveStyleSheetCandidates();
174 return;
175 }
176
177 scope->didChangeStyleSheetContents();
178
179 m_mutatedRules = true;
180}
181
182void CSSStyleSheet::didMutate()
183{
184 auto* scope = styleScope();
185 if (!scope)
186 return;
187 scope->didChangeStyleSheetContents();
188}
189
190void CSSStyleSheet::clearOwnerNode()
191{
192 m_ownerNode = nullptr;
193}
194
195void CSSStyleSheet::reattachChildRuleCSSOMWrappers()
196{
197 for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) {
198 if (!m_childRuleCSSOMWrappers[i])
199 continue;
200 m_childRuleCSSOMWrappers[i]->reattach(*m_contents->ruleAt(i));
201 }
202}
203
204void CSSStyleSheet::setDisabled(bool disabled)
205{
206 if (disabled == m_isDisabled)
207 return;
208 m_isDisabled = disabled;
209
210 if (auto* scope = styleScope())
211 scope->didChangeActiveStyleSheetCandidates();
212}
213
214void CSSStyleSheet::setMediaQueries(Ref<MediaQuerySet>&& mediaQueries)
215{
216 m_mediaQueries = WTFMove(mediaQueries);
217 if (m_mediaCSSOMWrapper && m_mediaQueries)
218 m_mediaCSSOMWrapper->reattach(m_mediaQueries.get());
219 reportMediaQueryWarningIfNeeded(ownerDocument(), m_mediaQueries.get());
220}
221
222unsigned CSSStyleSheet::length() const
223{
224 return m_contents->ruleCount();
225}
226
227CSSRule* CSSStyleSheet::item(unsigned index)
228{
229 unsigned ruleCount = length();
230 if (index >= ruleCount)
231 return nullptr;
232
233 ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == ruleCount);
234 if (m_childRuleCSSOMWrappers.size() < ruleCount)
235 m_childRuleCSSOMWrappers.grow(ruleCount);
236
237 RefPtr<CSSRule>& cssRule = m_childRuleCSSOMWrappers[index];
238 if (!cssRule)
239 cssRule = m_contents->ruleAt(index)->createCSSOMWrapper(this);
240 return cssRule.get();
241}
242
243bool CSSStyleSheet::canAccessRules() const
244{
245 if (m_isOriginClean)
246 return m_isOriginClean.value();
247
248 URL baseURL = m_contents->baseURL();
249 if (baseURL.isEmpty())
250 return true;
251 Document* document = ownerDocument();
252 if (!document)
253 return true;
254 return document->securityOrigin().canRequest(baseURL);
255}
256
257RefPtr<CSSRuleList> CSSStyleSheet::rules()
258{
259 if (!canAccessRules())
260 return nullptr;
261 // IE behavior.
262 auto ruleList = StaticCSSRuleList::create();
263 unsigned ruleCount = length();
264 for (unsigned i = 0; i < ruleCount; ++i)
265 ruleList->rules().append(item(i));
266 return ruleList;
267}
268
269ExceptionOr<unsigned> CSSStyleSheet::insertRule(const String& ruleString, unsigned index)
270{
271 ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount());
272
273 if (index > length())
274 return Exception { IndexSizeError };
275 RefPtr<StyleRuleBase> rule = CSSParser::parseRule(m_contents.get().parserContext(), m_contents.ptr(), ruleString);
276
277 if (!rule)
278 return Exception { SyntaxError };
279
280 RuleMutationScope mutationScope(this, RuleInsertion, is<StyleRuleKeyframes>(*rule) ? downcast<StyleRuleKeyframes>(rule.get()) : nullptr);
281
282 bool success = m_contents.get().wrapperInsertRule(rule.releaseNonNull(), index);
283 if (!success)
284 return Exception { HierarchyRequestError };
285 if (!m_childRuleCSSOMWrappers.isEmpty())
286 m_childRuleCSSOMWrappers.insert(index, RefPtr<CSSRule>());
287
288 return index;
289}
290
291ExceptionOr<void> CSSStyleSheet::deleteRule(unsigned index)
292{
293 ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount());
294
295 if (index >= length())
296 return Exception { IndexSizeError };
297 RuleMutationScope mutationScope(this);
298
299 m_contents->wrapperDeleteRule(index);
300
301 if (!m_childRuleCSSOMWrappers.isEmpty()) {
302 if (m_childRuleCSSOMWrappers[index])
303 m_childRuleCSSOMWrappers[index]->setParentStyleSheet(nullptr);
304 m_childRuleCSSOMWrappers.remove(index);
305 }
306
307 return { };
308}
309
310ExceptionOr<int> CSSStyleSheet::addRule(const String& selector, const String& style, Optional<unsigned> index)
311{
312 StringBuilder text;
313 text.append(selector);
314 text.appendLiteral(" { ");
315 text.append(style);
316 if (!style.isEmpty())
317 text.append(' ');
318 text.append('}');
319 auto insertRuleResult = insertRule(text.toString(), index.valueOr(length()));
320 if (insertRuleResult.hasException())
321 return insertRuleResult.releaseException();
322
323 // As per Microsoft documentation, always return -1.
324 return -1;
325}
326
327RefPtr<CSSRuleList> CSSStyleSheet::cssRules()
328{
329 if (!canAccessRules())
330 return nullptr;
331 if (!m_ruleListCSSOMWrapper)
332 m_ruleListCSSOMWrapper = std::make_unique<StyleSheetCSSRuleList>(this);
333 return m_ruleListCSSOMWrapper.get();
334}
335
336String CSSStyleSheet::href() const
337{
338 return m_contents->originalURL();
339}
340
341URL CSSStyleSheet::baseURL() const
342{
343 return m_contents->baseURL();
344}
345
346bool CSSStyleSheet::isLoading() const
347{
348 return m_contents->isLoading();
349}
350
351MediaList* CSSStyleSheet::media() const
352{
353 if (!m_mediaQueries)
354 return nullptr;
355
356 if (!m_mediaCSSOMWrapper)
357 m_mediaCSSOMWrapper = MediaList::create(m_mediaQueries.get(), const_cast<CSSStyleSheet*>(this));
358 return m_mediaCSSOMWrapper.get();
359}
360
361CSSStyleSheet* CSSStyleSheet::parentStyleSheet() const
362{
363 return m_ownerRule ? m_ownerRule->parentStyleSheet() : nullptr;
364}
365
366CSSStyleSheet& CSSStyleSheet::rootStyleSheet()
367{
368 auto* root = this;
369 while (root->parentStyleSheet())
370 root = root->parentStyleSheet();
371 return *root;
372}
373
374const CSSStyleSheet& CSSStyleSheet::rootStyleSheet() const
375{
376 return const_cast<CSSStyleSheet&>(*this).rootStyleSheet();
377}
378
379Document* CSSStyleSheet::ownerDocument() const
380{
381 auto& root = rootStyleSheet();
382 return root.ownerNode() ? &root.ownerNode()->document() : nullptr;
383}
384
385Style::Scope* CSSStyleSheet::styleScope()
386{
387 auto* ownerNode = rootStyleSheet().ownerNode();
388 if (!ownerNode)
389 return nullptr;
390 return &Style::Scope::forNode(*ownerNode);
391}
392
393void CSSStyleSheet::clearChildRuleCSSOMWrappers()
394{
395 m_childRuleCSSOMWrappers.clear();
396}
397
398CSSStyleSheet::RuleMutationScope::RuleMutationScope(CSSStyleSheet* sheet, RuleMutationType mutationType, StyleRuleKeyframes* insertedKeyframesRule)
399 : m_styleSheet(sheet)
400 , m_mutationType(mutationType)
401 , m_insertedKeyframesRule(insertedKeyframesRule)
402{
403 ASSERT(m_styleSheet);
404 m_contentsWereClonedForMutation = m_styleSheet->willMutateRules();
405}
406
407CSSStyleSheet::RuleMutationScope::RuleMutationScope(CSSRule* rule)
408 : m_styleSheet(rule ? rule->parentStyleSheet() : nullptr)
409 , m_mutationType(OtherMutation)
410 , m_contentsWereClonedForMutation(ContentsWereNotClonedForMutation)
411 , m_insertedKeyframesRule(nullptr)
412{
413 if (m_styleSheet)
414 m_contentsWereClonedForMutation = m_styleSheet->willMutateRules();
415}
416
417CSSStyleSheet::RuleMutationScope::~RuleMutationScope()
418{
419 if (m_styleSheet)
420 m_styleSheet->didMutateRules(m_mutationType, m_contentsWereClonedForMutation, m_insertedKeyframesRule);
421}
422
423}
424