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