HTML preload scanner doesn't need to remember whether we're scanning the body
[WebKit-https.git] / Source / WebCore / html / parser / HTMLPreloadScanner.cpp
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 "HTMLDocumentParser.h"
34 #include "HTMLTokenizer.h"
35 #include "HTMLNames.h"
36 #include "HTMLParserIdioms.h"
37 #include "InputTypeNames.h"
38 #include "LinkRelAttribute.h"
39 #include "MediaList.h"
40 #include "MediaQueryEvaluator.h"
41
42 namespace WebCore {
43
44 using namespace HTMLNames;
45
46 class PreloadTask {
47 public:
48     explicit PreloadTask(const HTMLToken& token)
49         : m_tagName(token.name().data(), token.name().size())
50         , m_linkIsStyleSheet(false)
51         , m_linkMediaAttributeIsScreen(true)
52         , m_inputIsImage(false)
53     {
54         processAttributes(token.attributes());
55     }
56
57     void processAttributes(const HTMLToken::AttributeList& attributes)
58     {
59         if (m_tagName != imgTag
60             && m_tagName != inputTag
61             && m_tagName != linkTag
62             && m_tagName != scriptTag
63             && m_tagName != baseTag)
64             return;
65
66         for (HTMLToken::AttributeList::const_iterator iter = attributes.begin();
67              iter != attributes.end(); ++iter) {
68             AtomicString attributeName(iter->m_name.data(), iter->m_name.size());
69             String attributeValue = StringImpl::create8BitIfPossible(iter->m_value.data(), iter->m_value.size());
70
71             if (attributeName == charsetAttr)
72                 m_charset = attributeValue;
73
74             if (m_tagName == scriptTag || m_tagName == imgTag) {
75                 if (attributeName == srcAttr)
76                     setUrlToLoad(attributeValue);
77                 else if (attributeName == crossoriginAttr && !attributeValue.isNull())
78                     m_crossOriginMode = stripLeadingAndTrailingHTMLSpaces(attributeValue);
79             } else if (m_tagName == linkTag) {
80                 if (attributeName == hrefAttr)
81                     setUrlToLoad(attributeValue);
82                 else if (attributeName == relAttr)
83                     m_linkIsStyleSheet = relAttributeIsStyleSheet(attributeValue);
84                 else if (attributeName == mediaAttr)
85                     m_linkMediaAttributeIsScreen = linkMediaAttributeIsScreen(attributeValue);
86             } else if (m_tagName == inputTag) {
87                 if (attributeName == srcAttr)
88                     setUrlToLoad(attributeValue);
89                 else if (attributeName == typeAttr)
90                     m_inputIsImage = equalIgnoringCase(attributeValue, InputTypeNames::image());
91             } else if (m_tagName == baseTag) {
92                 if (attributeName == hrefAttr)
93                     m_baseElementHref = stripLeadingAndTrailingHTMLSpaces(attributeValue);
94             }
95         }
96     }
97
98     static bool relAttributeIsStyleSheet(const String& attributeValue)
99     {
100         LinkRelAttribute rel(attributeValue);
101         return rel.m_isStyleSheet && !rel.m_isAlternate && rel.m_iconType == InvalidIcon && !rel.m_isDNSPrefetch;
102     }
103
104     static bool linkMediaAttributeIsScreen(const String& attributeValue)
105     {
106         if (attributeValue.isEmpty())
107             return true;
108         RefPtr<MediaQuerySet> mediaQueries = MediaQuerySet::createAllowingDescriptionSyntax(attributeValue);
109     
110         // Only preload screen media stylesheets. Used this way, the evaluator evaluates to true for any 
111         // rules containing complex queries (full evaluation is possible but it requires a frame and a style selector which
112         // may be problematic here).
113         MediaQueryEvaluator mediaQueryEvaluator("screen");
114         return mediaQueryEvaluator.eval(mediaQueries.get());
115     }
116
117     void setUrlToLoad(const String& attributeValue)
118     {
119         // We only respect the first src/href, per HTML5:
120         // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#attribute-name-state
121         if (!m_urlToLoad.isEmpty())
122             return;
123         m_urlToLoad = stripLeadingAndTrailingHTMLSpaces(attributeValue);
124     }
125
126     void preload(Document* document, const KURL& baseURL)
127     {
128         if (m_urlToLoad.isEmpty())
129             return;
130
131         CachedResourceLoader* cachedResourceLoader = document->cachedResourceLoader();
132         CachedResourceRequest request(ResourceRequest(document->completeURL(m_urlToLoad, baseURL)));
133         request.setInitiator(tagName());
134         if (m_tagName == scriptTag) {
135             request.mutableResourceRequest().setAllowCookies(crossOriginModeAllowsCookies());
136             cachedResourceLoader->preload(CachedResource::Script, request, m_charset);
137         }
138         else if (m_tagName == imgTag || (m_tagName == inputTag && m_inputIsImage))
139             cachedResourceLoader->preload(CachedResource::ImageResource, request, String());
140         else if (m_tagName == linkTag && m_linkIsStyleSheet && m_linkMediaAttributeIsScreen) 
141             cachedResourceLoader->preload(CachedResource::CSSStyleSheet, request, m_charset);
142     }
143
144     const AtomicString& tagName() const { return m_tagName; }
145     const String& baseElementHref() const { return m_baseElementHref; }
146
147 private:
148
149     bool crossOriginModeAllowsCookies()
150     {
151         return m_crossOriginMode.isNull() || equalIgnoringCase(m_crossOriginMode, "use-credentials");
152     }
153
154     AtomicString m_tagName;
155     String m_urlToLoad;
156     String m_charset;
157     String m_baseElementHref;
158     String m_crossOriginMode;
159     bool m_linkIsStyleSheet;
160     bool m_linkMediaAttributeIsScreen;
161     bool m_inputIsImage;
162 };
163
164 HTMLPreloadScanner::HTMLPreloadScanner(Document* document)
165     : m_document(document)
166     , m_cssScanner(document)
167     , m_tokenizer(HTMLTokenizer::create(HTMLDocumentParser::usePreHTML5ParserQuirks(document)))
168     , m_inStyle(false)
169 {
170 }
171
172 void HTMLPreloadScanner::appendToEnd(const SegmentedString& source)
173 {
174     m_source.append(source);
175 }
176
177 void HTMLPreloadScanner::scan()
178 {
179     // When we start scanning, our best prediction of the baseElementURL is the real one!
180     m_predictedBaseElementURL = m_document->baseElementURL();
181
182     // FIXME: We should save and re-use these tokens in HTMLDocumentParser if
183     // the pending script doesn't end up calling document.write.
184     while (m_tokenizer->nextToken(m_source, m_token)) {
185         processToken();
186         m_token.clear();
187     }
188 }
189
190 void HTMLPreloadScanner::processToken()
191 {
192     if (m_inStyle) {
193         if (m_token.type() == HTMLTokenTypes::Character)
194             m_cssScanner.scan(m_token);
195         else if (m_token.type() == HTMLTokenTypes::EndTag) {
196             m_inStyle = false;
197             m_cssScanner.reset();
198         }
199     }
200
201     if (m_token.type() != HTMLTokenTypes::StartTag)
202         return;
203
204     PreloadTask task(m_token);
205     m_tokenizer->updateStateFor(task.tagName(), m_document->frame());
206
207     if (task.tagName() == styleTag)
208         m_inStyle = true;
209
210     if (task.tagName() == baseTag)
211         updatePredictedBaseElementURL(KURL(m_document->url(), task.baseElementHref()));
212
213     task.preload(m_document, m_predictedBaseElementURL.isEmpty() ? m_document->baseURL() : m_predictedBaseElementURL);
214 }
215
216 void HTMLPreloadScanner::updatePredictedBaseElementURL(const KURL& baseElementURL)
217 {
218     // The first <base> element is the one that wins.
219     if (!m_predictedBaseElementURL.isEmpty())
220         return;
221     m_predictedBaseElementURL = baseElementURL;
222 }
223
224 }