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