ResizeObserver.h (11286B)
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/. */ 6 7 #ifndef mozilla_dom_ResizeObserver_h 8 #define mozilla_dom_ResizeObserver_h 9 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/BindingDeclarations.h" 17 #include "mozilla/dom/DOMRect.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" 24 25 // XXX Avoid including this here by moving function bodies to the cpp file 26 #include "nsPIDOMWindow.h" 27 28 namespace mozilla { 29 class ErrorResult; 30 31 namespace dom { 32 33 class Element; 34 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); 46 } 47 } 48 49 gfx::Size PhysicalSize(WritingMode aWM) const { 50 if (!aWM.IsVertical()) { 51 return mSize; 52 } 53 gfx::Size result(mSize); 54 std::swap(result.width, result.height); 55 return result; 56 } 57 58 bool operator==(const LogicalPixelSize& aOther) const { 59 return mSize == aOther.mSize; 60 } 61 bool operator!=(const LogicalPixelSize& aOther) const { 62 return !(*this == aOther); 63 } 64 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; } 69 70 private: 71 // |mSize.width| represents inline-size and |mSize.height| represents 72 // block-size. 73 gfx::Size mSize; 74 }; 75 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) 82 83 ResizeObservation(Element&, ResizeObserver&, ResizeObserverBoxOptions); 84 85 Element* Target() const { return mTarget; } 86 87 ResizeObserverBoxOptions BoxOptions() const { return mObservedBox; } 88 89 /** 90 * Returns whether the observed target element size differs from the saved 91 * mLastReportedSize. 92 */ 93 bool IsActive() const; 94 95 /** 96 * Update current mLastReportedSize to aSize. 97 */ 98 void UpdateLastReportedSize(const nsTArray<LogicalPixelSize>& aSize); 99 100 enum class RemoveFromObserver : bool { No, Yes }; 101 void Unlink(RemoveFromObserver); 102 103 protected: 104 ~ResizeObservation() { Unlink(RemoveFromObserver::No); }; 105 106 nsCOMPtr<Element> mTarget; 107 108 // Weak, observer always outlives us. 109 ResizeObserver* mObserver; 110 111 const ResizeObserverBoxOptions mObservedBox; 112 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; 117 }; 118 119 /** 120 * ResizeObserver interfaces and algorithms are based on 121 * https://drafts.csswg.org/resize-observer/#api 122 */ 123 class ResizeObserver final : public nsISupports, public nsWrapperCache { 124 public: 125 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 126 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserver) 127 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()); 134 } 135 136 nsISupports* GetParentObject() const { return mOwner; } 137 138 JSObject* WrapObject(JSContext* aCx, 139 JS::Handle<JSObject*> aGivenProto) override { 140 return ResizeObserver_Binding::Wrap(aCx, this, aGivenProto); 141 } 142 143 static already_AddRefed<ResizeObserver> Constructor( 144 const GlobalObject& aGlobal, ResizeObserverCallback& aCb, 145 ErrorResult& aRv); 146 147 void Observe(Element&, const ResizeObserverOptions&); 148 void Unobserve(Element&); 149 150 void Disconnect(); 151 152 /** 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. 158 */ 159 void GatherActiveObservations(uint32_t aDepth); 160 161 /** 162 * Returns whether this ResizeObserver has any active observations 163 * since last GatherActiveObservations(). 164 */ 165 bool HasActiveObservations() const { return !mActiveTargets.IsEmpty(); } 166 167 /** 168 * Returns whether this ResizeObserver has any skipped observations 169 * since last GatherActiveObservations(). 170 */ 171 bool HasSkippedObservations() const { return mHasSkippedTargets; } 172 173 /** 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. 180 */ 181 MOZ_CAN_RUN_SCRIPT uint32_t BroadcastActiveObservations(); 182 183 /** 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. 188 * 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. 192 * 193 * https://www.w3.org/TR/resize-observer-1/#calculate-box-size 194 */ 195 static AutoTArray<LogicalPixelSize, 1> CalculateBoxSize( 196 Element* aTarget, ResizeObserverBoxOptions aBox, 197 bool aForceFragmentHandling = false); 198 199 protected: 200 ~ResizeObserver() { Disconnect(); } 201 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; 212 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; 221 }; 222 223 /** 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. 227 */ 228 class ResizeObserverEntry final : public nsISupports, public nsWrapperCache { 229 public: 230 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 231 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserverEntry) 232 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"); 241 242 SetBorderBoxSize(aBorderBoxSize); 243 SetContentRectAndSize(aContentBoxSize); 244 SetDevicePixelContentSize(aDevicePixelContentBoxSize); 245 } 246 247 nsISupports* GetParentObject() const { return mOwner; } 248 249 JSObject* WrapObject(JSContext* aCx, 250 JS::Handle<JSObject*> aGivenProto) override { 251 return ResizeObserverEntry_Binding::Wrap(aCx, this, aGivenProto); 252 } 253 254 Element* Target() const { return mTarget; } 255 256 /** 257 * Returns the DOMRectReadOnly of target's content rect so it can be 258 * accessed from JavaScript in callback function of ResizeObserver. 259 */ 260 DOMRectReadOnly* ContentRect() const { return mContentRect; } 261 262 /** 263 * Returns target's logical border-box size, content-box size, and 264 * device-pixel-content-box as an array of ResizeObserverSize. 265 */ 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; 270 271 private: 272 ~ResizeObserverEntry() = default; 273 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); 280 281 nsCOMPtr<nsISupports> mOwner; 282 nsCOMPtr<Element> mTarget; 283 284 RefPtr<DOMRectReadOnly> mContentRect; 285 AutoTArray<RefPtr<ResizeObserverSize>, 1> mBorderBoxSize; 286 AutoTArray<RefPtr<ResizeObserverSize>, 1> mContentBoxSize; 287 AutoTArray<RefPtr<ResizeObserverSize>, 1> mDevicePixelContentBoxSize; 288 }; 289 290 class ResizeObserverSize final : public nsISupports, public nsWrapperCache { 291 public: 292 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 293 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserverSize) 294 295 ResizeObserverSize(nsISupports* aOwner, const LogicalPixelSize& aSize) 296 : mOwner(aOwner), mSize(aSize) { 297 MOZ_ASSERT(mOwner, "Need a non-null owner"); 298 } 299 300 nsISupports* GetParentObject() const { return mOwner; } 301 302 JSObject* WrapObject(JSContext* aCx, 303 JS::Handle<JSObject*> aGivenProto) override { 304 return ResizeObserverSize_Binding::Wrap(aCx, this, aGivenProto); 305 } 306 307 float InlineSize() const { return mSize.ISize(); } 308 float BlockSize() const { return mSize.BSize(); } 309 310 protected: 311 ~ResizeObserverSize() = default; 312 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; 318 }; 319 320 } // namespace dom 321 } // namespace mozilla 322 323 #endif // mozilla_dom_ResizeObserver_h