Chromium Code Reviews| Index: Source/core/html/parser/HTMLSrcsetParser.cpp |
| diff --git a/Source/core/html/parser/HTMLSrcsetParser.cpp b/Source/core/html/parser/HTMLSrcsetParser.cpp |
| index 35aa976503af65a7eba548215387d5f12e296021..26f6ca08ddca13811bcf12dd3b0735f085d01090 100644 |
| --- a/Source/core/html/parser/HTMLSrcsetParser.cpp |
| +++ b/Source/core/html/parser/HTMLSrcsetParser.cpp |
| @@ -37,54 +37,154 @@ |
| namespace WebCore { |
| -static bool compareByScaleFactor(const ImageCandidate& first, const ImageCandidate& second) |
| +static bool compareByDensity(const ImageCandidate& first, const ImageCandidate& second) |
| { |
| - return first.scaleFactor() < second.scaleFactor(); |
| + return first.density() < second.density(); |
| } |
| +enum DescriptorTokenizerState { |
| + Start, |
| + InParenthesis, |
| + AfterToken, |
| +}; |
| + |
| +struct DescriptorToken { |
| + unsigned start; |
| + unsigned length; |
| + DescriptorToken(unsigned start, unsigned length) |
| + : start(start) |
| + , length(length) |
| + { |
| + } |
| +}; |
| + |
| +template<typename CharType> |
| +static void appendDescriptorAndReset(const CharType* attributeStart, const CharType*& descriptorStart, const CharType* position, Vector<DescriptorToken>& descriptors) |
| +{ |
| + if (position > descriptorStart) |
| + descriptors.append(DescriptorToken(descriptorStart - attributeStart, position - descriptorStart)); |
| + descriptorStart = 0; |
| +} |
| + |
| +// The following is called appendCharacter to match the spec's terminology. |
| template<typename CharType> |
| -inline bool isComma(CharType character) |
| +static void appendCharacter(const CharType* descriptorStart, const CharType* position) |
| { |
| - return character == ','; |
| + // Since we don't copy the tokens, this just set the point where the descriptor tokens start. |
| + if (!descriptorStart) |
| + descriptorStart = position; |
| } |
| template<typename CharType> |
| -static bool parseDescriptors(const CharType* descriptorsStart, const CharType* descriptorsEnd, DescriptorParsingResult& result) |
| +static bool isEOF(const CharType* position, const CharType* end) |
| { |
| - const CharType* position = descriptorsStart; |
| - bool isValid = false; |
| - bool isEmptyDescriptor = !(descriptorsEnd > descriptorsStart); |
| - while (position < descriptorsEnd) { |
| - // 13.1. Let descriptor list be the result of splitting unparsed descriptors on spaces. |
| - skipWhile<CharType, isHTMLSpace<CharType> >(position, descriptorsEnd); |
| - const CharType* currentDescriptorStart = position; |
| - skipWhile<CharType, isNotHTMLSpace<CharType> >(position, descriptorsEnd); |
| - const CharType* currentDescriptorEnd = position; |
| + return position >= end; |
| +} |
| +template<typename CharType> |
| +static void tokenizeDescriptors(const CharType* attributeStart, |
| + const CharType*& position, |
| + const CharType* attributeEnd, |
| + Vector<DescriptorToken>& descriptors) |
| +{ |
| + DescriptorTokenizerState state = Start; |
| + const CharType* descriptorsStart = position; |
| + const CharType* currentDescriptorStart = descriptorsStart; |
| + while (true) { |
| + switch (state) { |
| + case Start: |
| + if (isEOF(position, attributeEnd)) { |
| + appendDescriptorAndReset(attributeStart, currentDescriptorStart, attributeEnd, descriptors); |
| + return; |
| + } |
| + if (isComma(*position)) { |
| + appendDescriptorAndReset(attributeStart, currentDescriptorStart, position, descriptors); |
| + ++position; |
| + return; |
| + } |
| + if (isHTMLSpace(*position)) { |
| + appendDescriptorAndReset(attributeStart, currentDescriptorStart, position, descriptors); |
| + currentDescriptorStart = position + 1; |
| + state = AfterToken; |
| + } else if (*position == '(') { |
| + appendCharacter(currentDescriptorStart, position); |
| + state = InParenthesis; |
| + } else { |
| + appendCharacter(currentDescriptorStart, position); |
| + } |
| + break; |
| + case InParenthesis: |
| + if (isEOF(position, attributeEnd)) { |
| + appendDescriptorAndReset(attributeStart, currentDescriptorStart, attributeEnd, descriptors); |
| + return; |
| + } |
| + if (*position == ')') { |
| + appendCharacter(currentDescriptorStart, position); |
| + state = Start; |
| + } else { |
| + appendCharacter(currentDescriptorStart, position); |
| + } |
| + break; |
| + case AfterToken: |
| + if (isEOF(position, attributeEnd)) |
| + return; |
| + if (!isHTMLSpace(*position)) { |
| + state = Start; |
| + currentDescriptorStart = position; |
| + --position; |
| + } |
| + break; |
| + } |
| ++position; |
| - ASSERT(currentDescriptorEnd > currentDescriptorStart); |
| - --currentDescriptorEnd; |
| - unsigned descriptorLength = currentDescriptorEnd - currentDescriptorStart; |
| - if (*currentDescriptorEnd == 'x') { |
| - if (result.foundDescriptor()) |
| + } |
| +} |
| + |
| +template<typename CharType> |
| +static bool parseDescriptors(const CharType* attribute, Vector<DescriptorToken>& descriptors, DescriptorParsingResult& result) |
| +{ |
| + for (Vector<DescriptorToken>::iterator it = descriptors.begin(); it != descriptors.end(); ++it) { |
| + if (it->length == 0) |
| + continue; |
| + CharType c = attribute[it->start + it->length - 1]; |
|
eseidel
2014/05/29 06:59:53
Should DescriptorToken have a helper for this valu
|
| + bool isValid = false; |
| + if (RuntimeEnabledFeatures::pictureSizesEnabled() && c == 'w') { |
| + if (result.hasDensity() || result.hasWidth()) |
| return false; |
| - result.scaleFactor = charactersToFloat(currentDescriptorStart, descriptorLength, &isValid); |
| - if (!isValid || result.scaleFactor < 0) |
| + int resourceWidth = charactersToInt(attribute + it->start, it->length - 1, &isValid); |
|
eseidel
2014/05/29 06:59:53
Should charactersToInt be a member on DescriptorTo
|
| + if (!isValid || resourceWidth <= 0) |
| return false; |
| - } else if (RuntimeEnabledFeatures::pictureSizesEnabled() && *currentDescriptorEnd == 'w') { |
| - if (result.foundDescriptor()) |
| + 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()) |
| return false; |
| - result.resourceWidth = charactersToInt(currentDescriptorStart, descriptorLength, &isValid); |
| - if (!isValid || result.resourceWidth <= 0) |
| + int resourceHeight = charactersToInt(attribute + it->start, it->length - 1, &isValid); |
| + if (!isValid || resourceHeight <= 0) |
| return false; |
| + result.setResourceHeight(resourceHeight); |
| + } else if (c == 'x') { |
| + if (result.hasDensity() || result.hasHeight() || result.hasWidth()) |
| + return false; |
| + int density = charactersToFloat(attribute + it->start, it->length - 1, &isValid); |
| + if (!isValid || density < 0) |
| + return false; |
| + result.setDensity(density); |
| } |
| } |
| - if (isEmptyDescriptor) |
| - result.scaleFactor = 1.0; |
| - return result.foundDescriptor(); |
| + return true; |
| } |
| -// http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content-1.html#processing-the-image-candidates |
| +static bool parseDescriptors(const String& attribute, Vector<DescriptorToken>& descriptors, DescriptorParsingResult& result) |
| +{ |
| + // FIXME: See if StringView can't be extended to replace DescriptorToken here. |
| + if (attribute.is8Bit()) { |
| + return parseDescriptors(attribute.characters8(), descriptors, result); |
| + } |
| + return parseDescriptors(attribute.characters16(), descriptors, result); |
| +} |
| + |
| +// http://picture.responsiveimages.org/#parse-srcset-attr |
| template<typename CharType> |
| static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, const CharType* attributeStart, unsigned length, Vector<ImageCandidate>& imageCandidates) |
| { |
| @@ -92,33 +192,38 @@ static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, con |
| const CharType* attributeEnd = position + length; |
| while (position < attributeEnd) { |
| - DescriptorParsingResult result; |
| - // 4. Splitting loop: Skip whitespace. |
| - skipWhile<CharType, isHTMLSpace<CharType> >(position, attributeEnd); |
| - if (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); |
| + 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; |
| - |
| - // If The current candidate is either totally empty or only contains space, skipping. |
| - if (*position == ',') { |
| - ++position; |
| - continue; |
| } |
| + const CharType* imageURLStart = position; |
| + // 6. Collect a sequence of characters that are not space characters, and let that be url. |
| - // 5. 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; |
| - if (position != attributeEnd && *(position - 1) == ',') { |
| - --imageURLEnd; |
| - result.scaleFactor = 1.0; |
| + DescriptorParsingResult result; |
| + |
| + // 8. If url ends with a U+002C COMMA character (,) |
| + if (isComma(*(position - 1))) { |
| + // Remove all trailing U+002C COMMA characters from url. |
| + imageURLEnd = position - 1; |
| + reverseSkipWhile<CharType, isComma>(imageURLEnd, imageURLStart); |
| + ++imageURLEnd; |
| + // If url is empty, then jump to the step labeled splitting loop. |
| + if (imageURLStart == imageURLEnd) |
| + continue; |
| } else { |
| - // 7. Collect a sequence of characters that are not "," (U+002C) characters, and let that be descriptors. |
| - skipWhile<CharType, isHTMLSpace<CharType> >(position, attributeEnd); |
| - const CharType* descriptorsStart = position; |
| - skipUntil<CharType, isComma<CharType> >(position, attributeEnd); |
| - const CharType* descriptorsEnd = position; |
| - if (!parseDescriptors(descriptorsStart, descriptorsEnd, result)) |
| + // 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; |
| + 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)) |
| continue; |
| } |
| @@ -144,6 +249,7 @@ static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, Vec |
| static ImageCandidate pickBestImageCandidate(float deviceScaleFactor, unsigned sourceSize, Vector<ImageCandidate>& imageCandidates) |
| { |
| + const float defaultDensityValue = 1.0; |
| bool ignoreSrc = false; |
| if (imageCandidates.isEmpty()) |
| return ImageCandidate(); |
| @@ -151,16 +257,18 @@ static ImageCandidate pickBestImageCandidate(float deviceScaleFactor, unsigned s |
| // http://picture.responsiveimages.org/#normalize-source-densities |
| for (Vector<ImageCandidate>::iterator it = imageCandidates.begin(); it != imageCandidates.end(); ++it) { |
| if (it->resourceWidth() > 0) { |
| - it->setScaleFactor((float)it->resourceWidth() / (float)sourceSize); |
| + it->setDensity((float)it->resourceWidth() / (float)sourceSize); |
| ignoreSrc = true; |
| + } else if (it->density() < 0) { |
| + it->setDensity(defaultDensityValue); |
| } |
| } |
| - std::stable_sort(imageCandidates.begin(), imageCandidates.end(), compareByScaleFactor); |
| + std::stable_sort(imageCandidates.begin(), imageCandidates.end(), compareByDensity); |
| unsigned i; |
| for (i = 0; i < imageCandidates.size() - 1; ++i) { |
| - if ((imageCandidates[i].scaleFactor() >= deviceScaleFactor) && (!ignoreSrc || !imageCandidates[i].srcOrigin())) |
| + if ((imageCandidates[i].density() >= deviceScaleFactor) && (!ignoreSrc || !imageCandidates[i].srcOrigin())) |
| break; |
| } |
| @@ -168,12 +276,12 @@ static ImageCandidate pickBestImageCandidate(float deviceScaleFactor, unsigned s |
| ASSERT(i > 0); |
| --i; |
| } |
| - float winningScaleFactor = imageCandidates[i].scaleFactor(); |
| + float winningDensity = imageCandidates[i].density(); |
| unsigned winner = i; |
| // 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].scaleFactor() == winningScaleFactor)) |
| + while ((i > 0) && (imageCandidates[--i].density() == winningDensity)) |
| winner = i; |
| return imageCandidates[winner]; |
| @@ -190,13 +298,10 @@ ImageCandidate bestFitSourceForSrcsetAttribute(float deviceScaleFactor, unsigned |
| ImageCandidate bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned sourceSize, const String& srcAttribute, const String& srcsetAttribute) |
| { |
| - DescriptorParsingResult defaultResult; |
| - defaultResult.scaleFactor = 1.0; |
| - |
| if (srcsetAttribute.isNull()) { |
| if (srcAttribute.isNull()) |
| return ImageCandidate(); |
| - return ImageCandidate(srcAttribute, 0, srcAttribute.length(), defaultResult, ImageCandidate::SrcOrigin); |
| + return ImageCandidate(srcAttribute, 0, srcAttribute.length(), DescriptorParsingResult(), ImageCandidate::SrcOrigin); |
| } |
| Vector<ImageCandidate> imageCandidates; |
| @@ -204,16 +309,13 @@ ImageCandidate bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned |
| parseImageCandidatesFromSrcsetAttribute(srcsetAttribute, imageCandidates); |
| if (!srcAttribute.isEmpty()) |
| - imageCandidates.append(ImageCandidate(srcAttribute, 0, srcAttribute.length(), defaultResult, ImageCandidate::SrcOrigin)); |
| + imageCandidates.append(ImageCandidate(srcAttribute, 0, srcAttribute.length(), DescriptorParsingResult(), ImageCandidate::SrcOrigin)); |
| return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates); |
| } |
| String bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned sourceSize, const String& srcAttribute, ImageCandidate& srcsetImageCandidate) |
| { |
| - DescriptorParsingResult defaultResult; |
| - defaultResult.scaleFactor = 1.0; |
| - |
| if (srcsetImageCandidate.isEmpty()) |
| return srcAttribute; |
| @@ -221,7 +323,7 @@ String bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned sourceS |
| imageCandidates.append(srcsetImageCandidate); |
| if (!srcAttribute.isEmpty()) |
| - imageCandidates.append(ImageCandidate(srcAttribute, 0, srcAttribute.length(), defaultResult, ImageCandidate::SrcOrigin)); |
| + imageCandidates.append(ImageCandidate(srcAttribute, 0, srcAttribute.length(), DescriptorParsingResult(), ImageCandidate::SrcOrigin)); |
| return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates).toString(); |
| } |