| Index: Source/core/html/parser/HTMLSrcsetParser.cpp
|
| diff --git a/Source/core/html/parser/HTMLSrcsetParser.cpp b/Source/core/html/parser/HTMLSrcsetParser.cpp
|
| index 9b1e5459fb54b36cbe9b1a6337b0113482054865..9725e96bdeaa9396b6600eaac69c36680ad01ad9 100644
|
| --- a/Source/core/html/parser/HTMLSrcsetParser.cpp
|
| +++ b/Source/core/html/parser/HTMLSrcsetParser.cpp
|
| @@ -38,54 +38,172 @@
|
|
|
| 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)
|
| + {
|
| + }
|
| +
|
| + unsigned lastIndex()
|
| + {
|
| + return start + length - 1;
|
| + }
|
| +
|
| + template<typename CharType>
|
| + int toInt(const CharType* attribute, bool& isValid)
|
| + {
|
| + return charactersToInt(attribute + start, length - 1, &isValid);
|
| + }
|
| +
|
| + template<typename CharType>
|
| + float toFloat(const CharType* attribute, bool& isValid)
|
| + {
|
| + return charactersToFloat(attribute + start, length - 1, &isValid);
|
| + }
|
| +};
|
| +
|
| template<typename CharType>
|
| -inline bool isComma(CharType character)
|
| +static void appendDescriptorAndReset(const CharType* attributeStart, const CharType*& descriptorStart, const CharType* position, Vector<DescriptorToken>& descriptors)
|
| {
|
| - return character == ',';
|
| + 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>
|
| -static bool parseDescriptors(const CharType* descriptorsStart, const CharType* descriptorsEnd, DescriptorParsingResult& result)
|
| +static void appendCharacter(const CharType* descriptorStart, const CharType* position)
|
| {
|
| - 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;
|
| + // 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 isEOF(const CharType* position, const CharType* end)
|
| +{
|
| + 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->lastIndex()];
|
| + bool isValid = false;
|
| + if (RuntimeEnabledFeatures::pictureSizesEnabled() && c == 'w') {
|
| + if (result.hasDensity() || result.hasWidth())
|
| + return false;
|
| + int resourceWidth = it->toInt(attribute, isValid);
|
| + if (!isValid || resourceWidth <= 0)
|
| + 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())
|
| return false;
|
| - result.scaleFactor = charactersToFloat(currentDescriptorStart, descriptorLength, &isValid);
|
| - if (!isValid || result.scaleFactor < 0)
|
| + int resourceHeight = it->toInt(attribute, isValid);
|
| + if (!isValid || resourceHeight <= 0)
|
| return false;
|
| - } else if (RuntimeEnabledFeatures::pictureSizesEnabled() && *currentDescriptorEnd == 'w') {
|
| - if (result.foundDescriptor())
|
| + result.setResourceHeight(resourceHeight);
|
| + } else if (c == 'x') {
|
| + if (result.hasDensity() || result.hasHeight() || result.hasWidth())
|
| return false;
|
| - result.resourceWidth = charactersToInt(currentDescriptorStart, descriptorLength, &isValid);
|
| - if (!isValid || result.resourceWidth <= 0)
|
| + int density = it->toFloat(attribute, 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)
|
| {
|
| @@ -93,33 +211,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;
|
| }
|
|
|
| @@ -145,6 +268,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();
|
| @@ -152,16 +276,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;
|
| }
|
|
|
| @@ -169,12 +295,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];
|
| @@ -191,13 +317,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;
|
| @@ -205,16 +328,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;
|
|
|
| @@ -222,7 +342,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();
|
| }
|
|
|