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 gfx::Size
PhysicalSize(WritingMode aWM
) const {
50 if (!aWM
.IsVertical()) {
53 gfx::Size
result(mSize
);
54 std::swap(result
.width
, result
.height
);
58 bool operator==(const LogicalPixelSize
& aOther
) const {
59 return mSize
== aOther
.mSize
;
61 bool operator!=(const LogicalPixelSize
& aOther
) const {
62 return !(*this == aOther
);
65 float ISize() const { return mSize
.width
; }
66 float BSize() const { return mSize
.height
; }
67 float& ISize() { return mSize
.width
; }
68 float& BSize() { return mSize
.height
; }
71 // |mSize.width| represents inline-size and |mSize.height| represents
76 // For the internal implementation in ResizeObserver. Normally, this is owned by
78 class ResizeObservation final
: public LinkedListElement
<ResizeObservation
> {
80 NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ResizeObservation
)
81 NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ResizeObservation
)
83 ResizeObservation(Element
&, ResizeObserver
&, ResizeObserverBoxOptions
);
85 Element
* Target() const { return mTarget
; }
87 ResizeObserverBoxOptions
BoxOptions() const { return mObservedBox
; }
90 * Returns whether the observed target element size differs from the saved
93 bool IsActive() const;
96 * Update current mLastReportedSize to aSize.
98 void UpdateLastReportedSize(const nsTArray
<LogicalPixelSize
>& aSize
);
100 enum class RemoveFromObserver
: bool { No
, Yes
};
101 void Unlink(RemoveFromObserver
);
104 ~ResizeObservation() { Unlink(RemoveFromObserver::No
); };
106 nsCOMPtr
<Element
> mTarget
;
108 // Weak, observer always outlives us.
109 ResizeObserver
* mObserver
;
111 const ResizeObserverBoxOptions mObservedBox
;
113 // The latest recorded of observed target.
114 // This will be CSS pixels for border-box/content-box, or device pixels for
115 // device-pixel-content-box.
116 AutoTArray
<LogicalPixelSize
, 1> mLastReportedSize
;
120 * ResizeObserver interfaces and algorithms are based on
121 * https://drafts.csswg.org/resize-observer/#api
123 class ResizeObserver final
: public nsISupports
, public nsWrapperCache
{
126 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
127 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserver
)
129 ResizeObserver(nsCOMPtr
<nsPIDOMWindowInner
>&& aOwner
, Document
* aDocument
,
130 ResizeObserverCallback
& aCb
)
131 : mOwner(std::move(aOwner
)), mDocument(aDocument
), mCallback(&aCb
) {
132 MOZ_ASSERT(mOwner
, "Need a non-null owner window");
133 MOZ_ASSERT(mDocument
, "Need a non-null doc");
134 MOZ_ASSERT(mDocument
== mOwner
->GetExtantDoc());
137 nsISupports
* GetParentObject() const { return mOwner
; }
139 JSObject
* WrapObject(JSContext
* aCx
,
140 JS::Handle
<JSObject
*> aGivenProto
) override
{
141 return ResizeObserver_Binding::Wrap(aCx
, this, aGivenProto
);
144 static already_AddRefed
<ResizeObserver
> Constructor(
145 const GlobalObject
& aGlobal
, ResizeObserverCallback
& aCb
,
148 void Observe(Element
&, const ResizeObserverOptions
&);
149 void Unobserve(Element
&);
154 * Gather all observations which have an observed target with size changed
155 * since last BroadcastActiveObservations() in this ResizeObserver.
156 * An observation will be skipped if the depth of its observed target is less
157 * or equal than aDepth. All gathered observations will be added to
160 void GatherActiveObservations(uint32_t aDepth
);
163 * Returns whether this ResizeObserver has any active observations
164 * since last GatherActiveObservations().
166 bool HasActiveObservations() const { return !mActiveTargets
.IsEmpty(); }
169 * Returns whether this ResizeObserver has any skipped observations
170 * since last GatherActiveObservations().
172 bool HasSkippedObservations() const { return mHasSkippedTargets
; }
175 * Invoke the callback function in JavaScript for all active observations
176 * and pass the sequence of ResizeObserverEntry so JavaScript can access them.
177 * The active observations' mLastReportedSize fields will be updated, and
178 * mActiveTargets will be cleared. It also returns the shallowest depth of
179 * elements from active observations or numeric_limits<uint32_t>::max() if
180 * there are not any active observations.
182 MOZ_CAN_RUN_SCRIPT
uint32_t BroadcastActiveObservations();
185 * Returns |aTarget|'s size in the form of gfx::Size (in pixels).
186 * If the target is an SVG that does not participate in CSS layout,
187 * its width and height are determined from bounding box. Otherwise, the
188 * relevant box is determined according to the |aBox| parameter.
190 * If dom.resize_observer.support_fragments is enabled, or if
191 * |aForceFragmentHandling| is true then the function reports the size of all
192 * fragments, and not just the first one.
194 * https://www.w3.org/TR/resize-observer-1/#calculate-box-size
196 static AutoTArray
<LogicalPixelSize
, 1> CalculateBoxSize(
197 Element
* aTarget
, ResizeObserverBoxOptions aBox
,
198 bool aForceFragmentHandling
= false);
201 ~ResizeObserver() { Disconnect(); }
203 nsCOMPtr
<nsPIDOMWindowInner
> mOwner
;
204 // The window's document at the time of ResizeObserver creation.
205 RefPtr
<Document
> mDocument
;
206 RefPtr
<ResizeObserverCallback
> mCallback
;
207 nsTArray
<RefPtr
<ResizeObservation
>> mActiveTargets
;
208 // The spec uses a list to store the skipped targets. However, it seems what
209 // we want is to check if there are any skipped targets (i.e. existence).
210 // Therefore, we use a boolean value to represent the existence of skipped
212 bool mHasSkippedTargets
= false;
214 // Combination of HashTable and LinkedList so we can iterate through
215 // the elements of HashTable in order of insertion time, so we can deliver
216 // observations in the correct order
217 // FIXME: it will be nice if we have our own data structure for this in the
218 // future, and mObservationMap should be considered the "owning" storage for
219 // the observations, so it'd be better to drop mObservationList later.
220 nsRefPtrHashtable
<nsPtrHashKey
<Element
>, ResizeObservation
> mObservationMap
;
221 LinkedList
<ResizeObservation
> mObservationList
;
225 * ResizeObserverEntry is the entry that contains the information for observed
226 * elements. This object is the one that's visible to JavaScript in callback
227 * function that is fired by ResizeObserver.
229 class ResizeObserverEntry final
: public nsISupports
, public nsWrapperCache
{
231 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
232 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserverEntry
)
235 nsISupports
* aOwner
, Element
& aTarget
,
236 const nsTArray
<LogicalPixelSize
>& aBorderBoxSize
,
237 const nsTArray
<LogicalPixelSize
>& aContentBoxSize
,
238 const nsTArray
<LogicalPixelSize
>& aDevicePixelContentBoxSize
)
239 : mOwner(aOwner
), mTarget(&aTarget
) {
240 MOZ_ASSERT(mOwner
, "Need a non-null owner");
241 MOZ_ASSERT(mTarget
, "Need a non-null target element");
243 SetBorderBoxSize(aBorderBoxSize
);
244 SetContentRectAndSize(aContentBoxSize
);
245 SetDevicePixelContentSize(aDevicePixelContentBoxSize
);
248 nsISupports
* GetParentObject() const { return mOwner
; }
250 JSObject
* WrapObject(JSContext
* aCx
,
251 JS::Handle
<JSObject
*> aGivenProto
) override
{
252 return ResizeObserverEntry_Binding::Wrap(aCx
, this, aGivenProto
);
255 Element
* Target() const { return mTarget
; }
258 * Returns the DOMRectReadOnly of target's content rect so it can be
259 * accessed from JavaScript in callback function of ResizeObserver.
261 DOMRectReadOnly
* ContentRect() const { return mContentRect
; }
264 * Returns target's logical border-box size, content-box size, and
265 * device-pixel-content-box as an array of ResizeObserverSize.
267 void GetBorderBoxSize(nsTArray
<RefPtr
<ResizeObserverSize
>>& aRetVal
) const;
268 void GetContentBoxSize(nsTArray
<RefPtr
<ResizeObserverSize
>>& aRetVal
) const;
269 void GetDevicePixelContentBoxSize(
270 nsTArray
<RefPtr
<ResizeObserverSize
>>& aRetVal
) const;
273 ~ResizeObserverEntry() = default;
275 // Set borderBoxSize.
276 void SetBorderBoxSize(const nsTArray
<LogicalPixelSize
>& aSize
);
277 // Set contentRect and contentBoxSize.
278 void SetContentRectAndSize(const nsTArray
<LogicalPixelSize
>& aSize
);
279 // Set devicePixelContentBoxSize.
280 void SetDevicePixelContentSize(const nsTArray
<LogicalPixelSize
>& aSize
);
282 nsCOMPtr
<nsISupports
> mOwner
;
283 nsCOMPtr
<Element
> mTarget
;
285 RefPtr
<DOMRectReadOnly
> mContentRect
;
286 AutoTArray
<RefPtr
<ResizeObserverSize
>, 1> mBorderBoxSize
;
287 AutoTArray
<RefPtr
<ResizeObserverSize
>, 1> mContentBoxSize
;
288 AutoTArray
<RefPtr
<ResizeObserverSize
>, 1> mDevicePixelContentBoxSize
;
291 class ResizeObserverSize final
: public nsISupports
, public nsWrapperCache
{
293 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
294 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserverSize
)
296 ResizeObserverSize(nsISupports
* aOwner
, const LogicalPixelSize
& aSize
)
297 : mOwner(aOwner
), mSize(aSize
) {
298 MOZ_ASSERT(mOwner
, "Need a non-null owner");
301 nsISupports
* GetParentObject() const { return mOwner
; }
303 JSObject
* WrapObject(JSContext
* aCx
,
304 JS::Handle
<JSObject
*> aGivenProto
) override
{
305 return ResizeObserverSize_Binding::Wrap(aCx
, this, aGivenProto
);
308 float InlineSize() const { return mSize
.ISize(); }
309 float BlockSize() const { return mSize
.BSize(); }
312 ~ResizeObserverSize() = default;
314 nsCOMPtr
<nsISupports
> mOwner
;
315 // The logical size value:
316 // 1. content-box/border-box: in CSS pixels.
317 // 2. device-pixel-content-box: in device pixels.
318 const LogicalPixelSize mSize
;
322 } // namespace mozilla
324 #endif // mozilla_dom_ResizeObserver_h