Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / parser / HTMLSrcsetParser.cpp
index 4313390..05b6ee6 100644 (file)
 #include "config.h"
 #include "core/html/parser/HTMLSrcsetParser.h"
 
+#include "core/dom/Document.h"
+#include "core/fetch/MemoryCache.h"
+#include "core/fetch/ResourceFetcher.h"
+#include "core/frame/FrameConsole.h"
+#include "core/frame/LocalFrame.h"
+#include "core/frame/UseCounter.h"
 #include "core/html/parser/HTMLParserIdioms.h"
+#include "core/inspector/ConsoleMessage.h"
 #include "platform/ParsingUtilities.h"
 #include "platform/RuntimeEnabledFeatures.h"
 
@@ -44,7 +51,7 @@ static bool compareByDensity(const ImageCandidate& first, const ImageCandidate&
 }
 
 enum DescriptorTokenizerState {
-    Start,
+    TokenStart,
     InParenthesis,
     AfterToken,
 };
@@ -67,13 +74,31 @@ struct DescriptorToken {
     template<typename CharType>
     int toInt(const CharType* attribute, bool& isValid)
     {
-        return charactersToIntStrict(attribute + start, length - 1, &isValid);
+        unsigned position = 0;
+        // Make sure the integer is a valid non-negative integer
+        // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-non-negative-integer
+        unsigned lengthExcludingDescriptor = length - 1;
+        while (position < lengthExcludingDescriptor) {
+            if (!isASCIIDigit(*(attribute + start + position))) {
+                isValid = false;
+                return 0;
+            }
+            ++position;
+        }
+        return charactersToIntStrict(attribute + start, lengthExcludingDescriptor, &isValid);
     }
 
     template<typename CharType>
     float toFloat(const CharType* attribute, bool& isValid)
     {
-        return charactersToFloat(attribute + start, length - 1, &isValid);
+        // Make sure the is a valid floating point number
+        // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number
+        unsigned lengthExcludingDescriptor = length - 1;
+        if (lengthExcludingDescriptor > 0 && *(attribute + start) == '+') {
+            isValid = false;
+            return 0;
+        }
+        return charactersToFloat(attribute + start, lengthExcludingDescriptor, &isValid);
     }
 };
 
