tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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