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