Bug 870022 - Part 7 - HTMLImageElement - Make <picture> aware, react to <source> tag and sizes attr changes. r=jst

This commit is contained in:
John Schoenick 2014-05-02 14:32:20 -07:00
Родитель 82db106ffb
Коммит 8efbaf6fec
6 изменённых файлов: 354 добавлений и 45 удалений

Просмотреть файл

@ -151,6 +151,21 @@ ResponsiveImageSelector::SetDefaultSource(const nsAString & aSpec)
return NS_OK; return NS_OK;
} }
uint32_t
ResponsiveImageSelector::NumCandidates(bool aIncludeDefault)
{
uint32_t candidates = mCandidates.Length();
// If present, the default candidate is the last item
if (!aIncludeDefault && candidates &&
(mCandidates[candidates - 1].Type() ==
ResponsiveImageCandidate::eCandidateType_Default)) {
candidates--;
}
return candidates;
}
void void
ResponsiveImageSelector::SetDefaultSource(nsIURI *aURL) ResponsiveImageSelector::SetDefaultSource(nsIURI *aURL)
{ {

Просмотреть файл

@ -37,6 +37,10 @@ public:
nsresult SetDefaultSource(const nsAString & aSpec); nsresult SetDefaultSource(const nsAString & aSpec);
void SetDefaultSource(nsIURI *aURL); void SetDefaultSource(nsIURI *aURL);
uint32_t NumCandidates(bool aIncludeDefault = true);
nsIContent *Content() { return mContent; }
// Get the URL for the selected best candidate // Get the URL for the selected best candidate
already_AddRefed<nsIURI> GetSelectedImageURL(); already_AddRefed<nsIURI> GetSelectedImageURL();
double GetSelectedImageDensity(); double GetSelectedImageDensity();

Просмотреть файл

@ -26,6 +26,10 @@
#include "nsFocusManager.h" #include "nsFocusManager.h"
#include "mozilla/dom/HTMLFormElement.h" #include "mozilla/dom/HTMLFormElement.h"
// Responsive images!
#include "mozilla/dom/HTMLSourceElement.h"
#include "mozilla/dom/ResponsiveImageSelector.h"
#include "imgIContainer.h" #include "imgIContainer.h"
#include "imgILoader.h" #include "imgILoader.h"
#include "imgINotificationObserver.h" #include "imgINotificationObserver.h"
@ -46,6 +50,21 @@ static const char *kPrefSrcsetEnabled = "dom.image.srcset.enabled";
NS_IMPL_NS_NEW_HTML_ELEMENT(Image) NS_IMPL_NS_NEW_HTML_ELEMENT(Image)
// Is aSubject a previous sibling of aNode.
static bool IsPreviousSibling(nsINode *aSubject, nsINode *aNode)
{
if (aSubject == aNode) {
return false;
}
nsINode *parent = aSubject->GetParentNode();
if (parent && parent == aNode->GetParentNode()) {
return parent->IndexOf(aSubject) < parent->IndexOf(aNode);
}
return false;
}
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
@ -351,6 +370,8 @@ HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
// Handle src/srcset/crossorigin updates. If aNotify is false, we are coming // Handle src/srcset/crossorigin updates. If aNotify is false, we are coming
// from the parser or some such place; we'll get bound after all the // from the parser or some such place; we'll get bound after all the
// attributes have been set, so we'll do the image load from BindToTree. // attributes have been set, so we'll do the image load from BindToTree.
nsCOMPtr<nsIContent> thisContent = AsContent();
if (aName == nsGkAtoms::src && if (aName == nsGkAtoms::src &&
aNameSpaceID == kNameSpaceID_None) { aNameSpaceID == kNameSpaceID_None) {
// SetAttr handles setting src in the non-responsive case, so only handle it // SetAttr handles setting src in the non-responsive case, so only handle it
@ -368,8 +389,14 @@ HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
AsContent()->IsInDoc() && AsContent()->IsInDoc() &&
IsSrcsetEnabled()) { IsSrcsetEnabled()) {
// We currently don't handle responsive mode until BindToTree // We currently don't handle responsive mode until BindToTree
UpdateSourceSet(aValue->GetStringValue()); PictureSourceSrcsetChanged(thisContent,
LoadSelectedImage(false, aNotify); aValue ? aValue->GetStringValue() : EmptyString(),
aNotify);
} else if (aName == nsGkAtoms::sizes &&
aNameSpaceID == kNameSpaceID_None &&
thisContent->IsInDoc() &&
HTMLPictureElement::IsPictureEnabled()) {
PictureSourceSizesChanged(thisContent, aValue->GetStringValue(), aNotify);
} else if (aName == nsGkAtoms::crossorigin && } else if (aName == nsGkAtoms::crossorigin &&
aNameSpaceID == kNameSpaceID_None && aNameSpaceID == kNameSpaceID_None &&
aNotify) { aNotify) {
@ -502,9 +529,12 @@ HTMLImageElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
UpdateFormOwner(); UpdateFormOwner();
} }
bool addedToPicture = aParent && aParent->Tag() == nsGkAtoms::picture &&
HTMLPictureElement::IsPictureEnabled();
bool haveSrcset = IsSrcsetEnabled() && bool haveSrcset = IsSrcsetEnabled() &&
HasAttr(kNameSpaceID_None, nsGkAtoms::srcset); HasAttr(kNameSpaceID_None, nsGkAtoms::srcset);
if (haveSrcset || HasAttr(kNameSpaceID_None, nsGkAtoms::src)) { if (addedToPicture || haveSrcset ||
HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
// FIXME: Bug 660963 it would be nice if we could just have // FIXME: Bug 660963 it would be nice if we could just have
// ClearBrokenState update our state and do it fast... // ClearBrokenState update our state and do it fast...
ClearBrokenState(); ClearBrokenState();
@ -512,15 +542,8 @@ HTMLImageElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
// We don't handle responsive changes when not bound to a tree, update them // We don't handle responsive changes when not bound to a tree, update them
// now if necessary // now if necessary
if (haveSrcset) { if (addedToPicture || haveSrcset) {
nsAutoString srcset; MaybeUpdateResponsiveSelector();
GetAttr(kNameSpaceID_None, nsGkAtoms::srcset, srcset);
UpdateSourceSet(srcset);
if (mResponsiveSelector) {
nsAutoString src;
GetAttr(kNameSpaceID_None, nsGkAtoms::src, src);
mResponsiveSelector->SetDefaultSource(src);
}
} }
// If loading is temporarily disabled, don't even launch MaybeLoadImage. // If loading is temporarily disabled, don't even launch MaybeLoadImage.
@ -796,37 +819,224 @@ HTMLImageElement::LoadSelectedImage(bool aForce, bool aNotify)
return rv; return rv;
} }
void
HTMLImageElement::PictureSourceSrcsetChanged(nsIContent *aSourceNode,
const nsAString& aNewValue,
bool aNotify)
{
if (aSourceNode != AsContent() && !HTMLPictureElement::IsPictureEnabled()) {
// Don't consider <source> nodes if picture is pref'd off
return;
}
nsIContent *currentSrc = mResponsiveSelector ? mResponsiveSelector->Content()
: nullptr;
if (aSourceNode == currentSrc) {
// We're currently using this node as our responsive selector source.
mResponsiveSelector->SetCandidatesFromSourceSet(aNewValue);
// Search for a new source if we are no longer valid.
MaybeUpdateResponsiveSelector(currentSrc);
LoadSelectedImage(false, aNotify);
} else if (currentSrc && IsPreviousSibling(currentSrc, aSourceNode)) {
// If we have a source and it is previous to the one being updated, ignore
return;
} else {
// This is previous to our current source or we don't have a current source,
// use it if valid.
if (TryCreateResponsiveSelector(aSourceNode, &aNewValue, nullptr)) {
LoadSelectedImage(false, aNotify);
}
}
}
void
HTMLImageElement::PictureSourceSizesChanged(nsIContent *aSourceNode,
const nsAString& aNewValue,
bool aNotify)
{
if (!HTMLPictureElement::IsPictureEnabled()) {
// Don't consider sizes at all if picture support is disabled
return;
}
nsIContent *currentSrc = mResponsiveSelector ? mResponsiveSelector->Content()
: nullptr;
if (aSourceNode == currentSrc) {
// We're currently using this node as our responsive selector source.
mResponsiveSelector->SetSizesFromDescriptor(aNewValue);
LoadSelectedImage(false, aNotify);
}
}
void
HTMLImageElement::PictureSourceAdded(nsIContent *aSourceNode)
{
// If the source node is previous to our current one, or ourselves if we have
// no responsive source, try to use it as a responsive source.
nsIContent *currentSrc = mResponsiveSelector ? mResponsiveSelector->Content()
: AsContent();
if (HTMLPictureElement::IsPictureEnabled() &&
IsPreviousSibling(aSourceNode, currentSrc) &&
TryCreateResponsiveSelector(aSourceNode, nullptr, nullptr)) {
LoadSelectedImage(false, true);
}
}
void
HTMLImageElement::PictureSourceRemoved(nsIContent *aSourceNode)
{
// If this is our current source, we'll need to find another one or leave
// responsive mode.
if (mResponsiveSelector && mResponsiveSelector->Content() == aSourceNode) {
MaybeUpdateResponsiveSelector(aSourceNode, true);
LoadSelectedImage(false, true);
}
}
bool
HTMLImageElement::MaybeUpdateResponsiveSelector(nsIContent *aCurrentSource,
bool aSourceRemoved)
{
nsIContent *thisContent = AsContent();
if (!aCurrentSource && mResponsiveSelector) {
aCurrentSource = mResponsiveSelector->Content();
}
// If we have a source with candidates, no update is needed unless it is being
// removed
if (aCurrentSource && !aSourceRemoved &&
mResponsiveSelector->NumCandidates()) {
return false;
}
// Otherwise, invalidate
bool hadSelector = !!mResponsiveSelector;
mResponsiveSelector = nullptr;
if (!IsSrcsetEnabled()) {
return hadSelector;
}
// See if there's another source node we could use.
bool pictureEnabled = HTMLPictureElement::IsPictureEnabled();
nsIContent *nextSource = nullptr;
if (pictureEnabled && aCurrentSource && aCurrentSource != thisContent) {
// If current source is the <img> tag, there is no next candidate. Otherwise,
// it's the next sibling of the current source.
MOZ_ASSERT(IsPreviousSibling(aCurrentSource, thisContent) &&
thisContent->GetParentNode()->Tag() == nsGkAtoms::picture);
nextSource = aCurrentSource->GetNextSibling();
} else if (!aCurrentSource) {
// If no current source at all, start from the first possible source, which
// is the first node of the <picture> element or ourselves if we're not a
// picture
nsINode *parent = pictureEnabled ? thisContent->GetParentNode() : nullptr;
if (parent && parent->Tag() == nsGkAtoms::picture) {
nextSource = parent->GetFirstChild();
} else {
nextSource = thisContent;
}
}
while (nextSource) {
if (nextSource == thisContent) {
// We are the last possible source, so stop searching if we match or
// not
TryCreateResponsiveSelector(nextSource);
break;
} else if (nextSource->Tag() == nsGkAtoms::source &&
TryCreateResponsiveSelector(nextSource)) {
// If this led to a valid source, stop
break;
}
nextSource = nextSource->GetNextSibling();
}
// State changed unless we didn't make a selector and didn't start with one
return mResponsiveSelector || hadSelector;
}
bool
HTMLImageElement::TryCreateResponsiveSelector(nsIContent *aSourceNode,
const nsAString *aSrcset,
const nsAString *aSizes)
{
if (!IsSrcsetEnabled()) {
return false;
}
bool pictureEnabled = HTMLPictureElement::IsPictureEnabled();
// Skip if this is not a <source> with matching media query
bool isSourceTag = aSourceNode->Tag() == nsGkAtoms::source;
if (isSourceTag) {
DebugOnly<nsINode *> parent(nsINode::GetParentNode());
MOZ_ASSERT(parent && parent->Tag() == nsGkAtoms::picture);
MOZ_ASSERT(IsPreviousSibling(aSourceNode, AsContent()));
MOZ_ASSERT(pictureEnabled);
HTMLSourceElement *src = static_cast<HTMLSourceElement*>(aSourceNode);
if (!src->MatchesCurrentMedia()) {
return false;
}
} else if (aSourceNode->Tag() == nsGkAtoms::img) {
// Otherwise this is the <img> tag itself
MOZ_ASSERT(aSourceNode == AsContent());
}
// Skip if has no srcset or an empty srcset
nsString srcset;
if (aSrcset) {
srcset = *aSrcset;
} else if (!aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::srcset,
srcset)) {
return false;
}
if (srcset.IsEmpty()) {
return false;
}
// Try to parse
nsRefPtr<ResponsiveImageSelector> sel = new ResponsiveImageSelector(this);
if (!sel->SetCandidatesFromSourceSet(srcset)) {
// No possible candidates, don't need to bother parsing sizes
return false;
}
if (pictureEnabled && aSizes) {
sel->SetSizesFromDescriptor(*aSizes);
} else if (pictureEnabled) {
nsAutoString sizes;
aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::sizes, sizes);
sel->SetSizesFromDescriptor(sizes);
}
// If this is the <img> tag, also pull in src as the default source
if (!isSourceTag) {
MOZ_ASSERT(aSourceNode == AsContent());
nsAutoString src;
if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src) && !src.IsEmpty()) {
sel->SetDefaultSource(src);
}
}
mResponsiveSelector = sel;
return true;
}
void void
HTMLImageElement::DestroyContent() HTMLImageElement::DestroyContent()
{ {
mResponsiveSelector = nullptr; mResponsiveSelector = nullptr;
} }
void
HTMLImageElement::UpdateSourceSet(const nsAString & aSrcset)
{
MOZ_ASSERT(IsSrcsetEnabled());
bool haveSrcset = !aSrcset.IsEmpty();
if (haveSrcset && !mResponsiveSelector) {
mResponsiveSelector = new ResponsiveImageSelector(this);
mResponsiveSelector->SetCandidatesFromSourceSet(aSrcset);
// src may have been set before we decided we were responsive
nsAutoString src;
if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src) && src.Length()) {
mResponsiveSelector->SetDefaultSource(src);
}
} else if (haveSrcset) {
mResponsiveSelector->SetCandidatesFromSourceSet(aSrcset);
} else if (mResponsiveSelector) {
// Clearing srcset, don't need responsive selector anymore
mResponsiveSelector = nullptr;
}
}
} // namespace dom } // namespace dom
} // namespace mozilla } // namespace mozilla

