Home | History | Annotate | Download | only in html
      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