void HTMLImageElement::PictureSourceSrcsetChanged(nsIContent *aSourceNode, const nsAString& aNewValue, bool aNotify) { bool isSelf = aSourceNode == this; if (!IsSrcsetEnabled() || (!isSelf && !HTMLPictureElement::IsPictureEnabled())) { return; } MOZ_ASSERT(isSelf || IsPreviousSibling(aSourceNode, this), "Should not be getting notifications for non-previous-siblings"); nsIContent *currentSrc = mResponsiveSelector ? mResponsiveSelector->Content() : nullptr; if (aSourceNode == currentSrc) { // We're currently using this node as our responsive selector // source. mResponsiveSelector->SetCandidatesFromSourceSet(aNewValue); } // This always triggers the image update steps per the spec, even if // we are not using this source. QueueImageLoadTask(); }
void HTMLImageElement::UpdateResponsiveSource() { if (!IsSrcsetEnabled()) { mResponsiveSelector = nullptr; return; } nsIContent *currentSource = mResponsiveSelector ? mResponsiveSelector->Content() : nullptr; bool pictureEnabled = HTMLPictureElement::IsPictureEnabled(); nsINode *parent = pictureEnabled ? this->nsINode::GetParentNode() : nullptr; nsINode *candidateSource = nullptr; if (parent && parent->Tag() == nsGkAtoms::picture) { // Walk source nodes previous to ourselves candidateSource = parent->GetFirstChild(); } else { candidateSource = this; } while (candidateSource) { if (candidateSource == currentSource) { // found no better source before current, re-run selection on // that and keep it if it's still usable. mResponsiveSelector->SelectImage(true); if (mResponsiveSelector->NumCandidates()) { break; } // no longer valid mResponsiveSelector = nullptr; if (candidateSource == this) { // No further possibilities break; } } else if (candidateSource == this) { // We are the last possible source if (!TryCreateResponsiveSelector(candidateSource->AsContent())) { // Failed to find any source mResponsiveSelector = nullptr; } break; } else if (candidateSource->Tag() == nsGkAtoms::source && TryCreateResponsiveSelector(candidateSource->AsContent())) { // This led to a valid source, stop break; } candidateSource = candidateSource->GetNextSibling(); } if (!candidateSource) { // Ran out of siblings without finding ourself, e.g. XBL magic. mResponsiveSelector = nullptr; } }
bool HTMLImageElement::HaveSrcsetOrInPicture() { if (IsSrcsetEnabled() && HasAttr(kNameSpaceID_None, nsGkAtoms::srcset)) { return true; } if (!HTMLPictureElement::IsPictureEnabled()) { return false; } Element *parent = nsINode::GetParentElement(); return (parent && parent->IsHTMLElement(nsGkAtoms::picture)); }
nsresult HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, const nsAttrValue* aValue, bool aNotify) { if (aNameSpaceID == kNameSpaceID_None && mForm && (aName == nsGkAtoms::name || aName == nsGkAtoms::id) && aValue && !aValue->IsEmptyString()) { // add the image to the hashtable as needed NS_ABORT_IF_FALSE(aValue->Type() == nsAttrValue::eAtom, "Expected atom value for name/id"); mForm->AddImageElementToTable(this, nsDependentAtomString(aValue->GetAtomValue())); } // 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 // attributes have been set, so we'll do the image load from BindToTree. if (aName == nsGkAtoms::src && aNameSpaceID == kNameSpaceID_None) { // SetAttr handles setting src in the non-responsive case, so only handle it // for responsive mode or unsetting if (!aValue) { CancelImageRequests(aNotify); } else if (mResponsiveSelector) { mResponsiveSelector->SetDefaultSource(aValue ? aValue->GetStringValue() : EmptyString()); LoadSelectedImage(false, aNotify); } } else if (aName == nsGkAtoms::srcset && aNameSpaceID == kNameSpaceID_None && aNotify && AsContent()->IsInDoc() && IsSrcsetEnabled()) { // We currently don't handle responsive mode until BindToTree UpdateSourceSet(aValue->GetStringValue()); LoadSelectedImage(false, aNotify); } else if (aName == nsGkAtoms::crossorigin && aNameSpaceID == kNameSpaceID_None && aNotify) { // We want aForce == true in this LoadImage call, because we want to force // a new load of the image with the new cross origin policy. nsCOMPtr<nsIURI> currentURI; if (NS_SUCCEEDED(GetCurrentURI(getter_AddRefs(currentURI))) && currentURI) { LoadImage(currentURI, true, aNotify); } } return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, aNotify); }
nsresult HTMLImageElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, bool aCompileEventHandlers) { nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent, aBindingParent, aCompileEventHandlers); NS_ENSURE_SUCCESS(rv, rv); nsImageLoadingContent::BindToTree(aDocument, aParent, aBindingParent, aCompileEventHandlers); if (aParent) { UpdateFormOwner(); } bool haveSrcset = IsSrcsetEnabled() && HasAttr(kNameSpaceID_None, nsGkAtoms::srcset); if (haveSrcset || HasAttr(kNameSpaceID_None, nsGkAtoms::src)) { // FIXME: Bug 660963 it would be nice if we could just have // ClearBrokenState update our state and do it fast... ClearBrokenState(); RemoveStatesSilently(NS_EVENT_STATE_BROKEN); // We don't handle responsive changes when not bound to a tree, update them // now if necessary if (haveSrcset) { nsAutoString srcset; 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. // Otherwise MaybeLoadImage may run later when someone has reenabled // loading. if (LoadingEnabled()) { nsContentUtils::AddScriptRunner( NS_NewRunnableMethod(this, &HTMLImageElement::MaybeLoadImage)); } } return rv; }
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; } }
nsresult HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, const nsAttrValue* aValue, bool aNotify) { if (aNameSpaceID == kNameSpaceID_None && mForm && (aName == nsGkAtoms::name || aName == nsGkAtoms::id) && aValue && !aValue->IsEmptyString()) { // add the image to the hashtable as needed MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom, "Expected atom value for name/id"); mForm->AddImageElementToTable(this, nsDependentAtomString(aValue->GetAtomValue())); } // 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 // attributes have been set, so we'll do the image load from BindToTree. nsAttrValueOrString attrVal(aValue); if (aName == nsGkAtoms::src && aNameSpaceID == kNameSpaceID_None && !aValue) { // SetAttr handles setting src since it needs to catch img.src = // img.src, so we only need to handle the unset case if (InResponsiveMode()) { if (mResponsiveSelector && mResponsiveSelector->Content() == this) { mResponsiveSelector->SetDefaultSource(NullString()); } QueueImageLoadTask(true); } else { // Bug 1076583 - We still behave synchronously in the non-responsive case CancelImageRequests(aNotify); } } else if (aName == nsGkAtoms::srcset && aNameSpaceID == kNameSpaceID_None && IsSrcsetEnabled()) { PictureSourceSrcsetChanged(this, attrVal.String(), aNotify); } else if (aName == nsGkAtoms::sizes && aNameSpaceID == kNameSpaceID_None && HTMLPictureElement::IsPictureEnabled()) { PictureSourceSizesChanged(this, attrVal.String(), aNotify); } else if (aName == nsGkAtoms::crossorigin && aNameSpaceID == kNameSpaceID_None && aNotify) { // Force a new load of the image with the new cross origin policy. if (InResponsiveMode()) { // per spec, full selection runs when this changes, even though // it doesn't directly affect the source selection QueueImageLoadTask(true); } else { // Bug 1076583 - We still use the older synchronous algorithm in // non-responsive mode. Force a new load of the image with the // new cross origin policy. ForceReload(aNotify); } } return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, aNotify); }
/* static */ bool HTMLImageElement::SelectSourceForTagWithAttrs(nsIDocument *aDocument, bool aIsSourceTag, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr, const nsAString& aSizesAttr, const nsAString& aTypeAttr, const nsAString& aMediaAttr, nsAString& aResult) { MOZ_ASSERT(aIsSourceTag || (aTypeAttr.IsEmpty() && aMediaAttr.IsEmpty()), "Passing type or media attrs makes no sense without aIsSourceTag"); MOZ_ASSERT(!aIsSourceTag || aSrcAttr.IsEmpty(), "Passing aSrcAttr makes no sense with aIsSourceTag set"); bool pictureEnabled = HTMLPictureElement::IsPictureEnabled(); if (aIsSourceTag && !pictureEnabled) { return false; } if (!IsSrcsetEnabled() || aSrcsetAttr.IsEmpty()) { if (!aIsSourceTag) { // For an <img> with no srcset, we would always select the src attr. aResult.Assign(aSrcAttr); return true; } // Otherwise, a <source> without srcset is never selected return false; } // Would not consider source tags with unsupported media or type if (aIsSourceTag && ((!aMediaAttr.IsVoid() && !HTMLSourceElement::WouldMatchMediaForDocument(aMediaAttr, aDocument)) || (!aTypeAttr.IsVoid() && !SupportedPictureSourceType(aTypeAttr)))) { return false; } // Using srcset or picture <source>, build a responsive selector for this tag. RefPtr<ResponsiveImageSelector> sel = new ResponsiveImageSelector(aDocument); sel->SetCandidatesFromSourceSet(aSrcsetAttr); if (pictureEnabled && !aSizesAttr.IsEmpty()) { sel->SetSizesFromDescriptor(aSizesAttr); } if (!aIsSourceTag) { sel->SetDefaultSource(aSrcAttr); } if (sel->GetSelectedImageURLSpec(aResult)) { return true; } if (!aIsSourceTag) { // <img> tag with no match would definitively load nothing. aResult.Truncate(); return true; } // <source> tags with no match would leave source yet-undetermined. return false; }
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->IsHTMLElement(nsGkAtoms::source); if (isSourceTag) { if (!SourceElementMatches(aSourceNode)) { return false; } } else if (aSourceNode->IsHTMLElement(nsGkAtoms::img)) { // Otherwise this is the <img> tag itself MOZ_ASSERT(aSourceNode == this); } // 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 RefPtr<ResponsiveImageSelector> sel = new ResponsiveImageSelector(aSourceNode); 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 == this); nsAutoString src; if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src) && !src.IsEmpty()) { sel->SetDefaultSource(src); } } mResponsiveSelector = sel; return true; }
bool HTMLImageElement::UpdateResponsiveSource() { bool hadSelector = !!mResponsiveSelector; if (!IsSrcsetEnabled()) { mResponsiveSelector = nullptr; return hadSelector; } nsIContent *currentSource = mResponsiveSelector ? mResponsiveSelector->Content() : nullptr; bool pictureEnabled = HTMLPictureElement::IsPictureEnabled(); Element *parent = pictureEnabled ? nsINode::GetParentElement() : nullptr; nsINode *candidateSource = nullptr; if (parent && parent->IsHTMLElement(nsGkAtoms::picture)) { // Walk source nodes previous to ourselves candidateSource = parent->GetFirstChild(); } else { candidateSource = this; } while (candidateSource) { if (candidateSource == currentSource) { // found no better source before current, re-run selection on // that and keep it if it's still usable. bool changed = mResponsiveSelector->SelectImage(true); if (mResponsiveSelector->NumCandidates()) { bool isUsableCandidate = true; // an otherwise-usable source element may still have a media query that may not // match any more. if (candidateSource->IsHTMLElement(nsGkAtoms::source) && !SourceElementMatches(candidateSource->AsContent())) { isUsableCandidate = false; } if (isUsableCandidate) { return changed; } } // no longer valid mResponsiveSelector = nullptr; if (candidateSource == this) { // No further possibilities break; } } else if (candidateSource == this) { // We are the last possible source if (!TryCreateResponsiveSelector(candidateSource->AsContent())) { // Failed to find any source mResponsiveSelector = nullptr; } break; } else if (candidateSource->IsHTMLElement(nsGkAtoms::source) && TryCreateResponsiveSelector(candidateSource->AsContent())) { // This led to a valid source, stop break; } candidateSource = candidateSource->GetNextSibling(); } if (!candidateSource) { // Ran out of siblings without finding ourself, e.g. XBL magic. mResponsiveSelector = nullptr; } return !hadSelector || mResponsiveSelector; }
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, this)); MOZ_ASSERT(pictureEnabled); // Check media and type HTMLSourceElement *src = static_cast<HTMLSourceElement*>(aSourceNode); if (!src->MatchesCurrentMedia()) { return false; } nsAutoString type; if (aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type) && !imgLoader::SupportImageWithMimeType( NS_ConvertUTF16toUTF8(type).get(), AcceptedMimeTypes::IMAGES_AND_DOCUMENTS) ) { return false; } } else if (aSourceNode->Tag() == nsGkAtoms::img) { // Otherwise this is the <img> tag itself MOZ_ASSERT(aSourceNode == this); } // 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(aSourceNode); 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 == this); nsAutoString src; if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src) && !src.IsEmpty()) { sel->SetDefaultSource(src); } } mResponsiveSelector = sel; return true; }