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) | |
65 descriptors.append(DescriptorToken(descriptorStart - attributeStart, pos ition - descriptorStart)); | |
66 descriptorStart = 0; | |
67 } | |
68 | |
69 // The following is called appendCharacter to match the spec's terminology. | |
70 template<typename CharType> | |
71 static void appendCharacter(const CharType* descriptorStart, const CharType* pos ition) | |
72 { | |
73 // Since we don't copy the tokens, this just set the point where the descrip tor tokens start. | |
74 if (!descriptorStart) | |
75 descriptorStart = position; | |
43 } | 76 } |
44 | 77 |
45 template<typename CharType> | 78 template<typename CharType> |
46 inline bool isComma(CharType character) | 79 static bool isEOF(const CharType* position, const CharType* end) |
47 { | 80 { |
48 return character == ','; | 81 return position >= end; |
49 } | 82 } |
50 | 83 |
51 template<typename CharType> | 84 template<typename CharType> |
52 static bool parseDescriptors(const CharType* descriptorsStart, const CharType* d escriptorsEnd, DescriptorParsingResult& result) | 85 static void tokenizeDescriptors(const CharType* attributeStart, |
86 const CharType*& position, | |
87 const CharType* attributeEnd, | |
88 Vector<DescriptorToken>& descriptors) | |
53 { | 89 { |
54 const CharType* position = descriptorsStart; | 90 DescriptorTokenizerState state = Start; |
55 bool isValid = false; | 91 const CharType* descriptorsStart = position; |
56 bool isEmptyDescriptor = !(descriptorsEnd > descriptorsStart); | 92 const CharType* currentDescriptorStart = descriptorsStart; |
57 while (position < descriptorsEnd) { | 93 while (true) { |
58 // 13.1. Let descriptor list be the result of splitting unparsed descrip tors on spaces. | 94 switch (state) { |
59 skipWhile<CharType, isHTMLSpace<CharType> >(position, descriptorsEnd); | 95 case Start: |
60 const CharType* currentDescriptorStart = position; | 96 if (isEOF(position, attributeEnd)) { |
61 skipWhile<CharType, isNotHTMLSpace<CharType> >(position, descriptorsEnd) ; | 97 appendDescriptorAndReset(attributeStart, currentDescriptorStart, attributeEnd, descriptors); |
62 const CharType* currentDescriptorEnd = position; | 98 return; |
99 } | |
100 if (isComma(*position)) { | |
101 appendDescriptorAndReset(attributeStart, currentDescriptorStart, position, descriptors); | |
102 ++position; | |
103 return; | |
104 } | |
105 if (isHTMLSpace(*position)) { | |
106 appendDescriptorAndReset(attributeStart, currentDescriptorStart, position, descriptors); | |
107 currentDescriptorStart = position + 1; | |
108 state = AfterToken; | |
109 } else if (*position == '(') { | |
110 appendCharacter(currentDescriptorStart, position); | |
111 state = InParenthesis; | |
112 } else { | |
113 appendCharacter(currentDescriptorStart, position); | |
114 } | |
115 break; | |
116 case InParenthesis: | |
117 if (isEOF(position, attributeEnd)) { | |
118 appendDescriptorAndReset(attributeStart, currentDescriptorStart, attributeEnd, descriptors); | |
119 return; | |
120 } | |
121 if (*position == ')') { | |
122 appendCharacter(currentDescriptorStart, position); | |
123 state = Start; | |
124 } else { | |
125 appendCharacter(currentDescriptorStart, position); | |
126 } | |
127 break; | |
128 case AfterToken: | |
129 if (isEOF(position, attributeEnd)) | |
130 return; | |
131 if (!isHTMLSpace(*position)) { | |
132 state = Start; | |
133 currentDescriptorStart = position; | |
134 --position; | |
135 } | |
136 break; | |
137 } | |
138 ++position; | |
139 } | |
140 } | |
63 | 141 |
64 ++position; | 142 template<typename CharType> |
65 ASSERT(currentDescriptorEnd > currentDescriptorStart); | 143 static bool parseDescriptors(const CharType* attribute, Vector<DescriptorToken>& descriptors, DescriptorParsingResult& result) |
66 --currentDescriptorEnd; | 144 { |
67 unsigned descriptorLength = currentDescriptorEnd - currentDescriptorStar t; | 145 for (Vector<DescriptorToken>::iterator it = descriptors.begin(); it != descr iptors.end(); ++it) { |
68 if (*currentDescriptorEnd == 'x') { | 146 if (it->length == 0) |
69 if (result.foundDescriptor()) | 147 continue; |
148 CharType c = attribute[it->start + it->length - 1]; | |
eseidel
2014/05/29 06:59:53
Should DescriptorToken have a helper for this valu
| |
149 bool isValid = false; | |
150 if (RuntimeEnabledFeatures::pictureSizesEnabled() && c == 'w') { | |
151 if (result.hasDensity() || result.hasWidth()) | |
70 return false; | 152 return false; |
71 result.scaleFactor = charactersToFloat(currentDescriptorStart, descr iptorLength, &isValid); | 153 int resourceWidth = charactersToInt(attribute + it->start, it->lengt h - 1, &isValid); |
eseidel
2014/05/29 06:59:53
Should charactersToInt be a member on DescriptorTo
| |
72 if (!isValid || result.scaleFactor < 0) | 154 if (!isValid || resourceWidth <= 0) |
73 return false; | 155 return false; |
74 } else if (RuntimeEnabledFeatures::pictureSizesEnabled() && *currentDesc riptorEnd == 'w') { | 156 result.setResourceWidth(resourceWidth); |
75 if (result.foundDescriptor()) | 157 } else if (RuntimeEnabledFeatures::pictureSizesEnabled() && c == 'h') { |
158 // This is here only for future compat purposes. | |
159 // The value of the 'h' descriptor is not used. | |
160 if (result.hasDensity() || result.hasHeight()) | |
76 return false; | 161 return false; |
77 result.resourceWidth = charactersToInt(currentDescriptorStart, descr iptorLength, &isValid); | 162 int resourceHeight = charactersToInt(attribute + it->start, it->leng th - 1, &isValid); |
78 if (!isValid || result.resourceWidth <= 0) | 163 if (!isValid || resourceHeight <= 0) |
79 return false; | 164 return false; |
165 result.setResourceHeight(resourceHeight); | |
166 } else if (c == 'x') { | |
167 if (result.hasDensity() || result.hasHeight() || result.hasWidth()) | |
168 return false; | |
169 int density = charactersToFloat(attribute + it->start, it->length - 1, &isValid); | |
170 if (!isValid || density < 0) | |
171 return false; | |
172 result.setDensity(density); | |
80 } | 173 } |
81 } | 174 } |
82 if (isEmptyDescriptor) | 175 return true; |
83 result.scaleFactor = 1.0; | |
84 return result.foundDescriptor(); | |
85 } | 176 } |
86 | 177 |
87 // http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content- 1.html#processing-the-image-candidates | 178 static bool parseDescriptors(const String& attribute, Vector<DescriptorToken>& d escriptors, DescriptorParsingResult& result) |
179 { | |
180 // FIXME: See if StringView can't be extended to replace DescriptorToken her e. | |
181 if (attribute.is8Bit()) { | |
182 return parseDescriptors(attribute.characters8(), descriptors, result); | |
183 } | |
184 return parseDescriptors(attribute.characters16(), descriptors, result); | |
185 } | |
186 | |
187 // http://picture.responsiveimages.org/#parse-srcset-attr | |
88 template<typename CharType> | 188 template<typename CharType> |
89 static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, con st CharType* attributeStart, unsigned length, Vector<ImageCandidate>& imageCandi dates) | 189 static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, con st CharType* attributeStart, unsigned length, Vector<ImageCandidate>& imageCandi dates) |
90 { | 190 { |
91 const CharType* position = attributeStart; | 191 const CharType* position = attributeStart; |
92 const CharType* attributeEnd = position + length; | 192 const CharType* attributeEnd = position + length; |
93 | 193 |
94 while (position < attributeEnd) { | 194 while (position < attributeEnd) { |
95 DescriptorParsingResult result; | 195 // 4. Splitting loop: Collect a sequence of characters that are space ch aracters or U+002C COMMA characters. |
96 // 4. Splitting loop: Skip whitespace. | 196 skipWhile<CharType, isHTMLSpaceOrComma<CharType> >(position, attributeEn d); |
97 skipWhile<CharType, isHTMLSpace<CharType> >(position, attributeEnd); | 197 if (position == attributeEnd) { |
98 if (position == attributeEnd) | 198 // Contrary to spec language - descriptor parsing happens on each ca ndidate, so when we reach the attributeEnd, we can exit. |
99 break; | 199 break; |
200 } | |
100 const CharType* imageURLStart = position; | 201 const CharType* imageURLStart = position; |
202 // 6. Collect a sequence of characters that are not space characters, an d let that be url. | |
101 | 203 |
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); | 204 skipUntil<CharType, isHTMLSpace<CharType> >(position, attributeEnd); |
110 const CharType* imageURLEnd = position; | 205 const CharType* imageURLEnd = position; |
111 | 206 |
112 if (position != attributeEnd && *(position - 1) == ',') { | 207 DescriptorParsingResult result; |
113 --imageURLEnd; | 208 |
114 result.scaleFactor = 1.0; | 209 // 8. If url ends with a U+002C COMMA character (,) |
210 if (isComma(*(position - 1))) { | |
211 // Remove all trailing U+002C COMMA characters from url. | |
212 imageURLEnd = position - 1; | |
213 reverseSkipWhile<CharType, isComma>(imageURLEnd, imageURLStart); | |
214 ++imageURLEnd; | |
215 // If url is empty, then jump to the step labeled splitting loop. | |
216 if (imageURLStart == imageURLEnd) | |
217 continue; | |
115 } else { | 218 } else { |
116 // 7. Collect a sequence of characters that are not "," (U+002C) cha racters, and let that be descriptors. | 219 // Advancing position here (contrary to spec) to avoid an useless ex tra state machine step. |
117 skipWhile<CharType, isHTMLSpace<CharType> >(position, attributeEnd); | 220 // Filed a spec bug: https://github.com/ResponsiveImagesCG/picture-e lement/issues/189 |
118 const CharType* descriptorsStart = position; | 221 ++position; |
119 skipUntil<CharType, isComma<CharType> >(position, attributeEnd); | 222 Vector<DescriptorToken> descriptorTokens; |
120 const CharType* descriptorsEnd = position; | 223 tokenizeDescriptors(attributeStart, position, attributeEnd, descript orTokens); |
121 if (!parseDescriptors(descriptorsStart, descriptorsEnd, result)) | 224 // Contrary to spec language - descriptor parsing happens on each ca ndidate. |
225 // This is a black-box equivalent, to avoid storing descriptor lists for each candidate. | |
226 if (!parseDescriptors(attribute, descriptorTokens, result)) | |
122 continue; | 227 continue; |
123 } | 228 } |
124 | 229 |
125 ASSERT(imageURLEnd > attributeStart); | 230 ASSERT(imageURLEnd > attributeStart); |
126 unsigned imageURLStartingPosition = imageURLStart - attributeStart; | 231 unsigned imageURLStartingPosition = imageURLStart - attributeStart; |
127 ASSERT(imageURLEnd > imageURLStart); | 232 ASSERT(imageURLEnd > imageURLStart); |
128 unsigned imageURLLength = imageURLEnd - imageURLStart; | 233 unsigned imageURLLength = imageURLEnd - imageURLStart; |
129 imageCandidates.append(ImageCandidate(attribute, imageURLStartingPositio n, imageURLLength, result, ImageCandidate::SrcsetOrigin)); | 234 imageCandidates.append(ImageCandidate(attribute, imageURLStartingPositio n, imageURLLength, result, ImageCandidate::SrcsetOrigin)); |
130 // 11. Return to the step labeled splitting loop. | 235 // 11. Return to the step labeled splitting loop. |
131 } | 236 } |
132 } | 237 } |
133 | 238 |
134 static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, Vec tor<ImageCandidate>& imageCandidates) | 239 static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, Vec tor<ImageCandidate>& imageCandidates) |
135 { | 240 { |
136 if (attribute.isNull()) | 241 if (attribute.isNull()) |
137 return; | 242 return; |
138 | 243 |
139 if (attribute.is8Bit()) | 244 if (attribute.is8Bit()) |
140 parseImageCandidatesFromSrcsetAttribute<LChar>(attribute, attribute.char acters8(), attribute.length(), imageCandidates); | 245 parseImageCandidatesFromSrcsetAttribute<LChar>(attribute, attribute.char acters8(), attribute.length(), imageCandidates); |
141 else | 246 else |
142 parseImageCandidatesFromSrcsetAttribute<UChar>(attribute, attribute.char acters16(), attribute.length(), imageCandidates); | 247 parseImageCandidatesFromSrcsetAttribute<UChar>(attribute, attribute.char acters16(), attribute.length(), imageCandidates); |
143 } | 248 } |
144 | 249 |
145 static ImageCandidate pickBestImageCandidate(float deviceScaleFactor, unsigned s ourceSize, Vector<ImageCandidate>& imageCandidates) | 250 static ImageCandidate pickBestImageCandidate(float deviceScaleFactor, unsigned s ourceSize, Vector<ImageCandidate>& imageCandidates) |
146 { | 251 { |
252 const float defaultDensityValue = 1.0; | |
147 bool ignoreSrc = false; | 253 bool ignoreSrc = false; |
148 if (imageCandidates.isEmpty()) | 254 if (imageCandidates.isEmpty()) |
149 return ImageCandidate(); | 255 return ImageCandidate(); |
150 | 256 |
151 // http://picture.responsiveimages.org/#normalize-source-densities | 257 // http://picture.responsiveimages.org/#normalize-source-densities |
152 for (Vector<ImageCandidate>::iterator it = imageCandidates.begin(); it != im ageCandidates.end(); ++it) { | 258 for (Vector<ImageCandidate>::iterator it = imageCandidates.begin(); it != im ageCandidates.end(); ++it) { |
153 if (it->resourceWidth() > 0) { | 259 if (it->resourceWidth() > 0) { |
154 it->setScaleFactor((float)it->resourceWidth() / (float)sourceSize); | 260 it->setDensity((float)it->resourceWidth() / (float)sourceSize); |
155 ignoreSrc = true; | 261 ignoreSrc = true; |
262 } else if (it->density() < 0) { | |
263 it->setDensity(defaultDensityValue); | |
156 } | 264 } |
157 } | 265 } |
158 | 266 |
159 std::stable_sort(imageCandidates.begin(), imageCandidates.end(), compareBySc aleFactor); | 267 std::stable_sort(imageCandidates.begin(), imageCandidates.end(), compareByDe nsity); |
160 | 268 |
161 unsigned i; | 269 unsigned i; |
162 for (i = 0; i < imageCandidates.size() - 1; ++i) { | 270 for (i = 0; i < imageCandidates.size() - 1; ++i) { |
163 if ((imageCandidates[i].scaleFactor() >= deviceScaleFactor) && (!ignoreS rc || !imageCandidates[i].srcOrigin())) | 271 if ((imageCandidates[i].density() >= deviceScaleFactor) && (!ignoreSrc | | !imageCandidates[i].srcOrigin())) |
164 break; | 272 break; |
165 } | 273 } |
166 | 274 |
167 if (imageCandidates[i].srcOrigin() && ignoreSrc) { | 275 if (imageCandidates[i].srcOrigin() && ignoreSrc) { |
168 ASSERT(i > 0); | 276 ASSERT(i > 0); |
169 --i; | 277 --i; |
170 } | 278 } |
171 float winningScaleFactor = imageCandidates[i].scaleFactor(); | 279 float winningDensity = imageCandidates[i].density(); |
172 | 280 |
173 unsigned winner = i; | 281 unsigned winner = i; |
174 // 16. If an entry b in candidates has the same associated ... pixel density as an earlier entry a in candidates, | 282 // 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 | 283 // then remove entry b |
176 while ((i > 0) && (imageCandidates[--i].scaleFactor() == winningScaleFactor) ) | 284 while ((i > 0) && (imageCandidates[--i].density() == winningDensity)) |
177 winner = i; | 285 winner = i; |
178 | 286 |
179 return imageCandidates[winner]; | 287 return imageCandidates[winner]; |
180 } | 288 } |
181 | 289 |
182 ImageCandidate bestFitSourceForSrcsetAttribute(float deviceScaleFactor, unsigned sourceSize, const String& srcsetAttribute) | 290 ImageCandidate bestFitSourceForSrcsetAttribute(float deviceScaleFactor, unsigned sourceSize, const String& srcsetAttribute) |
183 { | 291 { |
184 Vector<ImageCandidate> imageCandidates; | 292 Vector<ImageCandidate> imageCandidates; |
185 | 293 |
186 parseImageCandidatesFromSrcsetAttribute(srcsetAttribute, imageCandidates); | 294 parseImageCandidatesFromSrcsetAttribute(srcsetAttribute, imageCandidates); |
187 | 295 |
188 return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates ); | 296 return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates ); |
189 } | 297 } |
190 | 298 |
191 ImageCandidate bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned sourceSize, const String& srcAttribute, const String& srcsetAttribute) | 299 ImageCandidate bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned sourceSize, const String& srcAttribute, const String& srcsetAttribute) |
192 { | 300 { |
193 DescriptorParsingResult defaultResult; | |
194 defaultResult.scaleFactor = 1.0; | |
195 | |
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(), Descriptor ParsingResult(), 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(), DescriptorParsingResult(), 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; | |
215 defaultResult.scaleFactor = 1.0; | |
216 | |
217 if (srcsetImageCandidate.isEmpty()) | 319 if (srcsetImageCandidate.isEmpty()) |
218 return srcAttribute; | 320 return srcAttribute; |
219 | 321 |
220 Vector<ImageCandidate> imageCandidates; | 322 Vector<ImageCandidate> imageCandidates; |
221 imageCandidates.append(srcsetImageCandidate); | 323 imageCandidates.append(srcsetImageCandidate); |
222 | 324 |
223 if (!srcAttribute.isEmpty()) | 325 if (!srcAttribute.isEmpty()) |
224 imageCandidates.append(ImageCandidate(srcAttribute, 0, srcAttribute.leng th(), defaultResult, ImageCandidate::SrcOrigin)); | 326 imageCandidates.append(ImageCandidate(srcAttribute, 0, srcAttribute.leng th(), DescriptorParsingResult(), ImageCandidate::SrcOrigin)); |
225 | 327 |
226 return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates ).toString(); | 328 return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates ).toString(); |
227 } | 329 } |
228 | 330 |
229 } | 331 } |
OLD | NEW |