Add WTF::move()
[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
97 #if ENABLE(PICTURE_SIZES)
98         , RenderView* view, Frame* frame
99 #endif
100         )
101     {
102         ASSERT(isMainThread());
103         if (m_tagId >= TagId::Unknown)
104             return;
105         for (HTMLToken::AttributeList::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter) {
106             AtomicString attributeName(iter->name);
107             String attributeValue = StringImpl::create8BitIfPossible(iter->value);
108             processAttribute(attributeName, attributeValue);
109         }
110
111         // Resolve between src and srcSet if we have them.
112         if (!m_srcSetAttribute.isEmpty()) {
113             ImageCandidate imageCandidate = bestFitSourceForImageAttributes(m_deviceScaleFactor, m_urlToLoad, m_srcSetAttribute
114 #if ENABLE(PICTURE_SIZES)
115                 , SourceSizeList::parseSizesAttribute(m_sizesAttribute, view, frame)
116 #endif
117                 );
118             setUrlToLoad(imageCandidate.string.toString(), true);
119         }
120     }
121
122     std::unique_ptr<PreloadRequest> createPreloadRequest(const URL& predictedBaseURL)
123     {
124         if (!shouldPreload())
125             return nullptr;
126
127         auto request = std::make_unique<PreloadRequest>(initiatorFor(m_tagId), m_urlToLoad, predictedBaseURL, resourceType(), m_mediaAttribute);
128
129         request->setCrossOriginModeAllowsCookies(crossOriginModeAllowsCookies());
130         request->setCharset(charset());
131         return request;
132     }
133
134     static bool match(const AtomicString& name, const QualifiedName& qName)
135     {
136         ASSERT(isMainThread());
137         return qName.localName() == name;
138     }
139
140 private:
141     template<typename NameType>
142     void processAttribute(const NameType& attributeName, const String& attributeValue)
143     {
144         if (match(attributeName, charsetAttr))
145             m_charset = attributeValue;
146
147         if (m_tagId == TagId::Script || m_tagId == TagId::Img) {
148             if (match(attributeName, srcAttr))
149                 setUrlToLoad(attributeValue);
150             else if (match(attributeName, srcsetAttr) && m_srcSetAttribute.isNull())
151                 m_srcSetAttribute = attributeValue;
152 #if ENABLE(PICTURE_SIZES)
153             else if (match(attributeName, sizesAttr) && m_sizesAttribute.isNull())
154                 m_sizesAttribute = attributeValue;
155 #endif
156             else if (match(attributeName, crossoriginAttr) && !attributeValue.isNull())
157                 m_crossOriginMode = stripLeadingAndTrailingHTMLSpaces(attributeValue);
158         } else if (m_tagId == TagId::Link) {
159             if (match(attributeName, hrefAttr))
160                 setUrlToLoad(attributeValue);
161             else if (match(attributeName, relAttr))
162                 m_linkIsStyleSheet = relAttributeIsStyleSheet(attributeValue);
163             else if (match(attributeName, mediaAttr))
164                 m_mediaAttribute = attributeValue;
165         } else if (m_tagId == TagId::Input) {
166             if (match(attributeName, srcAttr))
167                 setUrlToLoad(attributeValue);
168             else if (match(attributeName, typeAttr))
169                 m_inputIsImage = equalIgnoringCase(attributeValue, InputTypeNames::image());
170         }
171     }
172
173     static bool relAttributeIsStyleSheet(const String& attributeValue)
174     {
175         LinkRelAttribute rel(attributeValue);
176         return rel.m_isStyleSheet && !rel.m_isAlternate && rel.m_iconType == InvalidIcon && !rel.m_isDNSPrefetch;
177     }
178
179     void setUrlToLoad(const String& value, bool allowReplacement = false)
180     {
181         // We only respect the first src/href, per HTML5:
182         // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#attribute-name-state
183         if (!allowReplacement && !m_urlToLoad.isEmpty())
184             return;
185         String url = stripLeadingAndTrailingHTMLSpaces(value);
186         if (url.isEmpty())
187             return;
188         m_urlToLoad = url;
189     }
190
191     const String& charset() const
192     {
193         // FIXME: Its not clear that this if is needed, the loader probably ignores charset for image requests anyway.
194         if (m_tagId == TagId::Img)
195             return emptyString();
196         return m_charset;
197     }
198
199     CachedResource::Type resourceType() const
200     {
201         if (m_tagId == TagId::Script)
202             return CachedResource::Script;
203         if (m_tagId == TagId::Img || (m_tagId == TagId::Input && m_inputIsImage))
204             return CachedResource::ImageResource;
205         if (m_tagId == TagId::Link && m_linkIsStyleSheet)
206             return CachedResource::CSSStyleSheet;
207         ASSERT_NOT_REACHED();
208         return CachedResource::RawResource;
209     }
210
211     bool shouldPreload()
212     {
213         if (m_urlToLoad.isEmpty())
214             return false;
215
216         if (m_tagId == TagId::Link && !m_linkIsStyleSheet)
217             return false;
218
219         if (m_tagId == TagId::Input && !m_inputIsImage)
220             return false;
221
222         return true;
223     }
224
225     bool crossOriginModeAllowsCookies()
226     {
227         return m_crossOriginMode.isNull() || equalIgnoringCase(m_crossOriginMode, "use-credentials");
228     }
229
230     TagId m_tagId;
231     String m_urlToLoad;
232     String m_srcSetAttribute;
233 #if ENABLE(PICTURE_SIZES)
234     String m_sizesAttribute;
235 #endif
236     String m_charset;
237     String m_crossOriginMode;
238     bool m_linkIsStyleSheet;
239     String m_mediaAttribute;
240     bool m_inputIsImage;
241     float m_deviceScaleFactor;
242 };
243
244 TokenPreloadScanner::TokenPreloadScanner(const URL& documentURL, float deviceScaleFactor)
245     : m_documentURL(documentURL)
246     , m_inStyle(false)
247     , m_deviceScaleFactor(deviceScaleFactor)
248 #if ENABLE(TEMPLATE_ELEMENT)
249     , m_templateCount(0)
250 #endif
251 {
252 }
253
254 TokenPreloadScanner::~TokenPreloadScanner()
255 {
256 }
257
258 TokenPreloadScannerCheckpoint TokenPreloadScanner::createCheckpoint()
259 {
260     TokenPreloadScannerCheckpoint checkpoint = m_checkpoints.size();
261     m_checkpoints.append(Checkpoint(m_predictedBaseElementURL, m_inStyle
262 #if ENABLE(TEMPLATE_ELEMENT)
263                                     , m_templateCount
264 #endif
265                                     ));
266     return checkpoint;
267 }
268
269 void TokenPreloadScanner::rewindTo(TokenPreloadScannerCheckpoint checkpointIndex)
270 {
271     ASSERT(checkpointIndex < m_checkpoints.size()); // If this ASSERT fires, checkpointIndex is invalid.
272     const Checkpoint& checkpoint = m_checkpoints[checkpointIndex];
273     m_predictedBaseElementURL = checkpoint.predictedBaseElementURL;
274     m_inStyle = checkpoint.inStyle;
275 #if ENABLE(TEMPLATE_ELEMENT)
276     m_templateCount = checkpoint.templateCount;
277 #endif
278     m_cssScanner.reset();
279     m_checkpoints.clear();
280 }
281
282 void TokenPreloadScanner::scan(const HTMLToken& token, Vector<std::unique_ptr<PreloadRequest>>& requests
283 #if ENABLE(PICTURE_SIZES)
284         , RenderView* view, Frame* frame
285 #endif
286         )
287 {
288     switch (token.type()) {
289     case HTMLToken::Character:
290         if (!m_inStyle)
291             return;
292         m_cssScanner.scan(token.data(), requests);
293         return;
294
295     case HTMLToken::EndTag: {
296         TagId tagId = tagIdFor(token.data());
297 #if ENABLE(TEMPLATE_ELEMENT)
298         if (tagId == TagId::Template) {
299             if (m_templateCount)
300                 --m_templateCount;
301             return;
302         }
303 #endif
304         if (tagId == TagId::Style) {
305             if (m_inStyle)
306                 m_cssScanner.reset();
307             m_inStyle = false;
308         }
309         return;
310     }
311
312     case HTMLToken::StartTag: {
313 #if ENABLE(TEMPLATE_ELEMENT)
314         if (m_templateCount)
315             return;
316 #endif
317         TagId tagId = tagIdFor(token.data());
318 #if ENABLE(TEMPLATE_ELEMENT)
319         if (tagId == TagId::Template) {
320             ++m_templateCount;
321             return;
322         }
323 #endif
324         if (tagId == TagId::Style) {
325             m_inStyle = true;
326             return;
327         }
328         if (tagId == TagId::Base) {
329             // The first <base> element is the one that wins.
330             if (!m_predictedBaseElementURL.isEmpty())
331                 return;
332             updatePredictedBaseURL(token);
333             return;
334         }
335
336         StartTagScanner scanner(tagId, m_deviceScaleFactor);
337         scanner.processAttributes(token.attributes()
338 #if ENABLE(PICTURE_SIZES)
339             , view, frame
340 #endif
341             );
342         if (auto request = scanner.createPreloadRequest(m_predictedBaseElementURL))
343             requests.append(WTF::move(request));
344         return;
345     }
346
347     default:
348         return;
349     }
350 }
351
352 template<typename Token>
353 void TokenPreloadScanner::updatePredictedBaseURL(const Token& token)
354 {
355     ASSERT(m_predictedBaseElementURL.isEmpty());
356     if (const typename Token::Attribute* hrefAttribute = token.getAttributeItem(hrefAttr))
357         m_predictedBaseElementURL = URL(m_documentURL, stripLeadingAndTrailingHTMLSpaces(hrefAttribute->value)).copy();
358 }
359
360 HTMLPreloadScanner::HTMLPreloadScanner(const HTMLParserOptions& options, const URL& documentURL, float deviceScaleFactor)
361     : m_scanner(documentURL, deviceScaleFactor)
362     , m_tokenizer(std::make_unique<HTMLTokenizer>(options))
363 {
364 }
365
366 HTMLPreloadScanner::~HTMLPreloadScanner()
367 {
368 }
369
370 void HTMLPreloadScanner::appendToEnd(const SegmentedString& source)
371 {
372     m_source.append(source);
373 }
374
375 void HTMLPreloadScanner::scan(HTMLResourcePreloader* preloader, const URL& startingBaseElementURL
376 #if ENABLE(PICTURE_SIZES)
377         , RenderView* view, Frame* frame
378 #endif
379         )
380 {
381     ASSERT(isMainThread()); // HTMLTokenizer::updateStateFor only works on the main thread.
382
383     // When we start scanning, our best prediction of the baseElementURL is the real one!
384     if (!startingBaseElementURL.isEmpty())
385         m_scanner.setPredictedBaseElementURL(startingBaseElementURL);
386
387     PreloadRequestStream requests;
388
389     while (m_tokenizer->nextToken(m_source, m_token)) {
390         if (m_token.type() == HTMLToken::StartTag)
391             m_tokenizer->updateStateFor(AtomicString(m_token.name()));
392         m_scanner.scan(m_token, requests
393 #if ENABLE(PICTURE_SIZES)
394             , view, frame
395 #endif
396             );
397         m_token.clear();
398     }
399
400     preloader->preload(WTF::move(requests));
401 }
402
403 }