| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (C) 2013 Apple Inc. All rights reserved. | |
| 3 * Copyright (C) 2013 Google Inc. All rights reserved. | |
| 4 * | |
| 5 * Redistribution and use in source and binary forms, with or without | |
| 6 * modification, are permitted provided that the following conditions are | |
| 7 * met: | |
| 8 * | |
| 9 * * Redistributions of source code must retain the above copyright | |
| 10 * notice, this list of conditions and the following disclaimer. | |
| 11 * * Redistributions in binary form must reproduce the above | |
| 12 * copyright notice, this list of conditions and the following disclaimer | |
| 13 * in the documentation and/or other materials provided with the | |
| 14 * distribution. | |
| 15 * * Neither the name of Google Inc. nor the names of its | |
| 16 * contributors may be used to endorse or promote products derived from | |
| 17 * this software without specific prior written permission. | |
| 18 * | |
| 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 30 */ | |
| 31 | |
| 32 #include "sky/engine/core/html/parser/HTMLSrcsetParser.h" | |
| 33 | |
| 34 #include "gen/sky/platform/RuntimeEnabledFeatures.h" | |
| 35 #include "sky/engine/core/html/parser/HTMLParserIdioms.h" | |
| 36 #include "sky/engine/platform/ParsingUtilities.h" | |
| 37 | |
| 38 namespace blink { | |
| 39 | |
| 40 static bool compareByDensity(const ImageCandidate& first, const ImageCandidate&
second) | |
| 41 { | |
| 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 | |
| 55 DescriptorToken(unsigned start, unsigned length) | |
| 56 : start(start) | |
| 57 , length(length) | |
| 58 { | |
| 59 } | |
| 60 | |
| 61 unsigned lastIndex() | |
| 62 { | |
| 63 return start + length - 1; | |
| 64 } | |
| 65 | |
| 66 template<typename CharType> | |
| 67 int toInt(const CharType* attribute, bool& isValid) | |
| 68 { | |
| 69 return charactersToIntStrict(attribute + start, length - 1, &isValid); | |
| 70 } | |
| 71 | |
| 72 template<typename CharType> | |
| 73 float toFloat(const CharType* attribute, bool& isValid) | |
| 74 { | |
| 75 return charactersToFloat(attribute + start, length - 1, &isValid); | |
| 76 } | |
| 77 }; | |
| 78 | |
| 79 template<typename CharType> | |
| 80 static void appendDescriptorAndReset(const CharType* attributeStart, const CharT
ype*& descriptorStart, const CharType* position, Vector<DescriptorToken>& descri
ptors) | |
| 81 { | |
| 82 if (position > descriptorStart) | |
| 83 descriptors.append(DescriptorToken(descriptorStart - attributeStart, pos
ition - descriptorStart)); | |
| 84 descriptorStart = 0; | |
| 85 } | |
| 86 | |
| 87 // The following is called appendCharacter to match the spec's terminology. | |
| 88 template<typename CharType> | |
| 89 static void appendCharacter(const CharType* descriptorStart, const CharType* pos
ition) | |
| 90 { | |
| 91 // Since we don't copy the tokens, this just set the point where the descrip
tor tokens start. | |
| 92 if (!descriptorStart) | |
| 93 descriptorStart = position; | |
| 94 } | |
| 95 | |
| 96 template<typename CharType> | |
| 97 static bool isEOF(const CharType* position, const CharType* end) | |
| 98 { | |
| 99 return position >= end; | |
| 100 } | |
| 101 | |
| 102 template<typename CharType> | |
| 103 static void tokenizeDescriptors(const CharType* attributeStart, | |
| 104 const CharType*& position, | |
| 105 const CharType* attributeEnd, | |
| 106 Vector<DescriptorToken>& descriptors) | |
| 107 { | |
| 108 DescriptorTokenizerState state = Start; | |
| 109 const CharType* descriptorsStart = position; | |
| 110 const CharType* currentDescriptorStart = descriptorsStart; | |
| 111 while (true) { | |
| 112 switch (state) { | |
| 113 case Start: | |
| 114 if (isEOF(position, attributeEnd)) { | |
| 115 appendDescriptorAndReset(attributeStart, currentDescriptorStart,
attributeEnd, descriptors); | |
| 116 return; | |
| 117 } | |
| 118 if (isComma(*position)) { | |
| 119 appendDescriptorAndReset(attributeStart, currentDescriptorStart,
position, descriptors); | |
| 120 ++position; | |
| 121 return; | |
| 122 } | |
| 123 if (isHTMLSpace(*position)) { | |
| 124 appendDescriptorAndReset(attributeStart, currentDescriptorStart,
position, descriptors); | |
| 125 currentDescriptorStart = position + 1; | |
| 126 state = AfterToken; | |
| 127 } else if (*position == '(') { | |
| 128 appendCharacter(currentDescriptorStart, position); | |
| 129 state = InParenthesis; | |
| 130 } else { | |
| 131 appendCharacter(currentDescriptorStart, position); | |
| 132 } | |
| 133 break; | |
| 134 case InParenthesis: | |
| 135 if (isEOF(position, attributeEnd)) { | |
| 136 appendDescriptorAndReset(attributeStart, currentDescriptorStart,
attributeEnd, descriptors); | |
| 137 return; | |
| 138 } | |
| 139 if (*position == ')') { | |
| 140 appendCharacter(currentDescriptorStart, position); | |
| 141 state = Start; | |
| 142 } else { | |
| 143 appendCharacter(currentDescriptorStart, position); | |
| 144 } | |
| 145 break; | |
| 146 case AfterToken: | |
| 147 if (isEOF(position, attributeEnd)) | |
| 148 return; | |
| 149 if (!isHTMLSpace(*position)) { | |
| 150 state = Start; | |
| 151 currentDescriptorStart = position; | |
| 152 --position; | |
| 153 } | |
| 154 break; | |
| 155 } | |
| 156 ++position; | |
| 157 } | |
| 158 } | |
| 159 | |
| 160 template<typename CharType> | |
| 161 static bool parseDescriptors(const CharType* attribute, Vector<DescriptorToken>&
descriptors, DescriptorParsingResult& result) | |
| 162 { | |
| 163 for (Vector<DescriptorToken>::iterator it = descriptors.begin(); it != descr
iptors.end(); ++it) { | |
| 164 if (it->length == 0) | |
| 165 continue; | |
| 166 CharType c = attribute[it->lastIndex()]; | |
| 167 bool isValid = false; | |
| 168 if (RuntimeEnabledFeatures::pictureSizesEnabled() && c == 'w') { | |
| 169 if (result.hasDensity() || result.hasWidth()) | |
| 170 return false; | |
| 171 int resourceWidth = it->toInt(attribute, isValid); | |
| 172 if (!isValid || resourceWidth <= 0) | |
| 173 return false; | |
| 174 result.setResourceWidth(resourceWidth); | |
| 175 } else if (RuntimeEnabledFeatures::pictureSizesEnabled() && c == 'h') { | |
| 176 // This is here only for future compat purposes. | |
| 177 // The value of the 'h' descriptor is not used. | |
| 178 if (result.hasDensity() || result.hasHeight()) | |
| 179 return false; | |
| 180 int resourceHeight = it->toInt(attribute, isValid); | |
| 181 if (!isValid || resourceHeight <= 0) | |
| 182 return false; | |
| 183 result.setResourceHeight(resourceHeight); | |
| 184 } else if (c == 'x') { | |
| 185 if (result.hasDensity() || result.hasHeight() || result.hasWidth()) | |
| 186 return false; | |
| 187 float density = it->toFloat(attribute, isValid); | |
| 188 if (!isValid || density < 0) | |
| 189 return false; | |
| 190 result.setDensity(density); | |
| 191 } | |
| 192 } | |
| 193 return true; | |
| 194 } | |
| 195 | |
| 196 static bool parseDescriptors(const String& attribute, Vector<DescriptorToken>& d
escriptors, DescriptorParsingResult& result) | |
| 197 { | |
| 198 // FIXME: See if StringView can't be extended to replace DescriptorToken her
e. | |
| 199 if (attribute.is8Bit()) { | |
| 200 return parseDescriptors(attribute.characters8(), descriptors, result); | |
| 201 } | |
| 202 return parseDescriptors(attribute.characters16(), descriptors, result); | |
| 203 } | |
| 204 | |
| 205 // http://picture.responsiveimages.org/#parse-srcset-attr | |
| 206 template<typename CharType> | |
| 207 static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, con
st CharType* attributeStart, unsigned length, Vector<ImageCandidate>& imageCandi
dates) | |
| 208 { | |
| 209 const CharType* position = attributeStart; | |
| 210 const CharType* attributeEnd = position + length; | |
| 211 | |
| 212 while (position < attributeEnd) { | |
| 213 // 4. Splitting loop: Collect a sequence of characters that are space ch
aracters or U+002C COMMA characters. | |
| 214 skipWhile<CharType, isHTMLSpaceOrComma<CharType> >(position, attributeEn
d); | |
| 215 if (position == attributeEnd) { | |
| 216 // Contrary to spec language - descriptor parsing happens on each ca
ndidate, so when we reach the attributeEnd, we can exit. | |
| 217 break; | |
| 218 } | |
| 219 const CharType* imageURLStart = position; | |
| 220 // 6. Collect a sequence of characters that are not space characters, an
d let that be url. | |
| 221 | |
| 222 skipUntil<CharType, isHTMLSpace<CharType> >(position, attributeEnd); | |
| 223 const CharType* imageURLEnd = position; | |
| 224 | |
| 225 DescriptorParsingResult result; | |
| 226 | |
| 227 // 8. If url ends with a U+002C COMMA character (,) | |
| 228 if (isComma(*(position - 1))) { | |
| 229 // Remove all trailing U+002C COMMA characters from url. | |
| 230 imageURLEnd = position - 1; | |
| 231 reverseSkipWhile<CharType, isComma>(imageURLEnd, imageURLStart); | |
| 232 ++imageURLEnd; | |
| 233 // If url is empty, then jump to the step labeled splitting loop. | |
| 234 if (imageURLStart == imageURLEnd) | |
| 235 continue; | |
| 236 } else { | |
| 237 // Advancing position here (contrary to spec) to avoid an useless ex
tra state machine step. | |
| 238 // Filed a spec bug: https://github.com/ResponsiveImagesCG/picture-e
lement/issues/189 | |
| 239 ++position; | |
| 240 Vector<DescriptorToken> descriptorTokens; | |
| 241 tokenizeDescriptors(attributeStart, position, attributeEnd, descript
orTokens); | |
| 242 // Contrary to spec language - descriptor parsing happens on each ca
ndidate. | |
| 243 // This is a black-box equivalent, to avoid storing descriptor lists
for each candidate. | |
| 244 if (!parseDescriptors(attribute, descriptorTokens, result)) | |
| 245 continue; | |
| 246 } | |
| 247 | |
| 248 ASSERT(imageURLEnd > attributeStart); | |
| 249 unsigned imageURLStartingPosition = imageURLStart - attributeStart; | |
| 250 ASSERT(imageURLEnd > imageURLStart); | |
| 251 unsigned imageURLLength = imageURLEnd - imageURLStart; | |
| 252 imageCandidates.append(ImageCandidate(attribute, imageURLStartingPositio
n, imageURLLength, result, ImageCandidate::SrcsetOrigin)); | |
| 253 // 11. Return to the step labeled splitting loop. | |
| 254 } | |
| 255 } | |
| 256 | |
| 257 static void parseImageCandidatesFromSrcsetAttribute(const String& attribute, Vec
tor<ImageCandidate>& imageCandidates) | |
| 258 { | |
| 259 if (attribute.isNull()) | |
| 260 return; | |
| 261 | |
| 262 if (attribute.is8Bit()) | |
| 263 parseImageCandidatesFromSrcsetAttribute<LChar>(attribute, attribute.char
acters8(), attribute.length(), imageCandidates); | |
| 264 else | |
| 265 parseImageCandidatesFromSrcsetAttribute<UChar>(attribute, attribute.char
acters16(), attribute.length(), imageCandidates); | |
| 266 } | |
| 267 | |
| 268 static ImageCandidate pickBestImageCandidate(float deviceScaleFactor, unsigned s
ourceSize, Vector<ImageCandidate>& imageCandidates) | |
| 269 { | |
| 270 const float defaultDensityValue = 1.0; | |
| 271 bool ignoreSrc = false; | |
| 272 if (imageCandidates.isEmpty()) | |
| 273 return ImageCandidate(); | |
| 274 | |
| 275 // http://picture.responsiveimages.org/#normalize-source-densities | |
| 276 for (Vector<ImageCandidate>::iterator it = imageCandidates.begin(); it != im
ageCandidates.end(); ++it) { | |
| 277 if (it->resourceWidth() > 0) { | |
| 278 it->setDensity((float)it->resourceWidth() / (float)sourceSize); | |
| 279 ignoreSrc = true; | |
| 280 } else if (it->density() < 0) { | |
| 281 it->setDensity(defaultDensityValue); | |
| 282 } | |
| 283 } | |
| 284 | |
| 285 std::stable_sort(imageCandidates.begin(), imageCandidates.end(), compareByDe
nsity); | |
| 286 | |
| 287 unsigned i; | |
| 288 for (i = 0; i < imageCandidates.size() - 1; ++i) { | |
| 289 if ((imageCandidates[i].density() >= deviceScaleFactor) && (!ignoreSrc |
| !imageCandidates[i].srcOrigin())) | |
| 290 break; | |
| 291 } | |
| 292 | |
| 293 if (imageCandidates[i].srcOrigin() && ignoreSrc) { | |
| 294 ASSERT(i > 0); | |
| 295 --i; | |
| 296 } | |
| 297 float winningDensity = imageCandidates[i].density(); | |
| 298 | |
| 299 unsigned winner = i; | |
| 300 // 16. If an entry b in candidates has the same associated ... pixel density
as an earlier entry a in candidates, | |
| 301 // then remove entry b | |
| 302 while ((i > 0) && (imageCandidates[--i].density() == winningDensity)) | |
| 303 winner = i; | |
| 304 | |
| 305 return imageCandidates[winner]; | |
| 306 } | |
| 307 | |
| 308 ImageCandidate bestFitSourceForSrcsetAttribute(float deviceScaleFactor, unsigned
sourceSize, const String& srcsetAttribute) | |
| 309 { | |
| 310 Vector<ImageCandidate> imageCandidates; | |
| 311 | |
| 312 parseImageCandidatesFromSrcsetAttribute(srcsetAttribute, imageCandidates); | |
| 313 | |
| 314 return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates
); | |
| 315 } | |
| 316 | |
| 317 ImageCandidate bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned
sourceSize, const String& srcAttribute, const String& srcsetAttribute) | |
| 318 { | |
| 319 if (srcsetAttribute.isNull()) { | |
| 320 if (srcAttribute.isNull()) | |
| 321 return ImageCandidate(); | |
| 322 return ImageCandidate(srcAttribute, 0, srcAttribute.length(), Descriptor
ParsingResult(), ImageCandidate::SrcOrigin); | |
| 323 } | |
| 324 | |
| 325 Vector<ImageCandidate> imageCandidates; | |
| 326 | |
| 327 parseImageCandidatesFromSrcsetAttribute(srcsetAttribute, imageCandidates); | |
| 328 | |
| 329 if (!srcAttribute.isEmpty()) | |
| 330 imageCandidates.append(ImageCandidate(srcAttribute, 0, srcAttribute.leng
th(), DescriptorParsingResult(), ImageCandidate::SrcOrigin)); | |
| 331 | |
| 332 return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates
); | |
| 333 } | |
| 334 | |
| 335 String bestFitSourceForImageAttributes(float deviceScaleFactor, unsigned sourceS
ize, const String& srcAttribute, ImageCandidate& srcsetImageCandidate) | |
| 336 { | |
| 337 if (srcsetImageCandidate.isEmpty()) | |
| 338 return srcAttribute; | |
| 339 | |
| 340 Vector<ImageCandidate> imageCandidates; | |
| 341 imageCandidates.append(srcsetImageCandidate); | |
| 342 | |
| 343 if (!srcAttribute.isEmpty()) | |
| 344 imageCandidates.append(ImageCandidate(srcAttribute, 0, srcAttribute.leng
th(), DescriptorParsingResult(), ImageCandidate::SrcOrigin)); | |
| 345 | |
| 346 return pickBestImageCandidate(deviceScaleFactor, sourceSize, imageCandidates
).toString(); | |
| 347 } | |
| 348 | |
| 349 } | |
| OLD | NEW |