Просмотреть файл

@ -12,16 +12,20 @@
#include "nsIDOMHTMLImageElement.h" #include "nsIDOMHTMLImageElement.h"
#include "imgRequestProxy.h" #include "imgRequestProxy.h"
#include "Units.h" #include "Units.h"
#include "mozilla/dom/ResponsiveImageSelector.h"
// Only needed for IsPictureEnabled()
#include "mozilla/dom/HTMLPictureElement.h"
namespace mozilla { namespace mozilla {
class EventChainPreVisitor; class EventChainPreVisitor;
namespace dom { namespace dom {
class ResponsiveImageSelector;
class HTMLImageElement MOZ_FINAL : public nsGenericHTMLElement, class HTMLImageElement MOZ_FINAL : public nsGenericHTMLElement,
public nsImageLoadingContent, public nsImageLoadingContent,
public nsIDOMHTMLImageElement public nsIDOMHTMLImageElement
{ {
friend class HTMLSourceElement;
public: public:
explicit HTMLImageElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo); explicit HTMLImageElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
virtual ~HTMLImageElement(); virtual ~HTMLImageElement();
@ -190,7 +194,27 @@ protected:
nsresult LoadSelectedImage(bool aForce, bool aNotify); nsresult LoadSelectedImage(bool aForce, bool aNotify);
// Update/create/destroy mResponsiveSelector // Update/create/destroy mResponsiveSelector
void UpdateSourceSet(const nsAString & aSrcset); void PictureSourceSrcsetChanged(nsIContent *aSourceNode,
const nsAString& aNewValue, bool aNotify);
void PictureSourceSizesChanged(nsIContent *aSourceNode,
const nsAString& aNewValue, bool aNotify);
void PictureSourceAdded(nsIContent *aSourceNode);
// This should be called prior to the unbind, such that nextsibling works
void PictureSourceRemoved(nsIContent *aSourceNode);
bool MaybeUpdateResponsiveSelector(nsIContent *aCurrentSource = nullptr,
bool aSourceRemoved = false);
// Given a <source> node that is a previous sibling *or* ourselves, try to
// create a ResponsiveSelector.
// If the node's srcset/sizes make for an invalid selector, returns
// false. This does not guarantee the resulting selector matches an image,
// only that it is valid.
bool TryCreateResponsiveSelector(nsIContent *aSourceNode,
const nsAString *aSrcset = nullptr,
const nsAString *aSizes = nullptr);
CSSIntPoint GetXY(); CSSIntPoint GetXY();
virtual void GetItemValueText(nsAString& text) MOZ_OVERRIDE; virtual void GetItemValueText(nsAString& text) MOZ_OVERRIDE;

Просмотреть файл

@ -7,9 +7,16 @@
#include "mozilla/dom/HTMLSourceElement.h" #include "mozilla/dom/HTMLSourceElement.h"
#include "mozilla/dom/HTMLSourceElementBinding.h" #include "mozilla/dom/HTMLSourceElementBinding.h"
#include "mozilla/dom/HTMLImageElement.h"
#include "mozilla/dom/ResponsiveImageSelector.h"
#include "nsGkAtoms.h"
#include "nsIMediaList.h" #include "nsIMediaList.h"
#include "nsCSSParser.h" #include "nsCSSParser.h"
#include "mozilla/Preferences.h"
NS_IMPL_NS_NEW_HTML_ELEMENT(Source) NS_IMPL_NS_NEW_HTML_ELEMENT(Source)
namespace mozilla { namespace mozilla {
@ -52,7 +59,28 @@ nsresult
HTMLSourceElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, HTMLSourceElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
const nsAttrValue* aValue, bool aNotify) const nsAttrValue* aValue, bool aNotify)
{ {
if (aName == nsGkAtoms::media) { // If we are associated with a <picture> with a valid <img>, notify it of
// responsive parameter changes
nsINode *parent = nsINode::GetParentNode();
if (aNameSpaceID == kNameSpaceID_None &&
(aName == nsGkAtoms::srcset || aName == nsGkAtoms::sizes) &&
parent && parent->Tag() == nsGkAtoms::picture && MatchesCurrentMedia()) {
nsString strVal = aValue ? aValue->GetStringValue() : EmptyString();
// Find all img siblings after this <source> and notify them of the change
nsCOMPtr<nsINode> sibling = AsContent();
while ( (sibling = sibling->GetNextSibling()) ) {
if (sibling->Tag() == nsGkAtoms::img) {
HTMLImageElement *img = static_cast<HTMLImageElement*>(sibling.get());
if (aName == nsGkAtoms::srcset) {
img->PictureSourceSrcsetChanged(AsContent(), strVal, aNotify);
} else if (aName == nsGkAtoms::sizes) {
img->PictureSourceSizesChanged(AsContent(), strVal, aNotify);
}
}
}
} else if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::media) {
mMediaList = nullptr; mMediaList = nullptr;
if (aValue) { if (aValue) {
nsString mediaStr = aValue->GetStringValue(); nsString mediaStr = aValue->GetStringValue();
@ -92,15 +120,41 @@ HTMLSourceElement::BindToTree(nsIDocument *aDocument,
aCompileEventHandlers); aCompileEventHandlers);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
if (!aParent || !aParent->IsNodeOfType(nsINode::eMEDIA)) if (aParent && aParent->IsNodeOfType(nsINode::eMEDIA)) {
return NS_OK; HTMLMediaElement* media = static_cast<HTMLMediaElement*>(aParent);
media->NotifyAddedSource();
HTMLMediaElement* media = static_cast<HTMLMediaElement*>(aParent); } else if (aParent && aParent->Tag() == nsGkAtoms::picture) {
media->NotifyAddedSource(); // Find any img siblings after this <source> and notify them
nsCOMPtr<nsINode> sibling = AsContent();
while ( (sibling = sibling->GetNextSibling()) ) {
if (sibling->Tag() == nsGkAtoms::img) {
HTMLImageElement *img = static_cast<HTMLImageElement*>(sibling.get());
img->PictureSourceAdded(AsContent());
}
}
}
return NS_OK; return NS_OK;
} }
void
HTMLSourceElement::UnbindFromTree(bool aDeep, bool aNullParent)
{
nsINode *parent = nsINode::GetParentNode();
if (parent && parent->Tag() == nsGkAtoms::picture) {
// Find all img siblings after this <source> and notify them of our demise
nsCOMPtr<nsINode> sibling = AsContent();
while ( (sibling = sibling->GetNextSibling()) ) {
if (sibling->Tag() == nsGkAtoms::img) {
HTMLImageElement *img = static_cast<HTMLImageElement*>(sibling.get());
img->PictureSourceRemoved(AsContent());
}
}
}
nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
}
JSObject* JSObject*
HTMLSourceElement::WrapNode(JSContext* aCx) HTMLSourceElement::WrapNode(JSContext* aCx)
{ {

Просмотреть файл

@ -17,6 +17,7 @@ class nsMediaList;
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
class ResponsiveImageSelector;
class HTMLSourceElement MOZ_FINAL : public nsGenericHTMLElement, class HTMLSourceElement MOZ_FINAL : public nsGenericHTMLElement,
public nsIDOMHTMLSourceElement public nsIDOMHTMLSourceElement
{ {
@ -39,6 +40,7 @@ public:
virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent, virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent, nsIContent* aBindingParent,
bool aCompileEventHandlers) MOZ_OVERRIDE; bool aCompileEventHandlers) MOZ_OVERRIDE;
virtual void UnbindFromTree(bool aDeep, bool aNullParent) MOZ_OVERRIDE;
// If this element's media attr matches for its owner document. Returns true // If this element's media attr matches for its owner document. Returns true
// if no media attr was set. // if no media attr was set.