forked from mirrors/gecko-dev
		
	 fde06f6d21
			
		
	
	
		fde06f6d21
		
	
	
	
	
		
			
			Also add some missing namespace qualifications
Done with:
./mach static-analysis check --checks="-*,modernize-concat-nested-namespaces" --fix dom/
./mach clang-format -p $(hg status dom/|grep ^M|awk '{print $2}')
Differential Revision: https://phabricator.services.mozilla.com/D95456
		
	
			
		
			
				
	
	
		
			732 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			732 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "mozilla/dom/ResponsiveImageSelector.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/PresShellInlines.h"
 | |
| #include "mozilla/ServoStyleSetInlines.h"
 | |
| #include "mozilla/TextUtils.h"
 | |
| #include "nsIURI.h"
 | |
| #include "mozilla/dom/Document.h"
 | |
| #include "mozilla/dom/DocumentInlines.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsPresContext.h"
 | |
| 
 | |
| #include "nsCSSProps.h"
 | |
| 
 | |
| using namespace mozilla;
 | |
| using namespace mozilla::dom;
 | |
| 
 | |
| namespace mozilla::dom {
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION(ResponsiveImageSelector, mOwnerNode)
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ResponsiveImageSelector, AddRef)
 | |
| NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ResponsiveImageSelector, Release)
 | |
| 
 | |
| static bool ParseInteger(const nsAString& aString, int32_t& aInt) {
 | |
|   nsContentUtils::ParseHTMLIntegerResultFlags parseResult;
 | |
|   aInt = nsContentUtils::ParseHTMLInteger(aString, &parseResult);
 | |
|   return !(parseResult &
 | |
|            (nsContentUtils::eParseHTMLInteger_Error |
 | |
|             nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput |
 | |
|             nsContentUtils::eParseHTMLInteger_NonStandard));
 | |
| }
 | |
| 
 | |
