Backed out changeset 9d8b4c0b99ed (bug 1945683) for causing btime failures. CLOSED...
[gecko.git] / dom / base / ResizeObserver.h
blob2fd630c66c29bef0491aba77fb2ef7fc8b1cebfe
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 {
124 public:
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,
145 ErrorResult& aRv);
147 void Observe(Element&, const ResizeObserverOptions&);
148 void Unobserve(Element&);
150 void Disconnect();
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
157 * mActiveTargets.
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);
199 protected:
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
210 // targets.
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 {
229 public:
230 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
231 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserverEntry)
233 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;
271 private:
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 {
291 public:
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(); }
310 protected:
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;
320 } // namespace dom
321 } // namespace mozilla
323 #endif // mozilla_dom_ResizeObserver_h