Bug 1852740: add tests for the `fetchpriority` attribute in Link headers. r=necko...
[gecko.git] / dom / base / ResponsiveImageSelector.cpp
blobe02561b1d7394abc7b5c63622f9206b20215c0d3
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)
55 } else if (*iter == char16_t('.')) {
56 // Do nothing, jumps to fraction part
57 } else {
58 return false;
61 // Fraction
62 if (*iter == char16_t('.')) {
63 ++iter;
64 if (iter == end || !IsAsciiDigit(*iter)) {
65 // U+002E FULL STOP character (.) must be followed by one or more ASCII
66 // digits
67 return false;
70 for (; iter != end && IsAsciiDigit(*iter); ++iter)
74 if (iter != end && (*iter == char16_t('e') || *iter == char16_t('E'))) {
75 ++iter;
76 if (*iter == char16_t('-') || *iter == char16_t('+')) {
77 ++iter;
80 if (iter == end || !IsAsciiDigit(*iter)) {
81 // Should have one or more ASCII digits
82 return false;
85 for (; iter != end && IsAsciiDigit(*iter); ++iter)
89 if (iter != end) {
90 return false;
93 nsresult rv;
94 aDouble = PromiseFlatString(aString).ToDouble(&rv);
95 return NS_SUCCEEDED(rv);
98 ResponsiveImageSelector::ResponsiveImageSelector(nsIContent* aContent)
99 : mOwnerNode(aContent), mSelectedCandidateIndex(-1) {}
101 ResponsiveImageSelector::ResponsiveImageSelector(dom::Document* aDocument)
102 : mOwnerNode(aDocument), mSelectedCandidateIndex(-1) {}
104 ResponsiveImageSelector::~ResponsiveImageSelector() = default;
106 void ResponsiveImageSelector::ParseSourceSet(
107 const nsAString& aSrcSet,
108 FunctionRef<void(ResponsiveImageCandidate&&)> aCallback) {
109 nsAString::const_iterator iter, end;
110 aSrcSet.BeginReading(iter);
111 aSrcSet.EndReading(end);
113 // Read URL / descriptor pairs
114 while (iter != end) {
115 nsAString::const_iterator url, urlEnd, descriptor;
117 // Skip whitespace and commas.
118 // Extra commas at this point are a non-fatal syntax error.
119 for (; iter != end &&
120 (nsContentUtils::IsHTMLWhitespace(*iter) || *iter == char16_t(','));
121 ++iter)
124 if (iter == end) {
125 break;
128 url = iter;
130 // Find end of url
131 for (; iter != end && !nsContentUtils::IsHTMLWhitespace(*iter); ++iter)
134 // Omit trailing commas from URL.
135 // Multiple commas are a non-fatal error.
136 while (iter != url) {
137 if (*(--iter) != char16_t(',')) {
138 iter++;
139 break;
143 const nsDependentSubstring& urlStr = Substring(url, iter);
145 MOZ_ASSERT(url != iter, "Shouldn't have empty URL at this point");
147 ResponsiveImageCandidate candidate;
148 if (candidate.ConsumeDescriptors(iter, end)) {
149 candidate.SetURLSpec(urlStr);
150 aCallback(std::move(candidate));
155 // http://www.whatwg.org/specs/web-apps/current-work/#processing-the-image-candidates
156 bool ResponsiveImageSelector::SetCandidatesFromSourceSet(
157 const nsAString& aSrcSet, nsIPrincipal* aTriggeringPrincipal) {
158 ClearSelectedCandidate();
160 if (!mOwnerNode || !mOwnerNode->GetBaseURI()) {
161 MOZ_ASSERT(false, "Should not be parsing SourceSet without a document");
162 return false;
165 mCandidates.Clear();
167 auto eachCandidate = [&](ResponsiveImageCandidate&& aCandidate) {
168 aCandidate.SetTriggeringPrincipal(
169 nsContentUtils::GetAttrTriggeringPrincipal(
170 Content(), aCandidate.URLString(), aTriggeringPrincipal));
171 AppendCandidateIfUnique(std::move(aCandidate));
174 ParseSourceSet(aSrcSet, eachCandidate);
176 bool parsedCandidates = !mCandidates.IsEmpty();
178 // Re-add default to end of list
179 MaybeAppendDefaultCandidate();
181 return parsedCandidates;
184 uint32_t ResponsiveImageSelector::NumCandidates(bool aIncludeDefault) {
185 uint32_t candidates = mCandidates.Length();
187 // If present, the default candidate is the last item
188 if (!aIncludeDefault && candidates && mCandidates.LastElement().IsDefault()) {
189 candidates--;
192 return candidates;
195 nsIContent* ResponsiveImageSelector::Content() {
196 return mOwnerNode->IsContent() ? mOwnerNode->AsContent() : nullptr;
199 dom::Document* ResponsiveImageSelector::Document() {
200 return mOwnerNode->OwnerDoc();
203 void ResponsiveImageSelector::ClearDefaultSource() {
204 ClearSelectedCandidate();
205 // Check if the last element of our candidates is a default
206 if (!mCandidates.IsEmpty() && mCandidates.LastElement().IsDefault()) {
207 mCandidates.RemoveLastElement();
211 void ResponsiveImageSelector::SetDefaultSource(nsIURI* aURI,
212 nsIPrincipal* aPrincipal) {
213 ClearDefaultSource();
214 mDefaultSourceTriggeringPrincipal = aPrincipal;
215 mDefaultSourceURL = VoidString();
216 if (aURI) {
217 nsAutoCString spec;
218 aURI->GetSpec(spec);
219 CopyUTF8toUTF16(spec, mDefaultSourceURL);
221 MaybeAppendDefaultCandidate();
224 void ResponsiveImageSelector::SetDefaultSource(const nsAString& aURLString,
225 nsIPrincipal* aPrincipal) {
226 ClearDefaultSource();
227 mDefaultSourceTriggeringPrincipal = aPrincipal;
228 mDefaultSourceURL = aURLString;
229 MaybeAppendDefaultCandidate();
232 void ResponsiveImageSelector::ClearSelectedCandidate() {
233 mSelectedCandidateIndex = -1;
234 mSelectedCandidateURL = nullptr;
237 bool ResponsiveImageSelector::SetSizesFromDescriptor(const nsAString& aSizes) {
238 ClearSelectedCandidate();
240 NS_ConvertUTF16toUTF8 sizes(aSizes);
241 mServoSourceSizeList.reset(Servo_SourceSizeList_Parse(&sizes));
242 return !!mServoSourceSizeList;
245 void ResponsiveImageSelector::AppendCandidateIfUnique(
246 ResponsiveImageCandidate&& aCandidate) {
247 int numCandidates = mCandidates.Length();
249 // With the exception of Default, which should not be added until we are done
250 // building the list.
251 if (aCandidate.IsDefault()) {
252 return;
255 // Discard candidates with identical parameters, they will never match
256 for (int i = 0; i < numCandidates; i++) {
257 if (mCandidates[i].HasSameParameter(aCandidate)) {
258 return;
262 mCandidates.AppendElement(std::move(aCandidate));
265 void ResponsiveImageSelector::MaybeAppendDefaultCandidate() {
266 if (mDefaultSourceURL.IsEmpty()) {
267 return;
270 int numCandidates = mCandidates.Length();
272 // https://html.spec.whatwg.org/multipage/embedded-content.html#update-the-source-set
273 // step 4.1.3:
274 // If child has a src attribute whose value is not the empty string and source
275 // set does not contain an image source with a density descriptor value of 1,
276 // and no image source with a width descriptor, append child's src attribute
277 // value to source set.
278 for (int i = 0; i < numCandidates; i++) {
279 if (mCandidates[i].IsComputedFromWidth()) {
280 return;
281 } else if (mCandidates[i].Density(this) == 1.0) {
282 return;
286 ResponsiveImageCandidate defaultCandidate;
287 defaultCandidate.SetParameterDefault();
288 defaultCandidate.SetURLSpec(mDefaultSourceURL);
289 defaultCandidate.SetTriggeringPrincipal(mDefaultSourceTriggeringPrincipal);
290 // We don't use MaybeAppend since we want to keep this even if it can never
291 // match, as it may if the source set changes.
292 mCandidates.AppendElement(std::move(defaultCandidate));
295 already_AddRefed<nsIURI> ResponsiveImageSelector::GetSelectedImageURL() {
296 SelectImage();
298 nsCOMPtr<nsIURI> url = mSelectedCandidateURL;
299 return url.forget();
302 bool ResponsiveImageSelector::GetSelectedImageURLSpec(nsAString& aResult) {
303 SelectImage();
305 if (mSelectedCandidateIndex == -1) {
306 return false;
309 aResult.Assign(mCandidates[mSelectedCandidateIndex].URLString());
310 return true;
313 double ResponsiveImageSelector::GetSelectedImageDensity() {
314 int bestIndex = GetSelectedCandidateIndex();
315 if (bestIndex < 0) {
316 return 1.0;
319 return mCandidates[bestIndex].Density(this);
322 nsIPrincipal* ResponsiveImageSelector::GetSelectedImageTriggeringPrincipal() {
323 int bestIndex = GetSelectedCandidateIndex();
324 if (bestIndex < 0) {
325 return nullptr;
328 return mCandidates[bestIndex].TriggeringPrincipal();
331 bool ResponsiveImageSelector::SelectImage(bool aReselect) {
332 if (!aReselect && mSelectedCandidateIndex != -1) {
333 // Already have selection
334 return false;
337 int oldBest = mSelectedCandidateIndex;
338 ClearSelectedCandidate();
340 int numCandidates = mCandidates.Length();
341 if (!numCandidates) {
342 return oldBest != -1;
345 dom::Document* doc = Document();
346 nsPresContext* pctx = doc->GetPresContext();
347 nsCOMPtr<nsIURI> baseURI = mOwnerNode->GetBaseURI();
349 if (!pctx || !baseURI) {
350 return oldBest != -1;
353 double displayDensity = pctx->CSSPixelsToDevPixels(1.0f);
354 double overrideDPPX = pctx->GetOverrideDPPX();
356 if (overrideDPPX > 0) {
357 displayDensity = overrideDPPX;
360 // Per spec, "In a UA-specific manner, choose one image source"
361 // - For now, select the lowest density greater than displayDensity, otherwise
362 // the greatest density available
364 // If the list contains computed width candidates, compute the current
365 // effective image width.
366 double computedWidth = -1;
367 for (int i = 0; i < numCandidates; i++) {
368 if (mCandidates[i].IsComputedFromWidth()) {
369 DebugOnly<bool> computeResult =
370 ComputeFinalWidthForCurrentViewport(&computedWidth);
371 MOZ_ASSERT(computeResult,
372 "Computed candidates not allowed without sizes data");
373 break;
377 int bestIndex = -1;
378 double bestDensity = -1.0;
379 for (int i = 0; i < numCandidates; i++) {
380 double candidateDensity = (computedWidth == -1)
381 ? mCandidates[i].Density(this)
382 : mCandidates[i].Density(computedWidth);
383 // - If bestIndex is below display density, pick anything larger.
384 // - Otherwise, prefer if less dense than bestDensity but still above
385 // displayDensity.
386 if (bestIndex == -1 ||
387 (bestDensity < displayDensity && candidateDensity > bestDensity) ||
388 (candidateDensity >= displayDensity &&
389 candidateDensity < bestDensity)) {
390 bestIndex = i;
391 bestDensity = candidateDensity;
395 MOZ_ASSERT(bestIndex >= 0 && bestIndex < numCandidates);
397 // Resolve URL
398 nsresult rv;
399 const nsAString& urlStr = mCandidates[bestIndex].URLString();
400 nsCOMPtr<nsIURI> candidateURL;
401 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(candidateURL),
402 urlStr, doc, baseURI);
404 mSelectedCandidateURL = NS_SUCCEEDED(rv) ? candidateURL : nullptr;
405 mSelectedCandidateIndex = bestIndex;
407 return mSelectedCandidateIndex != oldBest;
410 int ResponsiveImageSelector::GetSelectedCandidateIndex() {
411 SelectImage();
413 return mSelectedCandidateIndex;
416 bool ResponsiveImageSelector::ComputeFinalWidthForCurrentViewport(
417 double* aWidth) {
418 dom::Document* doc = Document();
419 PresShell* presShell = doc->GetPresShell();
420 nsPresContext* pctx = presShell ? presShell->GetPresContext() : nullptr;
422 if (!pctx) {
423 return false;
425 nscoord effectiveWidth =
426 presShell->StyleSet()->EvaluateSourceSizeList(mServoSourceSizeList.get());
428 *aWidth =
429 nsPresContext::AppUnitsToDoubleCSSPixels(std::max(effectiveWidth, 0));
430 return true;
433 ResponsiveImageCandidate::ResponsiveImageCandidate() {
434 mType = CandidateType::Invalid;
435 mValue.mDensity = 1.0;
438 void ResponsiveImageCandidate::SetURLSpec(const nsAString& aURLString) {
439 mURLString = aURLString;
442 void ResponsiveImageCandidate::SetTriggeringPrincipal(
443 nsIPrincipal* aPrincipal) {
444 mTriggeringPrincipal = aPrincipal;
447 void ResponsiveImageCandidate::SetParameterAsComputedWidth(int32_t aWidth) {
448 mType = CandidateType::ComputedFromWidth;
449 mValue.mWidth = aWidth;
452 void ResponsiveImageCandidate::SetParameterDefault() {
453 MOZ_ASSERT(!IsValid(), "double setting candidate type");
455 mType = CandidateType::Default;
456 // mValue shouldn't actually be used for this type, but set it to default
457 // anyway
458 mValue.mDensity = 1.0;
461 void ResponsiveImageCandidate::SetParameterInvalid() {
462 mType = CandidateType::Invalid;
463 // mValue shouldn't actually be used for this type, but set it to default
464 // anyway
465 mValue.mDensity = 1.0;
468 void ResponsiveImageCandidate::SetParameterAsDensity(double aDensity) {
469 MOZ_ASSERT(!IsValid(), "double setting candidate type");
471 mType = CandidateType::Density;
472 mValue.mDensity = aDensity;
475 // Represents all supported descriptors for a ResponsiveImageCandidate, though
476 // there is no candidate type that uses all of these. This should generally
477 // match the mValue union of ResponsiveImageCandidate.
478 struct ResponsiveImageDescriptors {
479 ResponsiveImageDescriptors() : mInvalid(false){};
481 Maybe<double> mDensity;
482 Maybe<int32_t> mWidth;
483 // We don't support "h" descriptors yet and they are not spec'd, but the
484 // current spec does specify that they can be silently ignored (whereas
485 // entirely unknown descriptors cause us to invalidate the candidate)
487 // If we ever start honoring them we should serialize them in
488 // AppendDescriptors.
489 Maybe<int32_t> mFutureCompatHeight;
490 // If this descriptor set is bogus, e.g. a value was added twice (and thus
491 // dropped) or an unknown descriptor was added.
492 bool mInvalid;
494 void AddDescriptor(const nsAString& aDescriptor);
495 bool Valid();
496 // Use the current set of descriptors to configure a candidate
497 void FillCandidate(ResponsiveImageCandidate& aCandidate);
500 // Try to parse a single descriptor from a string. If value already set or
501 // unknown, sets invalid flag.
502 // This corresponds to the descriptor "Descriptor parser" step in:
503 // https://html.spec.whatwg.org/#parse-a-srcset-attribute
504 void ResponsiveImageDescriptors::AddDescriptor(const nsAString& aDescriptor) {
505 if (aDescriptor.IsEmpty()) {
506 return;
509 // All currently supported descriptors end with an identifying character.
510 nsAString::const_iterator descStart, descType;
511 aDescriptor.BeginReading(descStart);
512 aDescriptor.EndReading(descType);
513 descType--;
514 const nsDependentSubstring& valueStr = Substring(descStart, descType);
515 if (*descType == char16_t('w')) {
516 int32_t possibleWidth;
517 // If the value is not a valid non-negative integer, it doesn't match this
518 // descriptor, fall through.
519 if (ParseInteger(valueStr, possibleWidth) && possibleWidth >= 0) {
520 if (possibleWidth != 0 && mWidth.isNothing() && mDensity.isNothing()) {
521 mWidth.emplace(possibleWidth);
522 } else {
523 // Valid width descriptor, but width or density were already seen, sizes
524 // support isn't enabled, or it parsed to 0, which is an error per spec
525 mInvalid = true;
528 return;
530 } else if (*descType == char16_t('h')) {
531 int32_t possibleHeight;
532 // If the value is not a valid non-negative integer, it doesn't match this
533 // descriptor, fall through.
534 if (ParseInteger(valueStr, possibleHeight) && possibleHeight >= 0) {
535 if (possibleHeight != 0 && mFutureCompatHeight.isNothing() &&
536 mDensity.isNothing()) {
537 mFutureCompatHeight.emplace(possibleHeight);
538 } else {
539 // Valid height descriptor, but height or density were already seen, or
540 // it parsed to zero, which is an error per spec
541 mInvalid = true;
544 return;
546 } else if (*descType == char16_t('x')) {
547 // If the value is not a valid floating point number, it doesn't match this
548 // descriptor, fall through.
549 double possibleDensity = 0.0;
550 if (ParseFloat(valueStr, possibleDensity)) {
551 if (possibleDensity >= 0.0 && mWidth.isNothing() &&
552 mDensity.isNothing() && mFutureCompatHeight.isNothing()) {
553 mDensity.emplace(possibleDensity);
554 } else {
555 // Valid density descriptor, but height or width or density were already
556 // seen, or it parsed to less than zero, which is an error per spec
557 mInvalid = true;
560 return;
564 // Matched no known descriptor, mark this descriptor set invalid
565 mInvalid = true;
568 bool ResponsiveImageDescriptors::Valid() {
569 return !mInvalid && !(mFutureCompatHeight.isSome() && mWidth.isNothing());
572 void ResponsiveImageDescriptors::FillCandidate(
573 ResponsiveImageCandidate& aCandidate) {
574 if (!Valid()) {
575 aCandidate.SetParameterInvalid();
576 } else if (mWidth.isSome()) {
577 MOZ_ASSERT(mDensity.isNothing()); // Shouldn't be valid
579 aCandidate.SetParameterAsComputedWidth(*mWidth);
580 } else if (mDensity.isSome()) {
581 MOZ_ASSERT(mWidth.isNothing()); // Shouldn't be valid
583 aCandidate.SetParameterAsDensity(*mDensity);
584 } else {
585 // A valid set of descriptors with no density nor width (e.g. an empty set)
586 // becomes 1.0 density, per spec
587 aCandidate.SetParameterAsDensity(1.0);
591 bool ResponsiveImageCandidate::ConsumeDescriptors(
592 nsAString::const_iterator& aIter,
593 const nsAString::const_iterator& aIterEnd) {
594 nsAString::const_iterator& iter = aIter;
595 const nsAString::const_iterator& end = aIterEnd;
597 bool inParens = false;
599 ResponsiveImageDescriptors descriptors;
601 // Parse descriptor list.
602 // This corresponds to the descriptor parsing loop from:
603 // https://html.spec.whatwg.org/#parse-a-srcset-attribute
605 // Skip initial whitespace
606 for (; iter != end && nsContentUtils::IsHTMLWhitespace(*iter); ++iter)
609 nsAString::const_iterator currentDescriptor = iter;
611 for (;; iter++) {
612 if (iter == end) {
613 descriptors.AddDescriptor(Substring(currentDescriptor, iter));
614 break;
615 } else if (inParens) {
616 if (*iter == char16_t(')')) {
617 inParens = false;
619 } else {
620 if (*iter == char16_t(',')) {
621 // End of descriptors, flush current descriptor and advance past comma
622 // before breaking
623 descriptors.AddDescriptor(Substring(currentDescriptor, iter));
624 iter++;
625 break;
627 if (nsContentUtils::IsHTMLWhitespace(*iter)) {
628 // End of current descriptor, consume it, skip spaces
629 // ("After descriptor" state in spec) before continuing
630 descriptors.AddDescriptor(Substring(currentDescriptor, iter));
631 for (; iter != end && nsContentUtils::IsHTMLWhitespace(*iter); ++iter)
633 if (iter == end) {
634 break;
636 currentDescriptor = iter;
637 // Leave one whitespace so the loop advances to this position next
638 // iteration
639 iter--;
640 } else if (*iter == char16_t('(')) {
641 inParens = true;
646 descriptors.FillCandidate(*this);
648 return IsValid();
651 bool ResponsiveImageCandidate::HasSameParameter(
652 const ResponsiveImageCandidate& aOther) const {
653 if (aOther.mType != mType) {
654 return false;
657 if (mType == CandidateType::Default) {
658 return true;
661 if (mType == CandidateType::Density) {
662 return aOther.mValue.mDensity == mValue.mDensity;
665 if (mType == CandidateType::Invalid) {
666 MOZ_ASSERT_UNREACHABLE("Comparing invalid candidates?");
667 return true;
670 if (mType == CandidateType::ComputedFromWidth) {
671 return aOther.mValue.mWidth == mValue.mWidth;
674 MOZ_ASSERT(false, "Somebody forgot to check for all uses of this enum");
675 return false;
678 double ResponsiveImageCandidate::Density(
679 ResponsiveImageSelector* aSelector) const {
680 if (mType == CandidateType::ComputedFromWidth) {
681 double width;
682 if (!aSelector->ComputeFinalWidthForCurrentViewport(&width)) {
683 return 1.0;
685 return Density(width);
688 // Other types don't need matching width
689 MOZ_ASSERT(mType == CandidateType::Default || mType == CandidateType::Density,
690 "unhandled candidate type");
691 return Density(-1);
694 void ResponsiveImageCandidate::AppendDescriptors(
695 nsAString& aDescriptors) const {
696 MOZ_ASSERT(IsValid());
697 switch (mType) {
698 case CandidateType::Default:
699 case CandidateType::Invalid:
700 return;
701 case CandidateType::ComputedFromWidth:
702 aDescriptors.Append(' ');
703 aDescriptors.AppendInt(mValue.mWidth);
704 aDescriptors.Append('w');
705 return;
706 case CandidateType::Density:
707 aDescriptors.Append(' ');
708 aDescriptors.AppendFloat(mValue.mDensity);
709 aDescriptors.Append('x');
710 return;
714 double ResponsiveImageCandidate::Density(double aMatchingWidth) const {
715 if (mType == CandidateType::Invalid) {
716 MOZ_ASSERT(false, "Getting density for uninitialized candidate");
717 return 1.0;
720 if (mType == CandidateType::Default) {
721 return 1.0;
724 if (mType == CandidateType::Density) {
725 return mValue.mDensity;
727 if (mType == CandidateType::ComputedFromWidth) {
728 if (aMatchingWidth < 0) {
729 MOZ_ASSERT(
730 false,
731 "Don't expect to have a negative matching width at this point");
732 return 1.0;
734 double density = double(mValue.mWidth) / aMatchingWidth;
735 MOZ_ASSERT(density > 0.0);
736 return density;
739 MOZ_ASSERT(false, "Unknown candidate type");
740 return 1.0;
743 } // namespace mozilla::dom