0c7ca33efc91190e9e8782bf51c2721122b7cf24
[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 "HTMLNames.h"
32 #include "HTMLParserIdioms.h"
33 #include "HTMLParserOptions.h"
34 #include "HTMLTokenizer.h"
35 #include "InputTypeNames.h"
36 #include "LinkRelAttribute.h"
37 #include "MediaList.h"
38 #include "MediaQueryEvaluator.h"
39 #include <wtf/Functional.h>
40 #include <wtf/MainThread.h>
41
42 namespace WebCore {
43
44 using namespace HTMLNames;
45
46 static bool isStartTag(const HTMLToken& token)
47 {
48     return token.type() == HTMLToken::StartTag;
49 }
50
51 static bool isStartOrEndTag(const HTMLToken& token)
52 {
53     return token.type() == HTMLToken::EndTag || isStartTag(token);
54 }
55
56 class StartTagScanner {
57 public:
58     explicit StartTagScanner(const AtomicString& tagName, const HTMLToken::AttributeList& attributes)
59         : m_tagName(tagName)
60         , m_linkIsStyleSheet(false)
61         , m_linkMediaAttributeIsScreen(true)
62         , m_inputIsImage(false)
63     {
64         processAttributes(attributes);
65     }
66
67     void processAttributes(const HTMLToken::AttributeList& attributes)
68     {
69         if (m_tagName != imgTag
70             && m_tagName != inputTag
71             && m_tagName != linkTag
72             && m_tagName != scriptTag)
73             return;
74
75         for (HTMLToken::AttributeList::const_iterator iter = attributes.begin();
76              iter != attributes.end(); ++iter) {
77             AtomicString attributeName(iter->m_name.data(), iter->m_name.size());
78             String attributeValue = StringImpl::create8BitIfPossible(iter->m_value.data(), iter->m_value.size());
79
80             if (attributeName == charsetAttr)
81                 m_charset = attributeValue;
82
83             if (m_tagName == scriptTag || m_tagName == imgTag) {
84                 if (attributeName == srcAttr)
85                     setUrlToLoad(attributeValue);
86                 else if (attributeName == crossoriginAttr && !attributeValue.isNull())
87                     m_crossOriginMode = stripLeadingAndTrailingHTMLSpaces(attributeValue);
88             } else if (m_tagName == linkTag) {
89                 if (attributeName == hrefAttr)
90                     setUrlToLoad(attributeValue);
91                 else if (attributeName == relAttr)
92                     m_linkIsStyleSheet = relAttributeIsStyleSheet(attributeValue);
93                 else if (attributeName == mediaAttr)
94                     m_linkMediaAttributeIsScreen = linkMediaAttributeIsScreen(attributeValue);
95             } else if (m_tagName == inputTag) {
96                 if (attributeName == srcAttr)
97                     setUrlToLoad(attributeValue);
98                 else if (attributeName == typeAttr)
99                     m_inputIsImage = equalIgnoringCase(attributeValue, InputTypeNames::image());
100             }
101         }
102     }
103
104     static bool relAttributeIsStyleSheet(const String& attributeValue)
105     {
106         LinkRelAttribute rel(attributeValue);
107         return rel.m_isStyleSheet && !rel.m_isAlternate && rel.m_iconType == InvalidIcon && !rel.m_isDNSPrefetch;
108     }
109
110     static bool linkMediaAttributeIsScreen(const String& attributeValue)
111     {
112         if (attributeValue.isEmpty())
113             return true;
114         RefPtr<MediaQuerySet> mediaQueries = MediaQuerySet::createAllowingDescriptionSyntax(attributeValue);
115     
116         // Only preload screen media stylesheets. Used this way, the evaluator evaluates to true for any 
117         // rules containing complex queries (full evaluation is possible but it requires a frame and a style selector which
118         // may be problematic here).
119         MediaQueryEvaluator mediaQueryEvaluator("screen");
120         return mediaQueryEvaluator.eval(mediaQueries.get());
121     }
122
123     void setUrlToLoad(const String& attributeValue)
124     {
125         // We only respect the first src/href, per HTML5:
126         // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#attribute-name-state
127         if (!m_urlToLoad.isEmpty())
128             return;
129         m_urlToLoad = stripLeadingAndTrailingHTMLSpaces(attributeValue);
130     }
131
132     const String& charset() const
133     {
134         // FIXME: Its not clear that this if is needed, the loader probably ignores charset for image requests anyway.
135         if (m_tagName == imgTag)
136             return emptyString();
137         return m_charset;
138     }
139
140     CachedResource::Type resourceType() const
141     {
142         if (m_tagName == scriptTag)
143             return CachedResource::Script;
144         if (m_tagName == imgTag || (m_tagName == inputTag && m_inputIsImage))
145             return CachedResource::ImageResource;
146         if (m_tagName == linkTag && m_linkIsStyleSheet && m_linkMediaAttributeIsScreen)
147             return CachedResource::CSSStyleSheet;
148         ASSERT_NOT_REACHED();
149         return CachedResource::RawResource;
150     }
151
152     bool shouldPreload()
153     {
154         if (m_urlToLoad.isEmpty())
155             return false;
156
157         if (m_tagName == linkTag && (!m_linkIsStyleSheet || !m_linkMediaAttributeIsScreen))
158             return false;
159
160         if (m_tagName == inputTag && !m_inputIsImage)
161             return false;
162         return true;
163     }
164
165     PassOwnPtr<PreloadRequest> createPreloadRequest(const KURL& predictedBaseURL)
166     {
167         if (!shouldPreload())
168             return nullptr;
169
170         OwnPtr<PreloadRequest> request = PreloadRequest::create(m_tagName, m_urlToLoad, predictedBaseURL, resourceType());
171         request->setCrossOriginModeAllowsCookies(crossOriginModeAllowsCookies());
172         request->setCharset(charset());
173         return request.release();
174     }
175
176     const AtomicString& tagName() const { return m_tagName; }
177
178 private:
179
180     bool crossOriginModeAllowsCookies()
181     {
182         return m_crossOriginMode.isNull() || equalIgnoringCase(m_crossOriginMode, "use-credentials");
183     }
184
185     AtomicString m_tagName;
186     String m_urlToLoad;
187     String m_charset;
188     String m_crossOriginMode;
189     bool m_linkIsStyleSheet;
190     bool m_linkMediaAttributeIsScreen;
191     bool m_inputIsImage;
192 };
193
194 HTMLPreloadScanner::HTMLPreloadScanner(const HTMLParserOptions& options, const KURL& documentURL)
195     : m_tokenizer(HTMLTokenizer::create(options))
196     , m_inStyle(false)
197     , m_documentURL(documentURL)
198 #if ENABLE(TEMPLATE_ELEMENT)
199     , m_templateCount(0)
200 #endif
201 {
202 }
203
204 void HTMLPreloadScanner::appendToEnd(const SegmentedString& source)
205 {
206     m_source.append(source);
207 }
208
209 // This function exists for convenience on the main thread and is not used by the background-thread preload scanner.
210 void HTMLPreloadScanner::scan(HTMLResourcePreloader* preloader, const KURL& startingBaseElementURL)
211 {
212     ASSERT(isMainThread()); // HTMLTokenizer::updateStateFor only works on the main thread.
213     // When we start scanning, our best prediction of the baseElementURL is the real one!
214     if (!startingBaseElementURL.isEmpty())
215         m_predictedBaseElementURL = startingBaseElementURL;
216
217     Vector<OwnPtr<PreloadRequest> > requests;
218     // Note: m_token is only used from this function and for the main thread.
219     // All other functions are passed a token.
220     while (m_tokenizer->nextToken(m_source, m_token)) {
221         if (isStartTag(m_token))
222             m_tokenizer->updateStateFor(AtomicString(m_token.name().data(), m_token.name().size()));
223         processToken(m_token, requests);
224         m_token.clear();
225     }
226     for (size_t i = 0; i < requests.size(); i++)
227         preloader->preload(requests[i].release());
228 }
229
230 #if ENABLE(TEMPLATE_ELEMENT)
231 bool HTMLPreloadScanner::processPossibleTemplateTag(const AtomicString& tagName, const HTMLToken& token)
232 {
233     if (isStartOrEndTag(token) && tagName == templateTag) {
234         if (isStartTag(token))
235             m_templateCount++;
236         else
237             m_templateCount--;
238         return true; // Twas our token.
239     }
240     // If we're in a template we "consume" all tokens.
241     return m_templateCount > 0;
242 }
243 #endif
244
245 bool HTMLPreloadScanner::processPossibleStyleTag(const AtomicString& tagName, const HTMLToken& token)
246 {
247     ASSERT(isStartOrEndTag(token));
248     if (tagName == styleTag) {
249         m_inStyle = isStartTag(token);
250         if (!m_inStyle)
251             m_cssScanner.reset();
252         return true;
253     }
254     return false;
255 }
256
257 bool HTMLPreloadScanner::processPossibleBaseTag(const AtomicString& tagName, const HTMLToken& token)
258 {
259     ASSERT(isStartTag(token));
260     if (tagName == baseTag) {
261         // The first <base> element is the one that wins.
262         if (!m_predictedBaseElementURL.isEmpty())
263             return true;
264
265         for (HTMLToken::AttributeList::const_iterator iter = token.attributes().begin(); iter != token.attributes().end(); ++iter) {
266             AtomicString attributeName(iter->m_name.data(), iter->m_name.size());
267             if (attributeName == hrefAttr) {
268                 String hrefValue = StringImpl::create8BitIfPossible(iter->m_value.data(), iter->m_value.size());
269                 m_predictedBaseElementURL = KURL(m_documentURL, stripLeadingAndTrailingHTMLSpaces(hrefValue));
270                 break;
271             }
272         }
273         return true;
274     }
275     return false;
276 }
277
278 void HTMLPreloadScanner::processToken(const HTMLToken& token, Vector<OwnPtr<PreloadRequest> >& requests)
279 {
280     // <style> is the only place we search for urls in non-start/end-tag tokens.
281     if (m_inStyle) {
282         if (token.type() != HTMLToken::Character)
283             return;
284         return m_cssScanner.scan(token, requests);
285     }
286     if (!isStartOrEndTag(token))
287         return;
288
289     AtomicString tagName(token.name().data(), token.name().size());
290 #if ENABLE(TEMPLATE_ELEMENT)
291     if (processPossibleTemplateTag(tagName, token))
292         return;
293 #endif
294     if (processPossibleStyleTag(tagName, token))
295         return;
296     if (!isStartTag(token))
297         return;
298     if (processPossibleBaseTag(tagName, token))
299         return;
300
301     StartTagScanner scanner(tagName, token.attributes());
302     OwnPtr<PreloadRequest> request =  scanner.createPreloadRequest(m_predictedBaseElementURL);
303     if (request)
304         requests.append(request.release());
305 }
306
307 }