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 * Copyright (C) 2003, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. 6 * Copyright (C) 2009 Rob Buis (rwlbuis (at) 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 "core/html/HTMLLinkElement.h" 27 28 #include "HTMLNames.h" 29 #include "RuntimeEnabledFeatures.h" 30 #include "bindings/v8/ScriptEventListener.h" 31 #include "core/css/MediaList.h" 32 #include "core/css/MediaQueryEvaluator.h" 33 #include "core/css/StyleSheetContents.h" 34 #include "core/css/resolver/StyleResolver.h" 35 #include "core/dom/Attribute.h" 36 #include "core/dom/Document.h" 37 #include "core/dom/DocumentStyleSheetCollection.h" 38 #include "core/dom/Event.h" 39 #include "core/dom/EventSender.h" 40 #include "core/html/LinkImport.h" 41 #include "core/loader/FrameLoader.h" 42 #include "core/loader/FrameLoaderClient.h" 43 #include "core/loader/cache/CSSStyleSheetResource.h" 44 #include "core/loader/cache/FetchRequest.h" 45 #include "core/loader/cache/ResourceFetcher.h" 46 #include "core/page/ContentSecurityPolicy.h" 47 #include "core/page/Frame.h" 48 #include "core/page/FrameView.h" 49 #include "wtf/StdLibExtras.h" 50 51 namespace WebCore { 52 53 using namespace HTMLNames; 54 55 static LinkEventSender& linkLoadEventSender() 56 { 57 DEFINE_STATIC_LOCAL(LinkEventSender, sharedLoadEventSender, (eventNames().loadEvent)); 58 return sharedLoadEventSender; 59 } 60 61 inline HTMLLinkElement::HTMLLinkElement(const QualifiedName& tagName, Document* document, bool createdByParser) 62 : HTMLElement(tagName, document) 63 , m_linkLoader(this) 64 , m_sizes(DOMSettableTokenList::create()) 65 , m_createdByParser(createdByParser) 66 , m_isInShadowTree(false) 67 , m_beforeLoadRecurseCount(0) 68 { 69 ASSERT(hasTagName(linkTag)); 70 ScriptWrappable::init(this); 71 } 72 73 PassRefPtr<HTMLLinkElement> HTMLLinkElement::create(const QualifiedName& tagName, Document* document, bool createdByParser) 74 { 75 return adoptRef(new HTMLLinkElement(tagName, document, createdByParser)); 76 } 77 78 HTMLLinkElement::~HTMLLinkElement() 79 { 80 m_link.clear(); 81 82 if (inDocument()) 83 document()->styleSheetCollection()->removeStyleSheetCandidateNode(this); 84 85 linkLoadEventSender().cancelEvent(this); 86 } 87 88 void HTMLLinkElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 89 { 90 if (name == relAttr) { 91 m_relAttribute = LinkRelAttribute(value); 92 process(); 93 } else if (name == hrefAttr) { 94 process(); 95 } else if (name == typeAttr) { 96 m_type = value; 97 process(); 98 } else if (name == sizesAttr) { 99 setSizes(value); 100 process(); 101 } else if (name == mediaAttr) { 102 m_media = value.string().lower(); 103 process(); 104 } else if (name == disabledAttr) { 105 if (LinkStyle* link = linkStyle()) 106 link->setDisabledState(!value.isNull()); 107 } else if (name == onbeforeloadAttr) 108 setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, name, value)); 109 else { 110 if (name == titleAttr) { 111 if (LinkStyle* link = linkStyle()) 112 link->setSheetTitle(value); 113 } 114 115 HTMLElement::parseAttribute(name, value); 116 } 117 } 118 119 bool HTMLLinkElement::shouldLoadLink() 120 { 121 bool continueLoad = true; 122 RefPtr<Document> originalDocument = document(); 123 int recursionRank = ++m_beforeLoadRecurseCount; 124 if (!dispatchBeforeLoadEvent(getNonEmptyURLAttribute(hrefAttr))) 125 continueLoad = false; 126 127 // A beforeload handler might have removed us from the document or changed the document. 128 if (continueLoad && (!inDocument() || document() != originalDocument)) 129 continueLoad = false; 130 131 // If the beforeload handler recurses into the link element by mutating it, we should only 132 // let the latest (innermost) mutation occur. 133 if (recursionRank != m_beforeLoadRecurseCount) 134 continueLoad = false; 135 136 if (recursionRank == 1) 137 m_beforeLoadRecurseCount = 0; 138 139 return continueLoad; 140 } 141 142 LinkResource* HTMLLinkElement::linkResourceToProcess() 143 { 144 bool visible = inDocument() && !m_isInShadowTree; 145 if (!visible) { 146 ASSERT(!linkStyle() || !linkStyle()->hasSheet()); 147 return 0; 148 } 149 150 if (!m_link) { 151 if (m_relAttribute.isImport() && RuntimeEnabledFeatures::htmlImportsEnabled()) 152 m_link = LinkImport::create(this); 153 else { 154 RefPtr<LinkStyle> link = LinkStyle::create(this); 155 if (fastHasAttribute(disabledAttr)) 156 link->setDisabledState(true); 157 m_link = link.release(); 158 } 159 } 160 161 return m_link.get(); 162 } 163 164 LinkStyle* HTMLLinkElement::linkStyle() const 165 { 166 if (!m_link || m_link->type() != LinkResource::Style) 167 return 0; 168 return static_cast<LinkStyle*>(m_link.get()); 169 } 170 171 LinkImport* HTMLLinkElement::linkImport() const 172 { 173 if (!m_link || m_link->type() != LinkResource::Import) 174 return 0; 175 return static_cast<LinkImport*>(m_link.get()); 176 } 177 178 Document* HTMLLinkElement::import() const 179 { 180 if (LinkImport* link = linkImport()) 181 return linkImport()->importedDocument(); 182 return 0; 183 } 184 185 void HTMLLinkElement::process() 186 { 187 if (LinkResource* link = linkResourceToProcess()) 188 link->process(); 189 } 190 191 Node::InsertionNotificationRequest HTMLLinkElement::insertedInto(ContainerNode* insertionPoint) 192 { 193 HTMLElement::insertedInto(insertionPoint); 194 if (!insertionPoint->inDocument()) 195 return InsertionDone; 196 197 m_isInShadowTree = isInShadowTree(); 198 if (m_isInShadowTree) 199 return InsertionDone; 200 201 document()->styleSheetCollection()->addStyleSheetCandidateNode(this, m_createdByParser); 202 203 process(); 204 return InsertionDone; 205 } 206 207 void HTMLLinkElement::removedFrom(ContainerNode* insertionPoint) 208 { 209 HTMLElement::removedFrom(insertionPoint); 210 if (!insertionPoint->inDocument()) 211 return; 212 213 m_linkLoader.released(); 214 215 if (m_isInShadowTree) { 216 ASSERT(!linkStyle() || !linkStyle()->hasSheet()); 217 return; 218 } 219 document()->styleSheetCollection()->removeStyleSheetCandidateNode(this); 220 221 RefPtr<StyleSheet> removedSheet = sheet(); 222 223 if (m_link) 224 m_link->ownerRemoved(); 225 226 if (document()->renderer()) 227 document()->removedStyleSheet(removedSheet.get()); 228 } 229 230 void HTMLLinkElement::finishParsingChildren() 231 { 232 m_createdByParser = false; 233 HTMLElement::finishParsingChildren(); 234 } 235 236 bool HTMLLinkElement::styleSheetIsLoading() const 237 { 238 return linkStyle() && linkStyle()->styleSheetIsLoading(); 239 } 240 241 void HTMLLinkElement::linkLoaded() 242 { 243 dispatchEvent(Event::create(eventNames().loadEvent, false, false)); 244 } 245 246 void HTMLLinkElement::linkLoadingErrored() 247 { 248 dispatchEvent(Event::create(eventNames().errorEvent, false, false)); 249 } 250 251 void HTMLLinkElement::didStartLinkPrerender() 252 { 253 dispatchEvent(Event::create(eventNames().webkitprerenderstartEvent, false, false)); 254 } 255 256 void HTMLLinkElement::didStopLinkPrerender() 257 { 258 dispatchEvent(Event::create(eventNames().webkitprerenderstopEvent, false, false)); 259 } 260 261 void HTMLLinkElement::didSendLoadForLinkPrerender() 262 { 263 dispatchEvent(Event::create(eventNames().webkitprerenderloadEvent, false, false)); 264 } 265 266 void HTMLLinkElement::didSendDOMContentLoadedForLinkPrerender() 267 { 268 dispatchEvent(Event::create(eventNames().webkitprerenderdomcontentloadedEvent, false, false)); 269 } 270 271 bool HTMLLinkElement::sheetLoaded() 272 { 273 ASSERT(linkStyle()); 274 return linkStyle()->sheetLoaded(); 275 } 276 277 void HTMLLinkElement::notifyLoadedSheetAndAllCriticalSubresources(bool errorOccurred) 278 { 279 ASSERT(linkStyle()); 280 linkStyle()->notifyLoadedSheetAndAllCriticalSubresources(errorOccurred); 281 } 282 283 void HTMLLinkElement::dispatchPendingLoadEvents() 284 { 285 linkLoadEventSender().dispatchPendingEvents(); 286 } 287 288 void HTMLLinkElement::dispatchPendingEvent(LinkEventSender* eventSender) 289 { 290 ASSERT_UNUSED(eventSender, eventSender == &linkLoadEventSender()); 291 ASSERT(m_link); 292 if (m_link->hasLoaded()) 293 linkLoaded(); 294 else 295 linkLoadingErrored(); 296 } 297 298 void HTMLLinkElement::scheduleEvent() 299 { 300 linkLoadEventSender().dispatchEventSoon(this); 301 } 302 303 void HTMLLinkElement::startLoadingDynamicSheet() 304 { 305 ASSERT(linkStyle()); 306 linkStyle()->startLoadingDynamicSheet(); 307 } 308 309 bool HTMLLinkElement::isURLAttribute(const Attribute& attribute) const 310 { 311 return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute); 312 } 313 314 KURL HTMLLinkElement::href() const 315 { 316 return document()->completeURL(getAttribute(hrefAttr)); 317 } 318 319 String HTMLLinkElement::rel() const 320 { 321 return getAttribute(relAttr); 322 } 323 324 String HTMLLinkElement::target() const 325 { 326 return getAttribute(targetAttr); 327 } 328 329 String HTMLLinkElement::type() const 330 { 331 return getAttribute(typeAttr); 332 } 333 334 IconType HTMLLinkElement::iconType() const 335 { 336 return m_relAttribute.iconType(); 337 } 338 339 String HTMLLinkElement::iconSizes() const 340 { 341 return m_sizes->toString(); 342 } 343 344 void HTMLLinkElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const 345 { 346 HTMLElement::addSubresourceAttributeURLs(urls); 347 348 // Favicons are handled by a special case in LegacyWebArchive::create() 349 if (m_relAttribute.iconType() != InvalidIcon) 350 return; 351 352 if (!m_relAttribute.isStyleSheet()) 353 return; 354 355 // Append the URL of this link element. 356 addSubresourceURL(urls, href()); 357 358 // Walk the URLs linked by the linked-to stylesheet. 359 if (CSSStyleSheet* styleSheet = const_cast<HTMLLinkElement*>(this)->sheet()) 360 styleSheet->contents()->addSubresourceStyleURLs(urls); 361 } 362 363 DOMSettableTokenList* HTMLLinkElement::sizes() const 364 { 365 return m_sizes.get(); 366 } 367 368 void HTMLLinkElement::setSizes(const String& value) 369 { 370 m_sizes->setValue(value); 371 } 372 373 374 PassRefPtr<LinkStyle> LinkStyle::create(HTMLLinkElement* owner) 375 { 376 return adoptRef(new LinkStyle(owner)); 377 } 378 379 LinkStyle::LinkStyle(HTMLLinkElement* owner) 380 : LinkResource(owner) 381 , m_disabledState(Unset) 382 , m_pendingSheetType(None) 383 , m_loading(false) 384 , m_firedLoad(false) 385 , m_loadedSheet(false) 386 { 387 } 388 389 LinkStyle::~LinkStyle() 390 { 391 if (m_sheet) 392 m_sheet->clearOwnerNode(); 393 394 if (m_resource) 395 m_resource->removeClient(this); 396 } 397 398 Document* LinkStyle::document() 399 { 400 return m_owner->document(); 401 } 402 403 void LinkStyle::setCSSStyleSheet(const String& href, const KURL& baseURL, const String& charset, const CSSStyleSheetResource* cachedStyleSheet) 404 { 405 if (!m_owner->inDocument()) { 406 ASSERT(!m_sheet); 407 return; 408 409 } 410 // Completing the sheet load may cause scripts to execute. 411 RefPtr<Node> protector(m_owner); 412 413 CSSParserContext parserContext(m_owner->document(), baseURL, charset); 414 415 if (RefPtr<StyleSheetContents> restoredSheet = const_cast<CSSStyleSheetResource*>(cachedStyleSheet)->restoreParsedStyleSheet(parserContext)) { 416 ASSERT(restoredSheet->isCacheable()); 417 ASSERT(!restoredSheet->isLoading()); 418 419 if (m_sheet) 420 clearSheet(); 421 m_sheet = CSSStyleSheet::create(restoredSheet, m_owner); 422 m_sheet->setMediaQueries(MediaQuerySet::create(m_owner->media())); 423 m_sheet->setTitle(m_owner->title()); 424 425 m_loading = false; 426 sheetLoaded(); 427 notifyLoadedSheetAndAllCriticalSubresources(false); 428 return; 429 } 430 431 RefPtr<StyleSheetContents> styleSheet = StyleSheetContents::create(href, parserContext); 432 433 if (m_sheet) 434 clearSheet(); 435 m_sheet = CSSStyleSheet::create(styleSheet, m_owner); 436 m_sheet->setMediaQueries(MediaQuerySet::create(m_owner->media())); 437 m_sheet->setTitle(m_owner->title()); 438 439 styleSheet->parseAuthorStyleSheet(cachedStyleSheet, m_owner->document()->securityOrigin()); 440 441 m_loading = false; 442 styleSheet->notifyLoadedSheet(cachedStyleSheet); 443 styleSheet->checkLoaded(); 444 445 if (styleSheet->isCacheable()) 446 const_cast<CSSStyleSheetResource*>(cachedStyleSheet)->saveParsedStyleSheet(styleSheet); 447 } 448 449 bool LinkStyle::sheetLoaded() 450 { 451 if (!styleSheetIsLoading()) { 452 removePendingSheet(); 453 return true; 454 } 455 return false; 456 } 457 458 void LinkStyle::notifyLoadedSheetAndAllCriticalSubresources(bool errorOccurred) 459 { 460 if (m_firedLoad) 461 return; 462 m_loadedSheet = !errorOccurred; 463 if (m_owner) 464 m_owner->scheduleEvent(); 465 m_firedLoad = true; 466 } 467 468 void LinkStyle::startLoadingDynamicSheet() 469 { 470 ASSERT(m_pendingSheetType < Blocking); 471 addPendingSheet(Blocking); 472 } 473 474 void LinkStyle::clearSheet() 475 { 476 ASSERT(m_sheet); 477 ASSERT(m_sheet->ownerNode() == m_owner); 478 m_sheet->clearOwnerNode(); 479 m_sheet = 0; 480 } 481 482 bool LinkStyle::styleSheetIsLoading() const 483 { 484 if (m_loading) 485 return true; 486 if (!m_sheet) 487 return false; 488 return m_sheet->contents()->isLoading(); 489 } 490 491 void LinkStyle::addPendingSheet(PendingSheetType type) 492 { 493 if (type <= m_pendingSheetType) 494 return; 495 m_pendingSheetType = type; 496 497 if (m_pendingSheetType == NonBlocking) 498 return; 499 m_owner->document()->styleSheetCollection()->addPendingSheet(); 500 } 501 502 void LinkStyle::removePendingSheet(RemovePendingSheetNotificationType notification) 503 { 504 PendingSheetType type = m_pendingSheetType; 505 m_pendingSheetType = None; 506 507 if (type == None) 508 return; 509 if (type == NonBlocking) { 510 // Document::removePendingSheet() triggers the style selector recalc for blocking sheets. 511 // FIXME: We don't have enough knowledge at this point to know if we're adding or removing a sheet 512 // so we can't call addedStyleSheet() or removedStyleSheet(). 513 m_owner->document()->styleResolverChanged(RecalcStyleImmediately); 514 return; 515 } 516 517 m_owner->document()->styleSheetCollection()->removePendingSheet( 518 notification == RemovePendingSheetNotifyImmediately 519 ? DocumentStyleSheetCollection::RemovePendingSheetNotifyImmediately 520 : DocumentStyleSheetCollection::RemovePendingSheetNotifyLater); 521 } 522 523 void LinkStyle::setDisabledState(bool disabled) 524 { 525 LinkStyle::DisabledState oldDisabledState = m_disabledState; 526 m_disabledState = disabled ? Disabled : EnabledViaScript; 527 if (oldDisabledState != m_disabledState) { 528 // If we change the disabled state while the sheet is still loading, then we have to 529 // perform three checks: 530 if (styleSheetIsLoading()) { 531 // Check #1: The sheet becomes disabled while loading. 532 if (m_disabledState == Disabled) 533 removePendingSheet(); 534 535 // Check #2: An alternate sheet becomes enabled while it is still loading. 536 if (m_owner->relAttribute().isAlternate() && m_disabledState == EnabledViaScript) 537 addPendingSheet(Blocking); 538 539 // Check #3: A main sheet becomes enabled while it was still loading and 540 // after it was disabled via script. It takes really terrible code to make this 541 // happen (a double toggle for no reason essentially). This happens on 542 // virtualplastic.net, which manages to do about 12 enable/disables on only 3 543 // sheets. :) 544 if (!m_owner->relAttribute().isAlternate() && m_disabledState == EnabledViaScript && oldDisabledState == Disabled) 545 addPendingSheet(Blocking); 546 547 // If the sheet is already loading just bail. 548 return; 549 } 550 551 if (m_sheet) 552 m_sheet->setDisabled(disabled); 553 554 // Load the sheet, since it's never been loaded before. 555 if (!m_sheet && m_disabledState == EnabledViaScript) { 556 if (m_owner->shouldProcessStyle()) 557 process(); 558 } else { 559 // FIXME: We don't have enough knowledge here to know if we should call addedStyleSheet() or removedStyleSheet(). 560 m_owner->document()->styleResolverChanged(DeferRecalcStyle); 561 } 562 } 563 } 564 565 void LinkStyle::process() 566 { 567 ASSERT(m_owner->shouldProcessStyle()); 568 String type = m_owner->typeValue().lower(); 569 LinkRequestBuilder builder(m_owner); 570 571 if (m_owner->relAttribute().iconType() != InvalidIcon && builder.url().isValid() && !builder.url().isEmpty()) { 572 if (!m_owner->shouldLoadLink()) 573 return; 574 if (!document()->securityOrigin()->canDisplay(builder.url())) 575 return; 576 if (!document()->contentSecurityPolicy()->allowImageFromSource(builder.url())) 577 return; 578 if (document()->frame()) 579 document()->frame()->loader()->client()->dispatchDidChangeIcons(m_owner->relAttribute().iconType()); 580 } 581 582 if (!m_owner->loadLink(type, builder.url())) 583 return; 584 585 if ((m_disabledState != Disabled) && m_owner->relAttribute().isStyleSheet() 586 && document()->frame() && builder.url().isValid()) { 587 588 if (m_resource) { 589 removePendingSheet(); 590 m_resource->removeClient(this); 591 m_resource = 0; 592 } 593 594 if (!m_owner->shouldLoadLink()) 595 return; 596 597 m_loading = true; 598 599 bool mediaQueryMatches = true; 600 if (!m_owner->media().isEmpty()) { 601 RefPtr<RenderStyle> documentStyle = StyleResolver::styleForDocument(document()); 602 RefPtr<MediaQuerySet> media = MediaQuerySet::create(m_owner->media()); 603 MediaQueryEvaluator evaluator(document()->frame()->view()->mediaType(), document()->frame(), documentStyle.get()); 604 mediaQueryMatches = evaluator.eval(media.get()); 605 } 606 607 // Don't hold up render tree construction and script execution on stylesheets 608 // that are not needed for the rendering at the moment. 609 bool blocking = mediaQueryMatches && !m_owner->isAlternate(); 610 addPendingSheet(blocking ? Blocking : NonBlocking); 611 612 // Load stylesheets that are not needed for the rendering immediately with low priority. 613 FetchRequest request = builder.build(blocking); 614 m_resource = document()->fetcher()->requestCSSStyleSheet(request); 615 616 if (m_resource) 617 m_resource->addClient(this); 618 else { 619 // The request may have been denied if (for example) the stylesheet is local and the document is remote. 620 m_loading = false; 621 removePendingSheet(); 622 } 623 } else if (m_sheet) { 624 // we no longer contain a stylesheet, e.g. perhaps rel or type was changed 625 RefPtr<StyleSheet> removedSheet = m_sheet; 626 clearSheet(); 627 document()->removedStyleSheet(removedSheet.get()); 628 } 629 } 630 631 void LinkStyle::setSheetTitle(const String& title) 632 { 633 if (m_sheet) 634 m_sheet->setTitle(title); 635 } 636 637 void LinkStyle::ownerRemoved() 638 { 639 if (m_sheet) 640 clearSheet(); 641 642 if (styleSheetIsLoading()) 643 removePendingSheet(RemovePendingSheetNotifyLater); 644 } 645 646 } // namespace WebCore 647