Rename first/second to key/value in HashMap iterators
[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 "Document.h"
33 #include "Element.h"
34 #include "HTMLLinkElement.h"
35 #include "HTMLNames.h"
36 #include "HTMLStyleElement.h"
37 #include "Page.h"
38 #include "PageGroup.h"
39 #include "ProcessingInstruction.h"
40 #include "SVGNames.h"
41 #include "SVGStyleElement.h"
42 #include "SelectorChecker.h"
43 #include "Settings.h"
44 #include "StyleResolver.h"
45 #include "StyleSheetContents.h"
46 #include "StyleSheetList.h"
47 #include "UserContentURLPattern.h"
48 #include "WebCoreMemoryInstrumentation.h"
49 #include <wtf/MemoryInstrumentationVector.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_pageGroupUserSheetCacheValid(false)
59     , m_hadActiveLoadingStylesheet(false)
60     , m_needsUpdateActiveStylesheetsOnStyleRecalc(false)
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     if (m_pageGroupUserSheets) {
76         for (size_t i = 0; i < m_pageGroupUserSheets->size(); ++i)
77             (*m_pageGroupUserSheets)[i]->clearOwnerNode();
78     }
79     if (m_userSheets) {
80         for (size_t i = 0; i < m_userSheets->size(); ++i)
81             (*m_userSheets)[i]->clearOwnerNode();
82     }
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     StyleResolver* styleResolver = m_document->styleResolver();
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     StyleResolver* styleResolver = m_document->styleResolver();
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::pageGroupUserSheets() const
138 {
139     if (m_pageGroupUserSheetCacheValid)
140         return m_pageGroupUserSheets.get();
141     
142     m_pageGroupUserSheetCacheValid = true;
143     
144     Page* owningPage = m_document->page();
145     if (!owningPage)
146         return 0;
147         
148     const PageGroup& pageGroup = owningPage->group();
149     const UserStyleSheetMap* sheetsMap = pageGroup.userStyleSheets();
150     if (!sheetsMap)
151         return 0;
152
153     UserStyleSheetMap::const_iterator end = sheetsMap->end();
154     for (UserStyleSheetMap::const_iterator it = sheetsMap->begin(); it != end; ++it) {
155         const UserStyleSheetVector* sheets = it->value.get();
156         for (unsigned i = 0; i < sheets->size(); ++i) {
157             const UserStyleSheet* sheet = sheets->at(i).get();
158             if (sheet->injectedFrames() == InjectInTopFrameOnly && m_document->ownerElement())
159                 continue;
160             if (!UserContentURLPattern::matchesPatterns(m_document->url(), sheet->whitelist(), sheet->blacklist()))
161                 continue;
162             RefPtr<CSSStyleSheet> groupSheet = CSSStyleSheet::createInline(const_cast<Document*>(m_document), sheet->url());
163             if (!m_pageGroupUserSheets)
164                 m_pageGroupUserSheets = adoptPtr(new Vector<RefPtr<CSSStyleSheet> >);
165             m_pageGroupUserSheets->append(groupSheet);
166             groupSheet->contents()->setIsUserStyleSheet(sheet->level() == UserStyleUserLevel);
167             groupSheet->contents()->parseString(sheet->source());
168         }
169     }
170
171     return m_pageGroupUserSheets.get();
172 }
173
174 void DocumentStyleSheetCollection::clearPageGroupUserSheets()
175 {
176     m_pageGroupUserSheetCacheValid = false;
177     if (m_pageGroupUserSheets && m_pageGroupUserSheets->size()) {
178         m_pageGroupUserSheets->clear();
179         m_document->styleResolverChanged(DeferRecalcStyle);
180     }
181 }
182
183 void DocumentStyleSheetCollection::updatePageGroupUserSheets()
184 {
185     clearPageGroupUserSheets();
186     if (pageGroupUserSheets() && pageGroupUserSheets()->size())
187         m_document->styleResolverChanged(RecalcStyleImmediately);
188 }
189
190 void DocumentStyleSheetCollection::addUserSheet(PassRefPtr<StyleSheetContents> userSheet)
191 {
192     if (!m_userSheets)
193         m_userSheets = adoptPtr(new Vector<RefPtr<CSSStyleSheet> >);
194     m_userSheets->append(CSSStyleSheet::create(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     StyleSheetCandidateListHashSet::iterator begin = m_styleSheetCandidateNodes.begin();
266     StyleSheetCandidateListHashSet::iterator end = m_styleSheetCandidateNodes.end();
267     for (StyleSheetCandidateListHashSet::iterator it = begin; it != end; ++it) {
268         Node* n = *it;
269         StyleSheet* sheet = 0;
270         if (n->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) {
271             // Processing instruction (XML documents only).
272             // We don't support linking to embedded CSS stylesheets, see <https://bugs.webkit.org/show_bug.cgi?id=49281> for discussion.
273             ProcessingInstruction* pi = static_cast<ProcessingInstruction*>(n);
274             sheet = pi->sheet();
275 #if ENABLE(XSLT)
276             // Don't apply XSL transforms to already transformed documents -- <rdar://problem/4132806>
277             if (pi->isXSL() && !m_document->transformSourceDocument()) {
278                 // Don't apply XSL transforms until loading is finished.
279                 if (!m_document->parsing())
280                     m_document->applyXSLTransform(pi);
281                 return;
282             }
283 #endif
284         } else if ((n->isHTMLElement() && (n->hasTagName(linkTag) || n->hasTagName(styleTag)))
285 #if ENABLE(SVG)
286                    ||  (n->isSVGElement() && n->hasTagName(SVGNames::styleTag))
287 #endif
288                    ) {
289             Element* e = static_cast<Element*>(n);
290             AtomicString title = e->getAttribute(titleAttr);
291             bool enabledViaScript = false;
292             if (e->hasLocalName(linkTag)) {
293                 // <LINK> element
294                 HTMLLinkElement* linkElement = static_cast<HTMLLinkElement*>(n);
295                 if (linkElement->isDisabled())
296                     continue;
297                 enabledViaScript = linkElement->isEnabledViaScript();
298                 if (linkElement->styleSheetIsLoading()) {
299                     // it is loading but we should still decide which style sheet set to use
300                     if (!enabledViaScript && !title.isEmpty() && m_preferredStylesheetSetName.isEmpty()) {
301                         const AtomicString& rel = e->getAttribute(relAttr);
302                         if (!rel.contains("alternate")) {
303                             m_preferredStylesheetSetName = title;
304                             m_selectedStylesheetSetName = title;
305                         }
306                     }
307                     continue;
308                 }
309                 if (!linkElement->sheet())
310                     title = nullAtom;
311             }
312             // Get the current preferred styleset. This is the
313             // set of sheets that will be enabled.
314 #if ENABLE(SVG)
315             if (n->isSVGElement() && n->hasTagName(SVGNames::styleTag))
316                 sheet = static_cast<SVGStyleElement*>(n)->sheet();
317             else
318 #endif
319             if (e->hasLocalName(linkTag))
320                 sheet = static_cast<HTMLLinkElement*>(n)->sheet();
321             else
322                 // <STYLE> element
323                 sheet = static_cast<HTMLStyleElement*>(n)->sheet();
324             // Check to see if this sheet belongs to a styleset
325             // (thus making it PREFERRED or ALTERNATE rather than
326             // PERSISTENT).
327             AtomicString rel = e->getAttribute(relAttr);
328             if (!enabledViaScript && !title.isEmpty()) {
329                 // Yes, we have a title.
330                 if (m_preferredStylesheetSetName.isEmpty()) {
331                     // No preferred set has been established. If
332                     // we are NOT an alternate sheet, then establish
333                     // us as the preferred set. Otherwise, just ignore
334                     // this sheet.
335                     if (e->hasLocalName(styleTag) || !rel.contains("alternate"))
336                         m_preferredStylesheetSetName = m_selectedStylesheetSetName = title;
337                 }
338                 if (title != m_preferredStylesheetSetName)
339                     sheet = 0;
340             }
341
342             if (rel.contains("alternate") && title.isEmpty())
343                 sheet = 0;
344         }
345         if (sheet)
346             sheets.append(sheet);
347     }
348 }
349
350 bool DocumentStyleSheetCollection::testAddedStyleSheetRequiresStyleRecalc(StyleSheetContents* stylesheet)
351 {
352     // See if all rules on the sheet are scoped to some specific ids or classes.
353     // Then test if we actually have any of those in the tree at the moment.
354     HashSet<AtomicStringImpl*> idScopes; 
355     HashSet<AtomicStringImpl*> classScopes;
356     if (!StyleResolver::determineStylesheetSelectorScopes(stylesheet, idScopes, classScopes))
357         return true;
358     // Invalidate the subtrees that match the scopes.
359     Node* node = m_document->firstChild();
360     while (node) {
361         if (!node->isStyledElement()) {
362             node = node->traverseNextNode();
363             continue;
364         }
365         StyledElement* element = static_cast<StyledElement*>(node);
366         if (SelectorChecker::elementMatchesSelectorScopes(element, idScopes, classScopes)) {
367             element->setNeedsStyleRecalc();
368             // The whole subtree is now invalidated, we can skip to the next sibling.
369             node = node->traverseNextSibling();
370             continue;
371         }
372         node = node->traverseNextNode();
373     }
374     return false;
375 }
376
377 void DocumentStyleSheetCollection::analyzeStyleSheetChange(UpdateFlag updateFlag, const Vector<RefPtr<StyleSheet> >& newStylesheets, StyleResolverUpdateType& styleResolverUpdateType, bool& requiresFullStyleRecalc)
378 {
379     styleResolverUpdateType = Reconstruct;
380     requiresFullStyleRecalc = true;
381     
382     // Stylesheets of <style> elements that @import stylesheets are active but loading. We need to trigger a full recalc when such loads are done.
383     bool hasActiveLoadingStylesheet = false;
384     unsigned newStylesheetCount = newStylesheets.size();
385     for (unsigned i = 0; i < newStylesheetCount; ++i) {
386         if (newStylesheets[i]->isLoading())
387             hasActiveLoadingStylesheet = true;
388     }
389     if (m_hadActiveLoadingStylesheet && !hasActiveLoadingStylesheet) {
390         m_hadActiveLoadingStylesheet = false;
391         return;
392     }
393     m_hadActiveLoadingStylesheet = hasActiveLoadingStylesheet;
394
395     if (updateFlag != OptimizedUpdate)
396         return;
397     if (!m_document->styleResolverIfExists())
398         return;
399
400     // Find out which stylesheets are new.
401     unsigned oldStylesheetCount = m_authorStyleSheets.size();
402     if (newStylesheetCount < oldStylesheetCount)
403         return;
404     Vector<StyleSheet*> addedSheets;
405     unsigned newIndex = 0;
406     for (unsigned oldIndex = 0; oldIndex < oldStylesheetCount; ++oldIndex) {
407         if (newIndex >= newStylesheetCount)
408             return;
409         while (m_authorStyleSheets[oldIndex] != newStylesheets[newIndex]) {
410             addedSheets.append(newStylesheets[newIndex].get());
411             ++newIndex;
412             if (newIndex == newStylesheetCount)
413                 return;
414         }
415         ++newIndex;
416     }
417     bool hasInsertions = !addedSheets.isEmpty();
418     while (newIndex < newStylesheetCount) {
419         addedSheets.append(newStylesheets[newIndex].get());
420         ++newIndex;
421     }
422     // If all new sheets were added at the end of the list we can just add them to existing StyleResolver.
423     // If there were insertions we need to re-add all the stylesheets so rules are ordered correctly.
424     styleResolverUpdateType = hasInsertions ? Reset : Additive;
425
426     // If we are already parsing the body and so may have significant amount of elements, put some effort into trying to avoid style recalcs.
427     if (!m_document->body() || m_document->hasNodesWithPlaceholderStyle())
428         return;
429     for (unsigned i = 0; i < addedSheets.size(); ++i) {
430         if (!addedSheets[i]->isCSSStyleSheet())
431             return;
432         if (addedSheets[i]->disabled())
433             continue;
434         if (testAddedStyleSheetRequiresStyleRecalc(static_cast<CSSStyleSheet*>(addedSheets[i])->contents()))
435             return;
436     }
437     requiresFullStyleRecalc = false;
438 }
439
440 static bool styleSheetsUseRemUnits(const Vector<RefPtr<StyleSheet> >& sheets)
441 {
442     for (unsigned i = 0; i < sheets.size(); ++i) {
443         if (!sheets[i]->isCSSStyleSheet())
444             continue;
445         if (static_cast<CSSStyleSheet*>(sheets[i].get())->contents()->usesRemUnits())
446             return true;
447     }
448     return false;
449 }
450
451 bool DocumentStyleSheetCollection::updateActiveStyleSheets(UpdateFlag updateFlag)
452 {
453     if (m_document->inStyleRecalc()) {
454         // SVG <use> element may manage to invalidate style selector in the middle of a style recalc.
455         // https://bugs.webkit.org/show_bug.cgi?id=54344
456         // FIXME: This should be fixed in SVG and the call site replaced by ASSERT(!m_inStyleRecalc).
457         m_needsUpdateActiveStylesheetsOnStyleRecalc = true;
458         m_document->scheduleForcedStyleRecalc();
459         return false;
460
461     }
462     if (!m_document->renderer() || !m_document->attached())
463         return false;
464
465     Vector<RefPtr<StyleSheet> > newStylesheets;
466     collectActiveStyleSheets(newStylesheets);
467
468     StyleResolverUpdateType styleResolverUpdateType;
469     bool requiresFullStyleRecalc;
470     analyzeStyleSheetChange(updateFlag, newStylesheets, styleResolverUpdateType, requiresFullStyleRecalc);
471
472     if (styleResolverUpdateType == Reconstruct)
473         m_document->clearStyleResolver();
474     else {
475         StyleResolver* styleResolver = m_document->styleResolver();
476         if (styleResolverUpdateType == Reset) {
477             styleResolver->resetAuthorStyle();
478             styleResolver->appendAuthorStylesheets(0, newStylesheets);
479         } else {
480             ASSERT(styleResolverUpdateType == Additive);
481             styleResolver->appendAuthorStylesheets(m_authorStyleSheets.size(), newStylesheets);
482         }
483         resetCSSFeatureFlags();
484     }
485     m_authorStyleSheets.swap(newStylesheets);
486
487     m_usesRemUnits = styleSheetsUseRemUnits(m_authorStyleSheets);
488     m_needsUpdateActiveStylesheetsOnStyleRecalc = false;
489
490     m_document->notifySeamlessChildDocumentsOfStylesheetUpdate();
491
492     return requiresFullStyleRecalc;
493 }
494
495 void DocumentStyleSheetCollection::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
496 {
497     MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::DOM);
498     info.addMember(m_pageUserSheet);
499     info.addMember(m_pageGroupUserSheets);
500     info.addMember(m_userSheets);
501     info.addMember(m_authorStyleSheets);
502     info.addListHashSet(m_styleSheetCandidateNodes);
503     info.addMember(m_preferredStylesheetSetName);
504     info.addMember(m_selectedStylesheetSetName);
505 }
506
507 }