@@ -106,12 +131,12 @@ static void tokenizeDescriptors(const CharType* attributeStart,
     const CharType* attributeEnd,
     Vector<DescriptorToken>& descriptors)
 {
-    DescriptorTokenizerState state = Start;
+    DescriptorTokenizerState state = TokenStart;
     const CharType* descriptorsStart = position;
     const CharType* currentDescriptorStart = descriptorsStart;
     while (true) {
         switch (state) {
-        case Start:
+        case TokenStart:
             if (isEOF(position, attributeEnd)) {
                 appendDescriptorAndReset(attributeStart, currentDescriptorStart, attributeEnd, descriptors);
                 return;
@@ -139,7 +164,7 @@ static void tokenizeDescriptors(const CharType* attributeStart,
             }
             if (*position == ')') {
                 appendCharacter(currentDescriptorStart, position);
-                state = Start;
+                state = TokenStart;
             } else {
                 appendCharacter(currentDescriptorStart, position);
             }
@@ -148,7 +173,7 @@ static void tokenizeDescriptors(const CharType* attributeStart,
             if (isEOF(position, attributeEnd))
                 return;
             if (!isHTMLSpace(*position)) {
-                state = Start;
+                state = TokenStart;
                 currentDescriptorStart = position;
                 --position;
             }
@@ -158,69 +183,97 @@ static void tokenizeDescriptors(const CharType* attributeStart,
     }
 }
 
+static void srcsetError(Document* document, String message)
+{
+    if (document && document->frame()) {
+        StringBuilder errorMessage;
+        errorMessage.append("Failed parsing 'srcset' attribute value since ");
+        errorMessage.append(message);
+        document->frame()->console().addMessage(ConsoleMessage::create(OtherMessageSource, ErrorMessageLevel, errorMessage.toString()));
+    }
+}
+
 template<typename CharType>
-static bool parseDescriptors(const CharType* attribute, Vector<DescriptorToken>& descriptors, DescriptorParsingResult& result)
+static bool parseDescriptors(const CharType* attribute, Vector<DescriptorToken>& descriptors, DescriptorParsingResult& result, Document* document)
 {
-    for (Vector<DescriptorToken>::iterator it = descriptors.begin(); it != descriptors.end(); ++it) {
-        if (it->length == 0)
+    for (DescriptorToken& descriptor : descriptors) {
+        if (descriptor.length == 0)
             continue;
-        CharType c = attribute[it->lastIndex()];
+        CharType c = attribute[descriptor.lastIndex()];
         bool isValid = false;
         if (RuntimeEnabledFeatures::pictureSizesEnabled() && c == 'w') {
-            if (result.hasDensity() || result.hasWidth())
+            if (result.hasDensity() || result.hasWidth()) {
+                srcsetError(document, "it has multiple 'w' descriptors or a mix of 'x' and 'w' descriptors.");
                 return false;
-            int resourceWidth = it->toInt(attribute, isValid);
-            if (!isValid || resourceWidth <= 0)
+            }
+            int resourceWidth = descriptor.toInt(attribute, isValid);
+            if (!isValid || resourceWidth <= 0) {
+                srcsetError(document, "its 'w' descriptor is invalid.");
                 return false;
+            }
             result.setResourceWidth(resourceWidth);
         } else if (RuntimeEnabledFeatures::pictureSizesEnabled() && c == 'h') {
             // This is here only for future compat purposes.
             // The value of the 'h' descriptor is not used.
-            if (result.hasDensity() || result.hasHeight())
+            if (result.hasDensity() || result.hasHeight()) {
+                srcsetError(document, "it has multiple 'h' descriptors or a mix of 'x' and 'h' descriptors.");
                 return false;
-            int resourceHeight = it->toInt(attribute, isValid);
-            if (!isValid || resourceHeight <= 0)
+            }
+            int resourceHeight = descriptor.toInt(attribute, isValid);
+            if (!isValid || resourceHeight <= 0) {
+                srcsetError(document, "its 'h' descriptor is invalid.");
                 return false;
+            }
             result.setResourceHeight(resourceHeight);
         } else if (c == 'x') {
-            if (result.hasDensity() || result.hasHeight() || result.hasWidth())
+            if (result.hasDensity() || result.hasHeight() || result.hasWidth()) {
+                srcsetError(document, "it has multiple 'x' descriptors or a mix of 'x' and 'w'/'h' descriptors.");
                 return false;
-            float density = it->toFloat(attribute, isValid);
-            if (!isValid || density < 0)
+            }
+            float density = descriptor.toFloat(attribute, isValid);
+            if (!isValid || density < 0) {
+                srcsetError(document, "its 'x' descriptor is invalid.");
                 return false;
+            }
             result.setDensity(density);
+        } else {
+            srcsetError(document, "it has an unknown descriptor.");
+            return false;
         }
     }
-    return true;
+    bool res = !result.hasHeight() || result.hasWidth();
+    if (!res)
+        srcsetError(document, "it has an 'h' descriptor and no 'w' descriptor.");
+    return res;
 }
 
-static bool parseDescriptors(const String& attribute, Vector<DescriptorToken>& descriptors, DescriptorParsingResult& result)
+static bool parseDescriptors(const String& attribute, Vector<DescriptorToken>& descriptors, DescriptorParsingResult& result, Document* document)
 {
     // FIXME: See if StringView can't be extended to replace DescriptorToken here.
     if (attribute.is8Bit()) {
-        return parseDescriptors(attribute.characters8(), descriptors, result);
+        return parseDescriptors(attribute.characters8(), descriptors, result, document);
     }
-    return parseDescriptors(attribute.characters16(), descriptors, result);
+    return parseDescriptors(attribute.characters16(), descriptors, result, document);
 }
 
 // http://picture.responsiveimages.org/#parse-srcset-attr
 template<typename CharType>
-static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, const CharType* attributeStart, unsigned length, Vector<ImageCandidate>& imageCandidates)
+static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, const CharType* attributeStart, unsigned length, Vector<ImageCandidate>& imageCandidates, Document* document)
 {
     const CharType* position = attributeStart;
     const CharType* attributeEnd = position + length;
 
     while (position < attributeEnd) {
         // 4. Splitting loop: Collect a sequence of characters that are space characters or U+002C COMMA characters.
-        skipWhile<CharType, isHTMLSpaceOrComma<CharType> >(position, attributeEnd);
+        skipWhile<CharType, isHTMLSpaceOrComma<CharType>>(position, attributeEnd);
         if (position == attributeEnd) {
             // Contrary to spec language - descriptor parsing happens on each candidate, so when we reach the attributeEnd, we can exit.
             break;
         }
         const CharType* imageURLStart = position;
-        // 6. Collect a sequence of characters that are not space characters, and let that be url.
 
-        skipUntil<CharType, isHTMLSpace<CharType> >(position, attributeEnd);
+        // 6. Collect a sequence of characters that are not space characters, and let that be url.
+        skipUntil<CharType, isHTMLSpace<CharType>>(position, attributeEnd);
         const CharType* imageURLEnd = position;
 
         DescriptorParsingResult result;
@@ -235,15 +288,19 @@ static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, con
             if (imageURLStart == imageURLEnd)
                 continue;
         } else {
-            // Advancing position here (contrary to spec) to avoid an useless extra state machine step.
-            // Filed a spec bug: https://github.com/ResponsiveImagesCG/picture-element/issues/189
-            ++position;
+            skipWhile<CharType, isHTMLSpace<CharType> >(position, attributeEnd);
             Vector<DescriptorToken> descriptorTokens;
             tokenizeDescriptors(attributeStart, position, attributeEnd, descriptorTokens);
             // Contrary to spec language - descriptor parsing happens on each candidate.
             // This is a black-box equivalent, to avoid storing descriptor lists for each candidate.
-            if (!parseDescriptors(attribute, descriptorTokens, result))
+            if (!parseDescriptors(attribute, descriptorTokens, result, document)) {
+                if (document) {
+                    UseCounter::count(document, UseCounter::SrcsetDroppedCandidate);
+                    if (document->frame())
+                        document->frame()->console().addMessage(ConsoleMessage::create(OtherMessageSource, ErrorMessageLevel, String("Dropped srcset candidate ") + String(imageURLStart, imageURLEnd - imageURLStart)));
+                }
                 continue;
+            }
         }
 
         ASSERT(imageURLEnd > attributeStart);
@@ -255,18 +312,63 @@ static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, con
     }
 }
 
-static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, Vector<ImageCandidate>& imageCandidates)
+static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, Vector<ImageCandidate>& imageCandidates, Document* document)
 {
     if (attribute.isNull())
         return;
 
     if (attribute.is8Bit())
-        parseImageCandidatesFromSrcsetAttribute<LChar>(attribute, attribute.characters8(), attribute.length(), imageCandidates);
+        parseImageCandidatesFromSrcsetAttribute<LChar>(attribute, attribute.characters8(), attribute.length(), imageCandidates, document);
     else
-        parseImageCandidatesFromSrcsetAttribute<UChar>(attribute, attribute.characters16(), attribute.length(), imageCandidates);
+        parseImageCandidatesFromSrcsetAttribute<UChar>(attribute, attribute.characters16(), attribute.length(), imageCandidates, document);
 }
 
