blob: 3a35b8bb72d299765eb06cafb1bbe3242d3e8f26 [file] [log] [blame]
/*
* 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