6% regression in intl1 page cycler on chromium-mac
[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 TokenPreloadScanner::TagId TokenPreloadScanner::tagIdFor(const HTMLToken::DataVector& data)
47 {
48     AtomicString tagName(data);
49     if (tagName == imgTag)
50         return ImgTagId;
51     if (tagName == inputTag)
52         return InputTagId;
53     if (tagName == linkTag)
54         return LinkTagId;
55     if (tagName == scriptTag)
56         return ScriptTagId;
57     if (tagName == styleTag)
58         return StyleTagId;
59     if (tagName == baseTag)
60         return BaseTagId;
61     if (tagName == templateTag)
62         return TemplateTagId;
63     return UnknownTagId;
64 }
65
66 TokenPreloadScanner::TagId TokenPreloadScanner::tagIdFor(const String& tagName)
67 {
68     if (threadSafeMatch(tagName, imgTag))
69         return ImgTagId;
70     if (threadSafeMatch(tagName, inputTag))
71         return InputTagId;
72     if (threadSafeMatch(tagName, linkTag))
73         return LinkTagId;
74     if (threadSafeMatch(tagName, scriptTag))
75         return ScriptTagId;
76     if (threadSafeMatch(tagName, styleTag))
77         return StyleTagId;
78     if (threadSafeMatch(tagName, baseTag))
79         return BaseTagId;
80     if (threadSafeMatch(tagName, templateTag))
81         return TemplateTagId;
82     return UnknownTagId;
83 }
84
85 String TokenPreloadScanner::initiatorFor(TagId tagId)
86 {
87     switch (tagId) {
88     case ImgTagId:
89         return "img";
90     case InputTagId:
91         return "input";
92     case LinkTagId:
93         return "link";
94     case ScriptTagId:
95         return "script";
96     case UnknownTagId:
97     case StyleTagId:
98     case BaseTagId:
99     case TemplateTagId:
100         ASSERT_NOT_REACHED();
101         return "unknown";
102     }
103     ASSERT_NOT_REACHED();
104     return "unknown";
105 }
106
107 class TokenPreloadScanner::StartTagScanner {
108 public:
109     explicit StartTagScanner(TagId tagId)
110         : m_tagId(tagId)
111         , m_linkIsStyleSheet(false)
112         , m_linkMediaAttributeIsScreen(true)
113         , m_inputIsImage(false)
114     {
115     }
116
117     void processAttributes(const HTMLToken::AttributeList& attributes)
118     {
119         ASSERT(isMainThread());
120         if (m_tagId >= UnknownTagId)
121             return;
122         for (HTMLToken::AttributeList::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter) {
123             AtomicString attributeName(iter->name);
124             String attributeValue = StringImpl::create8BitIfPossible(iter->value);
125             processAttribute(attributeName, attributeValue);
126         }
127     }
128
129 #if ENABLE(THREADED_HTML_PARSER)
130     void processAttributes(const Vector<CompactHTMLToken::Attribute>& attributes)
131     {
132         if (m_tagId >= UnknownTagId)
133             return;
134         for (Vector<CompactHTMLToken::Attribute>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter)
135             processAttribute(iter->name, iter->value);
136     }
137 #endif
138
139     PassOwnPtr<PreloadRequest> createPreloadRequest(const KURL& predictedBaseURL)
140     {
141         if (!shouldPreload())
142             return nullptr;
143
144         OwnPtr<PreloadRequest> request = PreloadRequest::create(initiatorFor(m_tagId), m_urlToLoad, predictedBaseURL, resourceType());
145         request->setCrossOriginModeAllowsCookies(crossOriginModeAllowsCookies());
146         request->setCharset(charset());
147         return request.release();
148     }
149
150 private:
151     void processAttribute(const String& attributeName, const String& attributeValue)
152     {
153         if (threadSafeMatch(attributeName, charsetAttr))
154             m_charset = attributeValue;
155
156         if (m_tagId == ScriptTagId || m_tagId == ImgTagId) {
157             if (threadSafeMatch(attributeName, srcAttr))
158                 setUrlToLoad(attributeValue);
159             else if (threadSafeMatch(attributeName, crossoriginAttr) && !attributeValue.isNull())
160                 m_crossOriginMode = stripLeadingAndTrailingHTMLSpaces(attributeValue);
161         } else if (m_tagId == LinkTagId) {
162             if (threadSafeMatch(attributeName, hrefAttr))
163                 setUrlToLoad(attributeValue);
164             else if (threadSafeMatch(attributeName, relAttr))
165                 m_linkIsStyleSheet = relAttributeIsStyleSheet(attributeValue);
166             else if (threadSafeMatch(attributeName, mediaAttr))
167                 m_linkMediaAttributeIsScreen = linkMediaAttributeIsScreen(attributeValue);
168         } else if (m_tagId == InputTagId) {
169             if (threadSafeMatch(attributeName, srcAttr))
170                 setUrlToLoad(attributeValue);
171             else if (threadSafeMatch(attributeName, typeAttr))
172                 m_inputIsImage = equalIgnoringCase(attributeValue, InputTypeNames::image());
173         }
174     }
175
176     static bool relAttributeIsStyleSheet(const String& attributeValue)
177     {
178         LinkRelAttribute rel(attributeValue);
179         return rel.m_isStyleSheet && !rel.m_isAlternate && rel.m_iconType == InvalidIcon && !rel.m_isDNSPrefetch;
180     }
181
182     static bool linkMediaAttributeIsScreen(const String& attributeValue)
183     {
184         if (attributeValue.isEmpty())
185             return true;
186         RefPtr<MediaQuerySet> mediaQueries = MediaQuerySet::createAllowingDescriptionSyntax(attributeValue);
187     
188         // Only preload screen media stylesheets. Used this way, the evaluator evaluates to true for any 
189         // rules containing complex queries (full evaluation is possible but it requires a frame and a style selector which
190         // may be problematic here).
191         MediaQueryEvaluator mediaQueryEvaluator("screen");
192         return mediaQueryEvaluator.eval(mediaQueries.get());
193     }
194
195     void setUrlToLoad(const String& attributeValue)
196     {
197         // We only respect the first src/href, per HTML5:
198         // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#attribute-name-state
199         if (!m_urlToLoad.isEmpty())
200             return;
201         m_urlToLoad = stripLeadingAndTrailingHTMLSpaces(attributeValue);
202     }
203
204     const String& charset() const
205     {
206         // FIXME: Its not clear that this if is needed, the loader probably ignores charset for image requests anyway.
207         if (m_tagId == ImgTagId)
208             return emptyString();
209         return m_charset;
210     }
211
212     CachedResource::Type resourceType() const
213     {
214         if (m_tagId == ScriptTagId)
215             return CachedResource::Script;
216         if (m_tagId == ImgTagId || (m_tagId == InputTagId && m_inputIsImage))
217             return CachedResource::ImageResource;
218         if (m_tagId == LinkTagId && m_linkIsStyleSheet && m_linkMediaAttributeIsScreen)
219             return CachedResource::CSSStyleSheet;
220         ASSERT_NOT_REACHED();
221         return CachedResource::RawResource;
222     }
223
224     bool shouldPreload()
225     {
226         if (m_urlToLoad.isEmpty())
227             return false;
228
229         if (m_tagId == LinkTagId && (!m_linkIsStyleSheet || !m_linkMediaAttributeIsScreen))
230             return false;
231
232         if (m_tagId == InputTagId && !m_inputIsImage)
233             return false;
234
235         return true;
236     }
237
238     bool crossOriginModeAllowsCookies()
239     {
240         return m_crossOriginMode.isNull() || equalIgnoringCase(m_crossOriginMode, "use-credentials");
241     }
242
243     TagId m_tagId;
244     String m_urlToLoad;
245     String m_charset;
246     String m_crossOriginMode;
247     bool m_linkIsStyleSheet;
248     bool m_linkMediaAttributeIsScreen;
249     bool m_inputIsImage;
250 };
251
252 TokenPreloadScanner::TokenPreloadScanner(const KURL& documentURL)
253     : m_documentURL(documentURL)
254     , m_inStyle(false)
255 #if ENABLE(TEMPLATE_ELEMENT)
256     , m_templateCount(0)
257 #endif
258 {
259 }
260
261 TokenPreloadScanner::~TokenPreloadScanner()
262 {
263 }
264
265 TokenPreloadScannerCheckpoint TokenPreloadScanner::createCheckpoint()
266 {
267     TokenPreloadScannerCheckpoint checkpoint = m_checkpoints.size();
268     m_checkpoints.append(Checkpoint(m_predictedBaseElementURL, m_inStyle
269 #if ENABLE(TEMPLATE_ELEMENT)
270                                     , m_templateCount
271 #endif
272                                     ));
273     return checkpoint;
274 }
275
276 void TokenPreloadScanner::rewindTo(TokenPreloadScannerCheckpoint checkpointIndex)
277 {
278     ASSERT(checkpointIndex < m_checkpoints.size()); // If this ASSERT fires, checkpointIndex is invalid.
279     const Checkpoint& checkpoint = m_checkpoints[checkpointIndex];
280     m_predictedBaseElementURL = checkpoint.predictedBaseElementURL;
281     m_inStyle = checkpoint.inStyle;
282 #if ENABLE(TEMPLATE_ELEMENT)
283     m_templateCount = checkpoint.templateCount;
284 #endif
285     m_cssScanner.reset();
286     m_checkpoints.clear();
287 }
288
289 void TokenPreloadScanner::scan(const HTMLToken& token, Vector<OwnPtr<PreloadRequest> >& requests)
290 {
291     scanCommon(token, requests);
292 }
293
294 #if ENABLE(THREADED_HTML_PARSER)
295 void TokenPreloadScanner::scan(const CompactHTMLToken& token, Vector<OwnPtr<PreloadRequest> >& requests)
296 {
297     scanCommon(token, requests);
298 }
299 #endif
300
301 template<typename Token>
302 void TokenPreloadScanner::scanCommon(const Token& token, Vector<OwnPtr<PreloadRequest> >& requests)
303 {
304     switch (token.type()) {
305     case HTMLToken::Character: {
306         if (!m_inStyle)
307             return;
308         m_cssScanner.scan(token.data(), requests);
309         return;
310     }
311     case HTMLToken::EndTag: {
312         TagId tagId = tagIdFor(token.data());
313 #if ENABLE(TEMPLATE_ELEMENT)
314         if (tagId == TemplateTagId) {
315             if (m_templateCount)
316                 --m_templateCount;
317             return;
318         }
319 #endif
320         if (tagId == StyleTagId) {
321             if (m_inStyle)
322                 m_cssScanner.reset();
323             m_inStyle = false;
324         }
325         return;
326     }
327     case HTMLToken::StartTag: {
328 #if ENABLE(TEMPLATE_ELEMENT)
329         if (m_templateCount)
330             return;
331 #endif
332         TagId tagId = tagIdFor(token.data());
333 #if ENABLE(TEMPLATE_ELEMENT)
334         if (tagId == TemplateTagId) {
335             ++m_templateCount;
336             return;
337         }
338 #endif
339         if (tagId == StyleTagId) {
340             m_inStyle = true;
341             return;
342         }
343         if (tagId == BaseTagId) {
344             // The first <base> element is the one that wins.
345             if (!m_predictedBaseElementURL.isEmpty())
346                 return;
347             updatePredictedBaseURL(token);
348             return;
349         }
350
351         StartTagScanner scanner(tagId);
352         scanner.processAttributes(token.attributes());
353         OwnPtr<PreloadRequest> request = scanner.createPreloadRequest(m_predictedBaseElementURL);
354         if (request)
355             requests.append(request.release());
356         return;
357     }
358     default: {
359         return;
360     }
361     }
362 }
363
364 template<typename Token>
365 void TokenPreloadScanner::updatePredictedBaseURL(const Token& token)
366 {
367     ASSERT(m_predictedBaseElementURL.isEmpty());
368     if (const typename Token::Attribute* hrefAttribute = token.getAttributeItem(hrefAttr))
369         m_predictedBaseElementURL = KURL(m_documentURL, stripLeadingAndTrailingHTMLSpaces(hrefAttribute->value)).copy();
370 }
371
372 HTMLPreloadScanner::HTMLPreloadScanner(const HTMLParserOptions& options, const KURL& documentURL)
373     : m_scanner(documentURL)
374     , m_tokenizer(HTMLTokenizer::create(options))
375 {
376 }
377
378 HTMLPreloadScanner::~HTMLPreloadScanner()
379 {
380 }
381
382 void HTMLPreloadScanner::appendToEnd(const SegmentedString& source)
383 {
384     m_source.append(source);
385 }
386
387 void HTMLPreloadScanner::scan(HTMLResourcePreloader* preloader, const KURL& startingBaseElementURL)
388 {
389     ASSERT(isMainThread()); // HTMLTokenizer::updateStateFor only works on the main thread.
390
391     // When we start scanning, our best prediction of the baseElementURL is the real one!
392     if (!startingBaseElementURL.isEmpty())
393         m_scanner.setPredictedBaseElementURL(startingBaseElementURL);
394
395     PreloadRequestStream requests;
396
397     while (m_tokenizer->nextToken(m_source, m_token)) {
398         if (m_token.type() == HTMLToken::StartTag)
399             m_tokenizer->updateStateFor(AtomicString(m_token.name()));
400         m_scanner.scan(m_token, requests);
401         m_token.clear();
402     }
403
404     preloader->takeAndPreload(requests);
405 }
406
407 }