1 | /*
|
---|
2 | * (C) 1999-2003 Lars Knoll ([email protected])
|
---|
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 "Logging.h"
|
---|
32 | #include "MediaList.h"
|
---|
33 | #include "Node.h"
|
---|
34 | #include "SVGElementTypeHelpers.h"
|
---|
35 | #include "SVGStyleElement.h"
|
---|
36 | #include "SecurityOrigin.h"
|
---|
37 | #include "StyleResolver.h"
|
---|
38 | #include "StyleRule.h"
|
---|
39 | #include "StyleScope.h"
|
---|
40 | #include "StyleSheetContents.h"
|
---|
41 |
|
---|
42 | #include <wtf/HexNumber.h>
|
---|
43 | #include <wtf/text/StringBuilder.h>
|
---|
44 |
|
---|
45 | namespace WebCore {
|
---|
46 |
|
---|
47 | class StyleSheetCSSRuleList final : public CSSRuleList {
|
---|
48 | public:
|
---|
49 | StyleSheetCSSRuleList(CSSStyleSheet* sheet) : m_styleSheet(sheet) { }
|
---|
50 |
|
---|
51 | private:
|
---|
52 | void ref() final { m_styleSheet->ref(); }
|
---|
53 | void deref() final { m_styleSheet->deref(); }
|
---|
54 |
|
---|
55 | unsigned length() const final { return m_styleSheet->length(); }
|
---|
56 | CSSRule* item(unsigned index) const final { return m_styleSheet->item(index); }
|
---|
57 |
|
---|
58 | CSSStyleSheet* styleSheet() const final { return m_styleSheet; }
|
---|
59 |
|
---|
60 | CSSStyleSheet* m_styleSheet;
|
---|
61 | };
|
---|
62 |
|
---|
63 | #if ASSERT_ENABLED
|
---|
64 | static bool isAcceptableCSSStyleSheetParent(Node* parentNode)
|
---|
65 | {
|
---|
66 | // Only these nodes can be parents of StyleSheets, and they need to call clearOwnerNode() when moved out of document.
|
---|
67 | return !parentNode
|
---|
68 | || parentNode->isDocumentNode()
|
---|
69 | || is<HTMLLinkElement>(*parentNode)
|
---|
70 | || is<HTMLStyleElement>(*parentNode)
|
---|
71 | || is<SVGStyleElement>(*parentNode)
|
---|
72 | || parentNode->nodeType() == Node::PROCESSING_INSTRUCTION_NODE;
|
---|
73 | }
|
---|
74 | #endif // ASSERT_ENABLED
|
---|
75 |
|
---|
76 | Ref<CSSStyleSheet> CSSStyleSheet::create(Ref<StyleSheetContents>&& sheet, CSSImportRule* ownerRule)
|
---|
77 | {
|
---|
78 | return adoptRef(*new CSSStyleSheet(WTFMove(sheet), ownerRule));
|
---|
79 | }
|
---|
80 |
|
---|
81 | Ref<CSSStyleSheet> CSSStyleSheet::create(Ref<StyleSheetContents>&& sheet, Node& ownerNode, const std::optional<bool>& isCleanOrigin)
|
---|
82 | {
|
---|
83 | return adoptRef(*new CSSStyleSheet(WTFMove(sheet), ownerNode, TextPosition(), false, isCleanOrigin));
|
---|
84 | }
|
---|
85 |
|
---|
86 | Ref<CSSStyleSheet> CSSStyleSheet::createInline(Ref<StyleSheetContents>&& sheet, Element& owner, const TextPosition& startPosition)
|
---|
87 | {
|
---|
88 | return adoptRef(*new CSSStyleSheet(WTFMove(sheet), owner, startPosition, true, true));
|
---|
89 | }
|
---|
90 |
|
---|
91 | CSSStyleSheet::CSSStyleSheet(Ref<StyleSheetContents>&& contents, CSSImportRule* ownerRule)
|
---|
92 | : m_contents(WTFMove(contents))
|
---|
93 | , m_ownerRule(ownerRule)
|
---|
94 | {
|
---|
95 | if (auto* parent = parentStyleSheet())
|
---|
96 | m_styleScope = parent->styleScope();
|
---|
97 |
|
---|
98 | m_contents->registerClient(this);
|
---|
99 | }
|
---|
100 |
|
---|
101 | CSSStyleSheet::CSSStyleSheet(Ref<StyleSheetContents>&& contents, Node& ownerNode, const TextPosition& startPosition, bool isInlineStylesheet, const std::optional<bool>& isOriginClean)
|
---|
102 | : m_contents(WTFMove(contents))
|
---|
103 | , m_isInlineStylesheet(isInlineStylesheet)
|
---|
104 | , m_isOriginClean(isOriginClean)
|
---|
105 | , m_styleScope(Style::Scope::forNode(ownerNode))
|
---|
106 | , m_ownerNode(&ownerNode)
|
---|
107 | , m_startPosition(startPosition)
|
---|
108 | {
|
---|
109 | ASSERT(isAcceptableCSSStyleSheetParent(&ownerNode));
|
---|
110 | m_contents->registerClient(this);
|
---|
111 | }
|
---|
112 |
|
---|
113 | CSSStyleSheet::~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 |
|
---|
128 | CSSStyleSheet::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 |
|
---|
151 | void CSSStyleSheet::didMutateRuleFromCSSStyleDeclaration()
|
---|
152 | {
|
---|
153 | ASSERT(m_contents->isMutable());
|
---|
154 | ASSERT(m_contents->hasOneClient());
|
---|
155 | didMutate();
|
---|
156 | }
|
---|
157 |
|
---|
158 | void CSSStyleSheet::didMutateRules(RuleMutationType mutationType, WhetherContentsWereClonedForMutation contentsWereClonedForMutation, StyleRuleKeyframes* insertedKeyframesRule, const String& modifiedKeyframesRuleName)
|
---|
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 | if (mutationType == KeyframesRuleMutation) {
|
---|
178 | if (auto* ownerDocument = this->ownerDocument())
|
---|
179 | ownerDocument->keyframesRuleDidChange(modifiedKeyframesRuleName);
|
---|
180 | }
|
---|
181 |
|
---|
182 | scope->didChangeStyleSheetContents();
|
---|
183 |
|
---|
184 | m_mutatedRules = true;
|
---|
185 | }
|
---|
186 |
|
---|
187 | void CSSStyleSheet::didMutate()
|
---|
188 | {
|
---|
189 | auto* scope = styleScope();
|
---|
190 | if (!scope)
|
---|
191 | return;
|
---|
192 | scope->didChangeStyleSheetContents();
|
---|
193 | }
|
---|
194 |
|
---|
195 | void CSSStyleSheet::clearOwnerNode()
|
---|
196 | {
|
---|
197 | m_ownerNode = nullptr;
|
---|
198 | }
|
---|
199 |
|
---|
200 | void CSSStyleSheet::reattachChildRuleCSSOMWrappers()
|
---|
201 | {
|
---|
202 | for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) {
|
---|
203 | if (!m_childRuleCSSOMWrappers[i])
|
---|
204 | continue;
|
---|
205 | m_childRuleCSSOMWrappers[i]->reattach(*m_contents->ruleAt(i));
|
---|
206 | }
|
---|
207 | }
|
---|
208 |
|
---|
209 | void CSSStyleSheet::setDisabled(bool disabled)
|
---|
210 | {
|
---|
211 | if (disabled == m_isDisabled)
|
---|
212 | return;
|
---|
213 | m_isDisabled = disabled;
|
---|
214 |
|
---|
215 | if (auto* scope = styleScope())
|
---|
216 | scope->didChangeActiveStyleSheetCandidates();
|
---|
217 | }
|
---|
218 |
|
---|
219 | void CSSStyleSheet::setMediaQueries(Ref<MediaQuerySet>&& mediaQueries)
|
---|
220 | {
|
---|
221 | m_mediaQueries = WTFMove(mediaQueries);
|
---|
222 | if (m_mediaCSSOMWrapper && m_mediaQueries)
|
---|
223 | m_mediaCSSOMWrapper->reattach(m_mediaQueries.get());
|
---|
224 | }
|
---|
225 |
|
---|
226 | unsigned CSSStyleSheet::length() const
|
---|
227 | {
|
---|
228 | return m_contents->ruleCount();
|
---|
229 | }
|
---|
230 |
|
---|
231 | CSSRule* CSSStyleSheet::item(unsigned index)
|
---|
232 | {
|
---|
233 | unsigned ruleCount = length();
|
---|
234 | if (index >= ruleCount)
|
---|
235 | return nullptr;
|
---|
236 |
|
---|
237 | ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == ruleCount);
|
---|
238 | if (m_childRuleCSSOMWrappers.size() < ruleCount)
|
---|
239 | m_childRuleCSSOMWrappers.grow(ruleCount);
|
---|
240 |
|
---|
241 | RefPtr<CSSRule>& cssRule = m_childRuleCSSOMWrappers[index];
|
---|
242 | if (!cssRule)
|
---|
243 | cssRule = m_contents->ruleAt(index)->createCSSOMWrapper(this);
|
---|
244 | return cssRule.get();
|
---|
245 | }
|
---|
246 |
|
---|
247 | bool CSSStyleSheet::canAccessRules() const
|
---|
248 | {
|
---|
249 | if (m_isOriginClean)
|
---|
250 | return m_isOriginClean.value();
|
---|
251 |
|
---|
252 | URL baseURL = m_contents->baseURL();
|
---|
253 | if (baseURL.isEmpty())
|
---|
254 | return true;
|
---|
255 | Document* document = ownerDocument();
|
---|
256 | if (!document)
|
---|
257 | return true;
|
---|
258 | return document->securityOrigin().canRequest(baseURL);
|
---|
259 | }
|
---|
260 |
|
---|
261 | ExceptionOr<unsigned> CSSStyleSheet::insertRule(const String& ruleString, unsigned index)
|
---|
262 | {
|
---|
263 | LOG_WITH_STREAM(StyleSheets, stream << "CSSStyleSheet " << this << " insertRule() " << ruleString << " at " << index);
|
---|
264 |
|
---|
265 | ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount());
|
---|
266 |
|
---|
267 | if (index > length())
|
---|
268 | return Exception { IndexSizeError };
|
---|
269 | RefPtr<StyleRuleBase> rule = CSSParser::parseRule(m_contents.get().parserContext(), m_contents.ptr(), ruleString);
|
---|
270 |
|
---|
271 | if (!rule)
|
---|
272 | return Exception { SyntaxError };
|
---|
273 |
|
---|
274 | RuleMutationScope mutationScope(this, RuleInsertion, dynamicDowncast<StyleRuleKeyframes>(*rule));
|
---|
275 |
|
---|
276 | bool success = m_contents.get().wrapperInsertRule(rule.releaseNonNull(), index);
|
---|
277 | if (!success)
|
---|
278 | return Exception { HierarchyRequestError };
|
---|
279 | if (!m_childRuleCSSOMWrappers.isEmpty())
|
---|
280 | m_childRuleCSSOMWrappers.insert(index, RefPtr<CSSRule>());
|
---|
281 |
|
---|
282 | return index;
|
---|
283 | }
|
---|
284 |
|
---|
285 | ExceptionOr<void> CSSStyleSheet::deleteRule(unsigned index)
|
---|
286 | {
|
---|
287 | LOG_WITH_STREAM(StyleSheets, stream << "CSSStyleSheet " << this << " deleteRule(" << index << ")");
|
---|
288 |
|
---|
289 | ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount());
|
---|
290 |
|
---|
291 | if (index >= length())
|
---|
292 | return Exception { IndexSizeError };
|
---|
293 | RuleMutationScope mutationScope(this);
|
---|
294 |
|
---|
295 | m_contents->wrapperDeleteRule(index);
|
---|
296 |
|
---|
297 | if (!m_childRuleCSSOMWrappers.isEmpty()) {
|
---|
298 | if (m_childRuleCSSOMWrappers[index])
|
---|
299 | m_childRuleCSSOMWrappers[index]->setParentStyleSheet(nullptr);
|
---|
300 | m_childRuleCSSOMWrappers.remove(index);
|
---|
301 | }
|
---|
302 |
|
---|
303 | return { };
|
---|
304 | }
|
---|
305 |
|
---|
306 | ExceptionOr<int> CSSStyleSheet::addRule(const String& selector, const String& style, std::optional<unsigned> index)
|
---|
307 | {
|
---|
308 | LOG_WITH_STREAM(StyleSheets, stream << "CSSStyleSheet " << this << " addRule() selector " << selector << " style " << style << " at " << index);
|
---|
309 |
|
---|
310 | auto text = makeString(selector, " { ", style, !style.isEmpty() ? " " : "", '}');
|
---|
311 | auto insertRuleResult = insertRule(text, index.value_or(length()));
|
---|
312 | if (insertRuleResult.hasException())
|
---|
313 | return insertRuleResult.releaseException();
|
---|
314 | // As per Microsoft documentation, always return -1.
|
---|
315 | return -1;
|
---|
316 | }
|
---|
317 |
|
---|
318 | ExceptionOr<Ref<CSSRuleList>> CSSStyleSheet::cssRulesForBindings()
|
---|
319 | {
|
---|
320 | auto cssRules = this->cssRules();
|
---|
321 | if (!cssRules)
|
---|
322 | return Exception { SecurityError, "Not allowed to access cross-origin stylesheet"_s };
|
---|
323 | return cssRules.releaseNonNull();
|
---|
324 | }
|
---|
325 |
|
---|
326 | RefPtr<CSSRuleList> CSSStyleSheet::cssRules()
|
---|
327 | {
|
---|
328 | if (!canAccessRules())
|
---|
329 | return nullptr;
|
---|
330 | if (!m_ruleListCSSOMWrapper)
|
---|
331 | m_ruleListCSSOMWrapper = makeUnique<StyleSheetCSSRuleList>(this);
|
---|
332 | return m_ruleListCSSOMWrapper.get();
|
---|
333 | }
|
---|
334 |
|
---|
335 | String CSSStyleSheet::href() const
|
---|
336 | {
|
---|
337 | return m_contents->originalURL();
|
---|
338 | }
|
---|
339 |
|
---|
340 | URL CSSStyleSheet::baseURL() const
|
---|
341 | {
|
---|
342 | return m_contents->baseURL();
|
---|
343 | }
|
---|
344 |
|
---|
345 | bool CSSStyleSheet::isLoading() const
|
---|
346 | {
|
---|
347 | return m_contents->isLoading();
|
---|
348 | }
|
---|
349 |
|
---|
350 | MediaList* CSSStyleSheet::media() const
|
---|
351 | {
|
---|
352 | if (!m_mediaQueries)
|
---|
353 | return nullptr;
|
---|
354 | if (!m_mediaCSSOMWrapper)
|
---|
355 | m_mediaCSSOMWrapper = MediaList::create(m_mediaQueries.get(), const_cast<CSSStyleSheet*>(this));
|
---|
356 | return m_mediaCSSOMWrapper.get();
|
---|
357 | }
|
---|
358 |
|
---|
359 | CSSStyleSheet* CSSStyleSheet::parentStyleSheet() const
|
---|
360 | {
|
---|
361 | return m_ownerRule ? m_ownerRule->parentStyleSheet() : nullptr;
|
---|
362 | }
|
---|
363 |
|
---|
364 | CSSStyleSheet& CSSStyleSheet::rootStyleSheet()
|
---|
365 | {
|
---|
366 | auto* root = this;
|
---|
367 | while (root->parentStyleSheet())
|
---|
368 | root = root->parentStyleSheet();
|
---|
369 | return *root;
|
---|
370 | }
|
---|
371 |
|
---|
372 | const CSSStyleSheet& CSSStyleSheet::rootStyleSheet() const
|
---|
373 | {
|
---|
374 | return const_cast<CSSStyleSheet&>(*this).rootStyleSheet();
|
---|
375 | }
|
---|
376 |
|
---|
377 | Document* CSSStyleSheet::ownerDocument() const
|
---|
378 | {
|
---|
379 | auto& root = rootStyleSheet();
|
---|
380 | return root.ownerNode() ? &root.ownerNode()->document() : nullptr;
|
---|
381 | }
|
---|
382 |
|
---|
383 | Style::Scope* CSSStyleSheet::styleScope()
|
---|
384 | {
|
---|
385 | return m_styleScope.get();
|
---|
386 | }
|
---|
387 |
|
---|
388 | void CSSStyleSheet::clearChildRuleCSSOMWrappers()
|
---|
389 | {
|
---|
390 | m_childRuleCSSOMWrappers.clear();
|
---|
391 | }
|
---|
392 |
|
---|
393 | String CSSStyleSheet::debugDescription() const
|
---|
394 | {
|
---|
395 | return makeString("CSSStyleSheet "_s, "0x"_s, hex(reinterpret_cast<uintptr_t>(this), Lowercase), ' ', href());
|
---|
396 | }
|
---|
397 |
|
---|
398 | CSSStyleSheet::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 |
|
---|
407 | CSSStyleSheet::RuleMutationScope::RuleMutationScope(CSSRule* rule)
|
---|
408 | : m_styleSheet(rule ? rule->parentStyleSheet() : nullptr)
|
---|
409 | , m_mutationType(is<CSSKeyframesRule>(rule) ? KeyframesRuleMutation : OtherMutation)
|
---|
410 | , m_contentsWereClonedForMutation(ContentsWereNotClonedForMutation)
|
---|
411 | , m_insertedKeyframesRule(nullptr)
|
---|
412 | , m_modifiedKeyframesRuleName(is<CSSKeyframesRule>(rule) ? downcast<CSSKeyframesRule>(*rule).name() : emptyAtom())
|
---|
413 | {
|
---|
414 | if (m_styleSheet)
|
---|
415 | m_contentsWereClonedForMutation = m_styleSheet->willMutateRules();
|
---|
416 | }
|
---|
417 |
|
---|
418 | CSSStyleSheet::RuleMutationScope::~RuleMutationScope()
|
---|
419 | {
|
---|
420 | if (m_styleSheet)
|
---|
421 | m_styleSheet->didMutateRules(m_mutationType, m_contentsWereClonedForMutation, m_insertedKeyframesRule.get(), m_modifiedKeyframesRuleName);
|
---|
422 | }
|
---|
423 |
|
---|
424 | }
|
---|