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 |