REGRESSION(r179476): It broke the !ENABLE(PICTURE_SIZES) build
[WebKit-https.git] / Source / WebCore / html / parser / HTMLPreloadScanner.cpp
1 /*
2  * Copyright (C) 2008, 2014 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 "HTMLSrcsetParser.h"
34 #include "HTMLTokenizer.h"
35 #include "InputTypeNames.h"
36 #include "LinkRelAttribute.h"
37 #include "SourceSizeList.h"
38 #include <wtf/MainThread.h>
39
40 namespace WebCore {
41
42 using namespace HTMLNames;
43
44 TokenPreloadScanner::TagId TokenPreloadScanner::tagIdFor(const HTMLToken::DataVector& data)
45 {
46     AtomicString tagName(data);
47     if (tagName == imgTag)
48         return TagId::Img;
49     if (tagName == inputTag)
50         return TagId::Input;
51     if (tagName == linkTag)
52         return TagId::Link;
53     if (tagName == scriptTag)
54         return TagId::Script;
55     if (tagName == styleTag)
56         return TagId::Style;
57     if (tagName == baseTag)
58         return TagId::Base;
59     if (tagName == templateTag)
60         return TagId::Template;
61     return TagId::Unknown;
62 }
63
64 String TokenPreloadScanner::initiatorFor(TagId tagId)
65 {
66     switch (tagId) {
67     case TagId::Img:
68         return "img";
69     case TagId::Input:
70         return "input";
71     case TagId::Link:
72         return "link";
73     case TagId::Script:
74         return "script";
75     case TagId::Unknown:
76     case TagId::Style:
77     case TagId::Base:
78     case TagId::Template:
79         ASSERT_NOT_REACHED();
80         return "unknown";
81     }
82     ASSERT_NOT_REACHED();
83     return "unknown";
84 }
85
86 class TokenPreloadScanner::StartTagScanner {
87 public:
88     explicit StartTagScanner(TagId tagId, float deviceScaleFactor = 1.0)
89         : m_tagId(tagId)
90         , m_linkIsStyleSheet(false)
91         , m_inputIsImage(false)
92         , m_deviceScaleFactor(deviceScaleFactor)
93     {
94     }
95
96     void processAttributes(const HTMLToken::AttributeList& attributes, Document& document)
97     {
98         ASSERT(isMainThread());
99         if (m_tagId >= TagId::Unknown)
100             return;
101
102         for (HTMLToken::AttributeList::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter) {
103             AtomicString attributeName(iter->name);
104             String attributeValue = StringImpl::create8BitIfPossible(iter->value);
105             processAttribute(attributeName, attributeValue);
106         }
107
108         // Resolve between src and srcSet if we have them.
109         if (!m_srcSetAttribute.isEmpty()) {
110             unsigned sourceSize = 0;
111 #if ENABLE(PICTURE_SIZES)
112             sourceSize = parseSizesAttribute(m_sizesAttribute, document.renderView(), document.frame());
113 #endif
114             ImageCandidate imageCandidate = bestFitSourceForImageAttributes(m_deviceScaleFactor, m_urlToLoad, m_srcSetAttribute, sourceSize);
115             setUrlToLoad(imageCandidate.string.toString(), true);
116         }
117     }
118
119     std::unique_ptr<PreloadRequest> createPreloadRequest(const URL& predictedBaseURL)
120     {
121         if (!shouldPreload())
122             return nullptr;
123
124         auto request = std::make_unique<PreloadRequest>(initiatorFor(m_tagId), m_urlToLoad, predictedBaseURL, resourceType(), m_mediaAttribute);
125
126         request->setCrossOriginModeAllowsCookies(crossOriginModeAllowsCookies());
127         request->setCharset(charset());
128         return request;
129     }
130
131     static bool match(const AtomicString& name, const QualifiedName& qName)
132     {
133         ASSERT(isMainThread());
134         return qName.localName() == name;
135     }
136
137 private:
138     template<typename NameType>
139     void processAttribute(const NameType& attributeName, const String& attributeValue)
140     {
141         if (match(attributeName, charsetAttr))
142             m_charset = attributeValue;
143
144         if (m_tagId == TagId::Script || m_tagId == TagId::Img) {
145             if (match(attributeName, srcAttr))
146                 setUrlToLoad(attributeValue);
147             else if (match(attributeName, srcsetAttr) && m_srcSetAttribute.isNull())
148                 m_srcSetAttribute = attributeValue;
149 #if ENABLE(PICTURE_SIZES)
150             else if (match(attributeName, sizesAttr) && m_sizesAttribute.isNull())
151                 m_sizesAttribute = attributeValue;
152 #endif
153             else if (match(attributeName, crossoriginAttr) && !attributeValue.isNull())
154                 m_crossOriginMode = stripLeadingAndTrailingHTMLSpaces(attributeValue);
155         } else if (m_tagId == TagId::Link) {
156             if (match(attributeName, hrefAttr))
157                 setUrlToLoad(attributeValue);
158             else if (match(attributeName, relAttr))
159                 m_linkIsStyleSheet = relAttributeIsStyleSheet(attributeValue);
160             else if (match(attributeName, mediaAttr))
161                 m_mediaAttribute = attributeValue;
162         } else if (m_tagId == TagId::Input) {
163             if (match(attributeName, srcAttr))
164                 setUrlToLoad(attributeValue);
165             else if (match(attributeName, typeAttr))
166                 m_inputIsImage = equalIgnoringCase(attributeValue, InputTypeNames::image());
167         }
168     }
169
170     static bool relAttributeIsStyleSheet(const String& attributeValue)
171     {
172         LinkRelAttribute rel(attributeValue);
173         return rel.m_isStyleSheet && !rel.m_isAlternate && rel.m_iconType == InvalidIcon && !rel.m_isDNSPrefetch;
174     }
175
176     void setUrlToLoad(const String& value, bool allowReplacement = false)
177     {
178         // We only respect the first src/href, per HTML5:
179         // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#attribute-name-state
180         if (!allowReplacement && !m_urlToLoad.isEmpty())
181             return;
182         String url = stripLeadingAndTrailingHTMLSpaces(value);
183         if (url.isEmpty())
184             return;
185         m_urlToLoad = url;
186     }
187
188     const String& charset() const
189     {
190         // FIXME: Its not clear that this if is needed, the loader probably ignores charset for image requests anyway.
191         if (m_tagId == TagId::Img)
192             return emptyString();
193         return m_charset;
194     }
195
196     CachedResource::Type resourceType() const
197     {
198         if (m_tagId == TagId::Script)
199             return CachedResource::Script;
200         if (m_tagId == TagId::Img || (m_tagId == TagId::Input && m_inputIsImage))
201             return CachedResource::ImageResource;
202         if (m_tagId == TagId::Link && m_linkIsStyleSheet)
203             return CachedResource::CSSStyleSheet;
204         ASSERT_NOT_REACHED();
205         return CachedResource::RawResource;
206     }
207
208     bool shouldPreload()
209     {
210         if (m_urlToLoad.isEmpty())
211             return false;
212
213         if (m_tagId == TagId::Link && !m_linkIsStyleSheet)
214             return false;
215
216         if (m_tagId == TagId::Input && !m_inputIsImage)
217             return false;
218
219         return true;
220     }
221
222     bool crossOriginModeAllowsCookies()
223     {
224         return m_crossOriginMode.isNull() || equalIgnoringCase(m_crossOriginMode, "use-credentials");
225     }
226
227     TagId m_tagId;
228     String m_urlToLoad;
229     String m_srcSetAttribute;
230 #if ENABLE(PICTURE_SIZES)
231     String m_sizesAttribute;
232 #endif
233     String m_charset;
234     String m_crossOriginMode;
235     bool m_linkIsStyleSheet;
236     String m_mediaAttribute;
237     bool m_inputIsImage;
238     float m_deviceScaleFactor;
239 };
240
241 TokenPreloadScanner::TokenPreloadScanner(const URL& documentURL, float deviceScaleFactor)
242     : m_documentURL(documentURL)
243     , m_deviceScaleFactor(deviceScaleFactor)
244 {
245 }
246
247 void TokenPreloadScanner::scan(const HTMLToken& token, Vector<std::unique_ptr<PreloadRequest>>& requests, Document& document)
248 {
249     switch (token.type()) {
250     case HTMLToken::Character:
251         if (!m_inStyle)
252             return;
253         m_cssScanner.scan(token.characters(), requests);
254         return;
255
256     case HTMLToken::EndTag: {
257         TagId tagId = tagIdFor(token.name());
258 #if ENABLE(TEMPLATE_ELEMENT)
259         if (tagId == TagId::Template) {
260             if (m_templateCount)
261                 --m_templateCount;
262             return;
263         }
264 #endif
265         if (tagId == TagId::Style) {
266             if (m_inStyle)
267                 m_cssScanner.reset();
268             m_inStyle = false;
269         }
270         return;
271     }
272
273     case HTMLToken::StartTag: {
274 #if ENABLE(TEMPLATE_ELEMENT)
275         if (m_templateCount)
276             return;
277 #endif
278         TagId tagId = tagIdFor(token.name());
279 #if ENABLE(TEMPLATE_ELEMENT)
280         if (tagId == TagId::Template) {
281             ++m_templateCount;
282             return;
283         }
284 #endif
285         if (tagId == TagId::Style) {
286             m_inStyle = true;
287             return;
288         }
289         if (tagId == TagId::Base) {
290             // The first <base> element is the one that wins.
291             if (!m_predictedBaseElementURL.isEmpty())
292                 return;
293             updatePredictedBaseURL(token);
294             return;
295         }
296
297         StartTagScanner scanner(tagId, m_deviceScaleFactor);
298         scanner.processAttributes(token.attributes(), document);
299         if (auto request = scanner.createPreloadRequest(m_predictedBaseElementURL))
300             requests.append(WTF::move(request));
301         return;
302     }
303
304     default:
305         return;
306     }
307 }
308
309 void TokenPreloadScanner::updatePredictedBaseURL(const HTMLToken& token)
310 {
311     ASSERT(m_predictedBaseElementURL.isEmpty());
312     if (auto* hrefAttribute = findAttribute(token.attributes(), hrefAttr.localName().string()))
313         m_predictedBaseElementURL = URL(m_documentURL, stripLeadingAndTrailingHTMLSpaces(StringImpl::create8BitIfPossible(hrefAttribute->value))).copy();
314 }
315
316 HTMLPreloadScanner::HTMLPreloadScanner(const HTMLParserOptions& options, const URL& documentURL, float deviceScaleFactor)
317     : m_scanner(documentURL, deviceScaleFactor)
318     , m_tokenizer(options)
319 {
320 }
321
322 void HTMLPreloadScanner::appendToEnd(const SegmentedString& source)
323 {
324     m_source.append(source);
325 }
326
327 void HTMLPreloadScanner::scan(HTMLResourcePreloader& preloader, Document& document)
328 {
329     ASSERT(isMainThread()); // HTMLTokenizer::updateStateFor only works on the main thread.
330
331     const URL& startingBaseElementURL = document.baseElementURL();
332
333     // When we start scanning, our best prediction of the baseElementURL is the real one!
334     if (!startingBaseElementURL.isEmpty())
335         m_scanner.setPredictedBaseElementURL(startingBaseElementURL);
336
337     PreloadRequestStream requests;
338
339     while (auto token = m_tokenizer.nextToken(m_source)) {
340         if (token->type() == HTMLToken::StartTag)
341             m_tokenizer.updateStateFor(AtomicString(token->name()));
342         m_scanner.scan(*token, requests, document);
343     }
344
345     preloader.preload(WTF::move(requests));
346 }
347
348 }