Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright (C) 2013 Google Inc. All rights reserved. | 2 * Copyright (C) 2013 Google Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
| 6 * met: | 6 * met: |
| 7 * | 7 * |
| 8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 30 | 30 |
| 31 #include "config.h" | 31 #include "config.h" |
| 32 #include "core/html/parser/HTMLSrcsetParser.h" | 32 #include "core/html/parser/HTMLSrcsetParser.h" |
| 33 | 33 |
| 34 #include "RuntimeEnabledFeatures.h" | 34 #include "RuntimeEnabledFeatures.h" |
| 35 #include "core/html/parser/HTMLParserIdioms.h" | 35 #include "core/html/parser/HTMLParserIdioms.h" |
| 36 #include "platform/ParsingUtilities.h" | 36 #include "platform/ParsingUtilities.h" |
| 37 | 37 |
| 38 namespace WebCore { | 38 namespace WebCore { |
| 39 | 39 |
| 40 static bool compareByScaleFactor(const ImageCandidate& first, const ImageCandida te& second) | 40 static bool compareByDensity(const ImageCandidate& first, const ImageCandidate& second) |
| 41 { | 41 { |
| 42 return first.scaleFactor() < second.scaleFactor(); | 42 return first.density() < second.density(); |
| 43 } | |
| 44 | |
| 45 enum DescriptorTokenizerState { | |
| 46 Start, | |
| 47 InParenthesis, | |
| 48 AfterToken, | |
| 49 }; | |
| 50 | |
| 51 struct DescriptorToken { | |
| 52 unsigned start; | |
| 53 unsigned length; | |
| 54 DescriptorToken(unsigned start, unsigned length) | |
| 55 : start(start) | |
| 56 , length(length) | |
| 57 { | |
| 58 } | |
| 59 }; | |
| 60 | |
| 61 template<typename CharType> | |
| 62 static void appendDescriptorAndReset(const CharType* attributeStart, const CharT ype*& descriptorStart, const CharType* position, Vector<DescriptorToken>& descri ptors) | |
| 63 { | |
| 64 if (position > descriptorStart) { | |
|
eseidel
2014/05/28 22:24:59
I'm entertained that you use {} here and not below
Yoav Weiss
2014/05/29 05:17:40
Leftovers from adding a printf at some point.
OTOH
| |
| 65 descriptors.append(DescriptorToken(descriptorStart - attributeStart, pos ition - descriptorStart)); | |
| 66 } | |
| 67 descriptorStart = 0; | |
|
eseidel
2014/05/28 22:24:59
It's a bit odd that this has a side-effect...
Yoav Weiss
2014/05/29 05:17:40
This is why I added "AndReset" to the function nam
| |
| 68 } | |
| 69 | |
| 70 // The following is called appendCharacter to match the spec's terminology. | |
| 71 template<typename CharType> | |
| 72 static void appendCharacter(const CharType* descriptorStart, const CharType* pos ition) | |
| 73 { | |
| 74 // Since we don't copy the tokens, this just set the point where the descrip tor tokens start. | |
| 75 if (!descriptorStart) | |
| 76 descriptorStart = position; | |
| 43 } | 77 } |
| 44 | 78 |
| 45 template<typename CharType> | 79 template<typename CharType> |
| 46 inline bool isComma(CharType character) | 80 static bool isEOF(const CharType* position, const CharType* end) |
| 47 { | 81 { |
| 48 return character == ','; | 82 return position >= end; |
| 49 } | 83 } |
| 50 | 84 |
| 51 template<typename CharType> | 85 template<typename CharType> |
| 52 static bool parseDescriptors(const CharType* descriptorsStart, const CharType* d escriptorsEnd, DescriptorParsingResult& result) | 86 static void tokenizeDescriptors(const CharType* attributeStart, |
| 87 const CharType*& position, | |
| 88 const CharType* attributeEnd, | |
| 89 Vector<DescriptorToken>& descriptors) | |
| 53 { | 90 { |
| 54 const CharType* position = descriptorsStart; | 91 DescriptorTokenizerState state = Start; |
| 55 bool isValid = false; | 92 const CharType* descriptorsStart = position; |
| 56 bool isEmptyDescriptor = !(descriptorsEnd > descriptorsStart); | 93 const CharType* currentDescriptorStart = descriptorsStart; |
| 57 while (position < descriptorsEnd) { | 94 while (true) { |
| 58 // 13.1. Let descriptor list be the result of splitting unparsed descrip tors on spaces. | 95 switch (state) { |
| 59 skipWhile<CharType, isHTMLSpace<CharType> >(position, descriptorsEnd); | 96 case Start: |
| 60 const CharType* currentDescriptorStart = position; | 97 if (isEOF(position, attributeEnd)) { |
| 61 skipWhile<CharType, isNotHTMLSpace<CharType> >(position, descriptorsEnd) ; | 98 appendDescriptorAndReset(attributeStart, currentDescriptorStart, attributeEnd, descriptors); |
| 62 const CharType* currentDescriptorEnd = position; | 99 return; |
| 100 } | |
| 101 if (isComma(*position)) { | |
| 102 appendDescriptorAndReset(attributeStart, currentDescriptorStart, position, descriptors); | |
| 103 ++position; | |
| 104 return; | |
| 105 } | |
| 106 if (isHTMLSpace(*position)) { | |
| 107 appendDescriptorAndReset(attributeStart, currentDescriptorStart, position, descriptors); | |
| 108 currentDescriptorStart = position + 1; | |
| 109 state = AfterToken; | |
| 110 } else if (*position == '(') { | |
| 111 appendCharacter(currentDescriptorStart, position); | |
| 112 state = InParenthesis; | |
| 113 } else { | |
| 114 appendCharacter(currentDescriptorStart, position); | |
| 115 } | |
| 116 break; | |
| 117 case InParenthesis: | |
| 118 if (isEOF(position, attributeEnd)) { | |
| 119 appendDescriptorAndReset(attributeStart, currentDescriptorStart, attributeEnd, descriptors); | |
| 120 return; | |
| 121 } | |
| 122 if (*position == ')') { | |
| 123 appendCharacter(currentDescriptorStart, position); | |
| 124 state = Start; | |
| 125 } else { | |
| 126 appendCharacter(currentDescriptorStart, position); | |
| 127 } | |
| 128 break; | |
| 129 case AfterToken: | |
| 130 if (isEOF(position, attributeEnd)) | |
| 131 return; | |
| 132 if (!isHTMLSpace(*position)) { | |
| 133 state = Start; | |
| 134 currentDescriptorStart = position; | |
| 135 --position; | |
| 136 } | |
| 137 break; | |
| 138 } | |
| 139 ++position; | |
| 140 } | |
| 141 } | |
| 63 | 142 |
| 64 ++position; | 143 template<typename CharType> |
| 65 ASSERT(currentDescriptorEnd > currentDescriptorStart); | 144 static bool parseDescriptors(const CharType* attribute, Vector<DescriptorToken>& descriptors, DescriptorParsingResult& result) |
| 66 --currentDescriptorEnd; | 145 { |
| 67 unsigned descriptorLength = currentDescriptorEnd - currentDescriptorStar t; | 146 for (Vector<DescriptorToken>::iterator it = descriptors.begin(); it != descr iptors.end(); ++it) { |
| 68 if (*currentDescriptorEnd == 'x') { | 147 if (it->length == 0) |
| 69 if (result.foundDescriptor()) | 148 continue; |
| 149 CharType c = attribute[it->start + it->length - 1]; | |
| 150 bool isValid = false; | |
| 151 if (RuntimeEnabledFeatures::pictureSizesEnabled() && c == 'w') { | |
| 152 if (result.foundDensity() || result.foundWidth()) | |
| 70 return false; | 153 return false; |
| 71 result.scaleFactor = charactersToFloat(currentDescriptorStart, descr iptorLength, &isValid); | 154 result.resourceWidth = charactersToInt(attribute + it->start, it->le ngth - 1, &isValid); |
| 72 if (!isValid || result.scaleFactor < 0) | 155 if (!isValid || result.resourceWidth <= 0) |
| 73 return false; | 156 return false; |
| 74 } else if (RuntimeEnabledFeatures::pictureSizesEnabled() && *currentDesc riptorEnd == 'w') { | 157 } else if (RuntimeEnabledFeatures::pictureSizesEnabled() && c == 'h') { |
| 75 if (result.foundDescriptor()) | 158 // This is here only for future compat purposes. |
| 159 // The value of the 'h' descriptor is not used. | |
| 160 if (result.foundDensity() || result.foundHeight()) | |
| 76 return false; | 161 return false; |
| 77 result.resourceWidth = charactersToInt(currentDescriptorStart, descr iptorLength, &isValid); | 162 result.resourceHeight = charactersToInt(attribute + it->start, it->l ength - 1, &isValid); |
| 78 if (!isValid || result.resourceWidth <= 0) | 163 if (!isValid || result.resourceHeight <= 0) |
| 164 return false; | |
| 165 } else if (c == 'x') { | |
| 166 if (result.foundDensity() || result.foundHeight() || result.foundWid th()) | |
| 167 return false; | |
| 168 result.density = charactersToFloat(attribute + it->start, it->length - 1, &isValid); | |
| 169 if (!isValid || result.density < 0) | |
| 79 return false; | 170 return false; |
| 80 } | 171 } |
| 81 } | 172 } |
| 82 if (isEmptyDescriptor) | 173 return true; |
| 83 result.scaleFactor = 1.0; | |
| 84 return result.foundDescriptor(); | |
| 85 } | 174 } |
| 86 | 175 |
| 87 // http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content- 1.html#processing-the-image-candidates | 176 static bool parseDescriptors(const String& attribute, Vector<DescriptorToken>& d escriptors, DescriptorParsingResult& result) |
| 177 { | |
| 178 // FIXME: See if StringView can't be extended to replace DescriptorToken her e. | |
| 179 if (attribute.is8Bit()) { | |
| 180 return parseDescriptors(attribute.characters8(), descriptors, result); | |
| 181 } | |
| 182 return parseDescriptors(attribute.characters16(), descriptors, result); | |
| 183 } | |
| 184 | |
| 185 // http://picture.responsiveimages.org/#parse-srcset-attr | |
| 88 template<typename CharType> | 186 template<typename CharType> |
| 89 static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, con st CharType* attributeStart, unsigned length, Vector<ImageCandidate>& imageCandi dates) | 187 static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, con st CharType* attributeStart, unsigned length, Vector<ImageCandidate>& imageCandi dates) |
| 90 { | 188 { |
| 91 const CharType* position = attributeStart; | 189 const CharType* position = attributeStart; |
| 92 const CharType* attributeEnd = position + length; | 190 const CharType* attributeEnd = position + length; |
| 93 | 191 |
| 94 while (position < attributeEnd) { | 192 while (position < attributeEnd) { |
| 95 DescriptorParsingResult result; | 193 // 4. Splitting loop: Collect a sequence of characters that are space ch aracters or U+002C COMMA characters. |
| 96 // 4. Splitting loop: Skip whitespace. | 194 skipWhile<CharType, isHTMLSpaceOrComma<CharType> >(position, attributeEn d); |
| 97 skipWhile<CharType, isHTMLSpace<CharType> >(position, attributeEnd); | 195 if (position == attributeEnd) { |
| 98 if (position == attributeEnd) | 196 // Contrary to spec language - descriptor parsing happens on each ca ndidate, so when we reach the attributeEnd, we can exit. |
| 99 break; | 197 break; |
| 198 } | |
| 100 const CharType* imageURLStart = position; | 199 const CharType* imageURLStart = position; |
| 200 // 6. Collect a sequence of characters that are not space characters, an d let that be url. | |
| 101 | 201 |
| 102 // If The current candidate is either totally empty or only contains spa ce, skipping. | |
| 103 if (*position == ',') { | |
| 104 ++position; | |
| 105 continue; | |
| 106 } | |
| 107 | |
| 108 // 5. Collect a sequence of characters that are not space characters, an d let that be url. | |
| 109 skipUntil<CharType, isHTMLSpace<CharType> >(position, attributeEnd); | 202 skipUntil<CharType, isHTMLSpace<CharType> >(position, attributeEnd); |
| 110 const CharType* imageURLEnd = position; | 203 const CharType* imageURLEnd = position; |
| 111 | 204 |
| 112 if (position != attributeEnd && *(position - 1) == ',') { | 205 DescriptorParsingResult result; |
| 113 --imageURLEnd; | 206 |
| 114 result.scaleFactor = 1.0; | 207 // 8. If url ends with a U+002C COMMA character (,) |
| 208 if (isComma(*(position - 1))) { | |
| 209 // Remove all trailing U+002C COMMA characters from url. | |
| 210 imageURLEnd = position - 1; | |
| 211 reverseSkipWhile<CharType, isComma>(imageURLEnd, imageURLStart); | |
| 212 ++imageURLEnd; | |
| 213 // If url is empty, then jump to the step labeled splitting loop. | |
| 214 if (imageURLStart == imageURLEnd) | |
| 215 continue; | |
| 115 } else { | 216 } else { |
| 116 // 7. Collect a sequence of characters that are not "," (U+002C) cha racters, and let that be descriptors. | 217 // Advancing position here (contrary to spec) to avoid an useless ex tra state machine step. |
| 117 skipWhile<CharType, isHTMLSpace<CharType> >(position, attributeEnd); | 218 // Filed a spec bug: https://github.com/ResponsiveImagesCG/picture-e lement/issues/189 |
| 118 const CharType* descriptorsStart = position; | 219 ++position; |
| 119 skipUntil<CharType, isComma<CharType> >(position, attributeEnd); | 220 Vector<DescriptorToken> descriptorTokens; |
| 120 const CharType* descriptorsEnd = position; | 221 tokenizeDescriptors(attributeStart, position, attributeEnd, descript orTokens); |
| 121 if (!parseDescriptors(descriptorsStart, descriptorsEnd, result)) | 222 // Contrary to spec language - descriptor parsing happens on each ca ndidate. |
| 223 // This is a black-box equivalent, to avoid storing descriptor lists for each candidate. | |
| 224 if (!parseDescriptors(attribute, descriptorTokens, result)) | |
| 122 continue; | 225 continue; |
| 123 } | 226 } |
| 124 | 227 |
| 125 ASSERT(imageURLEnd > attributeStart); | 228 ASSERT(imageURLEnd > attributeStart); |
| 126 unsigned imageURLStartingPosition = imageURLStart - attributeStart; | 229 unsigned imageURLStartingPosition = imageURLStart - attributeStart; |
| 127 ASSERT(imageURLEnd > imageURLStart); | 230 ASSERT(imageURLEnd > imageURLStart); |
| 128 unsigned imageURLLength = imageURLEnd - imageURLStart; | 231 unsigned imageURLLength = imageURLEnd - imageURLStart; |
| 129 imageCandidates.append(ImageCandidate(attribute, imageURLStartingPositio n, imageURLLength, result, ImageCandidate::SrcsetOrigin)); | 232 imageCandidates.append(ImageCandidate(attribute, imageURLStartingPositio n, imageURLLength, result, ImageCandidate::SrcsetOrigin)); |
| 130 // 11. Return to the step labeled splitting loop. | 233 // 11. Return to the step labeled splitting loop. |
| 131 } | 234 } |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 144 | 247 |
| 145 static ImageCandidate pickBestImageCandidate(float deviceScaleFactor, unsigned s ourceSize, Vector<ImageCandidate>& imageCandidates) | 248 static ImageCandidate pickBestImageCandidate(float deviceScaleFactor, unsigned s ourceSize, Vector<ImageCandidate>& imageCandidates) |
| 146 { | 249 { |
| 147 bool ignoreSrc = false; | 250 bool ignoreSrc = false; |
| 148 if (imageCandidates.isEmpty()) | 251 if (imageCandidates.isEmpty()) |
| 149 return ImageCandidate(); | 252 return ImageCandidate(); |
| 150 | 253 |
| 151 // http://picture.responsiveimages.org/#normalize-source-densities | 254 // http://picture.responsiveimages.org/#normalize-source-densities |
| 152 for (Vector<ImageCandidate>::iterator it = imageCandidates.begin(); it != im ageCandidates.end(); ++it) { | 255 for (Vector<ImageCandidate>::iterator it = imageCandidates.begin(); it != im ageCandidates.end(); ++it) { |
| 153 if (it->resourceWidth() > 0) { | 256 if (it->resourceWidth() > 0) { |
| 154 it->setScaleFactor((float)it->resourceWidth() / (float)sourceSize); | 257 it->setDensity((float)it->resourceWidth() / (float)sourceSize); |
| 155 ignoreSrc = true; | 258 ignoreSrc = true; |
| 259 } else if (it->density() < 0) { | |
| 260 it->setDensity(1.0); | |
| 156 } | 261 } |
| 157 } | 262 } |
| 158 | 263 |
| 159 std::stable_sort(imageCandidates.begin(), imageCandidates.end(), compareBySc aleFactor); | 264 std::stable_sort(imageCandidates.begin(), imageCandidates.end(), compareByDe nsity); |
| 160 | 265 |
| 161 unsigned i; | 266 unsigned i; |
| 162 for (i = 0; i < imageCandidates.size() - 1; ++i) { | 267 for (i = 0; i < imageCandidates.size() - 1; ++i) { |
| 163 if ((imageCandidates[i].scaleFactor() >= deviceScaleFactor) && (!ignoreS rc || !imageCandidates[i].srcOrigin())) | 268 if ((imageCandidates[i].density() >= deviceScaleFactor) && (!ignoreSrc | | !imageCandidates[i].srcOrigin())) |
| 164 break; | 269 break; |
| 165 } | 270 } |
| 166 | 271 |
| 167 if (imageCandidates[i].srcOrigin() && ignoreSrc) { | 272 if (imageCandidates[i].srcOrigin() && ignoreSrc) { |
| 168 ASSERT(i > 0); | 273 ASSERT(i > 0); |
| 169 --i; | 274 --i; |
| 170 } | 275 } |
| 171 float winningScaleFactor = imageCandidates[i].scaleFactor(); | 276 float winningDensity = imageCandidates[i].density(); |
| 172 | 277 |
| 173 unsigned winner = i; | 278 unsigned winner = i; |
| 174 // 16. If an entry b in candidates has the same associated ... pixel density as an earlier entry a in candidates, | 279 // 16. If an entry b in candidates has the same associated ... pixel density as an earlier entry a in candidates, |
| 175 // then remove entry b | 280 // then remove entry b |
| 176 while ((i > 0) && (imageCandidates[--i].scaleFactor() == winningScaleFactor) ) | 281 while ((i > 0) && (imageCandidates[--i].density() == winningDensity)) |
| 177 winner = i; | 282 winner = i; |
| 178 | 283 |
| 179 return imageCandidates[winner]; | 284 return imageCandidates[winner]; |
| 180 } | 285 } |
| 181 | 286 |
| 182 ImageCandidate bestFitSourceForSrcsetAttribute(float deviceScaleFactor, unsigned sourceSize, const String& srcsetAttribute) | 287 ImageCandidate bestFitSourceForSrcsetAttribute(float deviceScaleFactor, unsigned sourceSize, const String& srcsetAttribute) |
| 183 { | 288 { |
| 184 Vector<ImageCandidate> imageCandidates; | 289 Vector<ImageCandidate> imageCandidates; |
| 185 | 290 |
| 186 parseImageCandidatesFromSrcsetAttribute(srcsetAttribute, imageCandidates); | 291 parseImageCandidatesFromSrcsetAttribute(srcsetAttribute, imageCandidates); |
| 187 | 292 |
| 188 return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates ); | 293 return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates ); |
| 189 } | 294 } |
| 190 | 295 |
| 191 ImageCandidate bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned sourceSize, const String& srcAttribute, const String& srcsetAttribute) | 296 ImageCandidate bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned sourceSize, const String& srcAttribute, const String& srcsetAttribute) |
| 192 { | 297 { |
| 193 DescriptorParsingResult defaultResult; | 298 DescriptorParsingResult defaultResult; |
| 194 defaultResult.scaleFactor = 1.0; | 299 defaultResult.density = 1.0; |
| 195 | 300 |
| 196 if (srcsetAttribute.isNull()) { | 301 if (srcsetAttribute.isNull()) { |
| 197 if (srcAttribute.isNull()) | 302 if (srcAttribute.isNull()) |
| 198 return ImageCandidate(); | 303 return ImageCandidate(); |
| 199 return ImageCandidate(srcAttribute, 0, srcAttribute.length(), defaultRes ult, ImageCandidate::SrcOrigin); | 304 return ImageCandidate(srcAttribute, 0, srcAttribute.length(), defaultRes ult, ImageCandidate::SrcOrigin); |
| 200 } | 305 } |
| 201 | 306 |
| 202 Vector<ImageCandidate> imageCandidates; | 307 Vector<ImageCandidate> imageCandidates; |
| 203 | 308 |
| 204 parseImageCandidatesFromSrcsetAttribute(srcsetAttribute, imageCandidates); | 309 parseImageCandidatesFromSrcsetAttribute(srcsetAttribute, imageCandidates); |
| 205 | 310 |
| 206 if (!srcAttribute.isEmpty()) | 311 if (!srcAttribute.isEmpty()) |
| 207 imageCandidates.append(ImageCandidate(srcAttribute, 0, srcAttribute.leng th(), defaultResult, ImageCandidate::SrcOrigin)); | 312 imageCandidates.append(ImageCandidate(srcAttribute, 0, srcAttribute.leng th(), defaultResult, ImageCandidate::SrcOrigin)); |
| 208 | 313 |
| 209 return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates ); | 314 return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates ); |
| 210 } | 315 } |
| 211 | 316 |
| 212 String bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned sourceS ize, const String& srcAttribute, ImageCandidate& srcsetImageCandidate) | 317 String bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned sourceS ize, const String& srcAttribute, ImageCandidate& srcsetImageCandidate) |
| 213 { | 318 { |
| 214 DescriptorParsingResult defaultResult; | 319 DescriptorParsingResult defaultResult; |
| 215 defaultResult.scaleFactor = 1.0; | 320 defaultResult.density = 1.0; |
| 216 | 321 |
| 217 if (srcsetImageCandidate.isEmpty()) | 322 if (srcsetImageCandidate.isEmpty()) |
| 218 return srcAttribute; | 323 return srcAttribute; |
| 219 | 324 |
| 220 Vector<ImageCandidate> imageCandidates; | 325 Vector<ImageCandidate> imageCandidates; |
| 221 imageCandidates.append(srcsetImageCandidate); | 326 imageCandidates.append(srcsetImageCandidate); |
| 222 | 327 |
| 223 if (!srcAttribute.isEmpty()) | 328 if (!srcAttribute.isEmpty()) |
| 224 imageCandidates.append(ImageCandidate(srcAttribute, 0, srcAttribute.leng th(), defaultResult, ImageCandidate::SrcOrigin)); | 329 imageCandidates.append(ImageCandidate(srcAttribute, 0, srcAttribute.leng th(), defaultResult, ImageCandidate::SrcOrigin)); |
| 225 | 330 |
| 226 return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates ).toString(); | 331 return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates ).toString(); |
| 227 } | 332 } |
| 228 | 333 |
| 229 } | 334 } |
| OLD | NEW |