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