Backed out changeset 9d8b4c0b99ed (bug 1945683) for causing btime failures. CLOSED...
[gecko.git] / dom / base / ResponsiveImageSelector.cpp
blob77b92577474bf74fab9ebb8cd540daee4099219c
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/. */
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"
18 #include "nsCSSProps.h"
20 using namespace mozilla;
21 using namespace mozilla::dom;
23 namespace mozilla::dom {
25 NS_IMPL_CYCLE_COLLECTION(ResponsiveImageSelector, mOwnerNode)
27 static bool ParseInteger(const nsAString& aString, int32_t& aInt) {
28 nsContentUtils::ParseHTMLIntegerResultFlags parseResult;
29 aInt = nsContentUtils::ParseHTMLInteger(aString, &parseResult);
30 return !(parseResult &
31 (nsContentUtils::eParseHTMLInteger_Error |
32 nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput |
33 nsContentUtils::eParseHTMLInteger_NonStandard));
36 static bool ParseFloat(const nsAString& aString, double& aDouble) {
37 // Check if it is a valid floating-point number first since the result of
38 // nsString.ToDouble() is more lenient than the spec,
39 // https://html.spec.whatwg.org/#valid-floating-point-number
40 nsAString::const_iterator iter, end;
41 aString.BeginReading(iter);
42 aString.EndReading(end);
44 if (iter == end) {
45 return false;
48 if (*iter == char16_t('-') && ++iter == end) {
49 return false;
52 if (IsAsciiDigit(*iter)) {
53 for (; iter != end && IsAsciiDigit(*iter); ++iter);
54 } else if (*iter == char16_t('.')) {
55 // Do nothing, jumps to fraction part
56 } else {
57 return false;
60 // Fraction
61 if (*iter == char16_t('.')) {
62 ++iter;
63 if (iter == end || !IsAsciiDigit(*iter)) {
64 // U+002E FULL STOP character (.) must be followed by one or more ASCII
65 // digits
66 return false;
69 for (; iter != end && IsAsciiDigit(*iter); ++iter);
72 if (iter != end && (*iter == char16_t('e') || *iter == char16_t('E'))) {
73 ++iter;
74 if (*iter == char16_t('-') || *iter == char16_t('+')) {
75 ++iter;
78 if (iter == end || !IsAsciiDigit(*iter)) {
79 // Should have one or more ASCII digits
80 return false;
83 for (; iter != end && IsAsciiDigit(*iter); ++iter);
86 if (iter != end) {
87 return false;
90 nsresult rv;
91 aDouble = PromiseFlatString(aString).ToDouble(&rv);
92 return NS_SUCCEEDED(rv);
95 ResponsiveImageSelector::ResponsiveImageSelector(nsIContent* aContent)
96 : mOwnerNode(aContent), mSelectedCandidateIndex(-1) {}
98 ResponsiveImageSelector::ResponsiveImageSelector(dom::Document* aDocument)
99 : mOwnerNode(aDocument), mSelectedCandidateIndex(-1) {}
101 ResponsiveImageSelector::~ResponsiveImageSelector() = default;
103 void ResponsiveImageSelector::ParseSourceSet(
104 const nsAString& aSrcSet,
105 FunctionRef<void(ResponsiveImageCandidate&&)> aCallback) {
106 nsAString::const_iterator iter, end;
107 aSrcSet.BeginReading(iter);
108 aSrcSet.EndReading(end);
110 // Read URL / descriptor pairs
111 while (iter != end) {
112 nsAString::const_iterator url, urlEnd, descriptor;
114 // Skip whitespace and commas.
115 // Extra commas at this point are a non-fatal syntax error.
116 for (; iter != end &&
117 (nsContentUtils::IsHTMLWhitespace(*iter) || *iter == char16_t(','));
118 ++iter);
120 if (iter == end) {
121 break;
124 url = iter;
126 // Find end of url
127 for (; iter != end && !nsContentUtils::IsHTMLWhitespace(*iter); ++iter);
129 // Omit trailing commas from URL.
130 // Multiple commas are a non-fatal error.
131 while (iter != url) {
132 if (*(--iter) != char16_t(',')) {
133 iter++;
134 break;
138 const nsDependentSubstring& urlStr = Substring(url, iter);
140 MOZ_ASSERT(url != iter, "Shouldn't have empty URL at this point");
142 ResponsiveImageCandidate candidate;
143 if (candidate.ConsumeDescriptors(iter, end)) {
144 candidate.SetURLSpec(urlStr);
145 aCallback(std::move(candidate));
150 // http://www.whatwg.org/specs/web-apps/current-work/#processing-the-image-candidates
151 bool ResponsiveImageSelector::SetCandidatesFromSourceSet(
152 const nsAString& aSrcSet, nsIPrincipal* aTriggeringPrincipal) {
153 ClearSelectedCandidate();
155 if (!mOwnerNode || !mOwnerNode->GetBaseURI()) {
156 MOZ_ASSERT(false, "Should not be parsing SourceSet without a document");
157 return false;
160 mCandidates.Clear();
162 auto eachCandidate = [&](ResponsiveImageCandidate&& aCandidate) {
163 aCandidate.SetTriggeringPrincipal(
164 nsContentUtils::GetAttrTriggeringPrincipal(
165 Content(), aCandidate.URLString(), aTriggeringPrincipal));
166 AppendCandidateIfUnique(std::move(aCandidate));
169 ParseSourceSet(aSrcSet, eachCandidate);
171 bool parsedCandidates = !mCandidates.IsEmpty();
173 // Re-add default to end of list
174 MaybeAppendDefaultCandidate();
176 return parsedCandidates;
179 uint32_t ResponsiveImageSelector::NumCandidates(bool aIncludeDefault) {
180 uint32_t candidates = mCandidates.Length();
182 // If present, the default candidate is the last item
183 if (!aIncludeDefault && candidates && mCandidates.LastElement().IsDefault()) {
184 candidates--;
187 return candidates;
190 nsIContent* ResponsiveImageSelector::Content() {
191 return mOwnerNode->IsContent() ? mOwnerNode->AsContent() : nullptr;
194 dom::Document* ResponsiveImageSelector::Document() {
195 return mOwnerNode->OwnerDoc();
198 void ResponsiveImageSelector::ClearDefaultSource() {
199 ClearSelectedCandidate();
200 // Check if the last element of our candidates is a default
201 if (!mCandidates.IsEmpty() && mCandidates.LastElement().IsDefault()) {
202 mCandidates.RemoveLastElement();
206 void ResponsiveImageSelector::SetDefaultSource(nsIURI* aURI,
207 nsIPrincipal* aPrincipal) {
208 ClearDefaultSource();
209 mDefaultSourceTriggeringPrincipal = aPrincipal;
210 mDefaultSourceURL = VoidString();
211 if (aURI) {
212 nsAutoCString spec;
213 aURI->GetSpec(spec);
214 CopyUTF8toUTF16(spec, mDefaultSourceURL);
216 MaybeAppendDefaultCandidate();
219 void ResponsiveImageSelector::SetDefaultSource(const nsAString& aURLString,
220 nsIPrincipal* aPrincipal) {
221 ClearDefaultSource();
222 mDefaultSourceTriggeringPrincipal = aPrincipal;
223 mDefaultSourceURL = aURLString;
224 MaybeAppendDefaultCandidate();
227 void ResponsiveImageSelector::ClearSelectedCandidate() {
228 mSelectedCandidateIndex = -1;
229 mSelectedCandidateURL = nullptr;
232 bool ResponsiveImageSelector::SetSizesFromDescriptor(const nsAString& aSizes) {
233 ClearSelectedCandidate();
235 NS_ConvertUTF16toUTF8 sizes(aSizes);
236 mServoSourceSizeList.reset(Servo_SourceSizeList_Parse(&sizes));
237 return !!mServoSourceSizeList;
240 void ResponsiveImageSelector::AppendCandidateIfUnique(
241 ResponsiveImageCandidate&& aCandidate) {
242 int numCandidates = mCandidates.Length();
244 // With the exception of Default, which should not be added until we are done
245 // building the list.
246 if (aCandidate.IsDefault()) {
247 return;
250 // Discard candidates with identical parameters, they will never match
251 for (int i = 0; i < numCandidates; i++) {
252 if (mCandidates[i].HasSameParameter(aCandidate)) {
253 return;
257 mCandidates.AppendElement(std::move(aCandidate));
260 void ResponsiveImageSelector::MaybeAppendDefaultCandidate() {
261 if (mDefaultSourceURL.IsEmpty()) {
262 return;
265 int numCandidates = mCandidates.Length();
267 // https://html.spec.whatwg.org/multipage/embedded-content.html#update-the-source-set
268 // step 4.1.3:
269 // If child has a src attribute whose value is not the empty string and source
270 // set does not contain an image source with a density descriptor value of 1,
271 // and no image source with a width descriptor, append child's src attribute
272 // value to source set.
273 for (int i = 0; i < numCandidates; i++) {
274 if (mCandidates[i].IsComputedFromWidth()) {
275 return;
276 } else if (mCandidates[i].Density(this) == 1.0) {
277 return;
281 ResponsiveImageCandidate defaultCandidate;
282 defaultCandidate.SetParameterDefault();
283 defaultCandidate.SetURLSpec(mDefaultSourceURL);
284 defaultCandidate.SetTriggeringPrincipal(mDefaultSourceTriggeringPrincipal);
285 // We don't use MaybeAppend since we want to keep this even if it can never
286 // match, as it may if the source set changes.
287 mCandidates.AppendElement(std::move(defaultCandidate));
290 already_AddRefed<nsIURI> ResponsiveImageSelector::GetSelectedImageURL() {
291 SelectImage();
293 nsCOMPtr<nsIURI> url = mSelectedCandidateURL;
294 return url.forget();
297 bool ResponsiveImageSelector::GetSelectedImageURLSpec(nsAString& aResult) {
298 SelectImage();
300 if (mSelectedCandidateIndex == -1) {
301 return false;
304 aResult.Assign(mCandidates[mSelectedCandidateIndex].URLString());
305 return true;
308 double ResponsiveImageSelector::GetSelectedImageDensity() {
309 int bestIndex = GetSelectedCandidateIndex();
310 if (bestIndex < 0) {
311 return 1.0;
314 return mCandidates[bestIndex].Density(this);
317 nsIPrincipal* ResponsiveImageSelector::GetSelectedImageTriggeringPrincipal() {
318 int bestIndex = GetSelectedCandidateIndex();
319 if (bestIndex < 0) {
320 return nullptr;
323 return mCandidates[bestIndex].TriggeringPrincipal();
326 bool ResponsiveImageSelector::SelectImage(bool aReselect) {
327 if (!aReselect && mSelectedCandidateIndex != -1) {
328 // Already have selection
329 return false;
332 int oldBest = mSelectedCandidateIndex;
333 ClearSelectedCandidate();
335 int numCandidates = mCandidates.Length();
336 if (!numCandidates) {
337 return oldBest != -1;
340 dom::Document* doc = Document();
341 nsPresContext* pctx = doc->GetPresContext();
342 nsCOMPtr<nsIURI> baseURI = mOwnerNode->GetBaseURI();
344 if (!pctx || !baseURI) {
345 return oldBest != -1;
348 double displayDensity = pctx->CSSPixelsToDevPixels(1.0f);
349 double overrideDPPX = pctx->GetOverrideDPPX();
351 if (overrideDPPX > 0) {
352 displayDensity = overrideDPPX;
354 if (doc->ShouldResistFingerprinting(RFPTarget::WindowDevicePixelRatio)) {
355 displayDensity = nsRFPService::GetDevicePixelRatioAtZoom(1);
358 // Per spec, "In a UA-specific manner, choose one image source"
359 // - For now, select the lowest density greater than displayDensity, otherwise
360 // the greatest density available
362 // If the list contains computed width candidates, compute the current
363 // effective image width.
364 double computedWidth = -1;
365 for (int i = 0; i < numCandidates; i++) {
366 if (mCandidates[i].IsComputedFromWidth()) {
367 DebugOnly<bool> computeResult =
368 ComputeFinalWidthForCurrentViewport(&computedWidth);
369 MOZ_ASSERT(computeResult,
370 "Computed candidates not allowed without sizes data");
371 break;
375 int bestIndex = -1;
376 double bestDensity = -1.0;
377 for (int i = 0; i < numCandidates; i++) {
378 double candidateDensity = (computedWidth == -1)
379 ? mCandidates[i].Density(this)
380 : mCandidates[i].Density(computedWidth);
381 // - If bestIndex is below display density, pick anything larger.
382 // - Otherwise, prefer if less dense than bestDensity but still above
383 // displayDensity.
384 if (bestIndex == -1 ||
385 (bestDensity < displayDensity && candidateDensity > bestDensity) ||
386 (candidateDensity >= displayDensity &&
387 candidateDensity < bestDensity)) {
388 bestIndex = i;
389 bestDensity = candidateDensity;
393 MOZ_ASSERT(bestIndex >= 0 && bestIndex < numCandidates);
395 // Resolve URL
396 nsresult rv;
397 const nsAString& urlStr = mCandidates[bestIndex].URLString();
398 nsCOMPtr<nsIURI> candidateURL;
399 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(candidateURL),
400 urlStr, doc, baseURI);
402 mSelectedCandidateURL = NS_SUCCEEDED(rv) ? candidateURL : nullptr;
403 mSelectedCandidateIndex = bestIndex;
405 return mSelectedCandidateIndex != oldBest;
408 int ResponsiveImageSelector::GetSelectedCandidateIndex() {
409 SelectImage();
411 return mSelectedCandidateIndex;
414 bool ResponsiveImageSelector::ComputeFinalWidthForCurrentViewport(
415 double* aWidth) {
416 dom::Document* doc = Document();
417 PresShell* presShell = doc->GetPresShell();
418 nsPresContext* pctx = presShell ? presShell->GetPresContext() : nullptr;
420 if (!pctx) {
421 return false;
423 nscoord effectiveWidth =
424 presShell->StyleSet()->EvaluateSourceSizeList(mServoSourceSizeList.get());
426 *aWidth =
427 nsPresContext::AppUnitsToDoubleCSSPixels(std::max(effectiveWidth, 0));
428 return true;
431 ResponsiveImageCandidate::ResponsiveImageCandidate() {
432 mType = CandidateType::Invalid;
433 mValue.mDensity = 1.0;
436 void ResponsiveImageCandidate::SetURLSpec(const nsAString& aURLString) {
437 mURLString = aURLString;
440 void ResponsiveImageCandidate::SetTriggeringPrincipal(
441 nsIPrincipal* aPrincipal) {
442 mTriggeringPrincipal = aPrincipal;
445 void ResponsiveImageCandidate::SetParameterAsComputedWidth(int32_t aWidth) {
446 mType = CandidateType::ComputedFromWidth;
447 mValue.mWidth = aWidth;
450 void ResponsiveImageCandidate::SetParameterDefault() {
451 MOZ_ASSERT(!IsValid(), "double setting candidate type");
453 mType = CandidateType::Default;
454 // mValue shouldn't actually be used for this type, but set it to default
455 // anyway
456 mValue.mDensity = 1.0;
459 void ResponsiveImageCandidate::SetParameterInvalid() {
460 mType = CandidateType::Invalid;
461 // mValue shouldn't actually be used for this type, but set it to default
462 // anyway
463 mValue.mDensity = 1.0;
466 void ResponsiveImageCandidate::SetParameterAsDensity(double aDensity) {
467 MOZ_ASSERT(!IsValid(), "double setting candidate type");
469 mType = CandidateType::Density;
470 mValue.mDensity = aDensity;
473 // Represents all supported descriptors for a ResponsiveImageCandidate, though
474 // there is no candidate type that uses all of these. This should generally
475 // match the mValue union of ResponsiveImageCandidate.
476 struct ResponsiveImageDescriptors {
477 ResponsiveImageDescriptors() : mInvalid(false) {};
479 Maybe<double> mDensity;
480 Maybe<int32_t> mWidth;
481 // We don't support "h" descriptors yet and they are not spec'd, but the
482 // current spec does specify that they can be silently ignored (whereas
483 // entirely unknown descriptors cause us to invalidate the candidate)
485 // If we ever start honoring them we should serialize them in
486 // AppendDescriptors.
487 Maybe<int32_t> mFutureCompatHeight;
488 // If this descriptor set is bogus, e.g. a value was added twice (and thus
489 // dropped) or an unknown descriptor was added.
490 bool mInvalid;
492 void AddDescriptor(const nsAString& aDescriptor);
493 bool Valid();
494 // Use the current set of descriptors to configure a candidate
495 void FillCandidate(ResponsiveImageCandidate& aCandidate);
498 // Try to parse a single descriptor from a string. If value already set or
499 // unknown, sets invalid flag.
500 // This corresponds to the descriptor "Descriptor parser" step in:
501 // https://html.spec.whatwg.org/#parse-a-srcset-attribute
502 void ResponsiveImageDescriptors::AddDescriptor(const nsAString& aDescriptor) {
503 if (aDescriptor.IsEmpty()) {
504 return;
507 // All currently supported descriptors end with an identifying character.
508 nsAString::const_iterator descStart, descType;
509 aDescriptor.BeginReading(descStart);
510 aDescriptor.EndReading(descType);
511 descType--;
512 const nsDependentSubstring& valueStr = Substring(descStart, descType);
513 if (*descType == char16_t('w')) {
514 int32_t possibleWidth;
515 // If the value is not a valid non-negative integer, it doesn't match this
516 // descriptor, fall through.
517 if (ParseInteger(valueStr, possibleWidth) && possibleWidth >= 0) {
518 if (possibleWidth != 0 && mWidth.isNothing() && mDensity.isNothing()) {
519 mWidth.emplace(possibleWidth);
520 } else {
521 // Valid width descriptor, but width or density were already seen, sizes
522 // support isn't enabled, or it parsed to 0, which is an error per spec
523 mInvalid = true;
526 return;
528 } else if (*descType == char16_t('h')) {
529 int32_t possibleHeight;
530 // If the value is not a valid non-negative integer, it doesn't match this
531 // descriptor, fall through.
532 if (ParseInteger(valueStr, possibleHeight) && possibleHeight >= 0) {
533 if (possibleHeight != 0 && mFutureCompatHeight.isNothing() &&
534 mDensity.isNothing()) {
535 mFutureCompatHeight.emplace(possibleHeight);
536 } else {
537 // Valid height descriptor, but height or density were already seen, or
538 // it parsed to zero, which is an error per spec
539 mInvalid = true;
542 return;
544 } else if (*descType == char16_t('x')) {
545 // If the value is not a valid floating point number, it doesn't match this
546 // descriptor, fall through.
547 double possibleDensity = 0.0;
548 if (ParseFloat(valueStr, possibleDensity)) {
549 if (possibleDensity >= 0.0 && mWidth.isNothing() &&
550 mDensity.isNothing() && mFutureCompatHeight.isNothing()) {
551 mDensity.emplace(possibleDensity);
552 } else {
553 // Valid density descriptor, but height or width or density were already
554 // seen, or it parsed to less than zero, which is an error per spec
555 mInvalid = true;
558 return;
562 // Matched no known descriptor, mark this descriptor set invalid
563 mInvalid = true;
566 bool ResponsiveImageDescriptors::Valid() {
567 return !mInvalid && !(mFutureCompatHeight.isSome() && mWidth.isNothing());
570 void ResponsiveImageDescriptors::FillCandidate(
571 ResponsiveImageCandidate& aCandidate) {
572 if (!Valid()) {
573 aCandidate.SetParameterInvalid();
574 } else if (mWidth.isSome()) {
575 MOZ_ASSERT(mDensity.isNothing()); // Shouldn't be valid
577 aCandidate.SetParameterAsComputedWidth(*mWidth);
578 } else if (mDensity.isSome()) {
579 MOZ_ASSERT(mWidth.isNothing()); // Shouldn't be valid
581 aCandidate.SetParameterAsDensity(*mDensity);
582 } else {
583 // A valid set of descriptors with no density nor width (e.g. an empty set)
584 // becomes 1.0 density, per spec
585 aCandidate.SetParameterAsDensity(1.0);
589 bool ResponsiveImageCandidate::ConsumeDescriptors(
590 nsAString::const_iterator& aIter,
591 const nsAString::const_iterator& aIterEnd) {
592 nsAString::const_iterator& iter = aIter;
593 const nsAString::const_iterator& end = aIterEnd;
595 bool inParens = false;
597 ResponsiveImageDescriptors descriptors;
599 // Parse descriptor list.
600 // This corresponds to the descriptor parsing loop from:
601 // https://html.spec.whatwg.org/#parse-a-srcset-attribute
603 // Skip initial whitespace
604 for (; iter != end && nsContentUtils::IsHTMLWhitespace(*iter); ++iter);
606 nsAString::const_iterator currentDescriptor = iter;
608 for (;; iter++) {
609 if (iter == end) {
610 descriptors.AddDescriptor(Substring(currentDescriptor, iter));
611 break;
612 } else if (inParens) {
613 if (*iter == char16_t(')')) {
614 inParens = false;
616 } else {
617 if (*iter == char16_t(',')) {
618 // End of descriptors, flush current descriptor and advance past comma
619 // before breaking
620 descriptors.AddDescriptor(Substring(currentDescriptor, iter));
621 iter++;
622 break;
624 if (nsContentUtils::IsHTMLWhitespace(*iter)) {
625 // End of current descriptor, consume it, skip spaces
626 // ("After descriptor" state in spec) before continuing
627 descriptors.AddDescriptor(Substring(currentDescriptor, iter));
628 for (; iter != end && nsContentUtils::IsHTMLWhitespace(*iter); ++iter);
629 if (iter == end) {
630 break;
632 currentDescriptor = iter;
633 // Leave one whitespace so the loop advances to this position next
634 // iteration
635 iter--;
636 } else if (*iter == char16_t('(')) {
637 inParens = true;
642 descriptors.FillCandidate(*this);
644 return IsValid();
647 bool ResponsiveImageCandidate::HasSameParameter(
648 const ResponsiveImageCandidate& aOther) const {
649 if (aOther.mType != mType) {
650 return false;
653 if (mType == CandidateType::Default) {
654 return true;
657 if (mType == CandidateType::Density) {
658 return aOther.mValue.mDensity == mValue.mDensity;
661 if (mType == CandidateType::Invalid) {
662 MOZ_ASSERT_UNREACHABLE("Comparing invalid candidates?");
663 return true;
666 if (mType == CandidateType::ComputedFromWidth) {
667 return aOther.mValue.mWidth == mValue.mWidth;
670 MOZ_ASSERT(false, "Somebody forgot to check for all uses of this enum");
671 return false;
674 double ResponsiveImageCandidate::Density(
675 ResponsiveImageSelector* aSelector) const {
676 if (mType == CandidateType::ComputedFromWidth) {
677 double width;
678 if (!aSelector->ComputeFinalWidthForCurrentViewport(&width)) {
679 return 1.0;
681 return Density(width);
684 // Other types don't need matching width
685 MOZ_ASSERT(mType == CandidateType::Default || mType == CandidateType::Density,
686 "unhandled candidate type");
687 return Density(-1);
690 void ResponsiveImageCandidate::AppendDescriptors(
691 nsAString& aDescriptors) const {
692 MOZ_ASSERT(IsValid());
693 switch (mType) {
694 case CandidateType::Default:
695 case CandidateType::Invalid:
696 return;
697 case CandidateType::ComputedFromWidth:
698 aDescriptors.Append(' ');
699 aDescriptors.AppendInt(mValue.mWidth);
700 aDescriptors.Append('w');
701 return;
702 case CandidateType::Density:
703 aDescriptors.Append(' ');
704 aDescriptors.AppendFloat(mValue.mDensity);
705 aDescriptors.Append('x');
706 return;
710 double ResponsiveImageCandidate::Density(double aMatchingWidth) const {
711 if (mType == CandidateType::Invalid) {
712 MOZ_ASSERT(false, "Getting density for uninitialized candidate");
713 return 1.0;
716 if (mType == CandidateType::Default) {
717 return 1.0;
720 if (mType == CandidateType::Density) {
721 return mValue.mDensity;
723 if (mType == CandidateType::ComputedFromWidth) {
724 if (aMatchingWidth < 0) {
725 MOZ_ASSERT(
726 false,
727 "Don't expect to have a negative matching width at this point");
728 return 1.0;
730 double density = double(mValue.mWidth) / aMatchingWidth;
731 MOZ_ASSERT(density > 0.0);
732 return density;
735 MOZ_ASSERT(false, "Unknown candidate type");
736 return 1.0;
739 } // namespace mozilla::dom