WebCore/html/HTMLLinkElement.cpp
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     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  *
       
     8  * This library is free software; you can redistribute it and/or
       
     9  * modify it under the terms of the GNU Library General Public
       
    10  * License as published by the Free Software Foundation; either
       
    11  * version 2 of the License, or (at your option) any later version.
       
    12  *
       
    13  * This library is distributed in the hope that it will be useful,
       
    14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    16  * Library General Public License for more details.
       
    17  *
       
    18  * You should have received a copy of the GNU Library General Public License
       
    19  * along with this library; see the file COPYING.LIB.  If not, write to
       
    20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
       
    21  * Boston, MA 02110-1301, USA.
       
    22  */
       
    23 
       
    24 #include "config.h"
       
    25 #include "HTMLLinkElement.h"
       
    26 
       
    27 #include "Attribute.h"
       
    28 #include "CSSHelper.h"
       
    29 #include "CachedCSSStyleSheet.h"
       
    30 #include "DocLoader.h"
       
    31 #include "Document.h"
       
    32 #include "Frame.h"
       
    33 #include "FrameLoader.h"
       
    34 #include "FrameLoaderClient.h"
       
    35 #include "FrameTree.h"
       
    36 #include "HTMLNames.h"
       
    37 #include "MediaList.h"
       
    38 #include "MediaQueryEvaluator.h"
       
    39 #include "Page.h"
       
    40 #include "ResourceHandle.h"
       
    41 #include "ScriptEventListener.h"
       
    42 #include "Settings.h"
       
    43 #include <wtf/StdLibExtras.h>
       
    44 
       
    45 namespace WebCore {
       
    46 
       
    47 using namespace HTMLNames;
       
    48 
       
    49 inline HTMLLinkElement::HTMLLinkElement(const QualifiedName& tagName, Document* document, bool createdByParser)
       
    50     : HTMLElement(tagName, document)
       
    51     , m_disabledState(Unset)
       
    52     , m_loading(false)
       
    53     , m_createdByParser(createdByParser)
       
    54     , m_shouldProcessAfterAttach(false)
       
    55 {
       
    56     ASSERT(hasTagName(linkTag));
       
    57 }
       
    58 
       
    59 PassRefPtr<HTMLLinkElement> HTMLLinkElement::create(const QualifiedName& tagName, Document* document, bool createdByParser)
       
    60 {
       
    61     return adoptRef(new HTMLLinkElement(tagName, document, createdByParser));
       
    62 }
       
    63 
       
    64 HTMLLinkElement::~HTMLLinkElement()
       
    65 {
       
    66     if (m_cachedSheet) {
       
    67         m_cachedSheet->removeClient(this);
       
    68         if (m_loading && !isDisabled() && !isAlternate())
       
    69             document()->removePendingSheet();
       
    70     }
       
    71 }
       
    72 
       
    73 void HTMLLinkElement::setDisabledState(bool _disabled)
       
    74 {
       
    75     DisabledState oldDisabledState = m_disabledState;
       
    76     m_disabledState = _disabled ? Disabled : EnabledViaScript;
       
    77     if (oldDisabledState != m_disabledState) {
       
    78         // If we change the disabled state while the sheet is still loading, then we have to
       
    79         // perform three checks:
       
    80         if (isLoading()) {
       
    81             // Check #1: If the sheet becomes disabled while it was loading, and if it was either
       
    82             // a main sheet or a sheet that was previously enabled via script, then we need
       
    83             // to remove it from the list of pending sheets.
       
    84             if (m_disabledState == Disabled && (!m_relAttribute.m_isAlternate || oldDisabledState == EnabledViaScript))
       
    85                 document()->removePendingSheet();
       
    86 
       
    87             // Check #2: An alternate sheet becomes enabled while it is still loading.
       
    88             if (m_relAttribute.m_isAlternate && m_disabledState == EnabledViaScript)
       
    89                 document()->addPendingSheet();
       
    90 
       
    91             // Check #3: A main sheet becomes enabled while it was still loading and
       
    92             // after it was disabled via script.  It takes really terrible code to make this
       
    93             // happen (a double toggle for no reason essentially).  This happens on
       
    94             // virtualplastic.net, which manages to do about 12 enable/disables on only 3
       
    95             // sheets. :)
       
    96             if (!m_relAttribute.m_isAlternate && m_disabledState == EnabledViaScript && oldDisabledState == Disabled)
       
    97                 document()->addPendingSheet();
       
    98 
       
    99             // If the sheet is already loading just bail.
       
   100             return;
       
   101         }
       
   102 
       
   103         // Load the sheet, since it's never been loaded before.
       
   104         if (!m_sheet && m_disabledState == EnabledViaScript)
       
   105             process();
       
   106         else
       
   107             document()->updateStyleSelector(); // Update the style selector.
       
   108     }
       
   109 }
       
   110 
       
   111 StyleSheet* HTMLLinkElement::sheet() const
       
   112 {
       
   113     return m_sheet.get();
       
   114 }
       
   115 
       
   116 void HTMLLinkElement::parseMappedAttribute(Attribute* attr)
       
   117 {
       
   118     if (attr->name() == relAttr) {
       
   119         tokenizeRelAttribute(attr->value(), m_relAttribute);
       
   120         process();
       
   121     } else if (attr->name() == hrefAttr) {
       
   122         m_url = document()->completeURL(deprecatedParseURL(attr->value()));
       
   123         process();
       
   124     } else if (attr->name() == typeAttr) {
       
   125         m_type = attr->value();
       
   126         process();
       
   127     } else if (attr->name() == mediaAttr) {
       
   128         m_media = attr->value().string().lower();
       
   129         process();
       
   130     } else if (attr->name() == disabledAttr)
       
   131         setDisabledState(!attr->isNull());
       
   132     else if (attr->name() == onbeforeloadAttr)
       
   133         setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attr));
       
   134     else {
       
   135         if (attr->name() == titleAttr && m_sheet)
       
   136             m_sheet->setTitle(attr->value());
       
   137         HTMLElement::parseMappedAttribute(attr);
       
   138     }
       
   139 }
       
   140 
       
   141 void HTMLLinkElement::tokenizeRelAttribute(const AtomicString& rel, RelAttribute& relAttribute)
       
   142 {
       
   143     relAttribute.m_isStyleSheet = false;
       
   144     relAttribute.m_isIcon = false;
       
   145     relAttribute.m_isAlternate = false;
       
   146     relAttribute.m_isDNSPrefetch = false;
       
   147 #if ENABLE(LINK_PREFETCH)
       
   148     relAttribute.m_isLinkPrefetch = false;
       
   149 #endif
       
   150     if (equalIgnoringCase(rel, "stylesheet"))
       
   151         relAttribute.m_isStyleSheet = true;
       
   152     else if (equalIgnoringCase(rel, "icon") || equalIgnoringCase(rel, "shortcut icon"))
       
   153         relAttribute.m_isIcon = true;
       
   154     else if (equalIgnoringCase(rel, "dns-prefetch"))
       
   155         relAttribute.m_isDNSPrefetch = true;
       
   156 #if ENABLE(LINK_PREFETCH)
       
   157     else if (equalIgnoringCase(rel, "prefetch"))
       
   158         relAttribute.m_isLinkPrefetch = true;
       
   159 #endif
       
   160     else if (equalIgnoringCase(rel, "alternate stylesheet") || equalIgnoringCase(rel, "stylesheet alternate")) {
       
   161         relAttribute.m_isStyleSheet = true;
       
   162         relAttribute.m_isAlternate = true;
       
   163     } else {
       
   164         // Tokenize the rel attribute and set bits based on specific keywords that we find.
       
   165         String relString = rel.string();
       
   166         relString.replace('\n', ' ');
       
   167         Vector<String> list;
       
   168         relString.split(' ', list);
       
   169         Vector<String>::const_iterator end = list.end();
       
   170         for (Vector<String>::const_iterator it = list.begin(); it != end; ++it) {
       
   171             if (equalIgnoringCase(*it, "stylesheet"))
       
   172                 relAttribute.m_isStyleSheet = true;
       
   173             else if (equalIgnoringCase(*it, "alternate"))
       
   174                 relAttribute.m_isAlternate = true;
       
   175             else if (equalIgnoringCase(*it, "icon"))
       
   176                 relAttribute.m_isIcon = true;
       
   177         }
       
   178     }
       
   179 }
       
   180 
       
   181 void HTMLLinkElement::process()
       
   182 {
       
   183     if (!inDocument())
       
   184         return;
       
   185 
       
   186     String type = m_type.lower();
       
   187 
       
   188     // IE extension: location of small icon for locationbar / bookmarks
       
   189     // We'll record this URL per document, even if we later only use it in top level frames
       
   190     if (m_relAttribute.m_isIcon && m_url.isValid() && !m_url.isEmpty())
       
   191         document()->setIconURL(m_url.string(), type);
       
   192 
       
   193     if (m_relAttribute.m_isDNSPrefetch && document()->isDNSPrefetchEnabled() && m_url.isValid() && !m_url.isEmpty())
       
   194         ResourceHandle::prepareForURL(m_url);
       
   195 
       
   196 #if ENABLE(LINK_PREFETCH)
       
   197     if (m_relAttribute.m_isLinkPrefetch && m_url.isValid())
       
   198         document()->docLoader()->requestLinkPrefetch(m_url);
       
   199 #endif
       
   200 
       
   201     bool acceptIfTypeContainsTextCSS = document()->page() && document()->page()->settings() && document()->page()->settings()->treatsAnyTextCSSLinkAsStylesheet();
       
   202 
       
   203     // Stylesheet
       
   204     // This was buggy and would incorrectly match <link rel="alternate">, which has a different specified meaning. -dwh
       
   205     if (m_disabledState != Disabled && (m_relAttribute.m_isStyleSheet || (acceptIfTypeContainsTextCSS && type.contains("text/css"))) && document()->frame() && m_url.isValid()) {
       
   206         // also, don't load style sheets for standalone documents
       
   207         
       
   208         String charset = getAttribute(charsetAttr);
       
   209         if (charset.isEmpty() && document()->frame())
       
   210             charset = document()->frame()->loader()->writer()->encoding();
       
   211 
       
   212         if (m_cachedSheet) {
       
   213             if (m_loading)
       
   214                 document()->removePendingSheet();
       
   215             m_cachedSheet->removeClient(this);
       
   216             m_cachedSheet = 0;
       
   217         }
       
   218 
       
   219         if (!dispatchBeforeLoadEvent(m_url))
       
   220             return;
       
   221         
       
   222         m_loading = true;
       
   223         
       
   224         // Add ourselves as a pending sheet, but only if we aren't an alternate 
       
   225         // stylesheet.  Alternate stylesheets don't hold up render tree construction.
       
   226         if (!isAlternate())
       
   227             document()->addPendingSheet();
       
   228 
       
   229         m_cachedSheet = document()->docLoader()->requestCSSStyleSheet(m_url, charset);
       
   230         
       
   231         if (m_cachedSheet)
       
   232             m_cachedSheet->addClient(this);
       
   233         else {
       
   234             // The request may have been denied if (for example) the stylesheet is local and the document is remote.
       
   235             m_loading = false;
       
   236             if (!isAlternate())
       
   237                 document()->removePendingSheet();
       
   238         }
       
   239     } else if (m_sheet) {
       
   240         // we no longer contain a stylesheet, e.g. perhaps rel or type was changed
       
   241         m_sheet = 0;
       
   242         document()->updateStyleSelector();
       
   243     }
       
   244 }
       
   245     
       
   246 void HTMLLinkElement::processCallback(Node* node)
       
   247 {
       
   248     ASSERT_ARG(node, node && node->hasTagName(linkTag));
       
   249     static_cast<HTMLLinkElement*>(node)->process();
       
   250 }
       
   251 
       
   252 void HTMLLinkElement::insertedIntoDocument()
       
   253 {
       
   254     HTMLElement::insertedIntoDocument();
       
   255     document()->addStyleSheetCandidateNode(this, m_createdByParser);
       
   256 
       
   257     // Since processing a stylesheet link causes a beforeload event
       
   258     // to fire, it is possible for JavaScript to remove the element in the midst
       
   259     // of it being inserted into the DOM, which can lead to assertion failures
       
   260     // and crashes. Avoid this by postponing the beforeload/load until after
       
   261     // attach.
       
   262     m_shouldProcessAfterAttach = true;
       
   263 }
       
   264 
       
   265 void HTMLLinkElement::removedFromDocument()
       
   266 {
       
   267     HTMLElement::removedFromDocument();
       
   268 
       
   269     document()->removeStyleSheetCandidateNode(this);
       
   270 
       
   271     // FIXME: It's terrible to do a synchronous update of the style selector just because a <style> or <link> element got removed.
       
   272     if (document()->renderer())
       
   273         document()->updateStyleSelector();
       
   274     
       
   275     m_shouldProcessAfterAttach = false;
       
   276 }
       
   277 
       
   278 void HTMLLinkElement::attach()
       
   279 {
       
   280     if (m_shouldProcessAfterAttach) {
       
   281         m_shouldProcessAfterAttach = false;
       
   282         queuePostAttachCallback(&HTMLLinkElement::processCallback, this);
       
   283     }
       
   284 
       
   285     HTMLElement::attach();
       
   286 }
       
   287     
       
   288 void HTMLLinkElement::finishParsingChildren()
       
   289 {
       
   290     m_createdByParser = false;
       
   291     HTMLElement::finishParsingChildren();
       
   292 }
       
   293 
       
   294 void HTMLLinkElement::setCSSStyleSheet(const String& href, const KURL& baseURL, const String& charset, const CachedCSSStyleSheet* sheet)
       
   295 {
       
   296     m_sheet = CSSStyleSheet::create(this, href, baseURL, charset);
       
   297 
       
   298     bool strictParsing = !document()->inCompatMode();
       
   299     bool enforceMIMEType = strictParsing;
       
   300     bool crossOriginCSS = false;
       
   301     bool validMIMEType = false;
       
   302     bool needsSiteSpecificQuirks = document()->page() && document()->page()->settings()->needsSiteSpecificQuirks();
       
   303 
       
   304     // Check to see if we should enforce the MIME type of the CSS resource in strict mode.
       
   305     // Running in iWeb 2 is one example of where we don't want to - <rdar://problem/6099748>
       
   306     if (enforceMIMEType && document()->page() && !document()->page()->settings()->enforceCSSMIMETypeInStrictMode())
       
   307         enforceMIMEType = false;
       
   308 
       
   309 #if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD)
       
   310     if (enforceMIMEType && needsSiteSpecificQuirks) {
       
   311         // Covers both http and https, with or without "www."
       
   312         if (baseURL.string().contains("mcafee.com/japan/", false))
       
   313             enforceMIMEType = false;
       
   314     }
       
   315 #endif
       
   316 
       
   317     String sheetText = sheet->sheetText(enforceMIMEType, &validMIMEType);
       
   318     m_sheet->parseString(sheetText, strictParsing);
       
   319 
       
   320     // If we're loading a stylesheet cross-origin, and the MIME type is not
       
   321     // standard, require the CSS to at least start with a syntactically
       
   322     // valid CSS rule.
       
   323     // This prevents an attacker playing games by injecting CSS strings into
       
   324     // HTML, XML, JSON, etc. etc.
       
   325     if (!document()->securityOrigin()->canRequest(baseURL))
       
   326         crossOriginCSS = true;
       
   327 
       
   328     if (crossOriginCSS && !validMIMEType && !m_sheet->hasSyntacticallyValidCSSHeader())
       
   329         m_sheet = CSSStyleSheet::create(this, href, baseURL, charset);
       
   330 
       
   331     if (strictParsing && needsSiteSpecificQuirks) {
       
   332         // Work around <https://bugs.webkit.org/show_bug.cgi?id=28350>.
       
   333         DEFINE_STATIC_LOCAL(const String, slashKHTMLFixesDotCss, ("/KHTMLFixes.css"));
       
   334         DEFINE_STATIC_LOCAL(const String, mediaWikiKHTMLFixesStyleSheet, ("/* KHTML fix stylesheet */\n/* work around the horizontal scrollbars */\n#column-content { margin-left: 0; }\n\n"));
       
   335         // There are two variants of KHTMLFixes.css. One is equal to mediaWikiKHTMLFixesStyleSheet,
       
   336         // while the other lacks the second trailing newline.
       
   337         if (baseURL.string().endsWith(slashKHTMLFixesDotCss) && !sheetText.isNull() && mediaWikiKHTMLFixesStyleSheet.startsWith(sheetText)
       
   338                 && sheetText.length() >= mediaWikiKHTMLFixesStyleSheet.length() - 1) {
       
   339             ASSERT(m_sheet->length() == 1);
       
   340             ExceptionCode ec;
       
   341             m_sheet->deleteRule(0, ec);
       
   342         }
       
   343     }
       
   344 
       
   345     m_sheet->setTitle(title());
       
   346 
       
   347     RefPtr<MediaList> media = MediaList::createAllowingDescriptionSyntax(m_media);
       
   348     m_sheet->setMedia(media.get());
       
   349 
       
   350     m_loading = false;
       
   351     m_sheet->checkLoaded();
       
   352 }
       
   353 
       
   354 bool HTMLLinkElement::isLoading() const
       
   355 {
       
   356     if (m_loading)
       
   357         return true;
       
   358     if (!m_sheet)
       
   359         return false;
       
   360     return static_cast<CSSStyleSheet *>(m_sheet.get())->isLoading();
       
   361 }
       
   362 
       
   363 bool HTMLLinkElement::sheetLoaded()
       
   364 {
       
   365     if (!isLoading() && !isDisabled() && !isAlternate()) {
       
   366         document()->removePendingSheet();
       
   367         return true;
       
   368     }
       
   369     return false;
       
   370 }
       
   371 
       
   372 bool HTMLLinkElement::isURLAttribute(Attribute *attr) const
       
   373 {
       
   374     return attr->name() == hrefAttr;
       
   375 }
       
   376 
       
   377 KURL HTMLLinkElement::href() const
       
   378 {
       
   379     return document()->completeURL(getAttribute(hrefAttr));
       
   380 }
       
   381 
       
   382 String HTMLLinkElement::rel() const
       
   383 {
       
   384     return getAttribute(relAttr);
       
   385 }
       
   386 
       
   387 String HTMLLinkElement::target() const
       
   388 {
       
   389     return getAttribute(targetAttr);
       
   390 }
       
   391 
       
   392 String HTMLLinkElement::type() const
       
   393 {
       
   394     return getAttribute(typeAttr);
       
   395 }
       
   396 
       
   397 void HTMLLinkElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
       
   398 {
       
   399     HTMLElement::addSubresourceAttributeURLs(urls);
       
   400 
       
   401     // Favicons are handled by a special case in LegacyWebArchive::create()
       
   402     if (m_relAttribute.m_isIcon)
       
   403         return;
       
   404 
       
   405     if (!m_relAttribute.m_isStyleSheet)
       
   406         return;
       
   407     
       
   408     // Append the URL of this link element.
       
   409     addSubresourceURL(urls, href());
       
   410     
       
   411     // Walk the URLs linked by the linked-to stylesheet.
       
   412     if (StyleSheet* styleSheet = const_cast<HTMLLinkElement*>(this)->sheet())
       
   413         styleSheet->addSubresourceStyleURLs(urls);
       
   414 }
       
   415 
       
   416 }