Add WTF::move()
[WebKit-https.git] / Source / WebCore / dom / DocumentStyleSheetCollection.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2001 Dirk Mueller (mueller@kde.org)
5  *           (C) 2006 Alexey Proskuryakov (ap@webkit.org)
6  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2011, 2012 Apple Inc. All rights reserved.
7  * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
8  * Copyright (C) 2008, 2009, 2011, 2012 Google Inc. All rights reserved.
9  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
10  * Copyright (C) Research In Motion Limited 2010-2011. All rights reserved.
11  *
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Library General Public
14  * License as published by the Free Software Foundation; either
15  * version 2 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Library General Public License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public License
23  * along with this library; see the file COPYING.LIB.  If not, write to
24  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25  * Boston, MA 02110-1301, USA.
26  */
27
28 #include "config.h"
29 #include "DocumentStyleSheetCollection.h"
30
31 #include "CSSStyleSheet.h"
32 #include "Element.h"
33 #include "HTMLIFrameElement.h"
34 #include "HTMLLinkElement.h"
35 #include "HTMLStyleElement.h"
36 #include "Page.h"
37 #include "PageGroup.h"
38 #include "ProcessingInstruction.h"
39 #include "SVGNames.h"
40 #include "SVGStyleElement.h"
41 #include "Settings.h"
42 #include "StyleInvalidationAnalysis.h"
43 #include "StyleResolver.h"
44 #include "StyleSheetContents.h"
45 #include "StyleSheetList.h"
46 #include "UserContentController.h"
47 #include "UserContentURLPattern.h"
48
49 namespace WebCore {
50
51 using namespace HTMLNames;
52
53 DocumentStyleSheetCollection::DocumentStyleSheetCollection(Document& document)
54     : m_document(document)
55     , m_pendingStylesheets(0)
56     , m_injectedStyleSheetCacheValid(false)
57     , m_hadActiveLoadingStylesheet(false)
58     , m_pendingUpdateType(NoUpdate)
59     , m_usesSiblingRules(false)
60     , m_usesSiblingRulesOverride(false)
61     , m_usesFirstLineRules(false)
62     , m_usesFirstLetterRules(false)
63     , m_usesBeforeAfterRules(false)
64     , m_usesBeforeAfterRulesOverride(false)
65     , m_usesRemUnits(false)
66 {
67 }
68
69 void DocumentStyleSheetCollection::combineCSSFeatureFlags()
70 {
71     // Delay resetting the flags until after next style recalc since unapplying the style may not work without these set (this is true at least with before/after).
72     const StyleResolver& styleResolver = m_document.ensureStyleResolver();
73     m_usesSiblingRules = m_usesSiblingRules || styleResolver.usesSiblingRules();
74     m_usesFirstLineRules = m_usesFirstLineRules || styleResolver.usesFirstLineRules();
75     m_usesBeforeAfterRules = m_usesBeforeAfterRules || styleResolver.usesBeforeAfterRules();
76 }
77
78 void DocumentStyleSheetCollection::resetCSSFeatureFlags()
79 {
80     const StyleResolver& styleResolver = m_document.ensureStyleResolver();
81     m_usesSiblingRules = styleResolver.usesSiblingRules();
82     m_usesFirstLineRules = styleResolver.usesFirstLineRules();
83     m_usesBeforeAfterRules = styleResolver.usesBeforeAfterRules();
84 }
85
86 CSSStyleSheet* DocumentStyleSheetCollection::pageUserSheet()
87 {
88     if (m_pageUserSheet)
89         return m_pageUserSheet.get();
90     
91     Page* owningPage = m_document.page();
92     if (!owningPage)
93         return 0;
94     
95     String userSheetText = owningPage->userStyleSheet();
96     if (userSheetText.isEmpty())
97         return 0;
98     
99     // Parse the sheet and cache it.
100     m_pageUserSheet = CSSStyleSheet::createInline(m_document, m_document.settings()->userStyleSheetLocation());
101     m_pageUserSheet->contents().setIsUserStyleSheet(true);
102     m_pageUserSheet->contents().parseString(userSheetText);
103     return m_pageUserSheet.get();
104 }
105
106 void DocumentStyleSheetCollection::clearPageUserSheet()
107 {
108     if (m_pageUserSheet) {
109         m_pageUserSheet = 0;
110         m_document.styleResolverChanged(DeferRecalcStyle);
111     }
112 }
113
114 void DocumentStyleSheetCollection::updatePageUserSheet()
115 {
116     clearPageUserSheet();
117     if (pageUserSheet())
118         m_document.styleResolverChanged(RecalcStyleImmediately);
119 }
120
121 const Vector<RefPtr<CSSStyleSheet>>& DocumentStyleSheetCollection::injectedUserStyleSheets() const
122 {
123     updateInjectedStyleSheetCache();
124     return m_injectedUserStyleSheets;
125 }
126
127 const Vector<RefPtr<CSSStyleSheet>>& DocumentStyleSheetCollection::injectedAuthorStyleSheets() const
128 {
129     updateInjectedStyleSheetCache();
130     return m_injectedAuthorStyleSheets;
131 }
132
133 void DocumentStyleSheetCollection::updateInjectedStyleSheetCache() const
134 {
135     if (m_injectedStyleSheetCacheValid)
136         return;
137     m_injectedStyleSheetCacheValid = true;
138     m_injectedUserStyleSheets.clear();
139     m_injectedAuthorStyleSheets.clear();
140
141     Page* owningPage = m_document.page();
142     if (!owningPage)
143         return;
144
145     const auto* userContentController = owningPage->userContentController();
146     if (!userContentController)
147         return;
148
149     const UserStyleSheetMap* userStyleSheets = userContentController->userStyleSheets();
150     if (!userStyleSheets)
151         return;
152
153     for (auto& styleSheets : userStyleSheets->values()) {
154         for (const auto& sheet : *styleSheets) {
155             if (sheet->injectedFrames() == InjectInTopFrameOnly && m_document.ownerElement())
156                 continue;
157
158             if (!UserContentURLPattern::matchesPatterns(m_document.url(), sheet->whitelist(), sheet->blacklist()))
159                 continue;
160
161             RefPtr<CSSStyleSheet> groupSheet = CSSStyleSheet::createInline(const_cast<Document&>(m_document), sheet->url());
162             bool isUserStyleSheet = sheet->level() == UserStyleUserLevel;
163             if (isUserStyleSheet)
164                 m_injectedUserStyleSheets.append(groupSheet);
165             else
166                 m_injectedAuthorStyleSheets.append(groupSheet);
167
168             groupSheet->contents().setIsUserStyleSheet(isUserStyleSheet);
169             groupSheet->contents().parseString(sheet->source());
170         }
171     }
172 }
173
174 void DocumentStyleSheetCollection::invalidateInjectedStyleSheetCache()
175 {
176     if (!m_injectedStyleSheetCacheValid)
177         return;
178     m_injectedStyleSheetCacheValid = false;
179     if (m_injectedUserStyleSheets.isEmpty() && m_injectedAuthorStyleSheets.isEmpty())
180         return;
181     m_document.styleResolverChanged(DeferRecalcStyle);
182 }
183
184 void DocumentStyleSheetCollection::addAuthorSheet(PassRef<StyleSheetContents> authorSheet)
185 {
186     ASSERT(!authorSheet.get().isUserStyleSheet());
187     m_authorStyleSheets.append(CSSStyleSheet::create(WTF::move(authorSheet), &m_document));
188     m_document.styleResolverChanged(RecalcStyleImmediately);
189 }
190
191 void DocumentStyleSheetCollection::addUserSheet(PassRef<StyleSheetContents> userSheet)
192 {
193     ASSERT(userSheet.get().isUserStyleSheet());
194     m_userStyleSheets.append(CSSStyleSheet::create(WTF::move(userSheet), &m_document));
195     m_document.styleResolverChanged(RecalcStyleImmediately);
196 }
197
198 // This method is called whenever a top-level stylesheet has finished loading.
199 void DocumentStyleSheetCollection::removePendingSheet(RemovePendingSheetNotificationType notification)
200 {
201     // Make sure we knew this sheet was pending, and that our count isn't out of sync.
202     ASSERT(m_pendingStylesheets > 0);
203
204     m_pendingStylesheets--;
205     
206 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
207     if (!ownerElement())
208         printf("Stylesheet loaded at time %d. %d stylesheets still remain.\n", elapsedTime(), m_pendingStylesheets);
209 #endif
210
211     if (m_pendingStylesheets)
212         return;
213
214     if (notification == RemovePendingSheetNotifyLater) {
215         m_document.setNeedsNotifyRemoveAllPendingStylesheet();
216         return;
217     }
218     
219     m_document.didRemoveAllPendingStylesheet();
220 }
221
222 void DocumentStyleSheetCollection::addStyleSheetCandidateNode(Node& node, bool createdByParser)
223 {
224     if (!node.inDocument())
225         return;
226     
227     // Until the <body> exists, we have no choice but to compare document positions,
228     // since styles outside of the body and head continue to be shunted into the head
229     // (and thus can shift to end up before dynamically added DOM content that is also
230     // outside the body).
231     if ((createdByParser && m_document.body()) || m_styleSheetCandidateNodes.isEmpty()) {
232         m_styleSheetCandidateNodes.add(&node);
233         return;
234     }
235
236     // Determine an appropriate insertion point.
237     StyleSheetCandidateListHashSet::iterator begin = m_styleSheetCandidateNodes.begin();
238     StyleSheetCandidateListHashSet::iterator end = m_styleSheetCandidateNodes.end();
239     StyleSheetCandidateListHashSet::iterator it = end;
240     Node* followingNode = 0;
241     do {
242         --it;
243         Node* n = *it;
244         unsigned short position = n->compareDocumentPosition(&node);
245         if (position == Node::DOCUMENT_POSITION_FOLLOWING) {
246             m_styleSheetCandidateNodes.insertBefore(followingNode, &node);
247             return;
248         }
249         followingNode = n;
250     } while (it != begin);
251     
252     m_styleSheetCandidateNodes.insertBefore(followingNode, &node);
253 }
254
255 void DocumentStyleSheetCollection::removeStyleSheetCandidateNode(Node& node)
256 {
257     m_styleSheetCandidateNodes.remove(&node);
258 }
259
260 void DocumentStyleSheetCollection::collectActiveStyleSheets(Vector<RefPtr<StyleSheet>>& sheets)
261 {
262     if (m_document.settings() && !m_document.settings()->authorAndUserStylesEnabled())
263         return;
264
265     for (auto& node : m_styleSheetCandidateNodes) {
266         StyleSheet* sheet = nullptr;
267         if (node->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) {
268             // Processing instruction (XML documents only).
269             // We don't support linking to embedded CSS stylesheets, see <https://bugs.webkit.org/show_bug.cgi?id=49281> for discussion.
270             ProcessingInstruction* pi = toProcessingInstruction(node);
271             sheet = pi->sheet();
272 #if ENABLE(XSLT)
273             // Don't apply XSL transforms to already transformed documents -- <rdar://problem/4132806>
274             if (pi->isXSL() && !m_document.transformSourceDocument()) {
275                 // Don't apply XSL transforms until loading is finished.
276                 if (!m_document.parsing())
277                     m_document.applyXSLTransform(pi);
278                 return;
279             }
280 #endif
281         } else if ((node->isHTMLElement() && (toHTMLElement(*node).hasTagName(linkTag) || toHTMLElement(*node).hasTagName(styleTag))) || (node->isSVGElement() && toSVGElement(*node).hasTagName(SVGNames::styleTag))) {
282             Element& element = toElement(*node);
283             AtomicString title = element.getAttribute(titleAttr);
284             bool enabledViaScript = false;
285             if (isHTMLLinkElement(element)) {
286                 // <LINK> element
287                 HTMLLinkElement& linkElement = toHTMLLinkElement(element);
288                 if (linkElement.isDisabled())
289                     continue;
290                 enabledViaScript = linkElement.isEnabledViaScript();
291                 if (linkElement.styleSheetIsLoading()) {
292                     // it is loading but we should still decide which style sheet set to use
293                     if (!enabledViaScript && !title.isEmpty() && m_preferredStylesheetSetName.isEmpty()) {
294                         if (!linkElement.fastGetAttribute(relAttr).contains("alternate")) {
295                             m_preferredStylesheetSetName = title;
296                             m_selectedStylesheetSetName = title;
297                         }
298                     }
299                     continue;
300                 }
301                 if (!linkElement.sheet())
302                     title = nullAtom;
303             }
304             // Get the current preferred styleset. This is the
305             // set of sheets that will be enabled.
306             if (isSVGStyleElement(element))
307                 sheet = toSVGStyleElement(element).sheet();
308             else if (isHTMLLinkElement(element))
309                 sheet = toHTMLLinkElement(element).sheet();
310             else
311                 sheet = toHTMLStyleElement(element).sheet();
312             // Check to see if this sheet belongs to a styleset
313             // (thus making it PREFERRED or ALTERNATE rather than
314             // PERSISTENT).
315             auto& rel = element.fastGetAttribute(relAttr);
316             if (!enabledViaScript && !title.isEmpty()) {
317                 // Yes, we have a title.
318                 if (m_preferredStylesheetSetName.isEmpty()) {
319                     // No preferred set has been established. If
320                     // we are NOT an alternate sheet, then establish
321                     // us as the preferred set. Otherwise, just ignore
322                     // this sheet.
323                     if (isHTMLStyleElement(element) || !rel.contains("alternate"))
324                         m_preferredStylesheetSetName = m_selectedStylesheetSetName = title;
325                 }
326                 if (title != m_preferredStylesheetSetName)
327                     sheet = nullptr;
328             }
329
330             if (rel.contains("alternate") && title.isEmpty())
331                 sheet = nullptr;
332         }
333         if (sheet)
334             sheets.append(sheet);
335     }
336 }
337
338 void DocumentStyleSheetCollection::analyzeStyleSheetChange(UpdateFlag updateFlag, const Vector<RefPtr<CSSStyleSheet>>& newStylesheets, StyleResolverUpdateType& styleResolverUpdateType, bool& requiresFullStyleRecalc)
339 {
340     styleResolverUpdateType = Reconstruct;
341     requiresFullStyleRecalc = true;
342     
343     // Stylesheets of <style> elements that @import stylesheets are active but loading. We need to trigger a full recalc when such loads are done.
344     bool hasActiveLoadingStylesheet = false;
345     unsigned newStylesheetCount = newStylesheets.size();
346     for (unsigned i = 0; i < newStylesheetCount; ++i) {
347         if (newStylesheets[i]->isLoading())
348             hasActiveLoadingStylesheet = true;
349     }
350     if (m_hadActiveLoadingStylesheet && !hasActiveLoadingStylesheet) {
351         m_hadActiveLoadingStylesheet = false;
352         return;
353     }
354     m_hadActiveLoadingStylesheet = hasActiveLoadingStylesheet;
355
356     if (updateFlag != OptimizedUpdate)
357         return;
358     if (!m_document.styleResolverIfExists())
359         return;
360     StyleResolver& styleResolver = *m_document.styleResolverIfExists();
361
362     // Find out which stylesheets are new.
363     unsigned oldStylesheetCount = m_activeAuthorStyleSheets.size();
364     if (newStylesheetCount < oldStylesheetCount)
365         return;
366     Vector<StyleSheetContents*> addedSheets;
367     unsigned newIndex = 0;
368     for (unsigned oldIndex = 0; oldIndex < oldStylesheetCount; ++oldIndex) {
369         if (newIndex >= newStylesheetCount)
370             return;
371         while (m_activeAuthorStyleSheets[oldIndex] != newStylesheets[newIndex]) {
372             addedSheets.append(&newStylesheets[newIndex]->contents());
373             ++newIndex;
374             if (newIndex == newStylesheetCount)
375                 return;
376         }
377         ++newIndex;
378     }
379     bool hasInsertions = !addedSheets.isEmpty();
380     while (newIndex < newStylesheetCount) {
381         addedSheets.append(&newStylesheets[newIndex]->contents());
382         ++newIndex;
383     }
384     // If all new sheets were added at the end of the list we can just add them to existing StyleResolver.
385     // If there were insertions we need to re-add all the stylesheets so rules are ordered correctly.
386     styleResolverUpdateType = hasInsertions ? Reset : Additive;
387
388     // If we are already parsing the body and so may have significant amount of elements, put some effort into trying to avoid style recalcs.
389     if (!m_document.body() || m_document.hasNodesWithPlaceholderStyle())
390         return;
391     StyleInvalidationAnalysis invalidationAnalysis(addedSheets, styleResolver.mediaQueryEvaluator());
392     if (invalidationAnalysis.dirtiesAllStyle())
393         return;
394     invalidationAnalysis.invalidateStyle(m_document);
395     requiresFullStyleRecalc = false;
396 }
397
398 static bool styleSheetsUseRemUnits(const Vector<RefPtr<CSSStyleSheet>>& sheets)
399 {
400     for (unsigned i = 0; i < sheets.size(); ++i) {
401         if (sheets[i]->contents().usesRemUnits())
402             return true;
403     }
404     return false;
405 }
406
407 static void filterEnabledNonemptyCSSStyleSheets(Vector<RefPtr<CSSStyleSheet>>& result, const Vector<RefPtr<StyleSheet>>& sheets)
408 {
409     for (unsigned i = 0; i < sheets.size(); ++i) {
410         if (!sheets[i]->isCSSStyleSheet())
411             continue;
412         if (sheets[i]->disabled())
413             continue;
414         CSSStyleSheet* sheet = static_cast<CSSStyleSheet*>(sheets[i].get());
415         if (!sheet->length())
416             continue;
417         result.append(sheet);
418     }
419 }
420
421 bool DocumentStyleSheetCollection::updateActiveStyleSheets(UpdateFlag updateFlag)
422 {
423     if (m_document.inStyleRecalc()) {
424         // SVG <use> element may manage to invalidate style selector in the middle of a style recalc.
425         // https://bugs.webkit.org/show_bug.cgi?id=54344
426         // FIXME: This should be fixed in SVG and the call site replaced by ASSERT(!m_inStyleRecalc).
427         m_pendingUpdateType = FullUpdate;
428         m_document.scheduleForcedStyleRecalc();
429         return false;
430
431     }
432     if (!m_document.hasLivingRenderTree())
433         return false;
434
435     Vector<RefPtr<StyleSheet>> activeStyleSheets;
436     collectActiveStyleSheets(activeStyleSheets);
437
438     Vector<RefPtr<CSSStyleSheet>> activeCSSStyleSheets;
439     activeCSSStyleSheets.appendVector(injectedAuthorStyleSheets());
440     activeCSSStyleSheets.appendVector(documentAuthorStyleSheets());
441     filterEnabledNonemptyCSSStyleSheets(activeCSSStyleSheets, activeStyleSheets);
442
443     StyleResolverUpdateType styleResolverUpdateType;
444     bool requiresFullStyleRecalc;
445     analyzeStyleSheetChange(updateFlag, activeCSSStyleSheets, styleResolverUpdateType, requiresFullStyleRecalc);
446
447     if (styleResolverUpdateType == Reconstruct)
448         m_document.clearStyleResolver();
449     else {
450         StyleResolver& styleResolver = m_document.ensureStyleResolver();
451         if (styleResolverUpdateType == Reset) {
452             styleResolver.ruleSets().resetAuthorStyle();
453             styleResolver.appendAuthorStyleSheets(0, activeCSSStyleSheets);
454         } else {
455             ASSERT(styleResolverUpdateType == Additive);
456             styleResolver.appendAuthorStyleSheets(m_activeAuthorStyleSheets.size(), activeCSSStyleSheets);
457         }
458         resetCSSFeatureFlags();
459     }
460
461     m_weakCopyOfActiveStyleSheetListForFastLookup = nullptr;
462     m_activeAuthorStyleSheets.swap(activeCSSStyleSheets);
463     m_styleSheetsForStyleSheetList.swap(activeStyleSheets);
464
465     m_usesRemUnits = styleSheetsUseRemUnits(m_activeAuthorStyleSheets);
466     m_pendingUpdateType = NoUpdate;
467
468     return requiresFullStyleRecalc;
469 }
470
471 bool DocumentStyleSheetCollection::activeStyleSheetsContains(const CSSStyleSheet* sheet) const
472 {
473     if (!m_weakCopyOfActiveStyleSheetListForFastLookup) {
474         m_weakCopyOfActiveStyleSheetListForFastLookup = std::make_unique<HashSet<const CSSStyleSheet*>>();
475         for (unsigned i = 0; i < m_activeAuthorStyleSheets.size(); ++i)
476             m_weakCopyOfActiveStyleSheetListForFastLookup->add(m_activeAuthorStyleSheets[i].get());
477     }
478     return m_weakCopyOfActiveStyleSheetListForFastLookup->contains(sheet);
479 }
480
481 void DocumentStyleSheetCollection::detachFromDocument()
482 {
483     if (m_pageUserSheet)
484         m_pageUserSheet->detachFromDocument();
485     for (unsigned i = 0; i < m_injectedUserStyleSheets.size(); ++i)
486         m_injectedUserStyleSheets[i]->detachFromDocument();
487     for (unsigned i = 0; i < m_injectedAuthorStyleSheets.size(); ++i)
488         m_injectedAuthorStyleSheets[i]->detachFromDocument();
489     for (unsigned i = 0; i < m_userStyleSheets.size(); ++i)
490         m_userStyleSheets[i]->detachFromDocument();
491     for (unsigned i = 0; i < m_authorStyleSheets.size(); ++i)
492         m_authorStyleSheets[i]->detachFromDocument();
493 }
494
495 }