Replace WTF::move with WTFMove
[WebKit-https.git] / Source / WebCore / html / parser / HTMLPreloadScanner.cpp
index 9654c8d..4f15ef9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ * Copyright (C) 2008, 2014 Apple Inc. All Rights Reserved.
  * Copyright (C) 2009 Torch Mobile, Inc. http://www.torchmobile.com/
  * Copyright (C) 2010 Google Inc. All Rights Reserved.
  *
 #include "config.h"
 #include "HTMLPreloadScanner.h"
 
-#include "CachedResourceLoader.h"
-#include "Document.h"
-#include "HTMLDocumentParser.h"
-#include "HTMLTokenizer.h"
 #include "HTMLNames.h"
 #include "HTMLParserIdioms.h"
+#include "HTMLSrcsetParser.h"
+#include "HTMLTokenizer.h"
 #include "InputTypeNames.h"
 #include "LinkRelAttribute.h"
-#include "MediaList.h"
-#include "MediaQueryEvaluator.h"
+#include "SourceSizeList.h"
+#include <wtf/MainThread.h>
 
 namespace WebCore {
 
 using namespace HTMLNames;
 
-class PreloadTask {
+TokenPreloadScanner::TagId TokenPreloadScanner::tagIdFor(const HTMLToken::DataVector& data)
+{
+    AtomicString tagName(data);
+    if (tagName == iframeTag)
+        return TagId::Iframe;
+    if (tagName == imgTag)
+        return TagId::Img;
+    if (tagName == inputTag)
+        return TagId::Input;
+    if (tagName == linkTag)
+        return TagId::Link;
+    if (tagName == scriptTag)
+        return TagId::Script;
+    if (tagName == styleTag)
+        return TagId::Style;
+    if (tagName == baseTag)
+        return TagId::Base;
+    if (tagName == templateTag)
+        return TagId::Template;
+    if (tagName == metaTag)
+        return TagId::Meta;
+    return TagId::Unknown;
+}
+
+String TokenPreloadScanner::initiatorFor(TagId tagId)
+{
+    switch (tagId) {
+    case TagId::Iframe:
+        return "iframe";
+    case TagId::Img:
+        return "img";
+    case TagId::Input:
+        return "input";
+    case TagId::Link:
+        return "link";
+    case TagId::Script:
+        return "script";
+    case TagId::Unknown:
+    case TagId::Style:
+    case TagId::Base:
+    case TagId::Template:
+    case TagId::Meta:
+        ASSERT_NOT_REACHED();
+        return "unknown";
+    }
+    ASSERT_NOT_REACHED();
+    return "unknown";
+}
+
+class TokenPreloadScanner::StartTagScanner {
 public:
-    explicit PreloadTask(const HTMLToken& token)
-        : m_tagName(token.name().data(), token.name().size())
+    explicit StartTagScanner(TagId tagId, float deviceScaleFactor = 1.0)
+        : m_tagId(tagId)
         , m_linkIsStyleSheet(false)
-        , m_linkMediaAttributeIsScreen(true)
+        , m_metaIsViewport(false)
         , m_inputIsImage(false)
+        , m_deviceScaleFactor(deviceScaleFactor)
     {
-        processAttributes(token.attributes());
     }
 
-    void processAttributes(const HTMLToken::AttributeList& attributes)
+    void processAttributes(const HTMLToken::AttributeList& attributes, Document& document)
     {
-        if (m_tagName != imgTag
-            && m_tagName != inputTag
-            && m_tagName != linkTag
-            && m_tagName != scriptTag
-            && m_tagName != baseTag)
+        ASSERT(isMainThread());
+        if (m_tagId >= TagId::Unknown)
             return;
 
-        for (HTMLToken::AttributeList::const_iterator iter = attributes.begin();
-             iter != attributes.end(); ++iter) {
-            AtomicString attributeName(iter->m_name.data(), iter->m_name.size());
-            String attributeValue = StringImpl::create8BitIfPossible(iter->m_value.data(), iter->m_value.size());
+        for (auto& attribute : attributes) {
+            AtomicString attributeName(attribute.name);
+            String attributeValue = StringImpl::create8BitIfPossible(attribute.value);
+            processAttribute(attributeName, attributeValue);
+        }
 
-            if (attributeName == charsetAttr)
-                m_charset = attributeValue;
+        // Resolve between src and srcSet if we have them and the tag is img.
+        if (m_tagId == TagId::Img && !m_srcSetAttribute.isEmpty()) {
+            float sourceSize = 0;
+            sourceSize = parseSizesAttribute(m_sizesAttribute, document.renderView(), document.frame());
+            ImageCandidate imageCandidate = bestFitSourceForImageAttributes(m_deviceScaleFactor, m_urlToLoad, m_srcSetAttribute, sourceSize);
+            setUrlToLoad(imageCandidate.string.toString(), true);
+        }
+
+        if (m_metaIsViewport && !m_metaContent.isNull())
+            document.processViewport(m_metaContent, ViewportArguments::ViewportMeta);
+    }
+
+    std::unique_ptr<PreloadRequest> createPreloadRequest(const URL& predictedBaseURL)
+    {
+        if (!shouldPreload())
+            return nullptr;
+
+        auto request = std::make_unique<PreloadRequest>(initiatorFor(m_tagId), m_urlToLoad, predictedBaseURL, resourceType(), m_mediaAttribute);
+
+        request->setCrossOriginModeAllowsCookies(crossOriginModeAllowsCookies());
+        request->setCharset(charset());
+        return request;
+    }
+
+    static bool match(const AtomicString& name, const QualifiedName& qName)
+    {
+        ASSERT(isMainThread());
+        return qName.localName() == name;
+    }
 
-            if (m_tagName == scriptTag || m_tagName == imgTag) {
-                if (attributeName == srcAttr)
-                    setUrlToLoad(attributeValue);
-            } else if (m_tagName == linkTag) {
-                if (attributeName == hrefAttr)
-                    setUrlToLoad(attributeValue);
-                else if (attributeName == relAttr)
-                    m_linkIsStyleSheet = relAttributeIsStyleSheet(attributeValue);
-                else if (attributeName == mediaAttr)
-                    m_linkMediaAttributeIsScreen = linkMediaAttributeIsScreen(attributeValue);
-            } else if (m_tagName == inputTag) {
-                if (attributeName == srcAttr)
-                    setUrlToLoad(attributeValue);
-                else if (attributeName == typeAttr)
-                    m_inputIsImage = equalIgnoringCase(attributeValue, InputTypeNames::image());
-            } else if (m_tagName == baseTag) {
-                if (attributeName == hrefAttr)
-                    m_baseElementHref = stripLeadingAndTrailingHTMLSpaces(attributeValue);
+private:
+    void processImageAndScriptAttribute(const AtomicString& attributeName, const String& attributeValue)
+    {
+        if (match(attributeName, srcAttr))
+            setUrlToLoad(attributeValue);
+        else if (match(attributeName, crossoriginAttr) && !attributeValue.isNull())
+            m_crossOriginMode = stripLeadingAndTrailingHTMLSpaces(attributeValue);
+        else if (match(attributeName, charsetAttr))
+            m_charset = attributeValue;
+    }
+
+    void processAttribute(const AtomicString& attributeName, const String& attributeValue)
+    {
+        switch (m_tagId) {
+        case TagId::Iframe:
+            if (match(attributeName, srcAttr))
+                setUrlToLoad(attributeValue);
+            break;
+        case TagId::Img:
+            if (match(attributeName, srcsetAttr) && m_srcSetAttribute.isNull()) {
+                m_srcSetAttribute = attributeValue;
+                break;
+            }
+            if (match(attributeName, sizesAttr) && m_sizesAttribute.isNull()) {
+                m_sizesAttribute = attributeValue;
+                break;
             }
+            processImageAndScriptAttribute(attributeName, attributeValue);
+            break;
+        case TagId::Script:
+            processImageAndScriptAttribute(attributeName, attributeValue);
+            break;
+        case TagId::Link:
+            if (match(attributeName, hrefAttr))
+                setUrlToLoad(attributeValue);
+            else if (match(attributeName, relAttr))
+                m_linkIsStyleSheet = relAttributeIsStyleSheet(attributeValue);
+            else if (match(attributeName, mediaAttr))
+                m_mediaAttribute = attributeValue;
+            else if (match(attributeName, charsetAttr))
+                m_charset = attributeValue;
+            break;
+        case TagId::Input:
+            if (match(attributeName, srcAttr))
+                setUrlToLoad(attributeValue);
+            else if (match(attributeName, typeAttr))
+                m_inputIsImage = equalIgnoringCase(attributeValue, InputTypeNames::image());
+            break;
+        case TagId::Meta:
+            if (match(attributeName, contentAttr))
+                m_metaContent = attributeValue;
+            else if (match(attributeName, nameAttr))
+                m_metaIsViewport = equalIgnoringCase(attributeValue, "viewport");
+            break;
+        case TagId::Base:
+        case TagId::Style:
+        case TagId::Template:
+        case TagId::Unknown:
+            break;
         }
     }
 
     static bool relAttributeIsStyleSheet(const String& attributeValue)
     {
-        LinkRelAttribute rel(attributeValue);
-        return rel.m_isStyleSheet && !rel.m_isAlternate && rel.m_iconType == InvalidIcon && !rel.m_isDNSPrefetch;
+        LinkRelAttribute parsedAttribute { attributeValue };
+        return parsedAttribute.isStyleSheet && !parsedAttribute.isAlternate && parsedAttribute.iconType == InvalidIcon && !parsedAttribute.isDNSPrefetch;
     }
 
-    static bool linkMediaAttributeIsScreen(const String& attributeValue)
-    {
-        if (attributeValue.isEmpty())
-            return true;
-        RefPtr<MediaQuerySet> mediaQueries = MediaQuerySet::createAllowingDescriptionSyntax(attributeValue);
-    
-        // Only preload screen media stylesheets. Used this way, the evaluator evaluates to true for any 
-        // rules containing complex queries (full evaluation is possible but it requires a frame and a style selector which
-        // may be problematic here).
-        MediaQueryEvaluator mediaQueryEvaluator("screen");
-        return mediaQueryEvaluator.eval(mediaQueries.get());
-    }
-
-    void setUrlToLoad(const String& attributeValue)
+    void setUrlToLoad(const String& value, bool allowReplacement = false)
     {
         // We only respect the first src/href, per HTML5:
         // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#attribute-name-state
-        if (!m_urlToLoad.isEmpty())
+        if (!allowReplacement && !m_urlToLoad.isEmpty())
+            return;
+        String url = stripLeadingAndTrailingHTMLSpaces(value);
+        if (url.isEmpty())
             return;
-        m_urlToLoad = stripLeadingAndTrailingHTMLSpaces(attributeValue);
+        m_urlToLoad = url;
+    }
+
+    const String& charset() const
+    {
+        return m_charset;
+    }
+
+    CachedResource::Type resourceType() const
+    {
+        switch (m_tagId) {
+        case TagId::Iframe:
+            return CachedResource::MainResource;
+        case TagId::Script:
+            return CachedResource::Script;
+        case TagId::Img:
+        case TagId::Input:
+            ASSERT(m_tagId != TagId::Input || m_inputIsImage);
+            return CachedResource::ImageResource;
+        case TagId::Link:
+            ASSERT(m_linkIsStyleSheet);
+            return CachedResource::CSSStyleSheet;
+        case TagId::Meta:
+        case TagId::Unknown:
+        case TagId::Style:
+        case TagId::Base:
+        case TagId::Template:
+            break;
+        }
+        ASSERT_NOT_REACHED();
+        return CachedResource::RawResource;
     }
 
-    void preload(Document* document, bool scanningBody, const KURL& baseURL)
+    bool shouldPreload()
     {
         if (m_urlToLoad.isEmpty())
-            return;
+            return false;
 
-        CachedResourceLoader* cachedResourceLoader = document->cachedResourceLoader();
-        CachedResourceRequest request(ResourceRequest(document->completeURL(m_urlToLoad, baseURL)));
-        request.setInitiator(tagName(), document);
-        if (m_tagName == scriptTag)
-            cachedResourceLoader->preload(CachedResource::Script, request, m_charset, scanningBody);
-        else if (m_tagName == imgTag || (m_tagName == inputTag && m_inputIsImage))
-            cachedResourceLoader->preload(CachedResource::ImageResource, request, String(), scanningBody);
-        else if (m_tagName == linkTag && m_linkIsStyleSheet && m_linkMediaAttributeIsScreen) 
-            cachedResourceLoader->preload(CachedResource::CSSStyleSheet, request, m_charset, scanningBody);
+        if (protocolIs(m_urlToLoad, "data") || protocolIs(m_urlToLoad, "about"))
+            return false;
+
+        if (m_tagId == TagId::Link && !m_linkIsStyleSheet)
+            return false;
+
+        if (m_tagId == TagId::Input && !m_inputIsImage)
+            return false;
+
+        return true;
     }
 
-    const AtomicString& tagName() const { return m_tagName; }
-    const String& baseElementHref() const { return m_baseElementHref; }
+    bool crossOriginModeAllowsCookies()
+    {
+        return m_crossOriginMode.isNull() || equalIgnoringCase(m_crossOriginMode, "use-credentials");
+    }
 
-private:
-    AtomicString m_tagName;
+    TagId m_tagId;
     String m_urlToLoad;
+    String m_srcSetAttribute;
+    String m_sizesAttribute;
     String m_charset;
-    String m_baseElementHref;
+    String m_crossOriginMode;
     bool m_linkIsStyleSheet;
-    bool m_linkMediaAttributeIsScreen;
+    String m_mediaAttribute;
+    String m_metaContent;
+    bool m_metaIsViewport;
     bool m_inputIsImage;
+    float m_deviceScaleFactor;
 };
 
-HTMLPreloadScanner::HTMLPreloadScanner(Document* document)
-    : m_document(document)
-    , m_cssScanner(document)
-    , m_tokenizer(HTMLTokenizer::create(HTMLDocumentParser::usePreHTML5ParserQuirks(document)))
-    , m_bodySeen(false)
-    , m_inStyle(false)
-{
-}
-
-void HTMLPreloadScanner::appendToEnd(const SegmentedString& source)
+TokenPreloadScanner::TokenPreloadScanner(const URL& documentURL, float deviceScaleFactor)
+    : m_documentURL(documentURL)
+    , m_deviceScaleFactor(deviceScaleFactor)
 {
-    m_source.append(source);
 }
 
-void HTMLPreloadScanner::scan()
+void TokenPreloadScanner::scan(const HTMLToken& token, Vector<std::unique_ptr<PreloadRequest>>& requests, Document& document)
 {
-    // When we start scanning, our best prediction of the baseElementURL is the real one!
-    m_predictedBaseElementURL = m_document->baseElementURL();
-
-    // FIXME: We should save and re-use these tokens in HTMLDocumentParser if
-    // the pending script doesn't end up calling document.write.
-    while (m_tokenizer->nextToken(m_source, m_token)) {
-        processToken();
-        m_token.clear();
-    }
-}
+    switch (token.type()) {
+    case HTMLToken::Character:
+        if (!m_inStyle)
+            return;
+        m_cssScanner.scan(token.characters(), requests);
+        return;
 
-void HTMLPreloadScanner::processToken()
-{
-    if (m_inStyle) {
-        if (m_token.type() == HTMLTokenTypes::Character)
-            m_cssScanner.scan(m_token, scanningBody());
-        else if (m_token.type() == HTMLTokenTypes::EndTag) {
+    case HTMLToken::EndTag: {
+        TagId tagId = tagIdFor(token.name());
+#if ENABLE(TEMPLATE_ELEMENT)
+        if (tagId == TagId::Template) {
+            if (m_templateCount)
+                --m_templateCount;
+            return;
+        }
+#endif
+        if (tagId == TagId::Style) {
+            if (m_inStyle)
+                m_cssScanner.reset();
             m_inStyle = false;
-            m_cssScanner.reset();
         }
+        return;
     }
 
-    if (m_token.type() != HTMLTokenTypes::StartTag)
-        return;
+    case HTMLToken::StartTag: {
+#if ENABLE(TEMPLATE_ELEMENT)
+        if (m_templateCount)
+            return;
+#endif
+        TagId tagId = tagIdFor(token.name());
+#if ENABLE(TEMPLATE_ELEMENT)
+        if (tagId == TagId::Template) {
+            ++m_templateCount;
+            return;
+        }
+#endif
+        if (tagId == TagId::Style) {
+            m_inStyle = true;
+            return;
+        }
+        if (tagId == TagId::Base) {
+            // The first <base> element is the one that wins.
+            if (!m_predictedBaseElementURL.isEmpty())
+                return;
+            updatePredictedBaseURL(token);
+            return;
+        }
 
-    PreloadTask task(m_token);
-    m_tokenizer->updateStateFor(task.tagName(), m_document->frame());
+        StartTagScanner scanner(tagId, m_deviceScaleFactor);
+        scanner.processAttributes(token.attributes(), document);
+        if (auto request = scanner.createPreloadRequest(m_predictedBaseElementURL))
+            requests.append(WTFMove(request));
+        return;
+    }
 
-    if (task.tagName() == bodyTag)
-        m_bodySeen = true;
+    default:
+        return;
+    }
+}
 
-    if (task.tagName() == styleTag)
-        m_inStyle = true;
+void TokenPreloadScanner::updatePredictedBaseURL(const HTMLToken& token)
+{
+    ASSERT(m_predictedBaseElementURL.isEmpty());
+    if (auto* hrefAttribute = findAttribute(token.attributes(), hrefAttr.localName().string()))
+        m_predictedBaseElementURL = URL(m_documentURL, stripLeadingAndTrailingHTMLSpaces(StringImpl::create8BitIfPossible(hrefAttribute->value))).isolatedCopy();
+}
 
-    if (task.tagName() == baseTag)
-        updatePredictedBaseElementURL(KURL(m_document->url(), task.baseElementHref()));
+HTMLPreloadScanner::HTMLPreloadScanner(const HTMLParserOptions& options, const URL& documentURL, float deviceScaleFactor)
+    : m_scanner(documentURL, deviceScaleFactor)
+    , m_tokenizer(options)
+{
+}
 
-    task.preload(m_document, scanningBody(), m_predictedBaseElementURL.isEmpty() ? m_document->baseURL() : m_predictedBaseElementURL);
+void HTMLPreloadScanner::appendToEnd(const SegmentedString& source)
+{
+    m_source.append(source);
 }
 
-bool HTMLPreloadScanner::scanningBody() const
+void HTMLPreloadScanner::scan(HTMLResourcePreloader& preloader, Document& document)
 {
-    return m_document->body() || m_bodySeen;
+    ASSERT(isMainThread()); // HTMLTokenizer::updateStateFor only works on the main thread.
+
+    const URL& startingBaseElementURL = document.baseElementURL();
+
+    // When we start scanning, our best prediction of the baseElementURL is the real one!
+    if (!startingBaseElementURL.isEmpty())
+        m_scanner.setPredictedBaseElementURL(startingBaseElementURL);
+
+    PreloadRequestStream requests;
+
+    while (auto token = m_tokenizer.nextToken(m_source)) {
+        if (token->type() == HTMLToken::StartTag)
+            m_tokenizer.updateStateFor(AtomicString(token->name()));
+        m_scanner.scan(*token, requests, document);
+    }
+
+    preloader.preload(WTFMove(requests));
 }
 
-void HTMLPreloadScanner::updatePredictedBaseElementURL(const KURL& baseElementURL)
+bool testPreloadScannerViewportSupport(Document* document)
 {
-    // The first <base> element is the one that wins.
-    if (!m_predictedBaseElementURL.isEmpty())
-        return;
-    m_predictedBaseElementURL = baseElementURL;
+    ASSERT(document);
+    HTMLParserOptions options(*document);
+    HTMLPreloadScanner scanner(options, document->url());
+    HTMLResourcePreloader preloader(*document);
+    scanner.appendToEnd(String("<meta name=viewport content='width=400'>"));
+    scanner.scan(preloader, *document);
+    return (document->viewportArguments().width == 400);
 }
 
 }