Coverage Report

Created: 2018-09-25 14:53

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