| static bool ParseFloat(const nsAString& aString, double& aDouble) {
 | |
|   // Check if it is a valid floating-point number first since the result of
 | |
|   // nsString.ToDouble() is more lenient than the spec,
 | |
|   // https://html.spec.whatwg.org/#valid-floating-point-number
 | |
|   nsAString::const_iterator iter, end;
 | |
|   aString.BeginReading(iter);
 | |
|   aString.EndReading(end);
 | |
| 
 | |
|   if (iter == end) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (*iter == char16_t('-') && ++iter == end) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (IsAsciiDigit(*iter)) {
 | |
|     for (; iter != end && IsAsciiDigit(*iter); ++iter)
 | |
|       ;
 | |
|   } else if (*iter == char16_t('.')) {
 | |
|     // Do nothing, jumps to fraction part
 | |
|   } else {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Fraction
 | |
|   if (*iter == char16_t('.')) {
 | |
|     ++iter;
 | |
|     if (iter == end || !IsAsciiDigit(*iter)) {
 | |
|       // U+002E FULL STOP character (.) must be followed by one or more ASCII
 | |
|       // digits
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     for (; iter != end && IsAsciiDigit(*iter); ++iter)
 | |
|       ;
 | |
|   }
 | |
| 
 | |
|   if (iter != end && (*iter == char16_t('e') || *iter == char16_t('E'))) {
 | |
|     ++iter;
 | |
|     if (*iter == char16_t('-') || *iter == char16_t('+')) {
 | |
|       ++iter;
 | |
|     }
 | |
| 
 | |
|     if (iter == end || !IsAsciiDigit(*iter)) {
 | |
|       // Should have one or more ASCII digits
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     for (; iter != end && IsAsciiDigit(*iter); ++iter)
 | |
|       ;
 | |
|   }
 | |
| 
 | |
|   if (iter != end) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsresult rv;
 | |
|   aDouble = PromiseFlatString(aString).ToDouble(&rv);
 | |
|   return NS_SUCCEEDED(rv);
 | |
| }
 | |
| 
 | |
| ResponsiveImageSelector::ResponsiveImageSelector(nsIContent* aContent)
 | |
|     : mOwnerNode(aContent), mSelectedCandidateIndex(-1) {}
 | |
| 
 | |
| ResponsiveImageSelector::ResponsiveImageSelector(dom::Document* aDocument)
 | |
|     : mOwnerNode(aDocument), mSelectedCandidateIndex(-1) {}
 | |
| 
 | |
| ResponsiveImageSelector::~ResponsiveImageSelector() = default;
 | |
| 
 | |
| void ResponsiveImageSelector::ParseSourceSet(
 | |
|     const nsAString& aSrcSet,
 | |
|     FunctionRef<void(ResponsiveImageCandidate&&)> aCallback) {
 | |
|   nsAString::const_iterator iter, end;
 | |
|   aSrcSet.BeginReading(iter);
 | |
|   aSrcSet.EndReading(end);
 | |
| 
 | |
|   // Read URL / descriptor pairs
 | |
|   while (iter != end) {
 | |
|     nsAString::const_iterator url, urlEnd, descriptor;
 | |
| 
 | |
|     // Skip whitespace and commas.
 | |
|     // Extra commas at this point are a non-fatal syntax error.
 | |
|     for (; iter != end &&
 | |
|            (nsContentUtils::IsHTMLWhitespace(*iter) || *iter == char16_t(','));
 | |
|          ++iter)
 | |
|       ;
 | |
| 
 | |
|     if (iter == end) {
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     url = iter;
 | |
| 
 | |
|     // Find end of url
 | |
|     for (; iter != end && !nsContentUtils::IsHTMLWhitespace(*iter); ++iter)
 | |
|       ;
 | |
| 
 | |
|     // Omit trailing commas from URL.
 | |
|     // Multiple commas are a non-fatal error.
 | |
|     while (iter != url) {
 | |
|       if (*(--iter) != char16_t(',')) {
 | |
|         iter++;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     const nsDependentSubstring& urlStr = Substring(url, iter);
 | |
| 
 | |
|     MOZ_ASSERT(url != iter, "Shouldn't have empty URL at this point");
 | |
| 
 | |
|     ResponsiveImageCandidate candidate;
 | |
|     if (candidate.ConsumeDescriptors(iter, end)) {
 | |
|       candidate.SetURLSpec(urlStr);
 | |
|       aCallback(std::move(candidate));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| // http://www.whatwg.org/specs/web-apps/current-work/#processing-the-image-candidates
 | |
| bool ResponsiveImageSelector::SetCandidatesFromSourceSet(
 | |
|     const nsAString& aSrcSet, nsIPrincipal* aTriggeringPrincipal) {
 | |
|   ClearSelectedCandidate();
 | |
| 
 | |
|   if (!mOwnerNode || !mOwnerNode->GetBaseURI()) {
 | |
|     MOZ_ASSERT(false, "Should not be parsing SourceSet without a document");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   mCandidates.Clear();
 | |
| 
 | |
|   auto eachCandidate = [&](ResponsiveImageCandidate&& aCandidate) {
 | |
|     aCandidate.SetTriggeringPrincipal(
 | |
|         nsContentUtils::GetAttrTriggeringPrincipal(
 | |
|             Content(), aCandidate.URLString(), aTriggeringPrincipal));
 | |
|     AppendCandidateIfUnique(std::move(aCandidate));
 | |
|   };
 | |
| 
 | |
|   ParseSourceSet(aSrcSet, eachCandidate);
 | |
| 
 | |
|   bool parsedCandidates = !mCandidates.IsEmpty();
 | |
| 
 | |
|   // Re-add default to end of list
 | |
|   MaybeAppendDefaultCandidate();
 | |
| 
 | |
|   return parsedCandidates;
 | |
| }
 | |
| 
 | |
| uint32_t ResponsiveImageSelector::NumCandidates(bool aIncludeDefault) {
 | |
|   uint32_t candidates = mCandidates.Length();
 | |
| 
 | |
|   // If present, the default candidate is the last item
 | |
|   if (!aIncludeDefault && candidates && mCandidates.LastElement().IsDefault()) {
 | |
|     candidates--;
 | |
|   }
 | |
| 
 | |
|   return candidates;
 | |
| }
 | |
| 
 | |
| nsIContent* ResponsiveImageSelector::Content() {
 | |
|   return mOwnerNode->IsContent() ? mOwnerNode->AsContent() : nullptr;
 | |
| }
 | |
| 
 | |
| dom::Document* ResponsiveImageSelector::Document() {
 | |
|   return mOwnerNode->OwnerDoc();
 | |
| }
 | |
| 
 | |
| void ResponsiveImageSelector::SetDefaultSource(const nsAString& aURLString,
 | |
|                                                nsIPrincipal* aPrincipal) {
 | |
|   ClearSelectedCandidate();
 | |
| 
 | |
|   // Check if the last element of our candidates is a default
 | |
|   if (!mCandidates.IsEmpty() && mCandidates.LastElement().IsDefault()) {
 | |
|     mCandidates.RemoveLastElement();
 | |
|   }
 | |
| 
 | |
|   mDefaultSourceURL = aURLString;
 | |
|   mDefaultSourceTriggeringPrincipal = aPrincipal;
 | |
| 
 | |
|   // Add new default to end of list
 | |
|   MaybeAppendDefaultCandidate();
 | |
| }
 | |
| 
 | |
| void ResponsiveImageSelector::ClearSelectedCandidate() {
 | |
|   mSelectedCandidateIndex = -1;
 | |
|   mSelectedCandidateURL = nullptr;
 | |
| }
 | |
| 
 | |
| bool ResponsiveImageSelector::SetSizesFromDescriptor(const nsAString& aSizes) {
 | |
|   ClearSelectedCandidate();
 | |
| 
 | |
|   NS_ConvertUTF16toUTF8 sizes(aSizes);
 | |
|   mServoSourceSizeList = Servo_SourceSizeList_Parse(&sizes).Consume();
 | |
|   return !!mServoSourceSizeList;
 | |
| }
 | |
| 
 | |
| void ResponsiveImageSelector::AppendCandidateIfUnique(
 | |
|     ResponsiveImageCandidate&& aCandidate) {
 | |
|   int numCandidates = mCandidates.Length();
 | |
| 
 | |
|   // With the exception of Default, which should not be added until we are done
 | |
|   // building the list.
 | |
|   if (aCandidate.IsDefault()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Discard candidates with identical parameters, they will never match
 | |
|   for (int i = 0; i < numCandidates; i++) {
 | |
|     if (mCandidates[i].HasSameParameter(aCandidate)) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mCandidates.AppendElement(std::move(aCandidate));
 | |
| }
 | |
| 
 | |
| void ResponsiveImageSelector::MaybeAppendDefaultCandidate() {
 | |
|   if (mDefaultSourceURL.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   int numCandidates = mCandidates.Length();
 | |
| 
 | |
|   // https://html.spec.whatwg.org/multipage/embedded-content.html#update-the-source-set
 | |
|   // step 4.1.3:
 | |
|   // If child has a src attribute whose value is not the empty string and source
 | |
|   // set does not contain an image source with a density descriptor value of 1,
 | |
|   // and no image source with a width descriptor, append child's src attribute
 | |
|   // value to source set.
 | |
|   for (int i = 0; i < numCandidates; i++) {
 | |
|     if (mCandidates[i].IsComputedFromWidth()) {
 | |
|       return;
 | |
|     } else if (mCandidates[i].Density(this) == 1.0) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ResponsiveImageCandidate defaultCandidate;
 | |
|   defaultCandidate.SetParameterDefault();
 | |
|   defaultCandidate.SetURLSpec(mDefaultSourceURL);
 | |
|   defaultCandidate.SetTriggeringPrincipal(mDefaultSourceTriggeringPrincipal);
 | |
|   // We don't use MaybeAppend since we want to keep this even if it can never
 | |
|   // match, as it may if the source set changes.
 | |
|   mCandidates.AppendElement(std::move(defaultCandidate));
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIURI> ResponsiveImageSelector::GetSelectedImageURL() {
 | |
|   SelectImage();
 | |
| 
 | |
|   nsCOMPtr<nsIURI> url = mSelectedCandidateURL;
 | |
|   return url.forget();
 | |
| }
 | |
| 
 | |
| bool ResponsiveImageSelector::GetSelectedImageURLSpec(nsAString& aResult) {
 | |
|   SelectImage();
 | |
| 
 | |
|   if (mSelectedCandidateIndex == -1) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   aResult.Assign(mCandidates[mSelectedCandidateIndex].URLString());
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| double ResponsiveImageSelector::GetSelectedImageDensity() {
 | |
|   int bestIndex = GetSelectedCandidateIndex();
 | |
|   if (bestIndex < 0) {
 | |
|     return 1.0;
 | |
|   }
 | |
| 
 | |
|   return mCandidates[bestIndex].Density(this);
 | |
| }
 | |
| 
 | |
| nsIPrincipal* ResponsiveImageSelector::GetSelectedImageTriggeringPrincipal() {
 | |
|   int bestIndex = GetSelectedCandidateIndex();
 | |
|   if (bestIndex < 0) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return mCandidates[bestIndex].TriggeringPrincipal();
 | |
| }
 | |
| 
 | |
| bool ResponsiveImageSelector::SelectImage(bool aReselect) {
 | |
|   if (!aReselect && mSelectedCandidateIndex != -1) {
 | |
|     // Already have selection
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   int oldBest = mSelectedCandidateIndex;
 | |
|   ClearSelectedCandidate();
 | |
| 
 | |
|   int numCandidates = mCandidates.Length();
 | |
|   if (!numCandidates) {
 | |
|     return oldBest != -1;
 | |
|   }
 | |
| 
 | |
|   dom::Document* doc = Document();
 | |
|   nsPresContext* pctx = doc->GetPresContext();
 | |
|   nsCOMPtr<nsIURI> baseURI = mOwnerNode->GetBaseURI();
 | |
| 
 | |
|   if (!pctx || !baseURI) {
 | |
|     return oldBest != -1;
 | |
|   }
 | |
| 
 | |
|   double displayDensity = pctx->CSSPixelsToDevPixels(1.0f);
 | |
|   double overrideDPPX = pctx->GetOverrideDPPX();
 | |
| 
 | |
|   if (overrideDPPX > 0) {
 | |
|     displayDensity = overrideDPPX;
 | |
|   }
 | |
| 
 | |
|   // Per spec, "In a UA-specific manner, choose one image source"
 | |
|   // - For now, select the lowest density greater than displayDensity, otherwise
 | |
|   //   the greatest density available
 | |
| 
 | |
|   // If the list contains computed width candidates, compute the current
 | |
|   // effective image width.
 | |
|   double computedWidth = -1;
 | |
|   for (int i = 0; i < numCandidates; i++) {
 | |
|     if (mCandidates[i].IsComputedFromWidth()) {
 | |
|       DebugOnly<bool> computeResult =
 | |
|           ComputeFinalWidthForCurrentViewport(&computedWidth);
 | |
|       MOZ_ASSERT(computeResult,
 | |
|                  "Computed candidates not allowed without sizes data");
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   int bestIndex = -1;
 | |
|   double bestDensity = -1.0;
 | |
|   for (int i = 0; i < numCandidates; i++) {
 | |
|     double candidateDensity = (computedWidth == -1)
 | |
|                                   ? mCandidates[i].Density(this)
 | |
|                                   : mCandidates[i].Density(computedWidth);
 | |
|     // - If bestIndex is below display density, pick anything larger.
 | |
|     // - Otherwise, prefer if less dense than bestDensity but still above
 | |
|     //   displayDensity.
 | |
|     if (bestIndex == -1 ||
 | |
|         (bestDensity < displayDensity && candidateDensity > bestDensity) ||
 | |
|         (candidateDensity >= displayDensity &&
 | |
|          candidateDensity < bestDensity)) {
 | |
|       bestIndex = i;
 | |
|       bestDensity = candidateDensity;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(bestIndex >= 0 && bestIndex < numCandidates);
 | |
| 
 | |
|   // Resolve URL
 | |
|   nsresult rv;
 | |
|   const nsAString& urlStr = mCandidates[bestIndex].URLString();
 | |
|   nsCOMPtr<nsIURI> candidateURL;
 | |
|   rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(candidateURL),
 | |
|                                                  urlStr, doc, baseURI);
 | |
| 
 | |
|   mSelectedCandidateURL = NS_SUCCEEDED(rv) ? candidateURL : nullptr;
 | |
|   mSelectedCandidateIndex = bestIndex;
 | |
| 
 | |
|   return mSelectedCandidateIndex != oldBest;
 | |
| }
 | |
| 
 | |
| int ResponsiveImageSelector::GetSelectedCandidateIndex() {
 | |
|   SelectImage();
 | |
| 
 | |
|   return mSelectedCandidateIndex;
 | |
| }
 | |
| 
 | |
| bool ResponsiveImageSelector::ComputeFinalWidthForCurrentViewport(
 | |
|     double* aWidth) {
 | |
|   dom::Document* doc = Document();
 | |
|   PresShell* presShell = doc->GetPresShell();
 | |
|   nsPresContext* pctx = presShell ? presShell->GetPresContext() : nullptr;
 | |
| 
 | |
|   if (!pctx) {
 | |
|     return false;
 | |
|   }
 | |
|   nscoord effectiveWidth =
 | |
|       presShell->StyleSet()->EvaluateSourceSizeList(mServoSourceSizeList.get());
 | |
| 
 | |
|   *aWidth =
 | |
|       nsPresContext::AppUnitsToDoubleCSSPixels(std::max(effectiveWidth, 0));
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| ResponsiveImageCandidate::ResponsiveImageCandidate() {
 | |
|   mType = CandidateType::Invalid;
 | |
|   mValue.mDensity = 1.0;
 | |
| }
 | |
| 
 | |
| void ResponsiveImageCandidate::SetURLSpec(const nsAString& aURLString) {
 | |
|   mURLString = aURLString;
 | |
| }
 | |
| 
 | |
| void ResponsiveImageCandidate::SetTriggeringPrincipal(
 | |
|     nsIPrincipal* aPrincipal) {
 | |
|   mTriggeringPrincipal = aPrincipal;
 | |
| }
 | |
| 
 | |
| void ResponsiveImageCandidate::SetParameterAsComputedWidth(int32_t aWidth) {
 | |
|   mType = CandidateType::ComputedFromWidth;
 | |
|   mValue.mWidth = aWidth;
 | |
| }
 | |
| 
 | |
| void ResponsiveImageCandidate::SetParameterDefault() {
 | |
|   MOZ_ASSERT(!IsValid(), "double setting candidate type");
 | |
| 
 | |
|   mType = CandidateType::Default;
 | |
|   // mValue shouldn't actually be used for this type, but set it to default
 | |
|   // anyway
 | |
|   mValue.mDensity = 1.0;
 | |
| }
 | |
| 
 | |
| void ResponsiveImageCandidate::SetParameterInvalid() {
 | |
|   mType = CandidateType::Invalid;
 | |
|   // mValue shouldn't actually be used for this type, but set it to default
 | |
|   // anyway
 | |
|   mValue.mDensity = 1.0;
 | |
| }
 | |
| 
 | |
| void ResponsiveImageCandidate::SetParameterAsDensity(double aDensity) {
 | |
|   MOZ_ASSERT(!IsValid(), "double setting candidate type");
 | |
| 
 | |
|   mType = CandidateType::Density;
 | |
|   mValue.mDensity = aDensity;
 | |
| }
 | |
| 
 | |
| // Represents all supported descriptors for a ResponsiveImageCandidate, though
 | |
| // there is no candidate type that uses all of these. This should generally
 | |
| // match the mValue union of ResponsiveImageCandidate.
 | |
| struct ResponsiveImageDescriptors {
 | |
|   ResponsiveImageDescriptors() : mInvalid(false){};
 | |
| 
 | |
|   Maybe<double> mDensity;
 | |
|   Maybe<int32_t> mWidth;
 | |
|   // We don't support "h" descriptors yet and they are not spec'd, but the
 | |
|   // current spec does specify that they can be silently ignored (whereas
 | |
|   // entirely unknown descriptors cause us to invalidate the candidate)
 | |
|   //
 | |
|   // If we ever start honoring them we should serialize them in
 | |
|   // AppendDescriptors.
 | |
|   Maybe<int32_t> mFutureCompatHeight;
 | |
|   // If this descriptor set is bogus, e.g. a value was added twice (and thus
 | |
|   // dropped) or an unknown descriptor was added.
 | |
|   bool mInvalid;
 | |
| 
 | |
|   void AddDescriptor(const nsAString& aDescriptor);
 | |
|   bool Valid();
 | |
|   // Use the current set of descriptors to configure a candidate
 | |
|   void FillCandidate(ResponsiveImageCandidate& aCandidate);
 | |
| };
 | |
| 
 | |
| // Try to parse a single descriptor from a string. If value already set or
 | |
| // unknown, sets invalid flag.
 | |
| // This corresponds to the descriptor "Descriptor parser" step in:
 | |
| // https://html.spec.whatwg.org/#parse-a-srcset-attribute
 | |
| void ResponsiveImageDescriptors::AddDescriptor(const nsAString& aDescriptor) {
 | |
|   if (aDescriptor.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // All currently supported descriptors end with an identifying character.
 | |
|   nsAString::const_iterator descStart, descType;
 | |
|   aDescriptor.BeginReading(descStart);
 | |
|   aDescriptor.EndReading(descType);
 | |
|   descType--;
 | |
|   const nsDependentSubstring& valueStr = Substring(descStart, descType);
 | |
|   if (*descType == char16_t('w')) {
 | |
|     int32_t possibleWidth;
 | |
|     // If the value is not a valid non-negative integer, it doesn't match this
 | |
|     // descriptor, fall through.
 | |
|     if (ParseInteger(valueStr, possibleWidth) && possibleWidth >= 0) {
 | |
|       if (possibleWidth != 0 && mWidth.isNothing() && mDensity.isNothing()) {
 | |
|         mWidth.emplace(possibleWidth);
 | |
|       } else {
 | |
|         // Valid width descriptor, but width or density were already seen, sizes
 | |
|         // support isn't enabled, or it parsed to 0, which is an error per spec
 | |
|         mInvalid = true;
 | |
|       }
 | |
| 
 | |
|       return;
 | |
|     }
 | |
|   } else if (*descType == char16_t('h')) {
 | |
|     int32_t possibleHeight;
 | |
|     // If the value is not a valid non-negative integer, it doesn't match this
 | |
|     // descriptor, fall through.
 | |
|     if (ParseInteger(valueStr, possibleHeight) && possibleHeight >= 0) {
 | |
|       if (possibleHeight != 0 && mFutureCompatHeight.isNothing() &&
 | |
|           mDensity.isNothing()) {
 | |
|         mFutureCompatHeight.emplace(possibleHeight);
 | |
|       } else {
 | |
|         // Valid height descriptor, but height or density were already seen, or
 | |
|         // it parsed to zero, which is an error per spec
 | |
|         mInvalid = true;
 | |
|       }
 | |
| 
 | |
|       return;
 | |
|     }
 | |
|   } else if (*descType == char16_t('x')) {
 | |
|     // If the value is not a valid floating point number, it doesn't match this
 | |
|     // descriptor, fall through.
 | |
|     double possibleDensity = 0.0;
 | |
|     if (ParseFloat(valueStr, possibleDensity)) {
 | |
|       if (possibleDensity >= 0.0 && mWidth.isNothing() &&
 | |
|           mDensity.isNothing() && mFutureCompatHeight.isNothing()) {
 | |
|         mDensity.emplace(possibleDensity);
 | |
|       } else {
 | |
|         // Valid density descriptor, but height or width or density were already
 | |
|         // seen, or it parsed to less than zero, which is an error per spec
 | |
|         mInvalid = true;
 | |
|       }
 | |
| 
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Matched no known descriptor, mark this descriptor set invalid
 | |
|   mInvalid = true;
 | |
| }
 | |
| 
 | |
| bool ResponsiveImageDescriptors::Valid() {
 | |
|   return !mInvalid && !(mFutureCompatHeight.isSome() && mWidth.isNothing());
 | |
| }
 | |
| 
 | |
| void ResponsiveImageDescriptors::FillCandidate(
 | |
|     ResponsiveImageCandidate& aCandidate) {
 | |
|   if (!Valid()) {
 | |
|     aCandidate.SetParameterInvalid();
 | |
|   } else if (mWidth.isSome()) {
 | |
|     MOZ_ASSERT(mDensity.isNothing());  // Shouldn't be valid
 | |
| 
 | |
|     aCandidate.SetParameterAsComputedWidth(*mWidth);
 | |
|   } else if (mDensity.isSome()) {
 | |
|     MOZ_ASSERT(mWidth.isNothing());  // Shouldn't be valid
 | |
| 
 | |
|     aCandidate.SetParameterAsDensity(*mDensity);
 | |
|   } else {
 | |
|     // A valid set of descriptors with no density nor width (e.g. an empty set)
 | |
|     // becomes 1.0 density, per spec
 | |
|     aCandidate.SetParameterAsDensity(1.0);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool ResponsiveImageCandidate::ConsumeDescriptors(
 | |
|     nsAString::const_iterator& aIter,
 | |
|     const nsAString::const_iterator& aIterEnd) {
 | |
|   nsAString::const_iterator& iter = aIter;
 | |
|   const nsAString::const_iterator& end = aIterEnd;
 | |
| 
 | |
|   bool inParens = false;
 | |
| 
 | |
|   ResponsiveImageDescriptors descriptors;
 | |
| 
 | |
|   // Parse descriptor list.
 | |
|   // This corresponds to the descriptor parsing loop from:
 | |
|   // https://html.spec.whatwg.org/#parse-a-srcset-attribute
 | |
| 
 | |
|   // Skip initial whitespace
 | |
|   for (; iter != end && nsContentUtils::IsHTMLWhitespace(*iter); ++iter)
 | |
|     ;
 | |
| 
 | |
|   nsAString::const_iterator currentDescriptor = iter;
 | |
| 
 | |
|   for (;; iter++) {
 | |
|     if (iter == end) {
 | |
|       descriptors.AddDescriptor(Substring(currentDescriptor, iter));
 | |
|       break;
 | |
|     } else if (inParens) {
 | |
|       if (*iter == char16_t(')')) {
 | |
|         inParens = false;
 | |
|       }
 | |
|     } else {
 | |
|       if (*iter == char16_t(',')) {
 | |
|         // End of descriptors, flush current descriptor and advance past comma
 | |
|         // before breaking
 | |
|         descriptors.AddDescriptor(Substring(currentDescriptor, iter));
 | |
|         iter++;
 | |
|         break;
 | |
|       } else if (nsContentUtils::IsHTMLWhitespace(*iter)) {
 | |
|         // End of current descriptor, consume it, skip spaces
 | |
|         // ("After descriptor" state in spec) before continuing
 | |
|         descriptors.AddDescriptor(Substring(currentDescriptor, iter));
 | |
|         for (; iter != end && nsContentUtils::IsHTMLWhitespace(*iter); ++iter)
 | |
|           ;
 | |
|         if (iter == end) {
 | |
|           break;
 | |
|         }
 | |
|         currentDescriptor = iter;
 | |
|         // Leave one whitespace so the loop advances to this position next
 | |
|         // iteration
 | |
|         iter--;
 | |
|       } else if (*iter == char16_t('(')) {
 | |
|         inParens = true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   descriptors.FillCandidate(*this);
 | |
| 
 | |
|   return IsValid();
 | |
| }
 | |
| 
 | |
| bool ResponsiveImageCandidate::HasSameParameter(
 | |
|     const ResponsiveImageCandidate& aOther) const {
 | |
|   if (aOther.mType != mType) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (mType == CandidateType::Default) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   if (mType == CandidateType::Density) {
 | |
|     return aOther.mValue.mDensity == mValue.mDensity;
 | |
|   }
 | |
| 
 | |
|   if (mType == CandidateType::Invalid) {
 | |
|     MOZ_ASSERT_UNREACHABLE("Comparing invalid candidates?");
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   if (mType == CandidateType::ComputedFromWidth) {
 | |
|     return aOther.mValue.mWidth == mValue.mWidth;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(false, "Somebody forgot to check for all uses of this enum");
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| double ResponsiveImageCandidate::Density(
 | |
|     ResponsiveImageSelector* aSelector) const {
 | |
|   if (mType == CandidateType::ComputedFromWidth) {
 | |
|     double width;
 | |
|     if (!aSelector->ComputeFinalWidthForCurrentViewport(&width)) {
 | |
|       return 1.0;
 | |
|     }
 | |
|     return Density(width);
 | |
|   }
 | |
| 
 | |
|   // Other types don't need matching width
 | |
|   MOZ_ASSERT(mType == CandidateType::Default || mType == CandidateType::Density,
 | |
|              "unhandled candidate type");
 | |
|   return Density(-1);
 | |
| }
 | |
| 
 | |
| void ResponsiveImageCandidate::AppendDescriptors(
 | |
|     nsAString& aDescriptors) const {
 | |
|   MOZ_ASSERT(IsValid());
 | |
|   switch (mType) {
 | |
|     case CandidateType::Default:
 | |
|     case CandidateType::Invalid:
 | |
|       return;
 | |
|     case CandidateType::ComputedFromWidth:
 | |
|       aDescriptors.Append(' ');
 | |
|       aDescriptors.AppendInt(mValue.mWidth);
 | |
|       aDescriptors.Append('w');
 | |
|       return;
 | |
|     case CandidateType::Density:
 | |
|       aDescriptors.Append(' ');
 | |
|       aDescriptors.AppendFloat(mValue.mDensity);
 | |
|       aDescriptors.Append('x');
 | |
|       return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| double ResponsiveImageCandidate::Density(double aMatchingWidth) const {
 | |
|   if (mType == CandidateType::Invalid) {
 | |
|     MOZ_ASSERT(false, "Getting density for uninitialized candidate");
 | |
|     return 1.0;
 | |
|   }
 | |
| 
 | |
|   if (mType == CandidateType::Default) {
 | |
|     return 1.0;
 | |
|   }
 | |
| 
 | |
|   if (mType == CandidateType::Density) {
 | |
|     return mValue.mDensity;
 | |
|   }
 | |
|   if (mType == CandidateType::ComputedFromWidth) {
 | |
|     if (aMatchingWidth < 0) {
 | |
|       MOZ_ASSERT(
 | |
|           false,
 | |
|           "Don't expect to have a negative matching width at this point");
 | |
|       return 1.0;
 | |
|     }
 | |
|     double density = double(mValue.mWidth) / aMatchingWidth;
 | |
|     MOZ_ASSERT(density > 0.0);
 | |
|     return density;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(false, "Unknown candidate type");
 | |
|   return 1.0;
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla::dom
 |