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