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 "config.h"
33#include "HTMLSrcsetParser.h"
34
35#include "HTMLParserIdioms.h"
36#include "ParsingUtilities.h"
37
38namespace WebCore {
39
40static inline bool compareByDensity(const ImageCandidate& first, const ImageCandidate& second)
41{
42 return first.density < second.density;
43}
44
45enum DescriptorTokenizerState {
46 Initial,
47 InParenthesis,
48 AfterToken,
49};
50
51template<typename CharType>
52static void appendDescriptorAndReset(const CharType*& descriptorStart, const CharType* position, Vector<StringView>& descriptors)
53{
54 if (position > descriptorStart)
55 descriptors.append(StringView(descriptorStart, position - descriptorStart));
56 descriptorStart = nullptr;
57}
58
59// The following is called appendCharacter to match the spec's terminology.
60template<typename CharType>
61static void appendCharacter(const CharType* descriptorStart, const CharType* position)
62{
63 // Since we don't copy the tokens, this just set the point where the descriptor tokens start.
64 if (!descriptorStart)
65 descriptorStart = position;
66}
67
68template<typename CharType>
69static bool isEOF(const CharType* position, const CharType* end)
70{
71 return position >= end;
72}
73
74template<typename CharType>
75static void tokenizeDescriptors(const CharType*& position, const CharType* attributeEnd, Vector<StringView>& descriptors)
76{
77 DescriptorTokenizerState state = Initial;
78 const CharType* descriptorsStart = position;
79 const CharType* currentDescriptorStart = descriptorsStart;
80 for (; ; ++position) {
81 switch (state) {
82 case Initial:
83 if (isEOF(position, attributeEnd)) {
84 appendDescriptorAndReset(currentDescriptorStart, attributeEnd, descriptors);
85 return;
86 }
87 if (isComma(*position)) {
88 appendDescriptorAndReset(currentDescriptorStart, position, descriptors);
89 ++position;
90 return;
91 }
92 if (isHTMLSpace(*position)) {
93 appendDescriptorAndReset(currentDescriptorStart, position, descriptors);
94 currentDescriptorStart = position + 1;
95 state = AfterToken;
96 } else if (*position == '(') {
97 appendCharacter(currentDescriptorStart, position);
98 state = InParenthesis;
99 } else
100 appendCharacter(currentDescriptorStart, position);
101 break;
102 case InParenthesis:
103 if (isEOF(position, attributeEnd)) {
104 appendDescriptorAndReset(currentDescriptorStart, attributeEnd, descriptors);
105 return;
106 }
107 if (*position == ')') {
108 appendCharacter(currentDescriptorStart, position);
109 state = Initial;
110 } else
111 appendCharacter(currentDescriptorStart, position);
112 break;
113 case AfterToken:
114 if (isEOF(position, attributeEnd))
115 return;
116 if (!isHTMLSpace(*position)) {
117 state = Initial;
118 currentDescriptorStart = position;
119 --position;
120 }
121 break;
122 }
123 }
124}
125
126static bool parseDescriptors(Vector<StringView>& descriptors, DescriptorParsingResult& result)
127{
128 for (auto& descriptor : descriptors) {
129 if (descriptor.isEmpty())
130 continue;
131 unsigned descriptorCharPosition = descriptor.length() - 1;
132 UChar descriptorChar = descriptor[descriptorCharPosition];
133 descriptor = descriptor.substring(0, descriptorCharPosition);
134 if (descriptorChar == 'x') {
135 if (result.hasDensity() || result.hasHeight() || result.hasWidth())
136 return false;
137 Optional<double> density = parseValidHTMLFloatingPointNumber(descriptor);
138 if (!density || density.value() < 0)
139 return false;
140 result.setDensity(density.value());
141 } else if (descriptorChar == 'w') {
142 if (result.hasDensity() || result.hasWidth())
143 return false;
144 Optional<int> resourceWidth = parseValidHTMLNonNegativeInteger(descriptor);
145 if (!resourceWidth || resourceWidth.value() <= 0)
146 return false;
147 result.setResourceWidth(resourceWidth.value());
148 } else if (descriptorChar == 'h') {
149 // This is here only for future compat purposes.
150 // The value of the 'h' descriptor is not used.
151 if (result.hasDensity() || result.hasHeight())
152 return false;
153 Optional<int> resourceHeight = parseValidHTMLNonNegativeInteger(descriptor);
154 if (!resourceHeight || resourceHeight.value() <= 0)
155 return false;
156 result.setResourceHeight(resourceHeight.value());
157 } else
158 return false;
159 }
160 return !result.hasHeight() || result.hasWidth();
161}
162
163// http://picture.responsiveimages.org/#parse-srcset-attr
164template<typename CharType>
165static Vector<ImageCandidate> parseImageCandidatesFromSrcsetAttribute(const CharType* attributeStart, unsigned length)
166{
167 Vector<ImageCandidate> imageCandidates;
168
169 const CharType* attributeEnd = attributeStart + length;
170
171 for (const CharType* position = attributeStart; position < attributeEnd;) {
172 // 4. Splitting loop: Collect a sequence of characters that are space characters or U+002C COMMA characters.
173 skipWhile<CharType, isHTMLSpaceOrComma<CharType> >(position, attributeEnd);
174 if (position == attributeEnd) {
175 // Contrary to spec language - descriptor parsing happens on each candidate, so when we reach the attributeEnd, we can exit.
176 break;
177 }
178 const CharType* imageURLStart = position;
179 // 6. Collect a sequence of characters that are not space characters, and let that be url.
180
181 skipUntil<CharType, isHTMLSpace<CharType> >(position, attributeEnd);
182 const CharType* imageURLEnd = position;
183
184 DescriptorParsingResult result;
185
186 // 8. If url ends with a U+002C COMMA character (,)
187 if (isComma(*(position - 1))) {
188 // Remove all trailing U+002C COMMA characters from url.
189 imageURLEnd = position - 1;
190 reverseSkipWhile<CharType, isComma>(imageURLEnd, imageURLStart);
191 ++imageURLEnd;
192 // If url is empty, then jump to the step labeled splitting loop.
193 if (imageURLStart == imageURLEnd)
194 continue;
195 } else {
196 skipWhile<CharType, isHTMLSpace<CharType>>(position, attributeEnd);
197 Vector<StringView> descriptorTokens;
198 tokenizeDescriptors(position, attributeEnd, descriptorTokens);
199 // Contrary to spec language - descriptor parsing happens on each candidate.
200 // This is a black-box equivalent, to avoid storing descriptor lists for each candidate.
201 if (!parseDescriptors(descriptorTokens, result))
202 continue;
203 }
204
205 ASSERT(imageURLEnd > imageURLStart);
206 unsigned imageURLLength = imageURLEnd - imageURLStart;
207 imageCandidates.append(ImageCandidate(StringView(imageURLStart, imageURLLength), result, ImageCandidate::SrcsetOrigin));
208 // 11. Return to the step labeled splitting loop.
209 }
210 return imageCandidates;
211}
212
213Vector<ImageCandidate> parseImageCandidatesFromSrcsetAttribute(StringView attribute)
214{
215 // FIXME: We should consider replacing the direct pointers in the parsing process with StringView and positions.
216 if (attribute.is8Bit())
217 return parseImageCandidatesFromSrcsetAttribute<LChar>(attribute.characters8(), attribute.length());
218 else
219 return parseImageCandidatesFromSrcsetAttribute<UChar>(attribute.characters16(), attribute.length());
220}
221
222static ImageCandidate pickBestImageCandidate(float deviceScaleFactor, Vector<ImageCandidate>& imageCandidates, float sourceSize)
223{
224 bool ignoreSrc = false;
225 if (imageCandidates.isEmpty())
226 return ImageCandidate();
227
228 // http://picture.responsiveimages.org/#normalize-source-densities
229 for (auto& candidate : imageCandidates) {
230 if (candidate.resourceWidth > 0) {
231 candidate.density = static_cast<float>(candidate.resourceWidth) / sourceSize;
232 ignoreSrc = true;
233 } else if (candidate.density < 0)
234 candidate.density = DefaultDensityValue;
235 }
236
237 std::stable_sort(imageCandidates.begin(), imageCandidates.end(), compareByDensity);
238
239 unsigned i;
240 for (i = 0; i < imageCandidates.size() - 1; ++i) {
241 if ((imageCandidates[i].density >= deviceScaleFactor) && (!ignoreSrc || !imageCandidates[i].srcOrigin()))
242 break;
243 }
244
245 if (imageCandidates[i].srcOrigin() && ignoreSrc) {
246 ASSERT(i > 0);
247 --i;
248 }
249 float winningDensity = imageCandidates[i].density;
250
251 unsigned winner = i;
252 // 16. If an entry b in candidates has the same associated ... pixel density as an earlier entry a in candidates,
253 // then remove entry b
254 while ((i > 0) && (imageCandidates[--i].density == winningDensity))
255 winner = i;
256
257 return imageCandidates[winner];
258}
259
260ImageCandidate bestFitSourceForImageAttributes(float deviceScaleFactor, const AtomicString& srcAttribute, const AtomicString& srcsetAttribute, float sourceSize)
261{
262 if (srcsetAttribute.isNull()) {
263 if (srcAttribute.isNull())
264 return ImageCandidate();
265 return ImageCandidate(StringView(srcAttribute), DescriptorParsingResult(), ImageCandidate::SrcOrigin);
266 }
267
268 Vector<ImageCandidate> imageCandidates = parseImageCandidatesFromSrcsetAttribute(StringView(srcsetAttribute));
269
270 if (!srcAttribute.isEmpty())
271 imageCandidates.append(ImageCandidate(StringView(srcAttribute), DescriptorParsingResult(), ImageCandidate::SrcOrigin));
272
273 return pickBestImageCandidate(deviceScaleFactor, imageCandidates, sourceSize);
274}
275
276} // namespace WebCore
277