/src/mozilla-central/dom/base/ResponsiveImageSelector.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "mozilla/dom/ResponsiveImageSelector.h" |
8 | | #include "mozilla/ServoStyleSetInlines.h" |
9 | | #include "mozilla/TextUtils.h" |
10 | | #include "nsIURI.h" |
11 | | #include "nsIDocument.h" |
12 | | #include "nsContentUtils.h" |
13 | | #include "nsPresContext.h" |
14 | | |
15 | | #include "nsCSSProps.h" |
16 | | |
17 | | using namespace mozilla; |
18 | | using namespace mozilla::dom; |
19 | | |
20 | | namespace mozilla { |
21 | | namespace dom { |
22 | | |
23 | | NS_IMPL_CYCLE_COLLECTION(ResponsiveImageSelector, mOwnerNode) |
24 | | |
25 | | NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ResponsiveImageSelector, AddRef) |
26 | | NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ResponsiveImageSelector, Release) |
27 | | |
28 | | static bool |
29 | | ParseInteger(const nsAString& aString, int32_t& aInt) |
30 | 0 | { |
31 | 0 | nsContentUtils::ParseHTMLIntegerResultFlags parseResult; |
32 | 0 | aInt = nsContentUtils::ParseHTMLInteger(aString, &parseResult); |
33 | 0 | return !(parseResult & |
34 | 0 | ( nsContentUtils::eParseHTMLInteger_Error | |
35 | 0 | nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput | |
36 | 0 | nsContentUtils::eParseHTMLInteger_IsPercent | |
37 | 0 | nsContentUtils::eParseHTMLInteger_NonStandard )); |
38 | 0 | } |
39 | | |
40 | | static bool |
41 | | ParseFloat(const nsAString& aString, double& aDouble) |
42 | 0 | { |
43 | 0 | // Check if it is a valid floating-point number first since the result of |
44 | 0 | // nsString.ToDouble() is more lenient than the spec, |
45 | 0 | // https://html.spec.whatwg.org/#valid-floating-point-number |
46 | 0 | nsAString::const_iterator iter, end; |
47 | 0 | aString.BeginReading(iter); |
48 | 0 | aString.EndReading(end); |
49 | 0 |
|
50 | 0 | if (iter == end) { |
51 | 0 | return false; |
52 | 0 | } |
53 | 0 | |
54 | 0 | if (*iter == char16_t('-') && ++iter == end) { |
55 | 0 | return false; |
56 | 0 | } |
57 | 0 | |
58 | 0 | if (IsAsciiDigit(*iter)) { |
59 | 0 | for (; iter != end && IsAsciiDigit(*iter) ; ++iter); |
60 | 0 | } else if (*iter == char16_t('.')) { |
61 | 0 | // Do nothing, jumps to fraction part |
62 | 0 | } else { |
63 | 0 | return false; |
64 | 0 | } |
65 | 0 | |
66 | 0 | // Fraction |
67 | 0 | if (*iter == char16_t('.')) { |
68 | 0 | ++iter; |
69 | 0 | if (iter == end || !IsAsciiDigit(*iter)) { |
70 | 0 | // U+002E FULL STOP character (.) must be followed by one or more ASCII digits |
71 | 0 | return false; |
72 | 0 | } |
73 | 0 | |
74 | 0 | for (; iter != end && IsAsciiDigit(*iter) ; ++iter); |
75 | 0 | } |
76 | 0 |
|
77 | 0 | if (iter != end && (*iter == char16_t('e') || *iter == char16_t('E'))) { |
78 | 0 | ++iter; |
79 | 0 | if (*iter == char16_t('-') || *iter == char16_t('+')) { |
80 | 0 | ++iter; |
81 | 0 | } |
82 | 0 |
|
83 | 0 | if (iter == end || !IsAsciiDigit(*iter)) { |
84 | 0 | // Should have one or more ASCII digits |
85 | 0 | return false; |
86 | 0 | } |
87 | 0 | |
88 | 0 | for (; iter != end && IsAsciiDigit(*iter) ; ++iter); |
89 | 0 | } |
90 | 0 |
|
91 | 0 | if (iter != end) { |
92 | 0 | return false; |
93 | 0 | } |
94 | 0 | |
95 | 0 | nsresult rv; |
96 | 0 | aDouble = PromiseFlatString(aString).ToDouble(&rv); |
97 | 0 | return NS_SUCCEEDED(rv); |
98 | 0 | } |
99 | | |
100 | | ResponsiveImageSelector::ResponsiveImageSelector(nsIContent *aContent) |
101 | | : mOwnerNode(aContent), |
102 | | mSelectedCandidateIndex(-1) |
103 | 0 | { |
104 | 0 | } |
105 | | |
106 | | ResponsiveImageSelector::ResponsiveImageSelector(nsIDocument *aDocument) |
107 | | : mOwnerNode(aDocument), |
108 | | mSelectedCandidateIndex(-1) |
109 | 0 | { |
110 | 0 | } |
111 | | |
112 | | ResponsiveImageSelector::~ResponsiveImageSelector() |
113 | 0 | {} |
114 | | |
115 | | // http://www.whatwg.org/specs/web-apps/current-work/#processing-the-image-candidates |
116 | | bool |
117 | | ResponsiveImageSelector::SetCandidatesFromSourceSet(const nsAString & aSrcSet, |
118 | | nsIPrincipal* aTriggeringPrincipal) |
119 | 0 | { |
120 | 0 | ClearSelectedCandidate(); |
121 | 0 |
|
122 | 0 | nsCOMPtr<nsIURI> docBaseURI = mOwnerNode ? mOwnerNode->GetBaseURI() : nullptr; |
123 | 0 |
|
124 | 0 | if (!docBaseURI) { |
125 | 0 | MOZ_ASSERT(false, |
126 | 0 | "Should not be parsing SourceSet without a document"); |
127 | 0 | return false; |
128 | 0 | } |
129 | 0 |
|
130 | 0 | mCandidates.Clear(); |
131 | 0 |
|
132 | 0 | nsAString::const_iterator iter, end; |
133 | 0 | aSrcSet.BeginReading(iter); |
134 | 0 | aSrcSet.EndReading(end); |
135 | 0 |
|
136 | 0 | // Read URL / descriptor pairs |
137 | 0 | while (iter != end) { |
138 | 0 | nsAString::const_iterator url, urlEnd, descriptor; |
139 | 0 |
|
140 | 0 | // Skip whitespace and commas. |
141 | 0 | // Extra commas at this point are a non-fatal syntax error. |
142 | 0 | for (; iter != end && (nsContentUtils::IsHTMLWhitespace(*iter) || |
143 | 0 | *iter == char16_t(',')); ++iter); |
144 | 0 |
|
145 | 0 | if (iter == end) { |
146 | 0 | break; |
147 | 0 | } |
148 | 0 | |
149 | 0 | url = iter; |
150 | 0 |
|
151 | 0 | // Find end of url |
152 | 0 | for (;iter != end && !nsContentUtils::IsHTMLWhitespace(*iter); ++iter); |
153 | 0 |
|
154 | 0 | // Omit trailing commas from URL. |
155 | 0 | // Multiple commas are a non-fatal error. |
156 | 0 | while (iter != url) { |
157 | 0 | if (*(--iter) != char16_t(',')) { |
158 | 0 | iter++; |
159 | 0 | break; |
160 | 0 | } |
161 | 0 | } |
162 | 0 |
|
163 | 0 | const nsDependentSubstring &urlStr = Substring(url, iter); |
164 | 0 |
|
165 | 0 | MOZ_ASSERT(url != iter, "Shouldn't have empty URL at this point"); |
166 | 0 |
|
167 | 0 | ResponsiveImageCandidate candidate; |
168 | 0 | if (candidate.ConsumeDescriptors(iter, end)) { |
169 | 0 | candidate.SetURLSpec(urlStr); |
170 | 0 | candidate.SetTriggeringPrincipal(nsContentUtils::GetAttrTriggeringPrincipal( |
171 | 0 | Content(), urlStr, aTriggeringPrincipal)); |
172 | 0 | AppendCandidateIfUnique(candidate); |
173 | 0 | } |
174 | 0 | } |
175 | 0 |
|
176 | 0 | bool parsedCandidates = mCandidates.Length() > 0; |
177 | 0 |
|
178 | 0 | // Re-add default to end of list |
179 | 0 | MaybeAppendDefaultCandidate(); |
180 | 0 |
|
181 | 0 | return parsedCandidates; |
182 | 0 | } |
183 | | |
184 | | uint32_t |
185 | | ResponsiveImageSelector::NumCandidates(bool aIncludeDefault) |
186 | 0 | { |
187 | 0 | uint32_t candidates = mCandidates.Length(); |
188 | 0 |
|
189 | 0 | // If present, the default candidate is the last item |
190 | 0 | if (!aIncludeDefault && candidates && |
191 | 0 | (mCandidates[candidates - 1].Type() == |
192 | 0 | ResponsiveImageCandidate::eCandidateType_Default)) { |
193 | 0 | candidates--; |
194 | 0 | } |
195 | 0 |
|
196 | 0 | return candidates; |
197 | 0 | } |
198 | | |
199 | | nsIContent* |
200 | | ResponsiveImageSelector::Content() |
201 | 0 | { |
202 | 0 | return mOwnerNode->IsContent() ? mOwnerNode->AsContent() : nullptr; |
203 | 0 | } |
204 | | |
205 | | nsIDocument* |
206 | | ResponsiveImageSelector::Document() |
207 | 0 | { |
208 | 0 | return mOwnerNode->OwnerDoc(); |
209 | 0 | } |
210 | | |
211 | | void |
212 | | ResponsiveImageSelector::SetDefaultSource(const nsAString& aURLString, |
213 | | nsIPrincipal* aPrincipal) |
214 | 0 | { |
215 | 0 | ClearSelectedCandidate(); |
216 | 0 |
|
217 | 0 | // Check if the last element of our candidates is a default |
218 | 0 | int32_t candidates = mCandidates.Length(); |
219 | 0 | if (candidates && (mCandidates[candidates - 1].Type() == |
220 | 0 | ResponsiveImageCandidate::eCandidateType_Default)) { |
221 | 0 | mCandidates.RemoveElementAt(candidates - 1); |
222 | 0 | } |
223 | 0 |
|
224 | 0 | mDefaultSourceURL = aURLString; |
225 | 0 | mDefaultSourceTriggeringPrincipal = aPrincipal; |
226 | 0 |
|
227 | 0 | // Add new default to end of list |
228 | 0 | MaybeAppendDefaultCandidate(); |
229 | 0 | } |
230 | | |
231 | | void |
232 | | ResponsiveImageSelector::ClearSelectedCandidate() |
233 | 0 | { |
234 | 0 | mSelectedCandidateIndex = -1; |
235 | 0 | mSelectedCandidateURL = nullptr; |
236 | 0 | } |
237 | | |
238 | | bool |
239 | | ResponsiveImageSelector::SetSizesFromDescriptor(const nsAString & aSizes) |
240 | 0 | { |
241 | 0 | ClearSelectedCandidate(); |
242 | 0 |
|
243 | 0 | NS_ConvertUTF16toUTF8 sizes(aSizes); |
244 | 0 | mServoSourceSizeList.reset(Servo_SourceSizeList_Parse(&sizes)); |
245 | 0 | return !!mServoSourceSizeList; |
246 | 0 | } |
247 | | |
248 | | void |
249 | | ResponsiveImageSelector::AppendCandidateIfUnique(const ResponsiveImageCandidate & aCandidate) |
250 | 0 | { |
251 | 0 | int numCandidates = mCandidates.Length(); |
252 | 0 |
|
253 | 0 | // With the exception of Default, which should not be added until we are done |
254 | 0 | // building the list. |
255 | 0 | if (aCandidate.Type() == ResponsiveImageCandidate::eCandidateType_Default) { |
256 | 0 | return; |
257 | 0 | } |
258 | 0 | |
259 | 0 | // Discard candidates with identical parameters, they will never match |
260 | 0 | for (int i = 0; i < numCandidates; i++) { |
261 | 0 | if (mCandidates[i].HasSameParameter(aCandidate)) { |
262 | 0 | return; |
263 | 0 | } |
264 | 0 | } |
265 | 0 |
|
266 | 0 | mCandidates.AppendElement(aCandidate); |
267 | 0 | } |
268 | | |
269 | | void |
270 | | ResponsiveImageSelector::MaybeAppendDefaultCandidate() |
271 | 0 | { |
272 | 0 | if (mDefaultSourceURL.IsEmpty()) { |
273 | 0 | return; |
274 | 0 | } |
275 | 0 | |
276 | 0 | int numCandidates = mCandidates.Length(); |
277 | 0 |
|
278 | 0 | // https://html.spec.whatwg.org/multipage/embedded-content.html#update-the-source-set |
279 | 0 | // step 4.1.3: |
280 | 0 | // If child has a src attribute whose value is not the empty string and source |
281 | 0 | // set does not contain an image source with a density descriptor value of 1, |
282 | 0 | // and no image source with a width descriptor, append child's src attribute |
283 | 0 | // value to source set. |
284 | 0 | for (int i = 0; i < numCandidates; i++) { |
285 | 0 | if (mCandidates[i].IsComputedFromWidth()) { |
286 | 0 | return; |
287 | 0 | } else if (mCandidates[i].Density(this) == 1.0) { |
288 | 0 | return; |
289 | 0 | } |
290 | 0 | } |
291 | 0 |
|
292 | 0 | ResponsiveImageCandidate defaultCandidate; |
293 | 0 | defaultCandidate.SetParameterDefault(); |
294 | 0 | defaultCandidate.SetURLSpec(mDefaultSourceURL); |
295 | 0 | defaultCandidate.SetTriggeringPrincipal(mDefaultSourceTriggeringPrincipal); |
296 | 0 | // We don't use MaybeAppend since we want to keep this even if it can never |
297 | 0 | // match, as it may if the source set changes. |
298 | 0 | mCandidates.AppendElement(defaultCandidate); |
299 | 0 | } |
300 | | |
301 | | already_AddRefed<nsIURI> |
302 | | ResponsiveImageSelector::GetSelectedImageURL() |
303 | 0 | { |
304 | 0 | SelectImage(); |
305 | 0 |
|
306 | 0 | nsCOMPtr<nsIURI> url = mSelectedCandidateURL; |
307 | 0 | return url.forget(); |
308 | 0 | } |
309 | | |
310 | | bool |
311 | | ResponsiveImageSelector::GetSelectedImageURLSpec(nsAString& aResult) |
312 | 0 | { |
313 | 0 | SelectImage(); |
314 | 0 |
|
315 | 0 | if (mSelectedCandidateIndex == -1) { |
316 | 0 | return false; |
317 | 0 | } |
318 | 0 | |
319 | 0 | aResult.Assign(mCandidates[mSelectedCandidateIndex].URLString()); |
320 | 0 | return true; |
321 | 0 | } |
322 | | |
323 | | double |
324 | | ResponsiveImageSelector::GetSelectedImageDensity() |
325 | 0 | { |
326 | 0 | int bestIndex = GetSelectedCandidateIndex(); |
327 | 0 | if (bestIndex < 0) { |
328 | 0 | return 1.0; |
329 | 0 | } |
330 | 0 | |
331 | 0 | return mCandidates[bestIndex].Density(this); |
332 | 0 | } |
333 | | |
334 | | nsIPrincipal* |
335 | | ResponsiveImageSelector::GetSelectedImageTriggeringPrincipal() |
336 | 0 | { |
337 | 0 | int bestIndex = GetSelectedCandidateIndex(); |
338 | 0 | if (bestIndex < 0) { |
339 | 0 | return nullptr; |
340 | 0 | } |
341 | 0 | |
342 | 0 | return mCandidates[bestIndex].TriggeringPrincipal(); |
343 | 0 | } |
344 | | |
345 | | bool |
346 | | ResponsiveImageSelector::SelectImage(bool aReselect) |
347 | 0 | { |
348 | 0 | if (!aReselect && mSelectedCandidateIndex != -1) { |
349 | 0 | // Already have selection |
350 | 0 | return false; |
351 | 0 | } |
352 | 0 | |
353 | 0 | int oldBest = mSelectedCandidateIndex; |
354 | 0 | ClearSelectedCandidate(); |
355 | 0 |
|
356 | 0 | int numCandidates = mCandidates.Length(); |
357 | 0 | if (!numCandidates) { |
358 | 0 | return oldBest != -1; |
359 | 0 | } |
360 | 0 | |
361 | 0 | nsIDocument* doc = Document(); |
362 | 0 | nsPresContext* pctx = doc->GetPresContext(); |
363 | 0 | nsCOMPtr<nsIURI> baseURI = mOwnerNode->GetBaseURI(); |
364 | 0 |
|
365 | 0 | if (!pctx || !baseURI) { |
366 | 0 | return oldBest != -1; |
367 | 0 | } |
368 | 0 | |
369 | 0 | double displayDensity = pctx->CSSPixelsToDevPixels(1.0f); |
370 | 0 | double overrideDPPX = pctx->GetOverrideDPPX(); |
371 | 0 |
|
372 | 0 | if (overrideDPPX > 0) { |
373 | 0 | displayDensity = overrideDPPX; |
374 | 0 | } |
375 | 0 |
|
376 | 0 | // Per spec, "In a UA-specific manner, choose one image source" |
377 | 0 | // - For now, select the lowest density greater than displayDensity, otherwise |
378 | 0 | // the greatest density available |
379 | 0 |
|
380 | 0 | // If the list contains computed width candidates, compute the current |
381 | 0 | // effective image width. |
382 | 0 | double computedWidth = -1; |
383 | 0 | for (int i = 0; i < numCandidates; i++) { |
384 | 0 | if (mCandidates[i].IsComputedFromWidth()) { |
385 | 0 | DebugOnly<bool> computeResult = |
386 | 0 | ComputeFinalWidthForCurrentViewport(&computedWidth); |
387 | 0 | MOZ_ASSERT(computeResult, |
388 | 0 | "Computed candidates not allowed without sizes data"); |
389 | 0 | break; |
390 | 0 | } |
391 | 0 | } |
392 | 0 |
|
393 | 0 | int bestIndex = -1; |
394 | 0 | double bestDensity = -1.0; |
395 | 0 | for (int i = 0; i < numCandidates; i++) { |
396 | 0 | double candidateDensity = \ |
397 | 0 | (computedWidth == -1) ? mCandidates[i].Density(this) |
398 | 0 | : mCandidates[i].Density(computedWidth); |
399 | 0 | // - If bestIndex is below display density, pick anything larger. |
400 | 0 | // - Otherwise, prefer if less dense than bestDensity but still above |
401 | 0 | // displayDensity. |
402 | 0 | if (bestIndex == -1 || |
403 | 0 | (bestDensity < displayDensity && candidateDensity > bestDensity) || |
404 | 0 | (candidateDensity >= displayDensity && candidateDensity < bestDensity)) { |
405 | 0 | bestIndex = i; |
406 | 0 | bestDensity = candidateDensity; |
407 | 0 | } |
408 | 0 | } |
409 | 0 |
|
410 | 0 | MOZ_ASSERT(bestIndex >= 0 && bestIndex < numCandidates); |
411 | 0 |
|
412 | 0 | // Resolve URL |
413 | 0 | nsresult rv; |
414 | 0 | const nsAString& urlStr = mCandidates[bestIndex].URLString(); |
415 | 0 | nsCOMPtr<nsIURI> candidateURL; |
416 | 0 | rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(candidateURL), |
417 | 0 | urlStr, doc, baseURI); |
418 | 0 |
|
419 | 0 | mSelectedCandidateURL = NS_SUCCEEDED(rv) ? candidateURL : nullptr; |
420 | 0 | mSelectedCandidateIndex = bestIndex; |
421 | 0 |
|
422 | 0 | return mSelectedCandidateIndex != oldBest; |
423 | 0 | } |
424 | | |
425 | | int |
426 | | ResponsiveImageSelector::GetSelectedCandidateIndex() |
427 | 0 | { |
428 | 0 | SelectImage(); |
429 | 0 |
|
430 | 0 | return mSelectedCandidateIndex; |
431 | 0 | } |
432 | | |
433 | | bool |
434 | | ResponsiveImageSelector::ComputeFinalWidthForCurrentViewport(double *aWidth) |
435 | 0 | { |
436 | 0 | nsIDocument* doc = Document(); |
437 | 0 | nsIPresShell* presShell = doc->GetShell(); |
438 | 0 | nsPresContext* pctx = presShell ? presShell->GetPresContext() : nullptr; |
439 | 0 |
|
440 | 0 | if (!pctx) { |
441 | 0 | return false; |
442 | 0 | } |
443 | 0 | nscoord effectiveWidth = presShell->StyleSet()-> |
444 | 0 | EvaluateSourceSizeList(mServoSourceSizeList.get()); |
445 | 0 |
|
446 | 0 | *aWidth = nsPresContext::AppUnitsToDoubleCSSPixels(std::max(effectiveWidth, 0)); |
447 | 0 | return true; |
448 | 0 | } |
449 | | |
450 | | ResponsiveImageCandidate::ResponsiveImageCandidate() |
451 | 0 | { |
452 | 0 | mType = eCandidateType_Invalid; |
453 | 0 | mValue.mDensity = 1.0; |
454 | 0 | } |
455 | | |
456 | | ResponsiveImageCandidate::ResponsiveImageCandidate(const nsAString& aURLString, |
457 | | double aDensity, |
458 | | nsIPrincipal* aTriggeringPrincipal) |
459 | | : mURLString(aURLString) |
460 | | , mTriggeringPrincipal(aTriggeringPrincipal) |
461 | 0 | { |
462 | 0 | mType = eCandidateType_Density; |
463 | 0 | mValue.mDensity = aDensity; |
464 | 0 | } |
465 | | |
466 | | |
467 | | void |
468 | | ResponsiveImageCandidate::SetURLSpec(const nsAString& aURLString) |
469 | 0 | { |
470 | 0 | mURLString = aURLString; |
471 | 0 | } |
472 | | |
473 | | void |
474 | | ResponsiveImageCandidate::SetTriggeringPrincipal(nsIPrincipal* aPrincipal) |
475 | 0 | { |
476 | 0 | mTriggeringPrincipal = aPrincipal; |
477 | 0 | } |
478 | | |
479 | | void |
480 | | ResponsiveImageCandidate::SetParameterAsComputedWidth(int32_t aWidth) |
481 | 0 | { |
482 | 0 | mType = eCandidateType_ComputedFromWidth; |
483 | 0 | mValue.mWidth = aWidth; |
484 | 0 | } |
485 | | |
486 | | void |
487 | | ResponsiveImageCandidate::SetParameterDefault() |
488 | 0 | { |
489 | 0 | MOZ_ASSERT(mType == eCandidateType_Invalid, "double setting candidate type"); |
490 | 0 |
|
491 | 0 | mType = eCandidateType_Default; |
492 | 0 | // mValue shouldn't actually be used for this type, but set it to default |
493 | 0 | // anyway |
494 | 0 | mValue.mDensity = 1.0; |
495 | 0 | } |
496 | | |
497 | | void |
498 | | ResponsiveImageCandidate::SetParameterInvalid() |
499 | 0 | { |
500 | 0 | mType = eCandidateType_Invalid; |
501 | 0 | // mValue shouldn't actually be used for this type, but set it to default |
502 | 0 | // anyway |
503 | 0 | mValue.mDensity = 1.0; |
504 | 0 | } |
505 | | |
506 | | void |
507 | | ResponsiveImageCandidate::SetParameterAsDensity(double aDensity) |
508 | 0 | { |
509 | 0 | MOZ_ASSERT(mType == eCandidateType_Invalid, "double setting candidate type"); |
510 | 0 |
|
511 | 0 | mType = eCandidateType_Density; |
512 | 0 | mValue.mDensity = aDensity; |
513 | 0 | } |
514 | | |
515 | | // Represents all supported descriptors for a ResponsiveImageCandidate, though |
516 | | // there is no candidate type that uses all of these. This should generally |
517 | | // match the mValue union of ResponsiveImageCandidate. |
518 | | struct ResponsiveImageDescriptors { |
519 | | ResponsiveImageDescriptors() |
520 | 0 | : mInvalid(false) {}; |
521 | | |
522 | | Maybe<double> mDensity; |
523 | | Maybe<int32_t> mWidth; |
524 | | // We don't support "h" descriptors yet and they are not spec'd, but the |
525 | | // current spec does specify that they can be silently ignored (whereas |
526 | | // entirely unknown descriptors cause us to invalidate the candidate) |
527 | | Maybe<int32_t> mFutureCompatHeight; |
528 | | // If this descriptor set is bogus, e.g. a value was added twice (and thus |
529 | | // dropped) or an unknown descriptor was added. |
530 | | bool mInvalid; |
531 | | |
532 | | void AddDescriptor(const nsAString& aDescriptor); |
533 | | bool Valid(); |
534 | | // Use the current set of descriptors to configure a candidate |
535 | | void FillCandidate(ResponsiveImageCandidate &aCandidate); |
536 | | }; |
537 | | |
538 | | // Try to parse a single descriptor from a string. If value already set or |
539 | | // unknown, sets invalid flag. |
540 | | // This corresponds to the descriptor "Descriptor parser" step in: |
541 | | // https://html.spec.whatwg.org/#parse-a-srcset-attribute |
542 | | void |
543 | | ResponsiveImageDescriptors::AddDescriptor(const nsAString& aDescriptor) |
544 | 0 | { |
545 | 0 | if (aDescriptor.IsEmpty()) { |
546 | 0 | return; |
547 | 0 | } |
548 | 0 | |
549 | 0 | // All currently supported descriptors end with an identifying character. |
550 | 0 | nsAString::const_iterator descStart, descType; |
551 | 0 | aDescriptor.BeginReading(descStart); |
552 | 0 | aDescriptor.EndReading(descType); |
553 | 0 | descType--; |
554 | 0 | const nsDependentSubstring& valueStr = Substring(descStart, descType); |
555 | 0 | if (*descType == char16_t('w')) { |
556 | 0 | int32_t possibleWidth; |
557 | 0 | // If the value is not a valid non-negative integer, it doesn't match this |
558 | 0 | // descriptor, fall through. |
559 | 0 | if (ParseInteger(valueStr, possibleWidth) && possibleWidth >= 0) { |
560 | 0 | if (possibleWidth != 0 && mWidth.isNothing() && mDensity.isNothing()) { |
561 | 0 | mWidth.emplace(possibleWidth); |
562 | 0 | } else { |
563 | 0 | // Valid width descriptor, but width or density were already seen, sizes |
564 | 0 | // support isn't enabled, or it parsed to 0, which is an error per spec |
565 | 0 | mInvalid = true; |
566 | 0 | } |
567 | 0 |
|
568 | 0 | return; |
569 | 0 | } |
570 | 0 | } else if (*descType == char16_t('h')) { |
571 | 0 | int32_t possibleHeight; |
572 | 0 | // If the value is not a valid non-negative integer, it doesn't match this |
573 | 0 | // descriptor, fall through. |
574 | 0 | if (ParseInteger(valueStr, possibleHeight) && possibleHeight >= 0) { |
575 | 0 | if (possibleHeight != 0 && mFutureCompatHeight.isNothing() && |
576 | 0 | mDensity.isNothing()) { |
577 | 0 | mFutureCompatHeight.emplace(possibleHeight); |
578 | 0 | } else { |
579 | 0 | // Valid height descriptor, but height or density were already seen, or |
580 | 0 | // it parsed to zero, which is an error per spec |
581 | 0 | mInvalid = true; |
582 | 0 | } |
583 | 0 |
|
584 | 0 | return; |
585 | 0 | } |
586 | 0 | } else if (*descType == char16_t('x')) { |
587 | 0 | // If the value is not a valid floating point number, it doesn't match this |
588 | 0 | // descriptor, fall through. |
589 | 0 | double possibleDensity = 0.0; |
590 | 0 | if (ParseFloat(valueStr, possibleDensity)) { |
591 | 0 | if (possibleDensity >= 0.0 && |
592 | 0 | mWidth.isNothing() && |
593 | 0 | mDensity.isNothing() && |
594 | 0 | mFutureCompatHeight.isNothing()) { |
595 | 0 | mDensity.emplace(possibleDensity); |
596 | 0 | } else { |
597 | 0 | // Valid density descriptor, but height or width or density were already |
598 | 0 | // seen, or it parsed to less than zero, which is an error per spec |
599 | 0 | mInvalid = true; |
600 | 0 | } |
601 | 0 |
|
602 | 0 | return; |
603 | 0 | } |
604 | 0 | } |
605 | 0 |
|
606 | 0 | // Matched no known descriptor, mark this descriptor set invalid |
607 | 0 | mInvalid = true; |
608 | 0 | } |
609 | | |
610 | | bool |
611 | | ResponsiveImageDescriptors::Valid() |
612 | 0 | { |
613 | 0 | return !mInvalid && !(mFutureCompatHeight.isSome() && mWidth.isNothing()); |
614 | 0 | } |
615 | | |
616 | | void |
617 | | ResponsiveImageDescriptors::FillCandidate(ResponsiveImageCandidate &aCandidate) |
618 | 0 | { |
619 | 0 | if (!Valid()) { |
620 | 0 | aCandidate.SetParameterInvalid(); |
621 | 0 | } else if (mWidth.isSome()) { |
622 | 0 | MOZ_ASSERT(mDensity.isNothing()); // Shouldn't be valid |
623 | 0 |
|
624 | 0 | aCandidate.SetParameterAsComputedWidth(*mWidth); |
625 | 0 | } else if (mDensity.isSome()) { |
626 | 0 | MOZ_ASSERT(mWidth.isNothing()); // Shouldn't be valid |
627 | 0 |
|
628 | 0 | aCandidate.SetParameterAsDensity(*mDensity); |
629 | 0 | } else { |
630 | 0 | // A valid set of descriptors with no density nor width (e.g. an empty set) |
631 | 0 | // becomes 1.0 density, per spec |
632 | 0 | aCandidate.SetParameterAsDensity(1.0); |
633 | 0 | } |
634 | 0 | } |
635 | | |
636 | | bool |
637 | | ResponsiveImageCandidate::ConsumeDescriptors(nsAString::const_iterator& aIter, |
638 | | const nsAString::const_iterator& aIterEnd) |
639 | 0 | { |
640 | 0 | nsAString::const_iterator &iter = aIter; |
641 | 0 | const nsAString::const_iterator &end = aIterEnd; |
642 | 0 |
|
643 | 0 | bool inParens = false; |
644 | 0 |
|
645 | 0 | ResponsiveImageDescriptors descriptors; |
646 | 0 |
|
647 | 0 | // Parse descriptor list. |
648 | 0 | // This corresponds to the descriptor parsing loop from: |
649 | 0 | // https://html.spec.whatwg.org/#parse-a-srcset-attribute |
650 | 0 |
|
651 | 0 | // Skip initial whitespace |
652 | 0 | for (; iter != end && nsContentUtils::IsHTMLWhitespace(*iter); ++iter); |
653 | 0 |
|
654 | 0 | nsAString::const_iterator currentDescriptor = iter; |
655 | 0 |
|
656 | 0 | for (;; iter++) { |
657 | 0 | if (iter == end) { |
658 | 0 | descriptors.AddDescriptor(Substring(currentDescriptor, iter)); |
659 | 0 | break; |
660 | 0 | } else if (inParens) { |
661 | 0 | if (*iter == char16_t(')')) { |
662 | 0 | inParens = false; |
663 | 0 | } |
664 | 0 | } else { |
665 | 0 | if (*iter == char16_t(',')) { |
666 | 0 | // End of descriptors, flush current descriptor and advance past comma |
667 | 0 | // before breaking |
668 | 0 | descriptors.AddDescriptor(Substring(currentDescriptor, iter)); |
669 | 0 | iter++; |
670 | 0 | break; |
671 | 0 | } else if (nsContentUtils::IsHTMLWhitespace(*iter)) { |
672 | 0 | // End of current descriptor, consume it, skip spaces |
673 | 0 | // ("After descriptor" state in spec) before continuing |
674 | 0 | descriptors.AddDescriptor(Substring(currentDescriptor, iter)); |
675 | 0 | for (; iter != end && nsContentUtils::IsHTMLWhitespace(*iter); ++iter); |
676 | 0 | if (iter == end) { |
677 | 0 | break; |
678 | 0 | } |
679 | 0 | currentDescriptor = iter; |
680 | 0 | // Leave one whitespace so the loop advances to this position next iteration |
681 | 0 | iter--; |
682 | 0 | } else if (*iter == char16_t('(')) { |
683 | 0 | inParens = true; |
684 | 0 | } |
685 | 0 | } |
686 | 0 | } |
687 | 0 |
|
688 | 0 | descriptors.FillCandidate(*this); |
689 | 0 |
|
690 | 0 | return Type() != eCandidateType_Invalid; |
691 | 0 | } |
692 | | |
693 | | bool |
694 | | ResponsiveImageCandidate::HasSameParameter(const ResponsiveImageCandidate & aOther) const |
695 | 0 | { |
696 | 0 | if (aOther.mType != mType) { |
697 | 0 | return false; |
698 | 0 | } |
699 | 0 | |
700 | 0 | if (mType == eCandidateType_Default) { |
701 | 0 | return true; |
702 | 0 | } |
703 | 0 | |
704 | 0 | if (mType == eCandidateType_Density) { |
705 | 0 | return aOther.mValue.mDensity == mValue.mDensity; |
706 | 0 | } |
707 | 0 | |
708 | 0 | if (mType == eCandidateType_Invalid) { |
709 | 0 | MOZ_ASSERT(false, "Comparing invalid candidates?"); |
710 | 0 | return true; |
711 | 0 | } else if (mType == eCandidateType_ComputedFromWidth) { |
712 | 0 | return aOther.mValue.mWidth == mValue.mWidth; |
713 | 0 | } |
714 | 0 | |
715 | 0 | MOZ_ASSERT(false, "Somebody forgot to check for all uses of this enum"); |
716 | 0 | return false; |
717 | 0 | } |
718 | | |
719 | | const nsAString& |
720 | | ResponsiveImageCandidate::URLString() const |
721 | 0 | { |
722 | 0 | return mURLString; |
723 | 0 | } |
724 | | |
725 | | nsIPrincipal* |
726 | | ResponsiveImageCandidate::TriggeringPrincipal() const |
727 | 0 | { |
728 | 0 | return mTriggeringPrincipal; |
729 | 0 | } |
730 | | |
731 | | double |
732 | | ResponsiveImageCandidate::Density(ResponsiveImageSelector *aSelector) const |
733 | 0 | { |
734 | 0 | if (mType == eCandidateType_ComputedFromWidth) { |
735 | 0 | double width; |
736 | 0 | if (!aSelector->ComputeFinalWidthForCurrentViewport(&width)) { |
737 | 0 | return 1.0; |
738 | 0 | } |
739 | 0 | return Density(width); |
740 | 0 | } |
741 | 0 | |
742 | 0 | // Other types don't need matching width |
743 | 0 | MOZ_ASSERT(mType == eCandidateType_Default || mType == eCandidateType_Density, |
744 | 0 | "unhandled candidate type"); |
745 | 0 | return Density(-1); |
746 | 0 | } |
747 | | |
748 | | double |
749 | | ResponsiveImageCandidate::Density(double aMatchingWidth) const |
750 | 0 | { |
751 | 0 | if (mType == eCandidateType_Invalid) { |
752 | 0 | MOZ_ASSERT(false, "Getting density for uninitialized candidate"); |
753 | 0 | return 1.0; |
754 | 0 | } |
755 | 0 |
|
756 | 0 | if (mType == eCandidateType_Default) { |
757 | 0 | return 1.0; |
758 | 0 | } |
759 | 0 | |
760 | 0 | if (mType == eCandidateType_Density) { |
761 | 0 | return mValue.mDensity; |
762 | 0 | } else if (mType == eCandidateType_ComputedFromWidth) { |
763 | 0 | if (aMatchingWidth < 0) { |
764 | 0 | MOZ_ASSERT(false, "Don't expect to have a negative matching width at this point"); |
765 | 0 | return 1.0; |
766 | 0 | } |
767 | 0 | double density = double(mValue.mWidth) / aMatchingWidth; |
768 | 0 | MOZ_ASSERT(density > 0.0); |
769 | 0 | return density; |
770 | 0 | } |
771 | 0 |
|
772 | 0 | MOZ_ASSERT(false, "Unknown candidate type"); |
773 | 0 | return 1.0; |
774 | 0 | } |
775 | | |
776 | | bool |
777 | | ResponsiveImageCandidate::IsComputedFromWidth() const |
778 | 0 | { |
779 | 0 | if (mType == eCandidateType_ComputedFromWidth) { |
780 | 0 | return true; |
781 | 0 | } |
782 | 0 | |
783 | 0 | MOZ_ASSERT(mType == eCandidateType_Default || mType == eCandidateType_Density, |
784 | 0 | "Unknown candidate type"); |
785 | 0 | return false; |
786 | 0 | } |
787 | | |
788 | | } // namespace dom |
789 | | } // namespace mozilla |