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 * Copyright (C) 2003, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. 6 * Copyright (C) 2009 Rob Buis (rwlbuis@gmail.com) 7 * Copyright (C) 2011 Google Inc. All rights reserved. 8 * 9 * This library is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU Library General Public 11 * License as published by the Free Software Foundation; either 12 * version 2 of the License, or (at your option) any later version. 13 * 14 * This library is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Library General Public License for more details. 18 * 19 * You should have received a copy of the GNU Library General Public License 20 * along with this library; see the file COPYING.LIB. If not, write to 21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 * Boston, MA 02110-1301, USA. 23 */ 24 25#include "config.h" 26#include "HTMLLinkElement.h" 27 28#include "Attribute.h" 29#include "CachedCSSStyleSheet.h" 30#include "CachedResource.h" 31#include "CachedResourceLoader.h" 32#include "CachedResourceRequest.h" 33#include "Document.h" 34#include "DocumentStyleSheetCollection.h" 35#include "Event.h" 36#include "EventSender.h" 37#include "Frame.h" 38#include "FrameLoader.h" 39#include "FrameLoaderClient.h" 40#include "FrameTree.h" 41#include "FrameView.h" 42#include "HTMLNames.h" 43#include "HTMLParserIdioms.h" 44#include "MediaList.h" 45#include "MediaQueryEvaluator.h" 46#include "Page.h" 47#include "RenderStyle.h" 48#include "SecurityOrigin.h" 49#include "Settings.h" 50#include "StyleInheritedData.h" 51#include "StyleResolveForDocument.h" 52#include "StyleSheetContents.h" 53#include <wtf/Ref.h> 54#include <wtf/StdLibExtras.h> 55 56namespace WebCore { 57 58using namespace HTMLNames; 59 60static LinkEventSender& linkLoadEventSender() 61{ 62 DEPRECATED_DEFINE_STATIC_LOCAL(LinkEventSender, sharedLoadEventSender, (eventNames().loadEvent)); 63 return sharedLoadEventSender; 64} 65 66inline HTMLLinkElement::HTMLLinkElement(const QualifiedName& tagName, Document& document, bool createdByParser) 67 : HTMLElement(tagName, document) 68 , m_linkLoader(this) 69 , m_sizes(DOMSettableTokenList::create()) 70 , m_disabledState(Unset) 71 , m_loading(false) 72 , m_createdByParser(createdByParser) 73 , m_isInShadowTree(false) 74 , m_firedLoad(false) 75 , m_loadedSheet(false) 76 , m_pendingSheetType(Unknown) 77{ 78 ASSERT(hasTagName(linkTag)); 79} 80 81PassRefPtr<HTMLLinkElement> HTMLLinkElement::create(const QualifiedName& tagName, Document& document, bool createdByParser) 82{ 83 return adoptRef(new HTMLLinkElement(tagName, document, createdByParser)); 84} 85 86HTMLLinkElement::~HTMLLinkElement() 87{ 88 if (m_sheet) 89 m_sheet->clearOwnerNode(); 90 91 if (m_cachedSheet) 92 m_cachedSheet->removeClient(this); 93 94 if (inDocument()) 95 document().styleSheetCollection().removeStyleSheetCandidateNode(*this); 96 97 linkLoadEventSender().cancelEvent(this); 98} 99 100void HTMLLinkElement::setDisabledState(bool disabled) 101{ 102 DisabledState oldDisabledState = m_disabledState; 103 m_disabledState = disabled ? Disabled : EnabledViaScript; 104 if (oldDisabledState != m_disabledState) { 105 // If we change the disabled state while the sheet is still loading, then we have to 106 // perform three checks: 107 if (styleSheetIsLoading()) { 108 // Check #1: The sheet becomes disabled while loading. 109 if (m_disabledState == Disabled) 110 removePendingSheet(); 111 112 // Check #2: An alternate sheet becomes enabled while it is still loading. 113 if (m_relAttribute.m_isAlternate && m_disabledState == EnabledViaScript) 114 addPendingSheet(ActiveSheet); 115 116 // Check #3: A main sheet becomes enabled while it was still loading and 117 // after it was disabled via script. It takes really terrible code to make this 118 // happen (a double toggle for no reason essentially). This happens on 119 // virtualplastic.net, which manages to do about 12 enable/disables on only 3 120 // sheets. :) 121 if (!m_relAttribute.m_isAlternate && m_disabledState == EnabledViaScript && oldDisabledState == Disabled) 122 addPendingSheet(ActiveSheet); 123 124 // If the sheet is already loading just bail. 125 return; 126 } 127 128 // Load the sheet, since it's never been loaded before. 129 if (!m_sheet && m_disabledState == EnabledViaScript) 130 process(); 131 else 132 document().styleResolverChanged(DeferRecalcStyle); // Update the style selector. 133 } 134} 135 136void HTMLLinkElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 137{ 138 if (name == relAttr) { 139 m_relAttribute = LinkRelAttribute(value); 140 process(); 141 } else if (name == hrefAttr) { 142 process(); 143 } else if (name == typeAttr) { 144 m_type = value; 145 process(); 146 } else if (name == sizesAttr) { 147 setSizes(value); 148 process(); 149 } else if (name == mediaAttr) { 150 m_media = value.string().lower(); 151 process(); 152 } else if (name == disabledAttr) 153 setDisabledState(!value.isNull()); 154 else if (name == onbeforeloadAttr) 155 setAttributeEventListener(eventNames().beforeloadEvent, name, value); 156 else { 157 if (name == titleAttr && m_sheet) 158 m_sheet->setTitle(value); 159 HTMLElement::parseAttribute(name, value); 160 } 161} 162 163bool HTMLLinkElement::shouldLoadLink() 164{ 165 Ref<Document> originalDocument(document()); 166 if (!dispatchBeforeLoadEvent(getNonEmptyURLAttribute(hrefAttr))) 167 return false; 168 // A beforeload handler might have removed us from the document or changed the document. 169 if (!inDocument() || &document() != &originalDocument.get()) 170 return false; 171 return true; 172} 173 174void HTMLLinkElement::process() 175{ 176 if (!inDocument() || m_isInShadowTree) { 177 ASSERT(!m_sheet); 178 return; 179 } 180 181 String type = m_type.lower(); 182 URL url = getNonEmptyURLAttribute(hrefAttr); 183 184 if (!m_linkLoader.loadLink(m_relAttribute, type, m_sizes->toString(), url, &document())) 185 return; 186 187 bool acceptIfTypeContainsTextCSS = document().page() && document().page()->settings().treatsAnyTextCSSLinkAsStylesheet(); 188 189 if (m_disabledState != Disabled && (m_relAttribute.m_isStyleSheet || (acceptIfTypeContainsTextCSS && type.contains("text/css"))) 190 && document().frame() && url.isValid()) { 191 192 String charset = getAttribute(charsetAttr); 193 if (charset.isEmpty() && document().frame()) 194 charset = document().charset(); 195 196 if (m_cachedSheet) { 197 removePendingSheet(); 198 m_cachedSheet->removeClient(this); 199 m_cachedSheet = 0; 200 } 201 202 if (!shouldLoadLink()) 203 return; 204 205 m_loading = true; 206 207 bool mediaQueryMatches = true; 208 if (!m_media.isEmpty()) { 209 RefPtr<RenderStyle> documentStyle; 210 if (document().hasLivingRenderTree()) 211 documentStyle = Style::resolveForDocument(document()); 212 RefPtr<MediaQuerySet> media = MediaQuerySet::createAllowingDescriptionSyntax(m_media); 213 MediaQueryEvaluator evaluator(document().frame()->view()->mediaType(), document().frame(), documentStyle.get()); 214 mediaQueryMatches = evaluator.eval(media.get()); 215 } 216 217 // Don't hold up render tree construction and script execution on stylesheets 218 // that are not needed for the rendering at the moment. 219 bool isActive = mediaQueryMatches && !isAlternate(); 220 addPendingSheet(isActive ? ActiveSheet : InactiveSheet); 221 222 // Load stylesheets that are not needed for the rendering immediately with low priority. 223 ResourceLoadPriority priority = isActive ? ResourceLoadPriorityUnresolved : ResourceLoadPriorityVeryLow; 224 CachedResourceRequest request(ResourceRequest(document().completeURL(url)), charset, priority); 225 request.setInitiator(this); 226 m_cachedSheet = document().cachedResourceLoader()->requestCSSStyleSheet(request); 227 228 if (m_cachedSheet) 229 m_cachedSheet->addClient(this); 230 else { 231 // The request may have been denied if (for example) the stylesheet is local and the document is remote. 232 m_loading = false; 233 removePendingSheet(); 234 } 235 } else if (m_sheet) { 236 // we no longer contain a stylesheet, e.g. perhaps rel or type was changed 237 clearSheet(); 238 document().styleResolverChanged(DeferRecalcStyle); 239 } 240} 241 242void HTMLLinkElement::clearSheet() 243{ 244 ASSERT(m_sheet); 245 ASSERT(m_sheet->ownerNode() == this); 246 m_sheet->clearOwnerNode(); 247 m_sheet = 0; 248} 249 250Node::InsertionNotificationRequest HTMLLinkElement::insertedInto(ContainerNode& insertionPoint) 251{ 252 HTMLElement::insertedInto(insertionPoint); 253 if (!insertionPoint.inDocument()) 254 return InsertionDone; 255 256 m_isInShadowTree = isInShadowTree(); 257 if (m_isInShadowTree) 258 return InsertionDone; 259 260 document().styleSheetCollection().addStyleSheetCandidateNode(*this, m_createdByParser); 261 262 process(); 263 return InsertionDone; 264} 265 266void HTMLLinkElement::removedFrom(ContainerNode& insertionPoint) 267{ 268 HTMLElement::removedFrom(insertionPoint); 269 if (!insertionPoint.inDocument()) 270 return; 271 272 m_linkLoader.released(); 273 274 if (m_isInShadowTree) { 275 ASSERT(!m_sheet); 276 return; 277 } 278 document().styleSheetCollection().removeStyleSheetCandidateNode(*this); 279 280 if (m_sheet) 281 clearSheet(); 282 283 if (styleSheetIsLoading()) 284 removePendingSheet(RemovePendingSheetNotifyLater); 285 286 if (document().hasLivingRenderTree()) 287 document().styleResolverChanged(DeferRecalcStyleIfNeeded); 288} 289 290void HTMLLinkElement::finishParsingChildren() 291{ 292 m_createdByParser = false; 293 HTMLElement::finishParsingChildren(); 294} 295 296void HTMLLinkElement::setCSSStyleSheet(const String& href, const URL& baseURL, const String& charset, const CachedCSSStyleSheet* cachedStyleSheet) 297{ 298 if (!inDocument()) { 299 ASSERT(!m_sheet); 300 return; 301 } 302 // Completing the sheet load may cause scripts to execute. 303 Ref<HTMLLinkElement> protect(*this); 304 305 CSSParserContext parserContext(document(), baseURL, charset); 306 307 if (RefPtr<StyleSheetContents> restoredSheet = const_cast<CachedCSSStyleSheet*>(cachedStyleSheet)->restoreParsedStyleSheet(parserContext)) { 308 ASSERT(restoredSheet->isCacheable()); 309 ASSERT(!restoredSheet->isLoading()); 310 311 m_sheet = CSSStyleSheet::create(restoredSheet.releaseNonNull(), this); 312 m_sheet->setMediaQueries(MediaQuerySet::createAllowingDescriptionSyntax(m_media)); 313 m_sheet->setTitle(title()); 314 315 m_loading = false; 316 sheetLoaded(); 317 notifyLoadedSheetAndAllCriticalSubresources(false); 318 return; 319 } 320 321 Ref<StyleSheetContents> styleSheet(StyleSheetContents::create(href, parserContext)); 322 m_sheet = CSSStyleSheet::create(styleSheet.get(), this); 323 m_sheet->setMediaQueries(MediaQuerySet::createAllowingDescriptionSyntax(m_media)); 324 m_sheet->setTitle(title()); 325 326 styleSheet.get().parseAuthorStyleSheet(cachedStyleSheet, document().securityOrigin()); 327 328 m_loading = false; 329 styleSheet.get().notifyLoadedSheet(cachedStyleSheet); 330 styleSheet.get().checkLoaded(); 331 332 if (styleSheet.get().isCacheable()) 333 const_cast<CachedCSSStyleSheet*>(cachedStyleSheet)->saveParsedStyleSheet(styleSheet.get()); 334} 335 336bool HTMLLinkElement::styleSheetIsLoading() const 337{ 338 if (m_loading) 339 return true; 340 if (!m_sheet) 341 return false; 342 return m_sheet->contents().isLoading(); 343} 344 345void HTMLLinkElement::linkLoaded() 346{ 347 dispatchEvent(Event::create(eventNames().loadEvent, false, false)); 348} 349 350void HTMLLinkElement::linkLoadingErrored() 351{ 352 dispatchEvent(Event::create(eventNames().errorEvent, false, false)); 353} 354 355bool HTMLLinkElement::sheetLoaded() 356{ 357 if (!styleSheetIsLoading()) { 358 removePendingSheet(); 359 return true; 360 } 361 return false; 362} 363 364void HTMLLinkElement::dispatchPendingLoadEvents() 365{ 366 linkLoadEventSender().dispatchPendingEvents(); 367} 368 369void HTMLLinkElement::dispatchPendingEvent(LinkEventSender* eventSender) 370{ 371 ASSERT_UNUSED(eventSender, eventSender == &linkLoadEventSender()); 372 if (m_loadedSheet) 373 linkLoaded(); 374 else 375 linkLoadingErrored(); 376} 377 378void HTMLLinkElement::notifyLoadedSheetAndAllCriticalSubresources(bool errorOccurred) 379{ 380 if (m_firedLoad) 381 return; 382 m_loadedSheet = !errorOccurred; 383 linkLoadEventSender().dispatchEventSoon(this); 384 m_firedLoad = true; 385} 386 387void HTMLLinkElement::startLoadingDynamicSheet() 388{ 389 // We don't support multiple active sheets. 390 ASSERT(m_pendingSheetType < ActiveSheet); 391 addPendingSheet(ActiveSheet); 392} 393 394bool HTMLLinkElement::isURLAttribute(const Attribute& attribute) const 395{ 396 return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute); 397} 398 399URL HTMLLinkElement::href() const 400{ 401 return document().completeURL(getAttribute(hrefAttr)); 402} 403 404String HTMLLinkElement::rel() const 405{ 406 return getAttribute(relAttr); 407} 408 409String HTMLLinkElement::target() const 410{ 411 return getAttribute(targetAttr); 412} 413 414String HTMLLinkElement::type() const 415{ 416 return getAttribute(typeAttr); 417} 418 419IconType HTMLLinkElement::iconType() const 420{ 421 return m_relAttribute.m_iconType; 422} 423 424String HTMLLinkElement::iconSizes() const 425{ 426 return m_sizes->toString(); 427} 428 429void HTMLLinkElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const 430{ 431 HTMLElement::addSubresourceAttributeURLs(urls); 432 433 // Favicons are handled by a special case in LegacyWebArchive::create() 434 if (m_relAttribute.m_iconType != InvalidIcon) 435 return; 436 437 if (!m_relAttribute.m_isStyleSheet) 438 return; 439 440 // Append the URL of this link element. 441 addSubresourceURL(urls, href()); 442 443 // Walk the URLs linked by the linked-to stylesheet. 444 if (CSSStyleSheet* styleSheet = const_cast<HTMLLinkElement*>(this)->sheet()) 445 styleSheet->contents().addSubresourceStyleURLs(urls); 446} 447 448void HTMLLinkElement::addPendingSheet(PendingSheetType type) 449{ 450 if (type <= m_pendingSheetType) 451 return; 452 m_pendingSheetType = type; 453 454 if (m_pendingSheetType == InactiveSheet) 455 return; 456 document().styleSheetCollection().addPendingSheet(); 457} 458 459void HTMLLinkElement::removePendingSheet(RemovePendingSheetNotificationType notification) 460{ 461 PendingSheetType type = m_pendingSheetType; 462 m_pendingSheetType = Unknown; 463 464 if (type == Unknown) 465 return; 466 467 if (type == InactiveSheet) { 468 // Document just needs to know about the sheet for exposure through document.styleSheets 469 document().styleSheetCollection().updateActiveStyleSheets(DocumentStyleSheetCollection::OptimizedUpdate); 470 return; 471 } 472 473 document().styleSheetCollection().removePendingSheet( 474 notification == RemovePendingSheetNotifyImmediately 475 ? DocumentStyleSheetCollection::RemovePendingSheetNotifyImmediately 476 : DocumentStyleSheetCollection::RemovePendingSheetNotifyLater); 477} 478 479DOMSettableTokenList* HTMLLinkElement::sizes() const 480{ 481 return m_sizes.get(); 482} 483 484void HTMLLinkElement::setSizes(const String& value) 485{ 486 m_sizes->setValue(value); 487} 488 489} // namespace WebCore 490