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