-static ImageCandidate pickBestImageCandidate(float deviceScaleFactor, unsigned sourceSize, Vector<ImageCandidate>& imageCandidates)
+static int selectionLogic(Vector<ImageCandidate>& imageCandidates, float deviceScaleFactor, bool ignoreSrc)
+{
+    unsigned i = 0;
+
+    for (; i < imageCandidates.size() - 1; ++i) {
+        unsigned next = i + 1;
+        float nextDensity;
+        float currentDensity;
+        float geometricMean;
+        if (ignoreSrc) {
+            if (imageCandidates[i].srcOrigin())
+                continue;
+            if (imageCandidates[next].srcOrigin()) {
+                ++next;
+                if (next >= imageCandidates.size())
+                    break;
+                ASSERT(!imageCandidates[next].srcOrigin());
+            }
+        }
+
+        nextDensity = imageCandidates[next].density();
+        if (nextDensity < deviceScaleFactor)
+            continue;
+
+        currentDensity = imageCandidates[i].density();
+        geometricMean = sqrt(currentDensity * nextDensity);
+        if (deviceScaleFactor >= geometricMean)
+            return next;
+        break;
+    }
+    return i;
+}
+
+static unsigned avoidDownloadIfHigherDensityResourceIsInCache(Vector<ImageCandidate>& imageCandidates, unsigned winner, Document* document)
+{
+    if (!document)
+        return winner;
+    for (unsigned i = imageCandidates.size() - 1; i > winner; --i) {
+        KURL url = document->completeURL(stripLeadingAndTrailingHTMLSpaces(imageCandidates[i].url()));
+        if (memoryCache()->resourceForURL(url, document->fetcher()->getCacheIdentifier()))
+            return i;
+    }
+    return winner;
+}
+
+static ImageCandidate pickBestImageCandidate(float deviceScaleFactor, float sourceSize, Vector<ImageCandidate>& imageCandidates, Document* document = nullptr)
 {
     const float defaultDensityValue = 1.0;
     bool ignoreSrc = false;
@@ -274,48 +376,40 @@ static ImageCandidate pickBestImageCandidate(float deviceScaleFactor, unsigned s
         return ImageCandidate();
 
     // http://picture.responsiveimages.org/#normalize-source-densities
-    for (Vector<ImageCandidate>::iterator it = imageCandidates.begin(); it != imageCandidates.end(); ++it) {
-        if (it->resourceWidth() > 0) {
-            it->setDensity((float)it->resourceWidth() / (float)sourceSize);
+    for (ImageCandidate& image : imageCandidates) {
+        if (image.resourceWidth() > 0) {
+            image.setDensity((float)image.resourceWidth() / sourceSize);
             ignoreSrc = true;
-        } else if (it->density() < 0) {
-            it->setDensity(defaultDensityValue);
+        } else if (image.density() < 0) {
+            image.setDensity(defaultDensityValue);
         }
     }
 
     std::stable_sort(imageCandidates.begin(), imageCandidates.end(), compareByDensity);
 
-    unsigned i;
-    for (i = 0; i < imageCandidates.size() - 1; ++i) {
-        if ((imageCandidates[i].density() >= deviceScaleFactor) && (!ignoreSrc || !imageCandidates[i].srcOrigin()))
-            break;
-    }
-
-    if (imageCandidates[i].srcOrigin() && ignoreSrc) {
-        ASSERT(i > 0);
-        --i;
-    }
-    float winningDensity = imageCandidates[i].density();
+    unsigned winner = selectionLogic(imageCandidates, deviceScaleFactor, ignoreSrc);
+    ASSERT(winner < imageCandidates.size());
+    winner = avoidDownloadIfHigherDensityResourceIsInCache(imageCandidates, winner, document);
 
-    unsigned winner = i;
+    float winningDensity = imageCandidates[winner].density();
     // 16. If an entry b in candidates has the same associated ... pixel density as an earlier entry a in candidates,
     // then remove entry b
-    while ((i > 0) && (imageCandidates[--i].density() == winningDensity))
-        winner = i;
+    while ((winner > 0) && (imageCandidates[winner - 1].density() == winningDensity))
+        --winner;
 
     return imageCandidates[winner];
 }
 
-ImageCandidate bestFitSourceForSrcsetAttribute(float deviceScaleFactor, unsigned sourceSize, const String& srcsetAttribute)
+ImageCandidate bestFitSourceForSrcsetAttribute(float deviceScaleFactor, float sourceSize, const String& srcsetAttribute, Document* document)
 {
     Vector<ImageCandidate> imageCandidates;
 
-    parseImageCandidatesFromSrcsetAttribute(srcsetAttribute, imageCandidates);
+    parseImageCandidatesFromSrcsetAttribute(srcsetAttribute, imageCandidates, document);
 
-    return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates);
+    return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates, document);
 }
 
-ImageCandidate bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned sourceSize, const String& srcAttribute, const String& srcsetAttribute)
+ImageCandidate bestFitSourceForImageAttributes(float deviceScaleFactor, float sourceSize, const String& srcAttribute, const String& srcsetAttribute, Document* document)
 {
     if (srcsetAttribute.isNull()) {
         if (srcAttribute.isNull())
@@ -325,15 +419,15 @@ ImageCandidate bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned
 
     Vector<ImageCandidate> imageCandidates;
 
-    parseImageCandidatesFromSrcsetAttribute(srcsetAttribute, imageCandidates);
+    parseImageCandidatesFromSrcsetAttribute(srcsetAttribute, imageCandidates, document);
 
     if (!srcAttribute.isEmpty())
         imageCandidates.append(ImageCandidate(srcAttribute, 0, srcAttribute.length(), DescriptorParsingResult(), ImageCandidate::SrcOrigin));
 
-    return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates);
+    return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates, document);
 }
 
-String bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned sourceSize, const String& srcAttribute, ImageCandidate& srcsetImageCandidate)
+String bestFitSourceForImageAttributes(float deviceScaleFactor, float sourceSize, const String& srcAttribute, ImageCandidate& srcsetImageCandidate)
 {
     if (srcsetImageCandidate.isEmpty())
         return srcAttribute;