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/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