1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
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/. */
7 #ifndef mozilla_dom_ResizeObserver_h
8 #define mozilla_dom_ResizeObserver_h
10 #include "js/TypeDecls.h"
11 #include "mozilla/AppUnits.h"
12 #include "mozilla/Attributes.h"
13 #include "mozilla/ErrorResult.h"
14 #include "mozilla/LinkedList.h"
15 #include "mozilla/WritingModes.h"
16 #include "mozilla/dom/BindingDeclarations.h"
17 #include "mozilla/dom/ResizeObserverBinding.h"
19 #include "nsCycleCollectionParticipant.h"
20 #include "nsRefPtrHashtable.h"
22 #include "nsWrapperCache.h"
30 } // namespace mozilla
35 // For the internal implementation in ResizeObserver. Normally, this is owned by
37 class ResizeObservation final
: public LinkedListElement
<ResizeObservation
> {
39 NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ResizeObservation
)
40 NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ResizeObservation
)
42 ResizeObservation(Element
& aTarget
, ResizeObserverBoxOptions aBox
,
43 const WritingMode aWM
)
46 // This starts us with a 0,0 last-reported-size:
47 mLastReportedSize(aWM
),
48 mLastReportedWM(aWM
) {
49 MOZ_ASSERT(mTarget
, "Need a non-null target element");
52 Element
* Target() const { return mTarget
; }
54 ResizeObserverBoxOptions
BoxOptions() const { return mObservedBox
; }
57 * Returns whether the observed target element size differs from the saved
60 bool IsActive() const;
63 * Update current mLastReportedSize with size from aSize.
65 void UpdateLastReportedSize(const nsSize
& aSize
);
68 ~ResizeObservation() = default;
70 nsCOMPtr
<Element
> mTarget
;
72 const ResizeObserverBoxOptions mObservedBox
;
74 // The latest recorded size of observed target.
75 // Per the spec, observation.lastReportedSize should be entry.borderBoxSize
76 // or entry.contentBoxSize (i.e. logical size), instead of entry.contentRect
77 // (i.e. physical rect), so we store this as LogicalSize.
78 LogicalSize mLastReportedSize
;
79 WritingMode mLastReportedWM
;
83 * ResizeObserver interfaces and algorithms are based on
84 * https://drafts.csswg.org/resize-observer/#api
86 class ResizeObserver final
: public nsISupports
, public nsWrapperCache
{
88 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
89 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ResizeObserver
)
91 ResizeObserver(nsCOMPtr
<nsPIDOMWindowInner
>&& aOwner
, Document
* aDocument
,
92 ResizeObserverCallback
& aCb
)
93 : mOwner(std::move(aOwner
)), mDocument(aDocument
), mCallback(&aCb
) {
94 MOZ_ASSERT(mOwner
, "Need a non-null owner window");
95 MOZ_ASSERT(mDocument
, "Need a non-null doc");
96 MOZ_ASSERT(mDocument
== mOwner
->GetExtantDoc());
99 nsISupports
* GetParentObject() const { return mOwner
; }
101 JSObject
* WrapObject(JSContext
* aCx
,
102 JS::Handle
<JSObject
*> aGivenProto
) override
{
103 return ResizeObserver_Binding::Wrap(aCx
, this, aGivenProto
);
106 static already_AddRefed
<ResizeObserver
> Constructor(
107 const GlobalObject
& aGlobal
, ResizeObserverCallback
& aCb
,
110 void Observe(Element
& aTarget
, const ResizeObserverOptions
& aOptions
,
113 void Unobserve(Element
& target
, ErrorResult
& aRv
);
118 * Gather all observations which have an observed target with size changed
119 * since last BroadcastActiveObservations() in this ResizeObserver.
120 * An observation will be skipped if the depth of its observed target is less
121 * or equal than aDepth. All gathered observations will be added to
124 void GatherActiveObservations(uint32_t aDepth
);
127 * Returns whether this ResizeObserver has any active observations
128 * since last GatherActiveObservations().
130 bool HasActiveObservations() const { return !mActiveTargets
.IsEmpty(); }
133 * Returns whether this ResizeObserver has any skipped observations
134 * since last GatherActiveObservations().
136 bool HasSkippedObservations() const { return mHasSkippedTargets
; }
139 * Invoke the callback function in JavaScript for all active observations
140 * and pass the sequence of ResizeObserverEntry so JavaScript can access them.
141 * The active observations' mLastReportedSize fields will be updated, and
142 * mActiveTargets will be cleared. It also returns the shallowest depth of
143 * elements from active observations or numeric_limits<uint32_t>::max() if
144 * there are not any active observations.
146 MOZ_CAN_RUN_SCRIPT
uint32_t BroadcastActiveObservations();
149 ~ResizeObserver() { mObservationList
.clear(); }
151 nsCOMPtr
<nsPIDOMWindowInner
> mOwner
;
152 // The window's document at the time of ResizeObserver creation.
153 RefPtr
<Document
> mDocument
;
154 RefPtr
<ResizeObserverCallback
> mCallback
;
155 nsTArray
<RefPtr
<ResizeObservation
>> mActiveTargets
;
156 // The spec uses a list to store the skipped targets. However, it seems what
157 // we want is to check if there are any skipped targets (i.e. existence).
158 // Therefore, we use a boolean value to represent the existence of skipped
160 bool mHasSkippedTargets
;
162 // Combination of HashTable and LinkedList so we can iterate through
163 // the elements of HashTable in order of insertion time, so we can deliver
164 // observations in the correct order
165 // FIXME: it will be nice if we have our own data structure for this in the
166 // future, and mObservationMap should be considered the "owning" storage for
167 // the observations, so it'd be better to drop mObservationList later.
168 nsRefPtrHashtable
<nsPtrHashKey
<Element
>, ResizeObservation
> mObservationMap
;
169 LinkedList
<ResizeObservation
> mObservationList
;
173 * ResizeObserverEntry is the entry that contains the information for observed
174 * elements. This object is the one that's visible to JavaScript in callback
175 * function that is fired by ResizeObserver.
177 class ResizeObserverEntry final
: public nsISupports
, public nsWrapperCache
{
179 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
180 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ResizeObserverEntry
)
182 ResizeObserverEntry(nsISupports
* aOwner
, Element
& aTarget
,
183 const nsSize
& aBorderBoxSize
,
184 const nsSize
& aContentBoxSize
)
185 : mOwner(aOwner
), mTarget(&aTarget
) {
186 MOZ_ASSERT(mOwner
, "Need a non-null owner");
187 MOZ_ASSERT(mTarget
, "Need a non-null target element");
189 SetBorderBoxSize(aBorderBoxSize
);
190 SetContentRectAndSize(aContentBoxSize
);
193 nsISupports
* GetParentObject() const { return mOwner
; }
195 JSObject
* WrapObject(JSContext
* aCx
,
196 JS::Handle
<JSObject
*> aGivenProto
) override
{
197 return ResizeObserverEntry_Binding::Wrap(aCx
, this, aGivenProto
);
200 Element
* Target() const { return mTarget
; }
203 * Returns the DOMRectReadOnly of target's content rect so it can be
204 * accessed from JavaScript in callback function of ResizeObserver.
206 DOMRectReadOnly
* ContentRect() const { return mContentRect
; }
209 * Returns target's logical border-box size and content-box size as
210 * ResizeObserverSize.
212 ResizeObserverSize
* BorderBoxSize() const { return mBorderBoxSize
; }
213 ResizeObserverSize
* ContentBoxSize() const { return mContentBoxSize
; }
216 ~ResizeObserverEntry() = default;
218 // Set borderBoxSize.
219 void SetBorderBoxSize(const nsSize
& aSize
);
220 // Set contentRect and contentBoxSize.
221 void SetContentRectAndSize(const nsSize
& aSize
);
223 nsCOMPtr
<nsISupports
> mOwner
;
224 nsCOMPtr
<Element
> mTarget
;
226 RefPtr
<DOMRectReadOnly
> mContentRect
;
227 RefPtr
<ResizeObserverSize
> mBorderBoxSize
;
228 RefPtr
<ResizeObserverSize
> mContentBoxSize
;
231 class ResizeObserverSize final
: public nsISupports
, public nsWrapperCache
{
233 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
234 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ResizeObserverSize
)
236 // Note: the unit of |aSize| is app unit, and we convert it into css pixel in
237 // the public JS APIs.
238 ResizeObserverSize(nsISupports
* aOwner
, const nsSize
& aSize
,
239 const WritingMode aWM
)
240 : mOwner(aOwner
), mSize(aWM
, aSize
), mWM(aWM
) {
241 MOZ_ASSERT(mOwner
, "Need a non-null owner");
244 nsISupports
* GetParentObject() const { return mOwner
; }
246 JSObject
* WrapObject(JSContext
* aCx
,
247 JS::Handle
<JSObject
*> aGivenProto
) override
{
248 return ResizeObserverSize_Binding::Wrap(aCx
, this, aGivenProto
);
251 double InlineSize() const {
252 return NSAppUnitsToDoublePixels(mSize
.ISize(mWM
), AppUnitsPerCSSPixel());
255 double BlockSize() const {
256 return NSAppUnitsToDoublePixels(mSize
.BSize(mWM
), AppUnitsPerCSSPixel());
260 ~ResizeObserverSize() = default;
262 nsCOMPtr
<nsISupports
> mOwner
;
263 const LogicalSize mSize
;
264 const WritingMode mWM
;
268 } // namespace mozilla
270 #endif // mozilla_dom_ResizeObserver_h