LCOV - code coverage report
Current view: top level - dom/base - ResponsiveImageSelector.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 346 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 43 0.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13