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