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
11 #include "js/TypeDecls.h"
12 #include "mozilla/AppUnits.h"
13 #include "mozilla/Attributes.h"
14 #include "mozilla/LinkedList.h"
15 #include "mozilla/WritingModes.h"
16 #include "mozilla/dom/DOMRect.h"
17 #include "mozilla/dom/BindingDeclarations.h"
18 #include "mozilla/dom/ResizeObserverBinding.h"
20 #include "nsCycleCollectionParticipant.h"
21 #include "nsRefPtrHashtable.h"
23 #include "nsWrapperCache.h"
25 // XXX Avoid including this here by moving function bodies to the cpp file
26 #include "nsPIDOMWindow.h"
35 // The logical size in pixels.
36 // Note: if LogicalPixelSize have usages other than ResizeObserver in the
37 // future, it might be better to change LogicalSize into a template class, and
38 // use it to implement LogicalPixelSize.
39 class LogicalPixelSize
{
41 LogicalPixelSize() = default;
42 LogicalPixelSize(WritingMode aWM
, const gfx::Size
& aSize
) {
44 if (aWM
.IsVertical()) {
45 std::swap(mSize
.width
, mSize
.height
);
49 bool operator==(const LogicalPixelSize
& aOther
) const {
50 return mSize
== aOther
.mSize
;
52 bool operator!=(const LogicalPixelSize
& aOther
) const {
53 return !(*this == aOther
);
56 float ISize() const { return mSize
.width
; }
57 float BSize() const { return mSize
.height
; }
58 float& ISize() { return mSize
.width
; }
59 float& BSize() { return mSize
.height
; }
62 // |mSize.width| represents inline-size and |mSize.height| represents
67 // For the internal implementation in ResizeObserver. Normally, this is owned by
69 class ResizeObservation final
: public LinkedListElement
<ResizeObservation
> {
71 NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ResizeObservation
)
72 NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ResizeObservation
)
74 ResizeObservation(Element
&, ResizeObserver
&, ResizeObserverBoxOptions
,
77 Element
* Target() const { return mTarget
; }
79 ResizeObserverBoxOptions
BoxOptions() const { return mObservedBox
; }
82 * Returns whether the observed target element size differs from the saved
85 bool IsActive() const;
88 * Update current mLastReportedSize with size from aSize.
90 void UpdateLastReportedSize(const gfx::Size
& aSize
);
92 enum class RemoveFromObserver
: bool { No
, Yes
};
93 void Unlink(RemoveFromObserver
);
96 ~ResizeObservation() { Unlink(RemoveFromObserver::No
); };
98 nsCOMPtr
<Element
> mTarget
;
100 // Weak, observer always outlives us.
101 ResizeObserver
* mObserver
;
103 const ResizeObserverBoxOptions mObservedBox
;
105 // The latest recorded of observed target.
106 // This will be CSS pixels for border-box/content-box, or device pixels for
107 // device-pixel-content-box.
108 // Note: We use default constructor for this because we want to start with a
109 // (0, 0) size, per the spec.
110 LogicalPixelSize mLastReportedSize
;
114 * ResizeObserver interfaces and algorithms are based on
115 * https://drafts.csswg.org/resize-observer/#api
117 class ResizeObserver final
: public nsISupports
, public nsWrapperCache
{
119 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
120 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ResizeObserver
)
122 ResizeObserver(nsCOMPtr
<nsPIDOMWindowInner
>&& aOwner
, Document
* aDocument
,
123 ResizeObserverCallback
& aCb
)
124 : mOwner(std::move(aOwner
)), mDocument(aDocument
), mCallback(&aCb
) {
125 MOZ_ASSERT(mOwner
, "Need a non-null owner window");
126 MOZ_ASSERT(mDocument
, "Need a non-null doc");
127 MOZ_ASSERT(mDocument
== mOwner
->GetExtantDoc());
130 nsISupports
* GetParentObject() const { return mOwner
; }
132 JSObject
* WrapObject(JSContext
* aCx
,
133 JS::Handle
<JSObject
*> aGivenProto
) override
{
134 return ResizeObserver_Binding::Wrap(aCx
, this, aGivenProto
);
137 static already_AddRefed
<ResizeObserver
> Constructor(
138 const GlobalObject
& aGlobal
, ResizeObserverCallback
& aCb
,
141 void Observe(Element
& aTarget
, const ResizeObserverOptions
& aOptions
,
144 void Unobserve(Element
& target
, ErrorResult
& aRv
);
149 * Gather all observations which have an observed target with size changed
150 * since last BroadcastActiveObservations() in this ResizeObserver.
151 * An observation will be skipped if the depth of its observed target is less
152 * or equal than aDepth. All gathered observations will be added to
155 void GatherActiveObservations(uint32_t aDepth
);
158 * Returns whether this ResizeObserver has any active observations
159 * since last GatherActiveObservations().
161 bool HasActiveObservations() const { return !mActiveTargets
.IsEmpty(); }
164 * Returns whether this ResizeObserver has any skipped observations
165 * since last GatherActiveObservations().
167 bool HasSkippedObservations() const { return mHasSkippedTargets
; }
170 * Invoke the callback function in JavaScript for all active observations
171 * and pass the sequence of ResizeObserverEntry so JavaScript can access them.
172 * The active observations' mLastReportedSize fields will be updated, and
173 * mActiveTargets will be cleared. It also returns the shallowest depth of
174 * elements from active observations or numeric_limits<uint32_t>::max() if
175 * there are not any active observations.
177 MOZ_CAN_RUN_SCRIPT
uint32_t BroadcastActiveObservations();
180 ~ResizeObserver() { Disconnect(); }
182 nsCOMPtr
<nsPIDOMWindowInner
> mOwner
;
183 // The window's document at the time of ResizeObserver creation.
184 RefPtr
<Document
> mDocument
;
185 RefPtr
<ResizeObserverCallback
> mCallback
;
186 nsTArray
<RefPtr
<ResizeObservation
>> mActiveTargets
;
187 // The spec uses a list to store the skipped targets. However, it seems what
188 // we want is to check if there are any skipped targets (i.e. existence).
189 // Therefore, we use a boolean value to represent the existence of skipped
191 bool mHasSkippedTargets
;
193 // Combination of HashTable and LinkedList so we can iterate through
194 // the elements of HashTable in order of insertion time, so we can deliver
195 // observations in the correct order
196 // FIXME: it will be nice if we have our own data structure for this in the
197 // future, and mObservationMap should be considered the "owning" storage for
198 // the observations, so it'd be better to drop mObservationList later.
199 nsRefPtrHashtable
<nsPtrHashKey
<Element
>, ResizeObservation
> mObservationMap
;
200 LinkedList
<ResizeObservation
> mObservationList
;
204 * ResizeObserverEntry is the entry that contains the information for observed
205 * elements. This object is the one that's visible to JavaScript in callback
206 * function that is fired by ResizeObserver.
208 class ResizeObserverEntry final
: public nsISupports
, public nsWrapperCache
{
210 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
211 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ResizeObserverEntry
)
213 ResizeObserverEntry(nsISupports
* aOwner
, Element
& aTarget
,
214 const gfx::Size
& aBorderBoxSize
,
215 const gfx::Size
& aContentBoxSize
,
216 const gfx::Size
& aDevicePixelContentBoxSize
)
217 : mOwner(aOwner
), mTarget(&aTarget
) {
218 MOZ_ASSERT(mOwner
, "Need a non-null owner");
219 MOZ_ASSERT(mTarget
, "Need a non-null target element");
221 SetBorderBoxSize(aBorderBoxSize
);
222 SetContentRectAndSize(aContentBoxSize
);
223 SetDevicePixelContentSize(aDevicePixelContentBoxSize
);
226 nsISupports
* GetParentObject() const { return mOwner
; }
228 JSObject
* WrapObject(JSContext
* aCx
,
229 JS::Handle
<JSObject
*> aGivenProto
) override
{
230 return ResizeObserverEntry_Binding::Wrap(aCx
, this, aGivenProto
);
233 Element
* Target() const { return mTarget
; }
236 * Returns the DOMRectReadOnly of target's content rect so it can be
237 * accessed from JavaScript in callback function of ResizeObserver.
239 DOMRectReadOnly
* ContentRect() const { return mContentRect
; }
242 * Returns target's logical border-box size, content-box size, and
243 * device-pixel-content-box as an array of ResizeObserverSize.
245 void GetBorderBoxSize(nsTArray
<RefPtr
<ResizeObserverSize
>>& aRetVal
) const;
246 void GetContentBoxSize(nsTArray
<RefPtr
<ResizeObserverSize
>>& aRetVal
) const;
247 void GetDevicePixelContentBoxSize(
248 nsTArray
<RefPtr
<ResizeObserverSize
>>& aRetVal
) const;
251 ~ResizeObserverEntry() = default;
253 // Set borderBoxSize.
254 void SetBorderBoxSize(const gfx::Size
& aSize
);
255 // Set contentRect and contentBoxSize.
256 void SetContentRectAndSize(const gfx::Size
& aSize
);
257 // Set devicePixelContentBoxSize.
258 void SetDevicePixelContentSize(const gfx::Size
& aSize
);
260 nsCOMPtr
<nsISupports
> mOwner
;
261 nsCOMPtr
<Element
> mTarget
;
263 RefPtr
<DOMRectReadOnly
> mContentRect
;
264 RefPtr
<ResizeObserverSize
> mBorderBoxSize
;
265 RefPtr
<ResizeObserverSize
> mContentBoxSize
;
266 RefPtr
<ResizeObserverSize
> mDevicePixelContentBoxSize
;
269 class ResizeObserverSize final
: public nsISupports
, public nsWrapperCache
{
271 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
272 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ResizeObserverSize
)
274 ResizeObserverSize(nsISupports
* aOwner
, const gfx::Size
& aSize
,
275 const WritingMode aWM
)
276 : mOwner(aOwner
), mSize(aWM
, aSize
) {
277 MOZ_ASSERT(mOwner
, "Need a non-null owner");
280 nsISupports
* GetParentObject() const { return mOwner
; }
282 JSObject
* WrapObject(JSContext
* aCx
,
283 JS::Handle
<JSObject
*> aGivenProto
) override
{
284 return ResizeObserverSize_Binding::Wrap(aCx
, this, aGivenProto
);
287 double InlineSize() const { return mSize
.ISize(); }
288 double BlockSize() const { return mSize
.BSize(); }
291 ~ResizeObserverSize() = default;
293 nsCOMPtr
<nsISupports
> mOwner
;
294 // The logical size value:
295 // 1. content-box/border-box: in CSS pixels.
296 // 2. device-pixel-content-box: in device pixels.
297 const LogicalPixelSize mSize
;
301 } // namespace mozilla
303 #endif // mozilla_dom_ResizeObserver_h