Factor HTMLTokenScanner out of HTMLPreloadScanner
[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     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->name);
78             String attributeValue = StringImpl::create8BitIfPossible(iter->value);
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 TokenPreloadScanner::TokenPreloadScanner(const KURL& documentURL)
195     : m_documentURL(documentURL)
196     , m_inStyle(false)
197 #if ENABLE(TEMPLATE_ELEMENT)
198     , m_templateCount(0)
199 #endif
200 {
201 }
202
203 TokenPreloadScanner::~TokenPreloadScanner()
204 {
205 }
206
207 #if ENABLE(TEMPLATE_ELEMENT)
208 bool TokenPreloadScanner::processPossibleTemplateTag(const AtomicString& tagName, const HTMLToken& token)
209 {
210     if (isStartOrEndTag(token) && tagName == templateTag) {
211         if (isStartTag(token))
212             m_templateCount++;
213         else
214             m_templateCount--;
215         return true; // Twas our token.
216     }
217     // If we're in a template we "consume" all tokens.
218     return m_templateCount > 0;
219 }
220 #endif
221
222 bool TokenPreloadScanner::processPossibleStyleTag(const AtomicString& tagName, const HTMLToken& token)
223 {
224     ASSERT(isStartOrEndTag(token));
225     if (tagName != styleTag)
226         return false;
227
228     m_inStyle = isStartTag(token);
229
230     if (!m_inStyle)
231         m_cssScanner.reset();
232
233     return true;
234 }
235
236 bool TokenPreloadScanner::processPossibleBaseTag(const AtomicString& tagName, const HTMLToken& token)
237 {
238     ASSERT(isStartTag(token));
239     if (tagName != baseTag)
240         return false;
241
242     // The first <base> element is the one that wins.
243     if (!m_predictedBaseElementURL.isEmpty())
244         return true;
245
246     for (HTMLToken::AttributeList::const_iterator iter = token.attributes().begin(); iter != token.attributes().end(); ++iter) {
247         AtomicString attributeName(iter->name);
248         if (attributeName == hrefAttr) {
249             String hrefValue = StringImpl::create8BitIfPossible(iter->value);
250             m_predictedBaseElementURL = KURL(m_documentURL, stripLeadingAndTrailingHTMLSpaces(hrefValue));
251             break;
252         }
253     }
254     return true;
255 }
256
257 void TokenPreloadScanner::scan(const HTMLToken& token, Vector<OwnPtr<PreloadRequest> >& requests)
258 {
259     // <style> is the only place we search for urls in non-start/end-tag tokens.
260     if (m_inStyle) {
261         if (token.type() != HTMLToken::Character)
262             return;
263         const HTMLToken::DataVector& characters = token.characters();
264         return m_cssScanner.scan(characters.begin(), characters.end(), requests);
265     }
266
267     if (!isStartOrEndTag(token))
268         return;
269
270     AtomicString tagName(token.name());
271 #if ENABLE(TEMPLATE_ELEMENT)
272     if (processPossibleTemplateTag(tagName, token))
273         return;
274 #endif
275     if (processPossibleStyleTag(tagName, token))
276         return;
277     if (!isStartTag(token))
278         return;
279     if (processPossibleBaseTag(tagName, token))
280         return;
281
282     StartTagScanner scanner(tagName, token.attributes());
283     OwnPtr<PreloadRequest> request =  scanner.createPreloadRequest(m_predictedBaseElementURL);
284     if (request)
285         requests.append(request.release());
286 }
287
288 HTMLPreloadScanner::HTMLPreloadScanner(const HTMLParserOptions& options, const KURL& documentURL)
289     : m_scanner(documentURL)
290     , m_tokenizer(HTMLTokenizer::create(options))
291 {
292 }
293
294 HTMLPreloadScanner::~HTMLPreloadScanner()
295 {
296 }
297
298 void HTMLPreloadScanner::appendToEnd(const SegmentedString& source)
299 {
300     m_source.append(source);
301 }
302
303 void HTMLPreloadScanner::scan(HTMLResourcePreloader* preloader, const KURL& startingBaseElementURL)
304 {
305     ASSERT(isMainThread()); // HTMLTokenizer::updateStateFor only works on the main thread.
306
307     // When we start scanning, our best prediction of the baseElementURL is the real one!
308     if (!startingBaseElementURL.isEmpty())
309         m_scanner.setPredictedBaseElementURL(startingBaseElementURL);
310
311     Vector<OwnPtr<PreloadRequest> > requests;
312     while (m_tokenizer->nextToken(m_source, m_token)) {
313         if (isStartTag(m_token))
314             m_tokenizer->updateStateFor(AtomicString(m_token.name()));
315         m_scanner.scan(m_token, requests);
316         m_token.clear();
317     }
318     for (size_t i = 0; i < requests.size(); i++)
319         preloader->preload(requests[i].release());
320 }
321
322 }