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