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