Replace WTF::move with WTFMove
[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 == iframeTag)
48         return TagId::Iframe;
49     if (tagName == imgTag)
50         return TagId::Img;
51     if (tagName == inputTag)
52         return TagId::Input;
53     if (tagName == linkTag)
54         return TagId::Link;
55     if (tagName == scriptTag)
56         return TagId::Script;
57     if (tagName == styleTag)
58         return TagId::Style;
59     if (tagName == baseTag)
60         return TagId::Base;
61     if (tagName == templateTag)
62         return TagId::Template;
63     if (tagName == metaTag)
64         return TagId::Meta;
65     return TagId::Unknown;
66 }
67
68 String TokenPreloadScanner::initiatorFor(TagId tagId)
69 {
70     switch (tagId) {
71     case TagId::Iframe:
72         return "iframe";
73     case TagId::Img:
74         return "img";
75     case TagId::Input:
76         return "input";
77     case TagId::Link:
78         return "link";
79     case TagId::Script:
80         return "script";
81     case TagId::Unknown:
82     case TagId::Style:
83     case TagId::Base:
84     case TagId::Template:
85     case TagId::Meta:
86         ASSERT_NOT_REACHED();
87         return "unknown";
88     }
89     ASSERT_NOT_REACHED();
90     return "unknown";
91 }
92
93 class TokenPreloadScanner::StartTagScanner {
94 public:
95     explicit StartTagScanner(TagId tagId, float deviceScaleFactor = 1.0)
96         : m_tagId(tagId)
97         , m_linkIsStyleSheet(false)
98         , m_metaIsViewport(false)
99         , m_inputIsImage(false)
100         , m_deviceScaleFactor(deviceScaleFactor)
101     {
102     }
103
104     void processAttributes(const HTMLToken::AttributeList& attributes, Document& document)
105     {
106         ASSERT(isMainThread());
107         if (m_tagId >= TagId::Unknown)
108             return;
109
110         for (auto& attribute : attributes) {
111             AtomicString attributeName(attribute.name);
112             String attributeValue = StringImpl::create8BitIfPossible(attribute.value);
113             processAttribute(attributeName, attributeValue);
114         }
115
116         // Resolve between src and srcSet if we have them and the tag is img.
117         if (m_tagId == TagId::Img && !m_srcSetAttribute.isEmpty()) {
118             float sourceSize = 0;
119             sourceSize = parseSizesAttribute(m_sizesAttribute, document.renderView(), document.frame());
120             ImageCandidate imageCandidate = bestFitSourceForImageAttributes(m_deviceScaleFactor, m_urlToLoad, m_srcSetAttribute, sourceSize);
121             setUrlToLoad(imageCandidate.string.toString(), true);
122         }
123
124         if (m_metaIsViewport && !m_metaContent.isNull())
125             document.processViewport(m_metaContent, ViewportArguments::ViewportMeta);
126     }
127
128     std::unique_ptr<PreloadRequest> createPreloadRequest(const URL& predictedBaseURL)
129     {
130         if (!shouldPreload())
131             return nullptr;
132
133         auto request = std::make_unique<PreloadRequest>(initiatorFor(m_tagId), m_urlToLoad, predictedBaseURL, resourceType(), m_mediaAttribute);
134
135         request->setCrossOriginModeAllowsCookies(crossOriginModeAllowsCookies());
136         request->setCharset(charset());
137         return request;
138     }
139
140     static bool match(const AtomicString& name, const QualifiedName& qName)
141     {
142         ASSERT(isMainThread());
143         return qName.localName() == name;
144     }
145
146 private:
147     void processImageAndScriptAttribute(const AtomicString& attributeName, const String& attributeValue)
148     {
149         if (match(attributeName, srcAttr))
150             setUrlToLoad(attributeValue);
151         else if (match(attributeName, crossoriginAttr) && !attributeValue.isNull())
152             m_crossOriginMode = stripLeadingAndTrailingHTMLSpaces(attributeValue);
153         else if (match(attributeName, charsetAttr))
154             m_charset = attributeValue;
155     }
156
157     void processAttribute(const AtomicString& attributeName, const String& attributeValue)
158     {
159         switch (m_tagId) {
160         case TagId::Iframe:
161             if (match(attributeName, srcAttr))
162                 setUrlToLoad(attributeValue);
163             break;
164         case TagId::Img:
165             if (match(attributeName, srcsetAttr) && m_srcSetAttribute.isNull()) {
166                 m_srcSetAttribute = attributeValue;
167                 break;
168             }
169             if (match(attributeName, sizesAttr) && m_sizesAttribute.isNull()) {
170                 m_sizesAttribute = attributeValue;
171                 break;
172             }
173             processImageAndScriptAttribute(attributeName, attributeValue);
174             break;
175         case TagId::Script:
176             processImageAndScriptAttribute(attributeName, attributeValue);
177             break;
178         case TagId::Link:
179             if (match(attributeName, hrefAttr))
180                 setUrlToLoad(attributeValue);
181             else if (match(attributeName, relAttr))
182                 m_linkIsStyleSheet = relAttributeIsStyleSheet(attributeValue);
183             else if (match(attributeName, mediaAttr))
184                 m_mediaAttribute = attributeValue;
185             else if (match(attributeName, charsetAttr))
186                 m_charset = attributeValue;
187             break;
188         case TagId::Input:
189             if (match(attributeName, srcAttr))
190                 setUrlToLoad(attributeValue);
191             else if (match(attributeName, typeAttr))
192                 m_inputIsImage = equalIgnoringCase(attributeValue, InputTypeNames::image());
193             break;
194         case TagId::Meta:
195             if (match(attributeName, contentAttr))
196                 m_metaContent = attributeValue;
197             else if (match(attributeName, nameAttr))
198                 m_metaIsViewport = equalIgnoringCase(attributeValue, "viewport");
199             break;
200         case TagId::Base:
201         case TagId::Style:
202         case TagId::Template:
203         case TagId::Unknown:
204             break;
205         }
206     }
207
208     static bool relAttributeIsStyleSheet(const String& attributeValue)
209     {
210         LinkRelAttribute parsedAttribute { attributeValue };
211         return parsedAttribute.isStyleSheet && !parsedAttribute.isAlternate && parsedAttribute.iconType == InvalidIcon && !parsedAttribute.isDNSPrefetch;
212     }
213
214     void setUrlToLoad(const String& value, bool allowReplacement = false)
215     {
216         // We only respect the first src/href, per HTML5:
217         // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#attribute-name-state
218         if (!allowReplacement && !m_urlToLoad.isEmpty())
219             return;
220         String url = stripLeadingAndTrailingHTMLSpaces(value);
221         if (url.isEmpty())
222             return;
223         m_urlToLoad = url;
224     }
225
226     const String& charset() const
227     {
228         return m_charset;
229     }
230
231     CachedResource::Type resourceType() const
232     {
233         switch (m_tagId) {
234         case TagId::Iframe:
235             return CachedResource::MainResource;
236         case TagId::Script:
237             return CachedResource::Script;
238         case TagId::Img:
239         case TagId::Input:
240             ASSERT(m_tagId != TagId::Input || m_inputIsImage);
241             return CachedResource::ImageResource;
242         case TagId::Link:
243             ASSERT(m_linkIsStyleSheet);
244             return CachedResource::CSSStyleSheet;
245         case TagId::Meta:
246         case TagId::Unknown:
247         case TagId::Style:
248         case TagId::Base:
249         case TagId::Template:
250             break;
251         }
252         ASSERT_NOT_REACHED();
253         return CachedResource::RawResource;
254     }
255
256     bool shouldPreload()
257     {
258         if (m_urlToLoad.isEmpty())
259             return false;
260
261         if (protocolIs(m_urlToLoad, "data") || protocolIs(m_urlToLoad, "about"))
262             return false;
263
264         if (m_tagId == TagId::Link && !m_linkIsStyleSheet)
265             return false;
266
267         if (m_tagId == TagId::Input && !m_inputIsImage)
268             return false;
269
270         return true;
271     }
272
273     bool crossOriginModeAllowsCookies()
274     {
275         return m_crossOriginMode.isNull() || equalIgnoringCase(m_crossOriginMode, "use-credentials");
276     }
277
278     TagId m_tagId;
279     String m_urlToLoad;
280     String m_srcSetAttribute;
281     String m_sizesAttribute;
282     String m_charset;
283     String m_crossOriginMode;
284     bool m_linkIsStyleSheet;
285     String m_mediaAttribute;
286     String m_metaContent;
287     bool m_metaIsViewport;
288     bool m_inputIsImage;
289     float m_deviceScaleFactor;
290 };
291
292 TokenPreloadScanner::TokenPreloadScanner(const URL& documentURL, float deviceScaleFactor)
293     : m_documentURL(documentURL)
294     , m_deviceScaleFactor(deviceScaleFactor)
295 {
296 }
297
298 void TokenPreloadScanner::scan(const HTMLToken& token, Vector<std::unique_ptr<PreloadRequest>>& requests, Document& document)
299 {
300     switch (token.type()) {
301     case HTMLToken::Character:
302         if (!m_inStyle)
303             return;
304         m_cssScanner.scan(token.characters(), requests);
305         return;
306
307     case HTMLToken::EndTag: {
308         TagId tagId = tagIdFor(token.name());
309 #if ENABLE(TEMPLATE_ELEMENT)
310         if (tagId == TagId::Template) {
311             if (m_templateCount)
312                 --m_templateCount;
313             return;
314         }
315 #endif
316         if (tagId == TagId::Style) {
317             if (m_inStyle)
318                 m_cssScanner.reset();
319             m_inStyle = false;
320         }
321         return;
322     }
323
324     case HTMLToken::StartTag: {
325 #if ENABLE(TEMPLATE_ELEMENT)
326         if (m_templateCount)
327             return;
328 #endif
329         TagId tagId = tagIdFor(token.name());
330 #if ENABLE(TEMPLATE_ELEMENT)
331         if (tagId == TagId::Template) {
332             ++m_templateCount;
333             return;
334         }
335 #endif
336         if (tagId == TagId::Style) {
337             m_inStyle = true;
338             return;
339         }
340         if (tagId == TagId::Base) {
341             // The first <base> element is the one that wins.
342             if (!m_predictedBaseElementURL.isEmpty())
343                 return;
344             updatePredictedBaseURL(token);
345             return;
346         }
347
348         StartTagScanner scanner(tagId, m_deviceScaleFactor);
349         scanner.processAttributes(token.attributes(), document);
350         if (auto request = scanner.createPreloadRequest(m_predictedBaseElementURL))
351             requests.append(WTFMove(request));
352         return;
353     }
354
355     default:
356         return;
357     }
358 }
359
360 void TokenPreloadScanner::updatePredictedBaseURL(const HTMLToken& token)
361 {
362     ASSERT(m_predictedBaseElementURL.isEmpty());
363     if (auto* hrefAttribute = findAttribute(token.attributes(), hrefAttr.localName().string()))
364         m_predictedBaseElementURL = URL(m_documentURL, stripLeadingAndTrailingHTMLSpaces(StringImpl::create8BitIfPossible(hrefAttribute->value))).isolatedCopy();
365 }
366
367 HTMLPreloadScanner::HTMLPreloadScanner(const HTMLParserOptions& options, const URL& documentURL, float deviceScaleFactor)
368     : m_scanner(documentURL, deviceScaleFactor)
369     , m_tokenizer(options)
370 {
371 }
372
373 void HTMLPreloadScanner::appendToEnd(const SegmentedString& source)
374 {
375     m_source.append(source);
376 }
377
378 void HTMLPreloadScanner::scan(HTMLResourcePreloader& preloader, Document& document)
379 {
380     ASSERT(isMainThread()); // HTMLTokenizer::updateStateFor only works on the main thread.
381
382     const URL& startingBaseElementURL = document.baseElementURL();
383
384     // When we start scanning, our best prediction of the baseElementURL is the real one!
385     if (!startingBaseElementURL.isEmpty())
386         m_scanner.setPredictedBaseElementURL(startingBaseElementURL);
387
388     PreloadRequestStream requests;
389
390     while (auto token = m_tokenizer.nextToken(m_source)) {
391         if (token->type() == HTMLToken::StartTag)
392             m_tokenizer.updateStateFor(AtomicString(token->name()));
393         m_scanner.scan(*token, requests, document);
394     }
395
396     preloader.preload(WTFMove(requests));
397 }
398
399 bool testPreloadScannerViewportSupport(Document* document)
400 {
401     ASSERT(document);
402     HTMLParserOptions options(*document);
403     HTMLPreloadScanner scanner(options, document->url());
404     HTMLResourcePreloader preloader(*document);
405     scanner.appendToEnd(String("<meta name=viewport content='width=400'>"));
406     scanner.scan(preloader, *document);
407     return (document->viewportArguments().width == 400);
408 }
409
410 }