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
|