Home | History | Annotate | Download | only in dom
      1 /*
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      4  *           (C) 2001 Dirk Mueller (mueller (at) kde.org)
      5  *           (C) 2006 Alexey Proskuryakov (ap (at) webkit.org)
      6  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All rights reserved.
      7  * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
      8  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
      9  * Copyright (C) 2013 Google Inc. All rights reserved.
     10  *
     11  * This library is free software; you can redistribute it and/or
     12  * modify it under the terms of the GNU Library General Public
     13  * License as published by the Free Software Foundation; either
     14  * version 2 of the License, or (at your option) any later version.
     15  *
     16  * This library is distributed in the hope that it will be useful,
     17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     19  * Library General Public License for more details.
     20  *
     21  * You should have received a copy of the GNU Library General Public License
     22  * along with this library; see the file COPYING.LIB.  If not, write to
     23  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     24  * Boston, MA 02110-1301, USA.
     25  */
     26 
     27 #include "config.h"
     28 #include "core/dom/StyleSheetCollection.h"
     29 
     30 #include "HTMLNames.h"
     31 #include "SVGNames.h"
     32 #include "core/css/CSSStyleSheet.h"
     33 #include "core/css/StyleInvalidationAnalysis.h"
     34 #include "core/css/StyleSheetContents.h"
     35 #include "core/css/resolver/StyleResolver.h"
     36 #include "core/dom/Document.h"
     37 #include "core/dom/DocumentStyleSheetCollection.h"
     38 #include "core/dom/Element.h"
     39 #include "core/dom/ProcessingInstruction.h"
     40 #include "core/html/HTMLIFrameElement.h"
     41 #include "core/html/HTMLLinkElement.h"
     42 #include "core/html/HTMLStyleElement.h"
     43 #include "core/page/Page.h"
     44 #include "core/page/PageGroup.h"
     45 #include "core/page/Settings.h"
     46 #include "core/page/UserContentURLPattern.h"
     47 #include "core/svg/SVGStyleElement.h"
     48 
     49 namespace WebCore {
     50 
     51 using namespace HTMLNames;
     52 
     53 StyleSheetCollection::StyleSheetCollection(TreeScope* treeScope)
     54     : m_treeScope(treeScope)
     55     , m_hadActiveLoadingStylesheet(false)
     56 {
     57 }
     58 
     59 void StyleSheetCollection::addStyleSheetCandidateNode(Node* node, bool createdByParser)
     60 {
     61     if (!node->inDocument())
     62         return;
     63 
     64     // Until the <body> exists, we have no choice but to compare document positions,
     65     // since styles outside of the body and head continue to be shunted into the head
     66     // (and thus can shift to end up before dynamically added DOM content that is also
     67     // outside the body).
     68     if (createdByParser && document()->body())
     69         m_styleSheetCandidateNodes.parserAdd(node);
     70     else
     71         m_styleSheetCandidateNodes.add(node);
     72 
     73     if (!isHTMLStyleElement(node))
     74         return;
     75 
     76     ContainerNode* scopingNode = toHTMLStyleElement(node)->scopingNode();
     77     if (!isTreeScopeRoot(scopingNode))
     78         m_scopingNodesForStyleScoped.add(scopingNode);
     79 }
     80 
     81 void StyleSheetCollection::removeStyleSheetCandidateNode(Node* node, ContainerNode* scopingNode)
     82 {
     83     m_styleSheetCandidateNodes.remove(node);
     84 
     85     if (!isTreeScopeRoot(scopingNode))
     86         m_scopingNodesForStyleScoped.remove(scopingNode);
     87 }
     88 
     89 void StyleSheetCollection::collectStyleSheets(DocumentStyleSheetCollection* collections, Vector<RefPtr<StyleSheet> >& styleSheets, Vector<RefPtr<CSSStyleSheet> >& activeSheets)
     90 {
     91     if (document()->settings() && !document()->settings()->authorAndUserStylesEnabled())
     92         return;
     93 
     94     DocumentOrderedList::iterator begin = m_styleSheetCandidateNodes.begin();
     95     DocumentOrderedList::iterator end = m_styleSheetCandidateNodes.end();
     96     for (DocumentOrderedList::iterator it = begin; it != end; ++it) {
     97         Node* n = *it;
     98         StyleSheet* sheet = 0;
     99         CSSStyleSheet* activeSheet = 0;
    100         if (n->nodeType() == Node::PROCESSING_INSTRUCTION_NODE && !document()->isHTMLDocument()) {
    101             // Processing instruction (XML documents only).
    102             // We don't support linking to embedded CSS stylesheets, see <https://bugs.webkit.org/show_bug.cgi?id=49281> for discussion.
    103             ProcessingInstruction* pi = static_cast<ProcessingInstruction*>(n);
    104             // Don't apply XSL transforms to already transformed documents -- <rdar://problem/4132806>
    105             if (pi->isXSL() && !document()->transformSourceDocument()) {
    106                 // Don't apply XSL transforms until loading is finished.
    107                 if (!document()->parsing() && !pi->isLoading())
    108                     document()->applyXSLTransform(pi);
    109                 return;
    110             }
    111             sheet = pi->sheet();
    112             if (sheet && !sheet->disabled() && sheet->isCSSStyleSheet())
    113                 activeSheet = static_cast<CSSStyleSheet*>(sheet);
    114         } else if ((n->isHTMLElement() && (n->hasTagName(linkTag) || n->hasTagName(styleTag))) || (n->isSVGElement() && n->hasTagName(SVGNames::styleTag))) {
    115             Element* e = toElement(n);
    116             AtomicString title = e->getAttribute(titleAttr);
    117             bool enabledViaScript = false;
    118             if (e->hasLocalName(linkTag)) {
    119                 // <LINK> element
    120                 HTMLLinkElement* linkElement = toHTMLLinkElement(n);
    121                 enabledViaScript = linkElement->isEnabledViaScript();
    122                 if (!linkElement->isDisabled() && linkElement->styleSheetIsLoading()) {
    123                     // it is loading but we should still decide which style sheet set to use
    124                     if (!enabledViaScript && !title.isEmpty() && collections->preferredStylesheetSetName().isEmpty()) {
    125                         const AtomicString& rel = e->getAttribute(relAttr);
    126                         if (!rel.contains("alternate")) {
    127                             collections->setPreferredStylesheetSetName(title);
    128                             collections->setSelectedStylesheetSetName(title);
    129                         }
    130                     }
    131 
    132                     continue;
    133                 }
    134                 sheet = linkElement->sheet();
    135                 if (!sheet)
    136                     title = nullAtom;
    137             } else if (n->isSVGElement() && n->hasTagName(SVGNames::styleTag)) {
    138                 sheet = static_cast<SVGStyleElement*>(n)->sheet();
    139             } else {
    140                 sheet = static_cast<HTMLStyleElement*>(n)->sheet();
    141             }
    142 
    143             if (sheet && !sheet->disabled() && sheet->isCSSStyleSheet())
    144                 activeSheet = static_cast<CSSStyleSheet*>(sheet);
    145 
    146             // Check to see if this sheet belongs to a styleset
    147             // (thus making it PREFERRED or ALTERNATE rather than
    148             // PERSISTENT).
    149             AtomicString rel = e->getAttribute(relAttr);
    150             if (!enabledViaScript && sheet && !title.isEmpty()) {
    151                 // Yes, we have a title.
    152                 if (collections->preferredStylesheetSetName().isEmpty()) {
    153                     // No preferred set has been established. If
    154                     // we are NOT an alternate sheet, then establish
    155                     // us as the preferred set. Otherwise, just ignore
    156                     // this sheet.
    157                     if (e->hasLocalName(styleTag) || !rel.contains("alternate")) {
    158                         collections->setPreferredStylesheetSetName(title);
    159                         collections->setSelectedStylesheetSetName(title);
    160                     }
    161                 }
    162                 if (title != collections->preferredStylesheetSetName())
    163                     activeSheet = 0;
    164             }
    165 
    166             if (rel.contains("alternate") && title.isEmpty())
    167                 activeSheet = 0;
    168         }
    169         if (sheet)
    170             styleSheets.append(sheet);
    171         if (activeSheet)
    172             activeSheets.append(activeSheet);
    173     }
    174 }
    175 
    176 StyleSheetCollection::StyleResolverUpdateType StyleSheetCollection::compareStyleSheets(const Vector<RefPtr<CSSStyleSheet> >& oldStyleSheets, const Vector<RefPtr<CSSStyleSheet> >& newStylesheets, Vector<StyleSheetContents*>& addedSheets)
    177 {
    178     // Find out which stylesheets are new.
    179     unsigned newStylesheetCount = newStylesheets.size();
    180     unsigned oldStylesheetCount = oldStyleSheets.size();
    181     if (newStylesheetCount < oldStylesheetCount)
    182         return Reconstruct;
    183 
    184     unsigned newIndex = 0;
    185     for (unsigned oldIndex = 0; oldIndex < oldStylesheetCount; ++oldIndex) {
    186         if (newIndex >= newStylesheetCount)
    187             return Reconstruct;
    188         while (oldStyleSheets[oldIndex] != newStylesheets[newIndex]) {
    189             addedSheets.append(newStylesheets[newIndex]->contents());
    190             ++newIndex;
    191             if (newIndex == newStylesheetCount)
    192                 return Reconstruct;
    193         }
    194         ++newIndex;
    195     }
    196     bool hasInsertions = !addedSheets.isEmpty();
    197     while (newIndex < newStylesheetCount) {
    198         addedSheets.append(newStylesheets[newIndex]->contents());
    199         ++newIndex;
    200     }
    201     // If all new sheets were added at the end of the list we can just add them to existing StyleResolver.
    202     // If there were insertions we need to re-add all the stylesheets so rules are ordered correctly.
    203     return hasInsertions ? Reset : Additive;
    204 }
    205 
    206 bool StyleSheetCollection::activeLoadingStyleSheetLoaded(const Vector<RefPtr<CSSStyleSheet> >& newStyleSheets)
    207 {
    208     // StyleSheets of <style> elements that @import stylesheets are active but loading. We need to trigger a full recalc when such loads are done.
    209     bool hasActiveLoadingStylesheet = false;
    210     unsigned newStylesheetCount = newStyleSheets.size();
    211     for (unsigned i = 0; i < newStylesheetCount; ++i) {
    212         if (newStyleSheets[i]->isLoading())
    213             hasActiveLoadingStylesheet = true;
    214     }
    215     if (m_hadActiveLoadingStylesheet && !hasActiveLoadingStylesheet) {
    216         m_hadActiveLoadingStylesheet = false;
    217         return true;
    218     }
    219     m_hadActiveLoadingStylesheet = hasActiveLoadingStylesheet;
    220     return false;
    221 }
    222 
    223 void StyleSheetCollection::analyzeStyleSheetChange(StyleResolverUpdateMode updateMode, const Vector<RefPtr<CSSStyleSheet> >& oldStyleSheets, const Vector<RefPtr<CSSStyleSheet> >& newStyleSheets, StyleResolverUpdateType& styleResolverUpdateType, bool& requiresFullStyleRecalc)
    224 {
    225     styleResolverUpdateType = Reconstruct;
    226     requiresFullStyleRecalc = true;
    227 
    228     if (activeLoadingStyleSheetLoaded(newStyleSheets))
    229         return;
    230 
    231     if (updateMode != AnalyzedStyleUpdate)
    232         return;
    233     if (!document()->styleResolverIfExists())
    234         return;
    235 
    236     // Find out which stylesheets are new.
    237     Vector<StyleSheetContents*> addedSheets;
    238     styleResolverUpdateType = compareStyleSheets(oldStyleSheets, newStyleSheets, addedSheets);
    239 
    240     // If we are already parsing the body and so may have significant amount of elements, put some effort into trying to avoid style recalcs.
    241     if (!document()->body() || document()->hasNodesWithPlaceholderStyle())
    242         return;
    243     StyleInvalidationAnalysis invalidationAnalysis(addedSheets);
    244     if (invalidationAnalysis.dirtiesAllStyle())
    245         return;
    246     invalidationAnalysis.invalidateStyle(document());
    247     requiresFullStyleRecalc = false;
    248 }
    249 
    250 static void collectActiveCSSStyleSheetsFromSeamlessParents(Vector<RefPtr<CSSStyleSheet> >& sheets, Document* document)
    251 {
    252     HTMLIFrameElement* seamlessParentIFrame = document->seamlessParentIFrame();
    253     if (!seamlessParentIFrame)
    254         return;
    255     sheets.append(seamlessParentIFrame->document()->styleSheetCollection()->activeAuthorStyleSheets());
    256 }
    257 
    258 bool StyleSheetCollection::updateActiveStyleSheets(DocumentStyleSheetCollection* collections, StyleResolverUpdateMode updateMode, StyleResolverUpdateType& styleResolverUpdateType)
    259 {
    260     Vector<RefPtr<StyleSheet> > styleSheets;
    261     Vector<RefPtr<CSSStyleSheet> > activeCSSStyleSheets;
    262     activeCSSStyleSheets.append(collections->injectedAuthorStyleSheets());
    263     activeCSSStyleSheets.append(collections->documentAuthorStyleSheets());
    264     collectActiveCSSStyleSheetsFromSeamlessParents(activeCSSStyleSheets, document());
    265     collectStyleSheets(collections, styleSheets, activeCSSStyleSheets);
    266 
    267     bool requiresFullStyleRecalc;
    268     analyzeStyleSheetChange(updateMode, activeAuthorStyleSheets(), activeCSSStyleSheets, styleResolverUpdateType, requiresFullStyleRecalc);
    269 
    270     if (styleResolverUpdateType == Reconstruct) {
    271         document()->clearStyleResolver();
    272     } else {
    273         StyleResolver* styleResolver = document()->styleResolver();
    274         styleResolver->setBuildScopedStyleTreeInDocumentOrder(!scopingNodesForStyleScoped());
    275         if (styleResolverUpdateType == Reset) {
    276             if (DocumentOrderedList* styleScopedScopingNodes = scopingNodesForStyleScoped()) {
    277                 for (DocumentOrderedList::iterator it = styleScopedScopingNodes->begin(); it != styleScopedScopingNodes->end(); ++it)
    278                     styleResolver->resetAuthorStyle(toContainerNode(*it));
    279             }
    280             if (ListHashSet<Node*, 4>* removedNodes = scopingNodesRemoved()) {
    281                 for (ListHashSet<Node*, 4>::iterator it = removedNodes->begin(); it != removedNodes->end(); ++it)
    282                     styleResolver->resetAuthorStyle(toContainerNode(*it));
    283             }
    284             ASSERT(m_treeScope->rootNode() == document());
    285             styleResolver->resetAuthorStyle(toContainerNode(m_treeScope->rootNode()));
    286             styleResolver->appendAuthorStyleSheets(0, activeCSSStyleSheets);
    287         } else {
    288             ASSERT(styleResolverUpdateType == Additive);
    289             styleResolver->appendAuthorStyleSheets(m_activeAuthorStyleSheets.size(), activeCSSStyleSheets);
    290         }
    291     }
    292     m_scopingNodesForStyleScoped.didRemoveScopingNodes();
    293     m_activeAuthorStyleSheets.swap(activeCSSStyleSheets);
    294     m_styleSheetsForStyleSheetList.swap(styleSheets);
    295 
    296     return requiresFullStyleRecalc;
    297 }
    298 
    299 }
    300