Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / dom / base / ResizeObserver.h
blobeaa0e1726d1fbdb7a5e85437986788a21acd584b
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 "gfxPoint.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"
19 #include "nsCoord.h"
20 #include "nsCycleCollectionParticipant.h"
21 #include "nsRefPtrHashtable.h"
22 #include "nsTArray.h"
23 #include "nsWrapperCache.h"
25 // XXX Avoid including this here by moving function bodies to the cpp file
26 #include "nsPIDOMWindow.h"
28 namespace mozilla {
29 class ErrorResult;
31 namespace dom {
33 class Element;
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 {
40 public:
41 LogicalPixelSize() = default;
42 LogicalPixelSize(WritingMode aWM, const gfx::Size& aSize) {
43 mSize = aSize;
44 if (aWM.IsVertical()) {
45 std::swap(mSize.width, mSize.height);
49 gfx::Size PhysicalSize(WritingMode aWM) const {
50 if (!aWM.IsVertical()) {
51 return mSize;
53 gfx::Size result(mSize);
54 std::swap(result.width, result.height);
55 return result;
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; }
70 private:
71 // |mSize.width| represents inline-size and |mSize.height| represents
72 // block-size.
73 gfx::Size mSize;
76 // For the internal implementation in ResizeObserver. Normally, this is owned by
77 // ResizeObserver.
78 class ResizeObservation final : public LinkedListElement<ResizeObservation> {
79 public:
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; }
89 /**
90 * Returns whether the observed target element size differs from the saved
91 * mLastReportedSize.
93 bool IsActive() const;
95 /**
96 * Update current mLastReportedSize to aSize.
98 void UpdateLastReportedSize(const nsTArray<LogicalPixelSize>& aSize);
100 enum class RemoveFromObserver : bool { No, Yes };
101 void Unlink(RemoveFromObserver);
103 protected:
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 public:
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,
146 ErrorResult& aRv);
148 void Observe(Element&, const ResizeObserverOptions&);
149 void Unobserve(Element&);
151 void Disconnect();
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
158 * mActiveTargets.
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);
200 protected:
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
211 // targets.
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 {
230 public:
231 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
232 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserverEntry)
234 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;
272 private:
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 {
292 public:
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(); }
311 protected:
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;
321 } // namespace dom
322 } // namespace mozilla
324 #endif // mozilla_dom_ResizeObserver_h