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
{
125 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
126 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserver
)
128 ResizeObserver(nsCOMPtr
<nsPIDOMWindowInner
>&& aOwner
, Document
* aDocument
,
129 ResizeObserverCallback
& aCb
)
130 : mOwner(std::move(aOwner
)), mDocument(aDocument
), mCallback(&aCb
) {
131 MOZ_ASSERT(mOwner
, "Need a non-null owner window");
132 MOZ_ASSERT(mDocument
, "Need a non-null doc");
133 MOZ_ASSERT(mDocument
== mOwner
->GetExtantDoc());
136 nsISupports
* GetParentObject() const { return mOwner
; }
138 JSObject
* WrapObject(JSContext
* aCx
,
139 JS::Handle
<JSObject
*> aGivenProto
) override
{
140 return ResizeObserver_Binding::Wrap(aCx
, this, aGivenProto
);
143 static already_AddRefed
<ResizeObserver
> Constructor(
144 const GlobalObject
& aGlobal
, ResizeObserverCallback
& aCb
,
147 void Observe(Element
&, const ResizeObserverOptions
&);
148 void Unobserve(Element
&);
153 * Gather all observations which have an observed target with size changed
154 * since last BroadcastActiveObservations() in this ResizeObserver.
155 * An observation will be skipped if the depth of its observed target is less
156 * or equal than aDepth. All gathered observations will be added to
159 void GatherActiveObservations(uint32_t aDepth
);
162 * Returns whether this ResizeObserver has any active observations
163 * since last GatherActiveObservations().
165 bool HasActiveObservations() const { return !mActiveTargets
.IsEmpty(); }
168 * Returns whether this ResizeObserver has any skipped observations
169 * since last GatherActiveObservations().
171 bool HasSkippedObservations() const { return mHasSkippedTargets
; }
174 * Invoke the callback function in JavaScript for all active observations
175 * and pass the sequence of ResizeObserverEntry so JavaScript can access them.
176 * The active observations' mLastReportedSize fields will be updated, and
177 * mActiveTargets will be cleared. It also returns the shallowest depth of
178 * elements from active observations or numeric_limits<uint32_t>::max() if
179 * there are not any active observations.
181 MOZ_CAN_RUN_SCRIPT
uint32_t BroadcastActiveObservations();
184 * Returns |aTarget|'s size in the form of gfx::Size (in pixels).
185 * If the target is an SVG that does not participate in CSS layout,
186 * its width and height are determined from bounding box. Otherwise, the
187 * relevant box is determined according to the |aBox| parameter.
189 * If dom.resize_observer.support_fragments is enabled, or if
190 * |aForceFragmentHandling| is true then the function reports the size of all
191 * fragments, and not just the first one.
193 * https://www.w3.org/TR/resize-observer-1/#calculate-box-size
195 static AutoTArray
<LogicalPixelSize
, 1> CalculateBoxSize(
196 Element
* aTarget
, ResizeObserverBoxOptions aBox
,
197 bool aForceFragmentHandling
= false);
200 ~ResizeObserver() { Disconnect(); }
202 nsCOMPtr
<nsPIDOMWindowInner
> mOwner
;
203 // The window's document at the time of ResizeObserver creation.
204 RefPtr
<Document
> mDocument
;
205 RefPtr
<ResizeObserverCallback
> mCallback
;
206 nsTArray
<RefPtr
<ResizeObservation
>> mActiveTargets
;
207 // The spec uses a list to store the skipped targets. However, it seems what
208 // we want is to check if there are any skipped targets (i.e. existence).
209 // Therefore, we use a boolean value to represent the existence of skipped
211 bool mHasSkippedTargets
= false;
213 // Combination of HashTable and LinkedList so we can iterate through
214 // the elements of HashTable in order of insertion time, so we can deliver
215 // observations in the correct order
216 // FIXME: it will be nice if we have our own data structure for this in the
217 // future, and mObservationMap should be considered the "owning" storage for
218 // the observations, so it'd be better to drop mObservationList later.
219 nsRefPtrHashtable
<nsPtrHashKey
<Element
>, ResizeObservation
> mObservationMap
;
220 LinkedList
<ResizeObservation
> mObservationList
;
224 * ResizeObserverEntry is the entry that contains the information for observed
225 * elements. This object is the one that's visible to JavaScript in callback
226 * function that is fired by ResizeObserver.
228 class ResizeObserverEntry final
: public nsISupports
, public nsWrapperCache
{
230 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
231 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserverEntry
)
234 nsISupports
* aOwner
, Element
& aTarget
,
235 const nsTArray
<LogicalPixelSize
>& aBorderBoxSize
,
236 const nsTArray
<LogicalPixelSize
>& aContentBoxSize
,
237 const nsTArray
<LogicalPixelSize
>& aDevicePixelContentBoxSize
)
238 : mOwner(aOwner
), mTarget(&aTarget
) {
239 MOZ_ASSERT(mOwner
, "Need a non-null owner");
240 MOZ_ASSERT(mTarget
, "Need a non-null target element");
242 SetBorderBoxSize(aBorderBoxSize
);
243 SetContentRectAndSize(aContentBoxSize
);
244 SetDevicePixelContentSize(aDevicePixelContentBoxSize
);
247 nsISupports
* GetParentObject() const { return mOwner
; }
249 JSObject
* WrapObject(JSContext
* aCx
,
250 JS::Handle
<JSObject
*> aGivenProto
) override
{
251 return ResizeObserverEntry_Binding::Wrap(aCx
, this, aGivenProto
);
254 Element
* Target() const { return mTarget
; }
257 * Returns the DOMRectReadOnly of target's content rect so it can be
258 * accessed from JavaScript in callback function of ResizeObserver.
260 DOMRectReadOnly
* ContentRect() const { return mContentRect
; }
263 * Returns target's logical border-box size, content-box size, and
264 * device-pixel-content-box as an array of ResizeObserverSize.
266 void GetBorderBoxSize(nsTArray
<RefPtr
<ResizeObserverSize
>>& aRetVal
) const;
267 void GetContentBoxSize(nsTArray
<RefPtr
<ResizeObserverSize
>>& aRetVal
) const;
268 void GetDevicePixelContentBoxSize(
269 nsTArray
<RefPtr
<ResizeObserverSize
>>& aRetVal
) const;
272 ~ResizeObserverEntry() = default;
274 // Set borderBoxSize.
275 void SetBorderBoxSize(const nsTArray
<LogicalPixelSize
>& aSize
);
276 // Set contentRect and contentBoxSize.
277 void SetContentRectAndSize(const nsTArray
<LogicalPixelSize
>& aSize
);
278 // Set devicePixelContentBoxSize.
279 void SetDevicePixelContentSize(const nsTArray
<LogicalPixelSize
>& aSize
);
281 nsCOMPtr
<nsISupports
> mOwner
;
282 nsCOMPtr
<Element
> mTarget
;
284 RefPtr
<DOMRectReadOnly
> mContentRect
;
285 AutoTArray
<RefPtr
<ResizeObserverSize
>, 1> mBorderBoxSize
;
286 AutoTArray
<RefPtr
<ResizeObserverSize
>, 1> mContentBoxSize
;
287 AutoTArray
<RefPtr
<ResizeObserverSize
>, 1> mDevicePixelContentBoxSize
;
290 class ResizeObserverSize final
: public nsISupports
, public nsWrapperCache
{
292 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
293 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserverSize
)
295 ResizeObserverSize(nsISupports
* aOwner
, const LogicalPixelSize
& aSize
)
296 : mOwner(aOwner
), mSize(aSize
) {
297 MOZ_ASSERT(mOwner
, "Need a non-null owner");
300 nsISupports
* GetParentObject() const { return mOwner
; }
302 JSObject
* WrapObject(JSContext
* aCx
,
303 JS::Handle
<JSObject
*> aGivenProto
) override
{
304 return ResizeObserverSize_Binding::Wrap(aCx
, this, aGivenProto
);
307 float InlineSize() const { return mSize
.ISize(); }
308 float BlockSize() const { return mSize
.BSize(); }
311 ~ResizeObserverSize() = default;
313 nsCOMPtr
<nsISupports
> mOwner
;
314 // The logical size value:
315 // 1. content-box/border-box: in CSS pixels.
316 // 2. device-pixel-content-box: in device pixels.
317 const LogicalPixelSize mSize
;
321 } // namespace mozilla
323 #endif // mozilla_dom_ResizeObserver_h