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