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 #ifndef mozilla_dom_HTMLImageElement_h
8 #define mozilla_dom_HTMLImageElement_h
9 
10 #include "mozilla/Attributes.h"
11 #include "nsGenericHTMLElement.h"
12 #include "nsImageLoadingContent.h"
13 #include "Units.h"
14 #include "nsCycleCollectionParticipant.h"
15 
16 namespace mozilla {
17 class EventChainPreVisitor;
18 namespace dom {
19 
20 class ImageLoadTask;
21 
22 class ResponsiveImageSelector;
23 class HTMLImageElement final : public nsGenericHTMLElement,
24                                public nsImageLoadingContent {
25   friend class HTMLSourceElement;
26   friend class HTMLPictureElement;
27   friend class ImageLoadTask;
28 
29  public:
30   explicit HTMLImageElement(
31       already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
32 
33   static already_AddRefed<HTMLImageElement> Image(
34       const GlobalObject& aGlobal, const Optional<uint32_t>& aWidth,
35       const Optional<uint32_t>& aHeight, ErrorResult& aError);
36 
37   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLImageElement,
38                                            nsGenericHTMLElement)
39 
40   // nsISupports
41   NS_DECL_ISUPPORTS_INHERITED
42 
43   virtual bool Draggable() const override;
44 
45   // Element
46   virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
47 
48   // EventTarget
49   virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;
50 
51   NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLImageElement, img)
52 
53   // override from nsImageLoadingContent
54   CORSMode GetCORSMode() override;
55 
56   // nsIContent
57   virtual bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
58                               const nsAString& aValue,
59                               nsIPrincipal* aMaybeScriptedPrincipal,
60                               nsAttrValue& aResult) override;
61   virtual nsChangeHint GetAttributeChangeHint(const nsAtom* aAttribute,
62                                               int32_t aModType) const override;
63   NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override;
64   virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction()
65       const override;
66 
67   virtual nsresult GetEventTargetParent(
68       EventChainPreVisitor& aVisitor) override;
69 
70   bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
71                        int32_t* aTabIndex) override;
72 
73   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
74                               nsIContent* aBindingParent,
75                               bool aCompileEventHandlers) override;
76   virtual void UnbindFromTree(bool aDeep, bool aNullParent) override;
77 
78   virtual EventStates IntrinsicState() const override;
79   virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult,
80                          bool aPreallocateChildren) const override;
81 
82   virtual void NodeInfoChanged(nsIDocument* aOldDoc) override;
83 
84   nsresult CopyInnerTo(Element* aDest, bool aPreallocateChildren);
85 
86   void MaybeLoadImage(bool aAlwaysForceLoad);
87 
88   // Overrides for nsImageLoadingContent's GetNaturalHeight/Width, since we
89   // handle responsive scaling in the element's version of these methods.
90   NS_IMETHOD GetNaturalHeight(uint32_t* aNaturalHeight) override;
91   NS_IMETHOD GetNaturalWidth(uint32_t* aNaturalWidth) override;
92 
IsMap()93   bool IsMap() { return GetBoolAttr(nsGkAtoms::ismap); }
SetIsMap(bool aIsMap,ErrorResult & aError)94   void SetIsMap(bool aIsMap, ErrorResult& aError) {
95     SetHTMLBoolAttr(nsGkAtoms::ismap, aIsMap, aError);
96   }
Width()97   MOZ_CAN_RUN_SCRIPT uint32_t Width() {
98     return GetWidthHeightForImage(mCurrentRequest).width;
99   }
SetWidth(uint32_t aWidth,ErrorResult & aError)100   void SetWidth(uint32_t aWidth, ErrorResult& aError) {
101     SetUnsignedIntAttr(nsGkAtoms::width, aWidth, 0, aError);
102   }
Height()103   MOZ_CAN_RUN_SCRIPT uint32_t Height() {
104     return GetWidthHeightForImage(mCurrentRequest).height;
105   }
SetHeight(uint32_t aHeight,ErrorResult & aError)106   void SetHeight(uint32_t aHeight, ErrorResult& aError) {
107     SetUnsignedIntAttr(nsGkAtoms::height, aHeight, 0, aError);
108   }
109   uint32_t NaturalWidth();
110   uint32_t NaturalHeight();
111   bool Complete();
Hspace()112   uint32_t Hspace() { return GetUnsignedIntAttr(nsGkAtoms::hspace, 0); }
SetHspace(uint32_t aHspace,ErrorResult & aError)113   void SetHspace(uint32_t aHspace, ErrorResult& aError) {
114     SetUnsignedIntAttr(nsGkAtoms::hspace, aHspace, 0, aError);
115   }
Vspace()116   uint32_t Vspace() { return GetUnsignedIntAttr(nsGkAtoms::vspace, 0); }
SetVspace(uint32_t aVspace,ErrorResult & aError)117   void SetVspace(uint32_t aVspace, ErrorResult& aError) {
118     SetUnsignedIntAttr(nsGkAtoms::vspace, aVspace, 0, aError);
119   }
120 
GetAlt(nsAString & aAlt)121   void GetAlt(nsAString& aAlt) { GetHTMLAttr(nsGkAtoms::alt, aAlt); }
SetAlt(const nsAString & aAlt,ErrorResult & aError)122   void SetAlt(const nsAString& aAlt, ErrorResult& aError) {
123     SetHTMLAttr(nsGkAtoms::alt, aAlt, aError);
124   }
GetSrc(nsAString & aSrc)125   void GetSrc(nsAString& aSrc) { GetURIAttr(nsGkAtoms::src, nullptr, aSrc); }
SetSrc(const nsAString & aSrc,ErrorResult & aError)126   void SetSrc(const nsAString& aSrc, ErrorResult& aError) {
127     SetHTMLAttr(nsGkAtoms::src, aSrc, aError);
128   }
SetSrc(const nsAString & aSrc,nsIPrincipal * aTriggeringPrincipal,ErrorResult & aError)129   void SetSrc(const nsAString& aSrc, nsIPrincipal* aTriggeringPrincipal,
130               ErrorResult& aError) {
131     SetHTMLAttr(nsGkAtoms::src, aSrc, aTriggeringPrincipal, aError);
132   }
GetSrcset(nsAString & aSrcset)133   void GetSrcset(nsAString& aSrcset) {
134     GetHTMLAttr(nsGkAtoms::srcset, aSrcset);
135   }
SetSrcset(const nsAString & aSrcset,nsIPrincipal * aTriggeringPrincipal,ErrorResult & aError)136   void SetSrcset(const nsAString& aSrcset, nsIPrincipal* aTriggeringPrincipal,
137                  ErrorResult& aError) {
138     SetHTMLAttr(nsGkAtoms::srcset, aSrcset, aTriggeringPrincipal, aError);
139   }
GetCrossOrigin(nsAString & aResult)140   void GetCrossOrigin(nsAString& aResult) {
141     // Null for both missing and invalid defaults is ok, since we
142     // always parse to an enum value, so we don't need an invalid
143     // default, and we _want_ the missing default to be null.
144     GetEnumAttr(nsGkAtoms::crossorigin, nullptr, aResult);
145   }
SetCrossOrigin(const nsAString & aCrossOrigin,ErrorResult & aError)146   void SetCrossOrigin(const nsAString& aCrossOrigin, ErrorResult& aError) {
147     SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError);
148   }
GetUseMap(nsAString & aUseMap)149   void GetUseMap(nsAString& aUseMap) {
150     GetHTMLAttr(nsGkAtoms::usemap, aUseMap);
151   }
SetUseMap(const nsAString & aUseMap,ErrorResult & aError)152   void SetUseMap(const nsAString& aUseMap, ErrorResult& aError) {
153     SetHTMLAttr(nsGkAtoms::usemap, aUseMap, aError);
154   }
GetName(nsAString & aName)155   void GetName(nsAString& aName) { GetHTMLAttr(nsGkAtoms::name, aName); }
SetName(const nsAString & aName,ErrorResult & aError)156   void SetName(const nsAString& aName, ErrorResult& aError) {
157     SetHTMLAttr(nsGkAtoms::name, aName, aError);
158   }
GetAlign(nsAString & aAlign)159   void GetAlign(nsAString& aAlign) { GetHTMLAttr(nsGkAtoms::align, aAlign); }
SetAlign(const nsAString & aAlign,ErrorResult & aError)160   void SetAlign(const nsAString& aAlign, ErrorResult& aError) {
161     SetHTMLAttr(nsGkAtoms::align, aAlign, aError);
162   }
GetLongDesc(nsAString & aLongDesc)163   void GetLongDesc(nsAString& aLongDesc) {
164     GetURIAttr(nsGkAtoms::longdesc, nullptr, aLongDesc);
165   }
SetLongDesc(const nsAString & aLongDesc,ErrorResult & aError)166   void SetLongDesc(const nsAString& aLongDesc, ErrorResult& aError) {
167     SetHTMLAttr(nsGkAtoms::longdesc, aLongDesc, aError);
168   }
GetSizes(nsAString & aSizes)169   void GetSizes(nsAString& aSizes) { GetHTMLAttr(nsGkAtoms::sizes, aSizes); }
SetSizes(const nsAString & aSizes,ErrorResult & aError)170   void SetSizes(const nsAString& aSizes, ErrorResult& aError) {
171     SetHTMLAttr(nsGkAtoms::sizes, aSizes, aError);
172   }
173   void GetCurrentSrc(nsAString& aValue);
GetBorder(nsAString & aBorder)174   void GetBorder(nsAString& aBorder) {
175     GetHTMLAttr(nsGkAtoms::border, aBorder);
176   }
SetBorder(const nsAString & aBorder,ErrorResult & aError)177   void SetBorder(const nsAString& aBorder, ErrorResult& aError) {
178     SetHTMLAttr(nsGkAtoms::border, aBorder, aError);
179   }
SetReferrerPolicy(const nsAString & aReferrer,ErrorResult & aError)180   void SetReferrerPolicy(const nsAString& aReferrer, ErrorResult& aError) {
181     SetHTMLAttr(nsGkAtoms::referrerpolicy, aReferrer, aError);
182   }
GetReferrerPolicy(nsAString & aReferrer)183   void GetReferrerPolicy(nsAString& aReferrer) {
184     GetEnumAttr(nsGkAtoms::referrerpolicy, EmptyCString().get(), aReferrer);
185   }
186 
GetImageReferrerPolicy()187   net::ReferrerPolicy GetImageReferrerPolicy() override {
188     return GetReferrerPolicyAsEnum();
189   }
190 
191   MOZ_CAN_RUN_SCRIPT int32_t X();
192   MOZ_CAN_RUN_SCRIPT int32_t Y();
GetLowsrc(nsAString & aLowsrc)193   void GetLowsrc(nsAString& aLowsrc) {
194     GetURIAttr(nsGkAtoms::lowsrc, nullptr, aLowsrc);
195   }
SetLowsrc(const nsAString & aLowsrc,ErrorResult & aError)196   void SetLowsrc(const nsAString& aLowsrc, ErrorResult& aError) {
197     SetHTMLAttr(nsGkAtoms::lowsrc, aLowsrc, aError);
198   }
199 
200 #ifdef DEBUG
201   nsIDOMHTMLFormElement* GetForm() const;
202 #endif
203   void SetForm(nsIDOMHTMLFormElement* aForm);
204   void ClearForm(bool aRemoveFromForm);
205 
206   virtual void DestroyContent() override;
207 
208   void MediaFeatureValuesChanged();
209 
210   /**
211    * Given a hypothetical <img> or <source> tag with the given parameters,
212    * return what URI we would attempt to use, if any.  Used by the preloader to
213    * resolve sources prior to DOM creation.
214    *
215    * @param aDocument The document this image would be for, for referencing
216    *        viewport width and DPI/zoom
217    * @param aIsSourceTag If these parameters are for a <source> tag (as in a
218    *        <picture>) rather than an <img> tag. Note that some attrs are unused
219    *        when this is true an vice versa
220    * @param aSrcAttr [ignored if aIsSourceTag] The src attr for this image.
221    * @param aSrcsetAttr The srcset attr for this image/source
222    * @param aSizesAttr The sizes attr for this image/source
223    * @param aTypeAttr [ignored if !aIsSourceTag] The type attr for this source.
224    *                  Should be a void string to differentiate no type attribute
225    *                  from an empty one.
226    * @param aMediaAttr [ignored if !aIsSourceTag] The media attr for this
227    *                   source.  Should be a void string to differentiate no
228    *                   media attribute from an empty one.
229    * @param aResult A reference to store the resulting URL spec in if we
230    *                selected a source.  This value is not guaranteed to parse to
231    *                a valid URL, merely the URL that the tag would attempt to
232    *                resolve and load (which may be the empty string).  This
233    *                parameter is not modified if return value is false.
234    * @return True if we were able to select a final source, false if further
235    *         sources would be considered.  It follows that this always returns
236    *         true if !aIsSourceTag.
237    *
238    * Note that the return value may be true with an empty string as the result,
239    * which implies that the parameters provided describe a tag that would select
240    * no source.  This is distinct from a return of false which implies that
241    * further <source> or <img> tags would be considered.
242    */
243   static bool SelectSourceForTagWithAttrs(
244       nsIDocument* aDocument, bool aIsSourceTag, const nsAString& aSrcAttr,
245       const nsAString& aSrcsetAttr, const nsAString& aSizesAttr,
246       const nsAString& aTypeAttr, const nsAString& aMediaAttr,
247       nsAString& aResult);
248 
249   /**
250    * If this image's src pointers to an SVG document, flush the SVG document's
251    * use counters to telemetry.  Only used for testing purposes.
252    */
253   void FlushUseCounters();
254 
255  protected:
256   virtual ~HTMLImageElement();
257 
258   // Queues a task to run LoadSelectedImage pending stable state.
259   //
260   // Pending Bug 1076583 this is only used by the responsive image
261   // algorithm (InResponsiveMode()) -- synchronous actions when just
262   // using img.src will bypass this, and update source and kick off
263   // image load synchronously.
264   void QueueImageLoadTask(bool aAlwaysLoad);
265 
266   // True if we have a srcset attribute or a <picture> parent, regardless of if
267   // any valid responsive sources were parsed from either.
268   bool HaveSrcsetOrInPicture();
269 
270   // True if we are using the newer image loading algorithm. This will be the
271   // only mode after Bug 1076583
272   bool InResponsiveMode();
273 
274   // True if the given URL and density equal the last URL and density that was
275   // loaded by this element.
276   bool SelectedSourceMatchesLast(nsIURI* aSelectedSource,
277                                  double aSelectedDensity);
278 
279   // Resolve and load the current mResponsiveSelector (responsive mode) or src
280   // attr image.
281   nsresult LoadSelectedImage(bool aForce, bool aNotify, bool aAlwaysLoad);
282 
283   // True if this string represents a type we would support on <source type>
284   static bool SupportedPictureSourceType(const nsAString& aType);
285 
286   // Update/create/destroy mResponsiveSelector
287   void PictureSourceSrcsetChanged(nsIContent* aSourceNode,
288                                   const nsAString& aNewValue, bool aNotify);
289   void PictureSourceSizesChanged(nsIContent* aSourceNode,
290                                  const nsAString& aNewValue, bool aNotify);
291   // As we re-run the source selection on these mutations regardless,
292   // we don't actually care which changed or to what
293   void PictureSourceMediaOrTypeChanged(nsIContent* aSourceNode, bool aNotify);
294 
295   void PictureSourceAdded(nsIContent* aSourceNode);
296   // This should be called prior to the unbind, such that nextsibling works
297   void PictureSourceRemoved(nsIContent* aSourceNode);
298 
299   // Re-evaluates all source nodes (picture <source>,<img>) and finds
300   // the best source set for mResponsiveSelector. If a better source
301   // is found, creates a new selector and feeds the source to it. If
302   // the current ResponsiveSelector is not changed, runs
303   // SelectImage(true) to re-evaluate its candidates.
304   //
305   // Because keeping the existing selector is the common case (and we
306   // often do no-op reselections), this does not re-parse values for
307   // the existing mResponsiveSelector, meaning you need to update its
308   // parameters as appropriate before calling (or null it out to force
309   // recreation)
310   //
311   // Returns true if the source has changed, and false otherwise.
312   bool UpdateResponsiveSource();
313 
314   // Given a <source> node that is a previous sibling *or* ourselves, try to
315   // create a ResponsiveSelector.
316 
317   // If the node's srcset/sizes make for an invalid selector, returns
318   // false. This does not guarantee the resulting selector matches an image,
319   // only that it is valid.
320   bool TryCreateResponsiveSelector(Element* aSourceElement);
321 
322   MOZ_CAN_RUN_SCRIPT CSSIntPoint GetXY();
323   virtual JSObject* WrapNode(JSContext* aCx,
324                              JS::Handle<JSObject*> aGivenProto) override;
325   void UpdateFormOwner();
326 
327   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
328                                  const nsAttrValueOrString* aValue,
329                                  bool aNotify) override;
330 
331   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
332                                 const nsAttrValue* aValue,
333                                 const nsAttrValue* aOldValue,
334                                 nsIPrincipal* aMaybeScriptedPrincipal,
335                                 bool aNotify) override;
336   virtual nsresult OnAttrSetButNotChanged(int32_t aNamespaceID, nsAtom* aName,
337                                           const nsAttrValueOrString& aValue,
338                                           bool aNotify) override;
339 
340   // Override for nsImageLoadingContent.
AsContent()341   nsIContent* AsContent() override { return this; }
342 
343   // This is a weak reference that this element and the HTMLFormElement
344   // cooperate in maintaining.
345   HTMLFormElement* mForm;
346 
347   // Created when we're tracking responsive image state
348   RefPtr<ResponsiveImageSelector> mResponsiveSelector;
349 
350  private:
351   bool SourceElementMatches(Element* aSourceElement);
352 
353   static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
354                                     GenericSpecifiedValues* aGenericData);
355   /**
356    * This function is called by AfterSetAttr and OnAttrSetButNotChanged.
357    * It will not be called if the value is being unset.
358    *
359    * @param aNamespaceID the namespace of the attr being set
360    * @param aName the localname of the attribute being set
361    * @param aValue the value it's being set to represented as either a string or
362    *        a parsed nsAttrValue.
363    * @param aOldValue the value previously set. Will be null if no value was
364    *        previously set. This value should only be used when
365    *        aValueMaybeChanged is true; when aValueMaybeChanged is false,
366    *        aOldValue should be considered unreliable.
367    * @param aValueMaybeChanged will be false when this function is called from
368    *        OnAttrSetButNotChanged to indicate that the value was not changed.
369    * @param aNotify Whether we plan to notify document observers.
370    */
371   void AfterMaybeChangeAttr(int32_t aNamespaceID, nsAtom* aName,
372                             const nsAttrValueOrString& aValue,
373                             const nsAttrValue* aOldValue,
374                             nsIPrincipal* aMaybeScriptedPrincipal,
375                             bool aValueMaybeChanged, bool aNotify);
376 
377   bool mInDocResponsiveContent;
378   RefPtr<ImageLoadTask> mPendingImageLoadTask;
379   nsCOMPtr<nsIPrincipal> mSrcTriggeringPrincipal;
380   nsCOMPtr<nsIPrincipal> mSrcsetTriggeringPrincipal;
381 
382   // Last URL that was attempted to load by this element.
383   nsCOMPtr<nsIURI> mLastSelectedSource;
384   // Last pixel density that was selected.
385   double mCurrentDensity;
386 };
387 
388 }  // namespace dom
389 }  // namespace mozilla
390 
391 #endif /* mozilla_dom_HTMLImageElement_h */
392