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