Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(790)

Side by Side Diff: Source/core/html/parser/HTMLSrcsetParser.cpp

Issue 293423002: Refactor srcset parser to align it with spec changes (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Added tests and fixed a bug Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698