Home | History | Annotate | Download | only in parser
      1 /*
      2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
      3  * Copyright (C) 2009 Torch Mobile, Inc. http://www.torchmobile.com/
      4  * Copyright (C) 2010 Google Inc. All Rights Reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      9  * 1. Redistributions of source code must retain the above copyright
     10  *    notice, this list of conditions and the following disclaimer.
     11  * 2. Redistributions in binary form must reproduce the above copyright
     12  *    notice, this list of conditions and the following disclaimer in the
     13  *    documentation and/or other materials provided with the distribution.
     14  *
     15  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
     16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
     19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 #include "config.h"
     29 #include "HTMLPreloadScanner.h"
     30 
     31 #include "CachedResourceLoader.h"
     32 #include "Document.h"
     33 #include "InputType.h"
     34 #include "HTMLDocumentParser.h"
     35 #include "HTMLTokenizer.h"
     36 #include "HTMLLinkElement.h"
     37 #include "HTMLNames.h"
     38 #include "HTMLParserIdioms.h"
     39 #include "MediaList.h"
     40 #include "MediaQueryEvaluator.h"
     41 
     42 namespace WebCore {
     43 
     44 using namespace HTMLNames;
     45 
     46 namespace {
     47 
     48 class PreloadTask {
     49 public:
     50     PreloadTask(const HTMLToken& token)
     51         : m_tagName(token.name().data(), token.name().size())
     52         , m_linkIsStyleSheet(false)
     53         , m_linkMediaAttributeIsScreen(true)
     54         , m_inputIsImage(false)
     55     {
     56         processAttributes(token.attributes());
     57     }
     58 
     59     void processAttributes(const HTMLToken::AttributeList& attributes)
     60     {
     61         if (m_tagName != imgTag
     62             && m_tagName != inputTag
     63             && m_tagName != linkTag
     64             && m_tagName != scriptTag)
     65             return;
     66 
     67         for (HTMLToken::AttributeList::const_iterator iter = attributes.begin();
     68              iter != attributes.end(); ++iter) {
     69             AtomicString attributeName(iter->m_name.data(), iter->m_name.size());
     70             String attributeValue(iter->m_value.data(), iter->m_value.size());
     71 
     72             if (attributeName == charsetAttr)
     73                 m_charset = attributeValue;
     74 
     75             if (m_tagName == scriptTag || m_tagName == imgTag) {
     76                 if (attributeName == srcAttr)
     77                     setUrlToLoad(attributeValue);
     78             } else if (m_tagName == linkTag) {
     79                 if (attributeName == hrefAttr)
     80                     setUrlToLoad(attributeValue);
     81                 else if (attributeName == relAttr)
     82                     m_linkIsStyleSheet = relAttributeIsStyleSheet(attributeValue);
     83                 else if (attributeName == mediaAttr)
     84                     m_linkMediaAttributeIsScreen = linkMediaAttributeIsScreen(attributeValue);
     85             } else if (m_tagName == inputTag) {
     86                 if (attributeName == srcAttr)
     87                     setUrlToLoad(attributeValue);
     88                 else if (attributeName == typeAttr)
     89                     m_inputIsImage = equalIgnoringCase(attributeValue, InputTypeNames::image());
     90             }
     91         }
     92     }
     93 
     94     static bool relAttributeIsStyleSheet(const String& attributeValue)
     95     {
     96         HTMLLinkElement::RelAttribute rel;
     97         HTMLLinkElement::tokenizeRelAttribute(attributeValue, rel);
     98         return rel.m_isStyleSheet && !rel.m_isAlternate && !rel.m_isIcon && !rel.m_isDNSPrefetch;
     99     }
    100 
    101     static bool linkMediaAttributeIsScreen(const String& attributeValue)
    102     {
    103         if (attributeValue.isEmpty())
    104             return true;
    105         RefPtr<MediaList> mediaList = MediaList::createAllowingDescriptionSyntax(attributeValue);
    106 
    107         // Only preload screen media stylesheets. Used this way, the evaluator evaluates to true for any
    108         // rules containing complex queries (full evaluation is possible but it requires a frame and a style selector which
    109         // may be problematic here).
    110         MediaQueryEvaluator mediaQueryEvaluator("screen");
    111         return mediaQueryEvaluator.eval(mediaList.get());
    112     }
    113 
    114     void setUrlToLoad(const String& attributeValue)
    115     {
    116         // We only respect the first src/href, per HTML5:
    117         // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#attribute-name-state
    118         if (!m_urlToLoad.isEmpty())
    119             return;
    120         m_urlToLoad = stripLeadingAndTrailingHTMLSpaces(attributeValue);
    121     }
    122 
    123     void preload(Document* document, bool scanningBody)
    124     {
    125         if (m_urlToLoad.isEmpty())
    126             return;
    127 
    128         CachedResourceLoader* cachedResourceLoader = document->cachedResourceLoader();
    129         if (m_tagName == scriptTag)
    130             cachedResourceLoader->preload(CachedResource::Script, m_urlToLoad, m_charset, scanningBody);
    131         else if (m_tagName == imgTag || (m_tagName == inputTag && m_inputIsImage))
    132             cachedResourceLoader->preload(CachedResource::ImageResource, m_urlToLoad, String(), scanningBody);
    133         else if (m_tagName == linkTag && m_linkIsStyleSheet && m_linkMediaAttributeIsScreen)
    134             cachedResourceLoader->preload(CachedResource::CSSStyleSheet, m_urlToLoad, m_charset, scanningBody);
    135     }
    136 
    137     const AtomicString& tagName() const { return m_tagName; }
    138 
    139 private:
    140     AtomicString m_tagName;
    141     String m_urlToLoad;
    142     String m_charset;
    143     bool m_linkIsStyleSheet;
    144     bool m_linkMediaAttributeIsScreen;
    145     bool m_inputIsImage;
    146 };
    147 
    148 } // namespace
    149 
    150 HTMLPreloadScanner::HTMLPreloadScanner(Document* document)
    151     : m_document(document)
    152     , m_cssScanner(document)
    153     , m_tokenizer(HTMLTokenizer::create(HTMLDocumentParser::usePreHTML5ParserQuirks(document)))
    154     , m_bodySeen(false)
    155     , m_inStyle(false)
    156 {
    157 }
    158 
    159 void HTMLPreloadScanner::appendToEnd(const SegmentedString& source)
    160 {
    161     m_source.append(source);
    162 }
    163 
    164 void HTMLPreloadScanner::scan()
    165 {
    166     // FIXME: We should save and re-use these tokens in HTMLDocumentParser if
    167     // the pending script doesn't end up calling document.write.
    168     while (m_tokenizer->nextToken(m_source, m_token)) {
    169         processToken();
    170         m_token.clear();
    171     }
    172 }
    173 
    174 void HTMLPreloadScanner::processToken()
    175 {
    176     if (m_inStyle) {
    177         if (m_token.type() == HTMLToken::Character)
    178             m_cssScanner.scan(m_token, scanningBody());
    179         else if (m_token.type() == HTMLToken::EndTag) {
    180             m_inStyle = false;
    181             m_cssScanner.reset();
    182         }
    183     }
    184 
    185     if (m_token.type() != HTMLToken::StartTag)
    186         return;
    187 
    188     PreloadTask task(m_token);
    189     m_tokenizer->updateStateFor(task.tagName(), m_document->frame());
    190 
    191     if (task.tagName() == bodyTag)
    192         m_bodySeen = true;
    193 
    194     if (task.tagName() == styleTag)
    195         m_inStyle = true;
    196 
    197     task.preload(m_document, scanningBody());
    198 }
    199 
    200 bool HTMLPreloadScanner::scanningBody() const
    201 {
    202     return m_document->body() || m_bodySeen;
    203 }
    204 
    205 }
    206