Bug 870022 - Part 3.2 - Add sizes support to ResponsiveImageSelector. r=jst

This commit is contained in:
John Schoenick 2014-04-30 16:19:54 -07:00
parent 5dad81c8d3
commit 96d78944bf
2 changed files with 177 additions and 19 deletions

View File

@ -10,6 +10,12 @@
#include "nsPresContext.h"
#include "nsNetUtil.h"
#include "nsCSSParser.h"
#include "nsCSSProps.h"
#include "nsIMediaList.h"
#include "nsRuleNode.h"
#include "nsRuleData.h"
using namespace mozilla;
using namespace mozilla::dom;
@ -164,11 +170,36 @@ ResponsiveImageSelector::SetDefaultSource(nsIURI *aURL)
}
}
bool
ResponsiveImageSelector::SetSizesFromDescriptor(const nsAString & aSizes)
{
mSizeQueries.Clear();
mSizeValues.Clear();
mBestCandidateIndex = -1;
nsCSSParser cssParser;
if (!cssParser.ParseSourceSizeList(aSizes, nullptr, 0,
mSizeQueries, mSizeValues, true)) {
return false;
}
return mSizeQueries.Length() > 0;
}
void
ResponsiveImageSelector::AppendCandidateIfUnique(const ResponsiveImageCandidate & aCandidate)
{
// Discard candidates with identical parameters, they will never match
int numCandidates = mCandidates.Length();
// With the exception of Default, which should not be added until we are done
// building the list, the spec forbids mixing width and explicit density
// selectors in the same set.
if (numCandidates && mCandidates[0].Type() != aCandidate.Type()) {
return;
}
// Discard candidates with identical parameters, they will never match
for (int i = 0; i < numCandidates; i++) {
if (mCandidates[i].HasSameParameter(aCandidate)) {
return;
@ -214,7 +245,7 @@ ResponsiveImageSelector::GetSelectedImageDensity()
return 1.0;
}
return mCandidates[bestIndex].Density();
return mCandidates[bestIndex].Density(this);
}
int
@ -244,24 +275,91 @@ ResponsiveImageSelector::GetBestCandidateIndex()
// - For now, select the lowest density greater than displayDensity, otherwise
// the greatest density available
int bestIndex = 0; // First index will always be the best so far
double bestDensity = mCandidates[bestIndex].Density();
for (int i = 1; i < numCandidates; i++) {
double candidateDensity = mCandidates[i].Density();
// If the list contains computed width candidates, compute the current
// effective image width. Note that we currently disallow both computed and
// static density candidates in the same selector, so checking the first
// candidate is sufficient.
int32_t computedWidth = -1;
if (numCandidates && mCandidates[0].IsComputedFromWidth()) {
DebugOnly<bool> computeResult = \
ComputeFinalWidthForCurrentViewport(&computedWidth);
MOZ_ASSERT(computeResult,
"Computed candidates not allowed without sizes data");
// If we have a default candidate in the list, don't consider it when using
// computed widths. (It has a static 1.0 density that is inapplicable to a
// sized-image)
if (numCandidates > 1 && mCandidates[numCandidates - 1].Type() ==
ResponsiveImageCandidate::eCandidateType_Default) {
numCandidates--;
}
}
int bestIndex = -1;
double bestDensity = -1.0;
for (int i = 0; i < numCandidates; i++) {
double candidateDensity = \
(computedWidth == -1) ? mCandidates[i].Density(this)
: mCandidates[i].Density(computedWidth);
// - If bestIndex is below display density, pick anything larger.
// - Otherwise, prefer if less dense than bestDensity but still above
// displayDensity.
if ((bestDensity < displayDensity && candidateDensity > bestDensity) ||
(candidateDensity > displayDensity && candidateDensity < bestDensity)) {
if (bestIndex == -1 ||
(bestDensity < displayDensity && candidateDensity > bestDensity) ||
(candidateDensity >= displayDensity && candidateDensity < bestDensity)) {
bestIndex = i;
bestDensity = candidateDensity;
}
}
MOZ_ASSERT(bestIndex >= 0 && bestIndex < numCandidates);
mBestCandidateIndex = bestIndex;
return bestIndex;
}
bool
ResponsiveImageSelector::ComputeFinalWidthForCurrentViewport(int32_t *aWidth)
{
unsigned int numSizes = mSizeQueries.Length();
if (!numSizes) {
return false;
}
nsIDocument* doc = mContent ? mContent->OwnerDoc() : nullptr;
nsIPresShell *presShell = doc ? doc->GetShell() : nullptr;
nsPresContext *pctx = presShell ? presShell->GetPresContext() : nullptr;
if (!pctx) {
MOZ_ASSERT(false, "Unable to find presContext for this content");
return false;
}
MOZ_ASSERT(numSizes == mSizeValues.Length(),
"mSizeValues length differs from mSizeQueries");
unsigned int i;
for (i = 0; i < numSizes; i++) {
if (mSizeQueries[i]->Matches(pctx, nullptr)) {
break;
}
}
nscoord effectiveWidth;
if (i == numSizes) {
// No match defaults to 100% viewport
nsCSSValue defaultWidth(100.0f, eCSSUnit_ViewportWidth);
effectiveWidth = nsRuleNode::CalcLengthWithInitialFont(pctx,
defaultWidth);
} else {
effectiveWidth = nsRuleNode::CalcLengthWithInitialFont(pctx,
mSizeValues[i]);
}
MOZ_ASSERT(effectiveWidth >= 0);
*aWidth = nsPresContext::AppUnitsToIntCSSPixels(std::max(effectiveWidth, 0));
return true;
}
ResponsiveImageCandidate::ResponsiveImageCandidate()
{
mType = eCandidateType_Invalid;
@ -408,10 +506,33 @@ ResponsiveImageCandidate::HasSameParameter(const ResponsiveImageCandidate & aOth
return false;
}
double
ResponsiveImageCandidate::Density() const
already_AddRefed<nsIURI>
ResponsiveImageCandidate::URL() const
{
nsCOMPtr<nsIURI> url = mURL;
return url.forget();
}
double
ResponsiveImageCandidate::Density(ResponsiveImageSelector *aSelector) const
{
if (mType == eCandidateType_ComputedFromWidth) {
int32_t width;
if (!aSelector->ComputeFinalWidthForCurrentViewport(&width)) {
return 1.0;
}
return Density(width);
}
// Other types don't need matching width
MOZ_ASSERT(mType == eCandidateType_Default || mType == eCandidateType_Density,
"unhandled candidate type");
return Density(-1);
}
double
ResponsiveImageCandidate::Density(int32_t aMatchingWidth) const
{
// When we support 'sizes' this will get more interesting
if (mType == eCandidateType_Invalid) {
MOZ_ASSERT(false, "Getting density for uninitialized candidate");
return 1.0;
@ -423,19 +544,30 @@ ResponsiveImageCandidate::Density() const
if (mType == eCandidateType_Density) {
return mValue.mDensity;
} else if (mType == eCandidateType_ComputedFromWidth) {
if (aMatchingWidth <= 0) {
MOZ_ASSERT(false, "0 or negative matching width is invalid per spec");
return 1.0;
}
double density = double(mValue.mWidth) / double(aMatchingWidth);
MOZ_ASSERT(density > 0.0);
return density;
}
MOZ_ASSERT(false, "Bad candidate type in Density()");
MOZ_ASSERT(false, "Unknown candidate type");
return 1.0;
}
already_AddRefed<nsIURI>
ResponsiveImageCandidate::URL() const
bool
ResponsiveImageCandidate::IsComputedFromWidth() const
{
MOZ_ASSERT(mType != eCandidateType_Invalid,
"Getting URL of incomplete candidate");
nsCOMPtr<nsIURI> url = mURL;
return url.forget();
if (mType == eCandidateType_ComputedFromWidth) {
return true;
}
MOZ_ASSERT(mType == eCandidateType_Default || mType == eCandidateType_Density,
"Unknown candidate type");
return false;
}
} // namespace dom

View File

@ -10,6 +10,9 @@
#include "nsIContent.h"
#include "nsString.h"
class nsMediaQuery;
class nsCSSValue;
namespace mozilla {
namespace dom {
@ -17,6 +20,7 @@ class ResponsiveImageCandidate;
class ResponsiveImageSelector : public nsISupports
{
friend class ResponsiveImageCandidate;
public:
NS_DECL_ISUPPORTS
ResponsiveImageSelector(nsIContent *aContent);
@ -25,6 +29,10 @@ public:
// replace default source)
bool SetCandidatesFromSourceSet(const nsAString & aSrcSet);
// Fill the source sizes from a valid sizes descriptor. Returns false if
// descriptor is invalid.
bool SetSizesFromDescriptor(const nsAString & aSizesDescriptor);
// Set the default source, treated as the least-precedence 1.0 density source.
nsresult SetDefaultSource(const nsAString & aSpec);
void SetDefaultSource(nsIURI *aURL);
@ -48,11 +56,21 @@ private:
// Get index of best candidate
int GetBestCandidateIndex();
// Compute a density from a Candidate width. Returns false if sizes were not
// specified for this selector.
//
// aContext is the presContext to use for current viewport sizing, null will
// use the associated content's context.
bool ComputeFinalWidthForCurrentViewport(int32_t *aWidth);
nsCOMPtr<nsIContent> mContent;
// If this array contains an eCandidateType_Default, it should be the last
// element, such that the Setters can preserve/replace it respectively.
nsTArray<ResponsiveImageCandidate> mCandidates;
int mBestCandidateIndex;
nsTArray< nsAutoPtr<nsMediaQuery> > mSizeQueries;
nsTArray<nsCSSValue> mSizeValues;
};
class ResponsiveImageCandidate {
@ -78,7 +96,15 @@ public:
bool HasSameParameter(const ResponsiveImageCandidate & aOther) const;
already_AddRefed<nsIURI> URL() const;
double Density() const;
// Compute and return the density relative to a selector.
double Density(ResponsiveImageSelector *aSelector) const;
// If the width is already known. Useful when iterating over candidates to
// avoid having each call re-compute the width.
double Density(int32_t aMatchingWidth) const;
// If this selector is computed from the selector's matching width.
bool IsComputedFromWidth() const;
enum eCandidateType {
eCandidateType_Invalid,