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 /* rendering object for replaced elements with image data */
8 
9 #ifndef nsImageFrame_h___
10 #define nsImageFrame_h___
11 
12 #include "nsAtomicContainerFrame.h"
13 #include "nsIObserver.h"
14 
15 #include "imgINotificationObserver.h"
16 
17 #include "nsDisplayList.h"
18 #include "imgIContainer.h"
19 #include "mozilla/Attributes.h"
20 #include "mozilla/DebugOnly.h"
21 #include "mozilla/StaticPtr.h"
22 #include "nsIReflowCallback.h"
23 #include "nsTObserverArray.h"
24 
25 class nsFontMetrics;
26 class nsImageMap;
27 class nsIURI;
28 class nsILoadGroup;
29 class nsPresContext;
30 class nsImageFrame;
31 class nsTransform2D;
32 class nsImageLoadingContent;
33 
34 namespace mozilla {
35 class nsDisplayImage;
36 class PresShell;
37 namespace layers {
38 class ImageContainer;
39 class LayerManager;
40 }  // namespace layers
41 }  // namespace mozilla
42 
43 class nsImageListener final : public imgINotificationObserver {
44  protected:
45   virtual ~nsImageListener();
46 
47  public:
48   explicit nsImageListener(nsImageFrame* aFrame);
49 
50   NS_DECL_ISUPPORTS
51   NS_DECL_IMGINOTIFICATIONOBSERVER
52 
SetFrame(nsImageFrame * frame)53   void SetFrame(nsImageFrame* frame) { mFrame = frame; }
54 
55  private:
56   nsImageFrame* mFrame;
57 };
58 
59 class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback {
60  public:
61   template <typename T>
62   using Maybe = mozilla::Maybe<T>;
63   using Nothing = mozilla::Nothing;
64   using Visibility = mozilla::Visibility;
65 
66   typedef mozilla::image::ImgDrawResult ImgDrawResult;
67   typedef mozilla::layers::ImageContainer ImageContainer;
68   typedef mozilla::layers::LayerManager LayerManager;
69 
70   NS_DECL_FRAMEARENA_HELPERS(nsImageFrame)
71   NS_DECL_QUERYFRAME
72 
73   void DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData&) override;
74   void DidSetComputedStyle(ComputedStyle* aOldStyle) final;
75 
76   void Init(nsIContent* aContent, nsContainerFrame* aParent,
77             nsIFrame* aPrevInFlow) override;
78   void BuildDisplayList(nsDisplayListBuilder*, const nsDisplayListSet&) final;
79   nscoord GetMinISize(gfxContext* aRenderingContext) final;
80   nscoord GetPrefISize(gfxContext* aRenderingContext) final;
GetIntrinsicSize()81   mozilla::IntrinsicSize GetIntrinsicSize() final { return mIntrinsicSize; }
GetIntrinsicRatio()82   mozilla::AspectRatio GetIntrinsicRatio() const final {
83     return mIntrinsicRatio;
84   }
85   void Reflow(nsPresContext*, ReflowOutput&, const ReflowInput&,
86               nsReflowStatus&) override;
87 
88   nsresult GetContentForEvent(mozilla::WidgetEvent*,
89                               nsIContent** aContent) final;
90   nsresult HandleEvent(nsPresContext*, mozilla::WidgetGUIEvent*,
91                        nsEventStatus*) override;
92   mozilla::Maybe<Cursor> GetCursor(const nsPoint&) override;
93   nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
94                             int32_t aModType) final;
95 
96   void OnVisibilityChange(
97       Visibility aNewVisibility,
98       const Maybe<OnNonvisible>& aNonvisibleAction = Nothing()) final;
99 
100   void ResponsiveContentDensityChanged();
101   void SetupForContentURLRequest();
102   bool ShouldShowBrokenImageIcon() const;
103 
104   const mozilla::StyleImage* GetImageFromStyle() const;
105 
106 #ifdef ACCESSIBILITY
107   mozilla::a11y::AccType AccessibleType() override;
108 #endif
109 
IsFrameOfType(uint32_t aFlags)110   bool IsFrameOfType(uint32_t aFlags) const final {
111     return nsAtomicContainerFrame::IsFrameOfType(
112         aFlags & ~(nsIFrame::eReplaced | nsIFrame::eReplacedSizing));
113   }
114 
115 #ifdef DEBUG_FRAME_DUMP
116   nsresult GetFrameName(nsAString& aResult) const override;
117   void List(FILE* out = stderr, const char* aPrefix = "",
118             ListFlags aFlags = ListFlags()) const final;
119 #endif
120 
121   LogicalSides GetLogicalSkipSides() const final;
122 
ReleaseGlobals()123   static void ReleaseGlobals() {
124     if (gIconLoad) {
125       gIconLoad->Shutdown();
126       gIconLoad = nullptr;
127     }
128   }
129 
130   nsresult RestartAnimation();
131   nsresult StopAnimation();
132 
133   already_AddRefed<imgIRequest> GetCurrentRequest() const;
134   void Notify(imgIRequest*, int32_t aType, const nsIntRect* aData);
135 
136   /**
137    * Returns whether we should replace an element with an image corresponding to
138    * its 'content' CSS property.
139    */
140   static bool ShouldCreateImageFrameForContent(const mozilla::dom::Element&,
141                                                const ComputedStyle&);
142 
143   /**
144    * Function to test whether given an element and its style, that element
145    * should get an image frame.  Note that this method is only used by the
146    * frame constructor; it's only here because it uses gIconLoad for now.
147    */
148   static bool ShouldCreateImageFrameFor(const mozilla::dom::Element&,
149                                         const ComputedStyle&);
150 
151   ImgDrawResult DisplayAltFeedback(gfxContext& aRenderingContext,
152                                    const nsRect& aDirtyRect, nsPoint aPt,
153                                    uint32_t aFlags);
154 
155   ImgDrawResult DisplayAltFeedbackWithoutLayer(
156       nsDisplayItem*, mozilla::wr::DisplayListBuilder&,
157       mozilla::wr::IpcResourceUpdateQueue&,
158       const mozilla::layers::StackingContextHelper&,
159       mozilla::layers::RenderRootStateManager*, nsDisplayListBuilder*,
160       nsPoint aPt, uint32_t aFlags);
161 
162   /**
163    * Return a map element associated with this image.
164    */
165   mozilla::dom::Element* GetMapElement() const;
166 
167   /**
168    * Return true if the image has associated image map.
169    */
HasImageMap()170   bool HasImageMap() const { return mImageMap || GetMapElement(); }
171 
172   nsImageMap* GetImageMap();
GetExistingImageMap()173   nsImageMap* GetExistingImageMap() const { return mImageMap; }
174 
175   void AddInlineMinISize(gfxContext* aRenderingContext,
176                          InlineMinISizeData* aData) final;
177 
178   void DisconnectMap();
179 
180   // nsIReflowCallback
181   bool ReflowFinished() final;
182   void ReflowCallbackCanceled() final;
183 
184   // The kind of image frame we are.
185   enum class Kind : uint8_t {
186     // For an nsImageLoadingContent.
187     ImageElement,
188     // For css 'content: url(..)' on non-generated content.
189     ContentProperty,
190     // For a child of a ::before / ::after pseudo-element that had an url() item
191     // for the content property.
192     ContentPropertyAtIndex,
193     // For a list-style-image ::marker.
194     ListStyleImage,
195   };
196 
197   // Creates a suitable continuing frame for this frame.
198   nsImageFrame* CreateContinuingFrame(mozilla::PresShell*,
199                                       ComputedStyle*) const;
200 
201  private:
202   friend nsIFrame* NS_NewImageFrame(mozilla::PresShell*, ComputedStyle*);
203   friend nsIFrame* NS_NewImageFrameForContentProperty(mozilla::PresShell*,
204                                                       ComputedStyle*);
205   friend nsIFrame* NS_NewImageFrameForGeneratedContentIndex(mozilla::PresShell*,
206                                                             ComputedStyle*);
207   friend nsIFrame* NS_NewImageFrameForListStyleImage(mozilla::PresShell*,
208                                                      ComputedStyle*);
209 
nsImageFrame(ComputedStyle * aStyle,nsPresContext * aPresContext,Kind aKind)210   nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, Kind aKind)
211       : nsImageFrame(aStyle, aPresContext, kClassID, aKind) {}
212 
213   nsImageFrame(ComputedStyle*, nsPresContext* aPresContext, ClassID, Kind);
214 
215  protected:
nsImageFrame(ComputedStyle * aStyle,nsPresContext * aPresContext,ClassID aID)216   nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, ClassID aID)
217       : nsImageFrame(aStyle, aPresContext, aID, Kind::ImageElement) {}
218 
219   ~nsImageFrame() override;
220 
221   void EnsureIntrinsicSizeAndRatio();
222 
GotInitialReflow()223   bool GotInitialReflow() const {
224     return !HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
225   }
226 
227   SizeComputationResult ComputeSize(
228       gfxContext* aRenderingContext, mozilla::WritingMode aWM,
229       const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
230       const mozilla::LogicalSize& aMargin,
231       const mozilla::LogicalSize& aBorderPadding,
232       const mozilla::StyleSizeOverrides& aSizeOverrides,
233       mozilla::ComputeSizeFlags aFlags) final;
234 
235   bool IsServerImageMap();
236 
237   // Translate a point that is relative to our frame into a localized CSS pixel
238   // coordinate that is relative to the content area of this frame (inside the
239   // border+padding).
240   mozilla::CSSIntPoint TranslateEventCoords(const nsPoint& aPoint);
241 
242   bool GetAnchorHREFTargetAndNode(nsIURI** aHref, nsString& aTarget,
243                                   nsIContent** aNode);
244   /**
245    * Computes the width of the string that fits into the available space
246    *
247    * @param in aLength total length of the string in PRUnichars
248    * @param in aMaxWidth width not to be exceeded
249    * @param out aMaxFit length of the string that fits within aMaxWidth
250    *            in PRUnichars
251    * @return width of the string that fits within aMaxWidth
252    */
253   nscoord MeasureString(const char16_t* aString, int32_t aLength,
254                         nscoord aMaxWidth, uint32_t& aMaxFit,
255                         gfxContext& aContext, nsFontMetrics& aFontMetrics);
256 
257   void DisplayAltText(nsPresContext* aPresContext,
258                       gfxContext& aRenderingContext, const nsString& aAltText,
259                       const nsRect& aRect);
260 
261   ImgDrawResult PaintImage(gfxContext& aRenderingContext, nsPoint aPt,
262                            const nsRect& aDirtyRect, imgIContainer* aImage,
263                            uint32_t aFlags);
264 
265   /**
266    * If we're ready to decode - that is, if our current request's image is
267    * available and our decoding heuristics are satisfied - then trigger a decode
268    * for our image at the size we predict it will be drawn next time it's
269    * painted.
270    */
271   void MaybeDecodeForPredictedSize();
272 
273   /**
274    * Is this frame part of a ::marker pseudo?
275    */
276   bool IsForMarkerPseudo() const;
277 
278  protected:
279   friend class nsImageListener;
280   friend class nsImageLoadingContent;
281   friend class mozilla::PresShell;
282 
283   void OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage);
284   void OnFrameUpdate(imgIRequest* aRequest, const nsIntRect* aRect);
285   void OnLoadComplete(imgIRequest* aRequest, nsresult aStatus);
286 
287   /**
288    * Notification that aRequest will now be the current request.
289    */
290   void NotifyNewCurrentRequest(imgIRequest* aRequest, nsresult aStatus);
291 
292   /// Always sync decode our image when painting if @aForce is true.
SetForceSyncDecoding(bool aForce)293   void SetForceSyncDecoding(bool aForce) { mForceSyncDecoding = aForce; }
294 
295   /**
296    * Computes the predicted dest rect that we'll draw into, in app units, based
297    * upon the provided frame content box. (The content box is what
298    * nsDisplayImage::GetBounds() returns.)
299    * The result is not necessarily contained in the frame content box.
300    */
301   nsRect PredictedDestRect(const nsRect& aFrameContentBox);
302 
303  private:
304   void MaybeRecordContentUrlOnImageTelemetry();
305 
306   // random helpers
307   inline void SpecToURI(const nsAString& aSpec, nsIURI** aURI);
308 
309   inline void GetLoadGroup(nsPresContext* aPresContext,
310                            nsILoadGroup** aLoadGroup);
311   nscoord GetContinuationOffset() const;
312   void GetDocumentCharacterSet(nsACString& aCharset) const;
313   bool ShouldDisplaySelection();
314 
315   // Whether the image frame should use the mapped aspect ratio from width=""
316   // and height="".
317   bool ShouldUseMappedAspectRatio() const;
318 
319   // Recalculate mIntrinsicSize from the image.
320   bool UpdateIntrinsicSize();
321 
322   // Recalculate mIntrinsicRatio from the image.
323   bool UpdateIntrinsicRatio();
324 
325   /**
326    * This function calculates the transform for converting between
327    * source space & destination space. May fail if our image has a
328    * percent-valued or zero-valued height or width.
329    *
330    * @param aTransform The transform object to populate.
331    *
332    * @return whether we succeeded in creating the transform.
333    */
334   bool GetSourceToDestTransform(nsTransform2D& aTransform);
335 
336   /**
337    * Helper function to check whether the request corresponds to a load we don't
338    * care about.  Most of the decoder observer methods will bail early if this
339    * returns true.
340    */
341   bool IsPendingLoad(imgIRequest*) const;
342 
343   /**
344    * Updates mImage based on the current image request (cannot be null), and the
345    * image passed in (can be null), and invalidate layout and paint as needed.
346    */
347   void UpdateImage(imgIRequest*, imgIContainer*);
348 
349   /**
350    * Function to convert a dirty rect in the source image to a dirty
351    * rect for the image frame.
352    */
353   nsRect SourceRectToDest(const nsIntRect& aRect);
354 
355   /**
356    * Triggers invalidation for both our image display item and, if appropriate,
357    * our alt-feedback display item.
358    *
359    * @param aLayerInvalidRect The area to invalidate in layer space. If null,
360    * the entire layer will be invalidated.
361    * @param aFrameInvalidRect The area to invalidate in frame space. If null,
362    * the entire frame will be invalidated.
363    */
364   void InvalidateSelf(const nsIntRect* aLayerInvalidRect,
365                       const nsRect* aFrameInvalidRect);
366 
367   RefPtr<nsImageMap> mImageMap;
368 
369   RefPtr<nsImageListener> mListener;
370 
371   // An image request created for content: url(..) or list-style-image.
372   RefPtr<imgRequestProxy> mContentURLRequest;
373 
374   nsCOMPtr<imgIContainer> mImage;
375   nsCOMPtr<imgIContainer> mPrevImage;
376 
377   // The content-box size as if we are not fragmented, cached in the most recent
378   // reflow.
379   nsSize mComputedSize;
380 
381   mozilla::IntrinsicSize mIntrinsicSize;
382 
383   // Stores mImage's intrinsic ratio, or a default AspectRatio if there's no
384   // intrinsic ratio.
385   mozilla::AspectRatio mIntrinsicRatio;
386 
387   const Kind mKind;
388   bool mContentURLRequestRegistered;
389   bool mDisplayingIcon;
390   bool mFirstFrameComplete;
391   bool mReflowCallbackPosted;
392   bool mForceSyncDecoding;
393 
394   /* loading / broken image icon support */
395 
396   // XXXbz this should be handled by the prescontext, I think; that
397   // way we would have a single iconload per mozilla session instead
398   // of one per document...
399 
400   // LoadIcons: initiate the loading of the static icons used to show
401   // loading / broken images
402   nsresult LoadIcons(nsPresContext* aPresContext);
403   nsresult LoadIcon(const nsAString& aSpec, nsPresContext* aPresContext,
404                     imgRequestProxy** aRequest);
405 
406   class IconLoad final : public nsIObserver, public imgINotificationObserver {
407     // private class that wraps the data and logic needed for
408     // broken image and loading image icons
409    public:
410     IconLoad();
411 
412     void Shutdown();
413 
414     NS_DECL_ISUPPORTS
415     NS_DECL_NSIOBSERVER
416     NS_DECL_IMGINOTIFICATIONOBSERVER
417 
AddIconObserver(nsImageFrame * frame)418     void AddIconObserver(nsImageFrame* frame) {
419       MOZ_ASSERT(!mIconObservers.Contains(frame),
420                  "Observer shouldn't aleady be in array");
421       mIconObservers.AppendElement(frame);
422     }
423 
RemoveIconObserver(nsImageFrame * frame)424     void RemoveIconObserver(nsImageFrame* frame) {
425       mozilla::DebugOnly<bool> didRemove = mIconObservers.RemoveElement(frame);
426       MOZ_ASSERT(didRemove, "Observer not in array");
427     }
428 
429    private:
430     ~IconLoad() = default;
431 
432     void GetPrefs();
433     nsTObserverArray<nsImageFrame*> mIconObservers;
434 
435    public:
436     RefPtr<imgRequestProxy> mLoadingImage;
437     RefPtr<imgRequestProxy> mBrokenImage;
438     bool mPrefForceInlineAltText;
439     bool mPrefShowPlaceholders;
440     bool mPrefShowLoadingPlaceholder;
441   };
442 
443  public:
444   // singleton pattern: one LoadIcons instance is used
445   static mozilla::StaticRefPtr<IconLoad> gIconLoad;
446 
447   friend class mozilla::nsDisplayImage;
448   friend class nsDisplayGradient;
449 };
450 
451 namespace mozilla {
452 /**
453  * Note that nsDisplayImage does not receive events. However, an image element
454  * is replaced content so its background will be z-adjacent to the
455  * image itself, and hence receive events just as if the image itself
456  * received events.
457  */
458 class nsDisplayImage final : public nsPaintedDisplayItem {
459  public:
460   typedef mozilla::layers::LayerManager LayerManager;
461 
nsDisplayImage(nsDisplayListBuilder * aBuilder,nsImageFrame * aFrame,imgIContainer * aImage,imgIContainer * aPrevImage)462   nsDisplayImage(nsDisplayListBuilder* aBuilder, nsImageFrame* aFrame,
463                  imgIContainer* aImage, imgIContainer* aPrevImage)
464       : nsPaintedDisplayItem(aBuilder, aFrame),
465         mImage(aImage),
466         mPrevImage(aPrevImage) {
467     MOZ_COUNT_CTOR(nsDisplayImage);
468   }
~nsDisplayImage()469   ~nsDisplayImage() final { MOZ_COUNT_DTOR(nsDisplayImage); }
470 
471   nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder*) final;
472   void ComputeInvalidationRegion(nsDisplayListBuilder*,
473                                  const nsDisplayItemGeometry*,
474                                  nsRegion* aInvalidRegion) const final;
475   void Paint(nsDisplayListBuilder*, gfxContext* aCtx) final;
476 
477   /**
478    * @return The dest rect we'll use when drawing this image, in app units.
479    *         Not necessarily contained in this item's bounds.
480    */
481   nsRect GetDestRect() const;
482 
GetBounds(bool * aSnap)483   nsRect GetBounds(bool* aSnap) const {
484     *aSnap = true;
485     return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
486   }
487 
GetBounds(nsDisplayListBuilder *,bool * aSnap)488   nsRect GetBounds(nsDisplayListBuilder*, bool* aSnap) const final {
489     return GetBounds(aSnap);
490   }
491 
492   nsRegion GetOpaqueRegion(nsDisplayListBuilder*, bool* aSnap) const final;
493 
494   bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder&,
495                                mozilla::wr::IpcResourceUpdateQueue&,
496                                const StackingContextHelper&,
497                                mozilla::layers::RenderRootStateManager*,
498                                nsDisplayListBuilder*) final;
499 
500   NS_DISPLAY_DECL_NAME("Image", TYPE_IMAGE)
501  private:
502   nsCOMPtr<imgIContainer> mImage;
503   nsCOMPtr<imgIContainer> mPrevImage;
504 };
505 
506 }  // namespace mozilla
507 
508 #endif /* nsImageFrame_h___ */
509