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