| /* |
| * Copyright (C) 2013 Apple Inc. All rights reserved. |
| * Copyright (C) 2013 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "HTMLSrcsetParser.h" |
| |
| #include "HTMLParserIdioms.h" |
| #include "ParsingUtilities.h" |
| |
| namespace WebCore { |
| |
| static inline bool compareByDensity(const ImageCandidate& first, const ImageCandidate& second) |
| { |
| return first.density < second.density; |
| } |
| |
| enum DescriptorTokenizerState { |
| Initial, |
| InParenthesis, |
| AfterToken, |
| }; |
| |
| template<typename CharType> |
| static void appendDescriptorAndReset(const CharType*& descriptorStart, const CharType* position, Vector<StringView>& descriptors) |
| { |
| if (position > descriptorStart) |
| descriptors.append(StringView(descriptorStart, position - descriptorStart)); |
| descriptorStart = nullptr; |
| } |
| |
| // The following is called appendCharacter to match the spec's terminology. |
| template<typename CharType> |
| static void appendCharacter(const CharType* descriptorStart, const CharType* position) |
| { |
| // Since we don't copy the tokens, this just set the point where the descriptor tokens start. |
| if (!descriptorStart) |
| descriptorStart = position; |
| } |
| |
| template<typename CharType> |
| static bool isEOF(const CharType* position, const CharType* end) |
| { |
| return position >= end; |
| } |
| |
| template<typename CharType> |
| static void tokenizeDescriptors(const CharType*& position, const CharType* attributeEnd, Vector<StringView>& descriptors) |
| { |
| DescriptorTokenizerState state = Initial; |
| const CharType* descriptorsStart = position; |
| const CharType* currentDescriptorStart = descriptorsStart; |
| for (; ; ++position) { |
| switch (state) { |
| case Initial: |
| if (isEOF(position, attributeEnd)) { |
| appendDescriptorAndReset(currentDescriptorStart, attributeEnd, descriptors); |
| return; |
| } |
| if (isComma(*position)) { |
| appendDescriptorAndReset(currentDescriptorStart, position, descriptors); |
| ++position; |
| return; |
| } |
| if (isHTMLSpace(*position)) { |
| appendDescriptorAndReset(currentDescriptorStart, position, descriptors); |
| currentDescriptorStart = position + 1; |
| state = AfterToken; |
| } else if (*position == '(') { |
| appendCharacter(currentDescriptorStart, position); |
| state = InParenthesis; |
| } else |
| appendCharacter(currentDescriptorStart, position); |
| break; |
| case InParenthesis: |
| if (isEOF(position, attributeEnd)) { |
| appendDescriptorAndReset(currentDescriptorStart, attributeEnd, descriptors); |
| return; |
| } |
| if (*position == ')') { |
| appendCharacter(currentDescriptorStart, position); |
| state = Initial; |
| } else |
| appendCharacter(currentDescriptorStart, position); |
| break; |
| case AfterToken: |
| if (isEOF(position, attributeEnd)) |
| return; |
| if (!isHTMLSpace(*position)) { |
| state = Initial; |
| currentDescriptorStart = position; |
| --position; |
| } |
| break; |
| } |
| } |
| } |
| |
| static bool parseDescriptors(Vector<StringView>& descriptors, DescriptorParsingResult& result) |
| { |
| for (auto& descriptor : descriptors) { |
| if (descriptor.isEmpty()) |
| continue; |
| unsigned descriptorCharPosition = descriptor.length() - 1; |
| UChar descriptorChar = descriptor[descriptorCharPosition]; |
| descriptor = descriptor.substring(0, descriptorCharPosition); |
| if (descriptorChar == 'x') { |
| if (result.hasDensity() || result.hasHeight() || result.hasWidth()) |
| return false; |
| std::optional<double> density = parseValidHTMLFloatingPointNumber(descriptor); |
| if (!density || density.value() < 0) |
| return false; |
| result.setDensity(density.value()); |
| } else if (descriptorChar == 'w') { |
| if (result.hasDensity() || result.hasWidth()) |
| return false; |
| std::optional<int> resourceWidth = parseValidHTMLNonNegativeInteger(descriptor); |
| if (!resourceWidth || resourceWidth.value() <= 0) |
| return false; |
| result.setResourceWidth(resourceWidth.value()); |
| } else if (descriptorChar == 'h') { |
| // This is here only for future compat purposes. |
| // The value of the 'h' descriptor is not used. |
| if (result.hasDensity() || result.hasHeight()) |
| return false; |
| std::optional<int> resourceHeight = parseValidHTMLNonNegativeInteger(descriptor); |
| if (!resourceHeight || resourceHeight.value() <= 0) |
| return false; |
| result.setResourceHeight(resourceHeight.value()); |
| } else |
| return false; |
| } |
| return !result.hasHeight() || result.hasWidth(); |
| } |
| |
| // http://picture.responsiveimages.org/#parse-srcset-attr |
| template<typename CharType> |
| static Vector<ImageCandidate> parseImageCandidatesFromSrcsetAttribute(const CharType* attributeStart, unsigned length) |
| { |
| Vector<ImageCandidate> imageCandidates; |
| |
| const CharType* attributeEnd = attributeStart + length; |
| |
| for (const CharType* position = attributeStart; position < attributeEnd;) { |
| // 4. Splitting loop: Collect a sequence of characters that are space characters or U+002C COMMA characters. |
| skipWhile<CharType, isHTMLSpaceOrComma<CharType> >(position, attributeEnd); |
| if (position == attributeEnd) { |
| // Contrary to spec language - descriptor parsing happens on each candidate, so when we reach the attributeEnd, we can exit. |
| break; |
| } |
| const CharType* imageURLStart = position; |
| // 6. Collect a sequence of characters that are not space characters, and let that be url. |
| |
| skipUntil<CharType, isHTMLSpace<CharType> >(position, attributeEnd); |
| const CharType* imageURLEnd = position; |
| |
| DescriptorParsingResult result; |
| |
| // 8. If url ends with a U+002C COMMA character (,) |
| if (isComma(*(position - 1))) { |
| // Remove all trailing U+002C COMMA characters from url. |
| imageURLEnd = position - 1; |
| reverseSkipWhile<CharType, isComma>(imageURLEnd, imageURLStart); |
| ++imageURLEnd; |
| // If url is empty, then jump to the step labeled splitting loop. |
| if (imageURLStart == imageURLEnd) |
| continue; |
| } else { |
| skipWhile<CharType, isHTMLSpace<CharType>>(position, attributeEnd); |
| Vector<StringView> descriptorTokens; |
| tokenizeDescriptors(position, attributeEnd, descriptorTokens); |
| // Contrary to spec language - descriptor parsing happens on each candidate. |
| // This is a black-box equivalent, to avoid storing descriptor lists for each candidate. |
| if (!parseDescriptors(descriptorTokens, result)) |
| continue; |
| } |
| |
| ASSERT(imageURLEnd > imageURLStart); |
| unsigned imageURLLength = imageURLEnd - imageURLStart; |
| imageCandidates.append(ImageCandidate(StringView(imageURLStart, imageURLLength), result, ImageCandidate::SrcsetOrigin)); |
| // 11. Return to the step labeled splitting loop. |
| } |
| return imageCandidates; |
| } |
| |
| Vector<ImageCandidate> parseImageCandidatesFromSrcsetAttribute(StringView attribute) |
| { |
| // FIXME: We should consider replacing the direct pointers in the parsing process with StringView and positions. |
| if (attribute.is8Bit()) |
| return parseImageCandidatesFromSrcsetAttribute<LChar>(attribute.characters8(), attribute.length()); |
| else |
| return parseImageCandidatesFromSrcsetAttribute<UChar>(attribute.characters16(), attribute.length()); |
| } |
| |
| static ImageCandidate pickBestImageCandidate(float deviceScaleFactor, Vector<ImageCandidate>& imageCandidates, float sourceSize) |
| { |
| bool ignoreSrc = false; |
| if (imageCandidates.isEmpty()) |
| return ImageCandidate(); |
| |
| // http://picture.responsiveimages.org/#normalize-source-densities |
| for (auto& candidate : imageCandidates) { |
| if (candidate.resourceWidth > 0) { |
| candidate.density = static_cast<float>(candidate.resourceWidth) / sourceSize; |
| ignoreSrc = true; |
| } else if (candidate.density < 0) |
| candidate.density = DefaultDensityValue; |
| } |
| |
| std::stable_sort(imageCandidates.begin(), imageCandidates.end(), compareByDensity); |
| |
| unsigned i; |
| for (i = 0; i < imageCandidates.size() - 1; ++i) { |
| if ((imageCandidates[i].density >= deviceScaleFactor) && (!ignoreSrc || !imageCandidates[i].srcOrigin())) |
| break; |
| } |
| |
| if (imageCandidates[i].srcOrigin() && ignoreSrc) { |
| ASSERT(i > 0); |
| --i; |
| } |
| float winningDensity = imageCandidates[i].density; |
| |
| unsigned winner = i; |
| // 16. If an entry b in candidates has the same associated ... pixel density as an earlier entry a in candidates, |
| // then remove entry b |
| while ((i > 0) && (imageCandidates[--i].density == winningDensity)) |
| winner = i; |
| |
| return imageCandidates[winner]; |
| } |
| |
| ImageCandidate bestFitSourceForImageAttributes(float deviceScaleFactor, const AtomicString& srcAttribute, const AtomicString& srcsetAttribute, float sourceSize) |
| { |
| if (srcsetAttribute.isNull()) { |
| if (srcAttribute.isNull()) |
| return ImageCandidate(); |
| return ImageCandidate(StringView(srcAttribute), DescriptorParsingResult(), ImageCandidate::SrcOrigin); |
| } |
| |
| Vector<ImageCandidate> imageCandidates = parseImageCandidatesFromSrcsetAttribute(StringView(srcsetAttribute)); |
| |
| if (!srcAttribute.isEmpty()) |
| imageCandidates.append(ImageCandidate(StringView(srcAttribute), DescriptorParsingResult(), ImageCandidate::SrcOrigin)); |
| |
| return pickBestImageCandidate(deviceScaleFactor, imageCandidates, sourceSize); |
| } |
| |
| } // namespace WebCore |