diff --git a/dom/base/ResizeObserver.cpp b/dom/base/ResizeObserver.cpp index c3302efee3..67467b0fc3 100644 --- a/dom/base/ResizeObserver.cpp +++ b/dom/base/ResizeObserver.cpp @@ -29,7 +29,8 @@ namespace dom { * * https://drafts.csswg.org/resize-observer/#calculate-depth-for-node-h */ -static uint32_t GetNodeDepth(nsINode* aNode) { +static uint32_t +GetNodeDepth(nsINode* aNode) { uint32_t depth = 1; MOZ_ASSERT(aNode, "Node shouldn't be null"); @@ -43,6 +44,50 @@ static uint32_t GetNodeDepth(nsINode* aNode) { return depth; } +/** + * Returns |aTarget|'s size in the form of an nsSize. + * If the target is an SVG, width and height are determined from the bounding box. + */ +static nsSize +GetTargetSize(Element* aTarget, ResizeObserverBoxOptions aBox) { + nsSize size; + nsIFrame* frame = aTarget->GetPrimaryFrame(); + + if (!frame) { + return size; + } + + if (aTarget->IsSVGElement()) { + // Per the spec, an SVG size is always its bounding box size, no matter what + // box option you choose, because SVG elements do not use the standard CSS box + // model. + gfxRect bbox = nsSVGUtils::GetBBox(frame); + size.width = NSFloatPixelsToAppUnits(bbox.width, AppUnitsPerCSSPixel()); + size.height = NSFloatPixelsToAppUnits(bbox.height, AppUnitsPerCSSPixel()); + } else { + // Per the spec, non-replaced inline Elements will always have an empty + // content rect. We therefore always use the same empty size for + // non-replaced inline elements here, and their IsActive() will + // always return false. (So its observation won't be fired.) + if (!frame->IsFrameOfType(nsIFrame::eReplaced) && + frame->IsFrameOfType(nsIFrame::eLineParticipant)) { + return size; + } + + switch (aBox) { + case ResizeObserverBoxOptions::Border_box: + // GetSize() includes the content area, borders, and padding. + size = frame->GetSize(); + break; + case ResizeObserverBoxOptions::Content_box: + default: + size = frame->GetContentRectRelativeToSelf().Size(); + } + } + + return size; +} + NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObserver) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) @@ -98,6 +143,7 @@ ResizeObserver::Constructor(const GlobalObject& aGlobal, void ResizeObserver::Observe(Element* aTarget, + const ResizeObserverOptions& aOptions, ErrorResult& aRv) { if (!aTarget) { @@ -108,7 +154,9 @@ ResizeObserver::Observe(Element* aTarget, RefPtr observation; if (!mObservationMap.Get(aTarget, getter_AddRefs(observation))) { - observation = new ResizeObservation(this, aTarget); + nsIFrame* frame = aTarget->GetPrimaryFrame(); + WritingMode wm = frame ? frame->GetWritingMode() : WritingMode(); + observation = new ResizeObservation(aTarget->OwnerDoc(), aTarget, aOptions.mBox, wm); mObservationMap.Put(aTarget, observation); mObservationList.insertBack(observation); @@ -190,11 +238,14 @@ ResizeObserver::BroadcastActiveObservations() Sequence> entries; for (auto observation : mActiveTargets) { - RefPtr entry = - new ResizeObserverEntry(this, observation->Target()); + Element* target = observation->Target(); + RefPtr entry = new ResizeObserverEntry(this, target); - nsRect rect = observation->GetTargetRect(); - entry->SetContentRect(rect); + nsSize borderBoxSize = GetTargetSize(target, ResizeObserverBoxOptions::Border_box); + entry->SetBorderBoxSize(borderBoxSize); + + nsSize contentBoxSize = GetTargetSize(target, ResizeObserverBoxOptions::Content_box); + entry->SetContentRectAndSize(contentBoxSize); if (!entries.AppendElement(entry.forget(), fallible)) { // Out of memory. @@ -203,7 +254,14 @@ ResizeObserver::BroadcastActiveObservations() // Sync the broadcast size of observation so the next size inspection // will be based on the updated size from last delivered observations. - observation->UpdateBroadcastSize(rect); + switch (observation->BoxOptions()) { + case ResizeObserverBoxOptions::Border_box: + observation->UpdateLastReportedSize(borderBoxSize); + break; + case ResizeObserverBoxOptions::Content_box: + default: + observation->UpdateLastReportedSize(contentBoxSize); + } uint32_t targetDepth = GetNodeDepth(observation->Target()); @@ -229,8 +287,11 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserverEntry) NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserverEntry) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObserverEntry, - mTarget, mContentRect, - mOwner) + mOwner, + mTarget, + mContentRect, + mBorderBoxSize, + mContentBoxSize) already_AddRefed ResizeObserverEntry::Constructor(const GlobalObject& aGlobal, @@ -243,28 +304,42 @@ ResizeObserverEntry::Constructor(const GlobalObject& aGlobal, } void -ResizeObserverEntry::SetContentRect(nsRect aRect) -{ - RefPtr contentRect = new DOMRect(mTarget); +ResizeObserverEntry::SetBorderBoxSize(const nsSize& aSize) { + nsIFrame* frame = mTarget->GetPrimaryFrame(); + WritingMode wm = frame ? frame->GetWritingMode() : WritingMode(); + mBorderBoxSize = new ResizeObserverSize(this, aSize, wm); +} + +void +ResizeObserverEntry::SetContentRectAndSize(const nsSize& aSize) { nsIFrame* frame = mTarget->GetPrimaryFrame(); - if (frame) { - nsMargin padding = frame->GetUsedPadding(); - - // Per the spec, we need to include padding in contentRect of - // ResizeObserverEntry. - aRect.x = padding.left; - aRect.y = padding.top; - } - - contentRect->SetLayoutRect(aRect); + // Update mContentRect. + nsMargin padding = frame ? frame->GetUsedPadding(): nsMargin(); + // Per the spec, we need to use the top-left padding offset as the origin of + // our contentRect. + nsRect rect(nsPoint(padding.left, padding.top), aSize); + RefPtr contentRect = new DOMRect(mTarget); + contentRect->SetLayoutRect(rect); mContentRect = contentRect.forget(); + + // Update mContentBoxSize. + WritingMode wm = frame ? frame->GetWritingMode() : WritingMode(); + mContentBoxSize = new ResizeObserverSize(this, aSize, wm); } ResizeObserverEntry::~ResizeObserverEntry() { } +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObserverSize, mOwner) +NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserverSize) +NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserverSize) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObserverSize) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObservation) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) @@ -281,48 +356,40 @@ ResizeObservation::Constructor(const GlobalObject& aGlobal, Element* aTarget, ErrorResult& aRv) { + ResizeObserverOptions options; + options.mBox = ResizeObserverBoxOptions::Content_box; + nsIFrame* frame = aTarget->GetPrimaryFrame(); + WritingMode wm = frame ? frame->GetWritingMode() : WritingMode(); RefPtr observation = - new ResizeObservation(aGlobal.GetAsSupports(), aTarget); + new ResizeObservation(aGlobal.GetAsSupports(), aTarget, options.mBox, wm); return observation.forget(); } bool ResizeObservation::IsActive() const { - nsRect rect = GetTargetRect(); - return (rect.width != mBroadcastWidth || rect.height != mBroadcastHeight); + nsIFrame* frame = mTarget->GetPrimaryFrame(); + WritingMode wm = frame ? frame->GetWritingMode() : WritingMode(); + LogicalSize size(wm, GetTargetSize(mTarget, mObservedBox)); + return mLastReportedSize.ISize(mLastReportedWM) != size.ISize(wm) || + mLastReportedSize.BSize(mLastReportedWM) != size.BSize(wm); } +void +ResizeObservation::UpdateLastReportedSize(const nsSize& aSize) { + nsIFrame* frame = mTarget->GetPrimaryFrame(); + mLastReportedWM = frame ? frame->GetWritingMode() : WritingMode(); + mLastReportedSize = LogicalSize(mLastReportedWM, aSize); +} + +/* void ResizeObservation::UpdateBroadcastSize(nsRect aRect) { mBroadcastWidth = aRect.width; mBroadcastHeight = aRect.height; } - -nsRect -ResizeObservation::GetTargetRect() const -{ - nsRect rect; - nsIFrame* frame = mTarget->GetPrimaryFrame(); - - if (frame) { - if (mTarget->IsSVGElement()) { - gfxRect bbox = nsSVGUtils::GetBBox(frame); - rect.width = NSFloatPixelsToAppUnits(bbox.width, AppUnitsPerCSSPixel()); - rect.height = NSFloatPixelsToAppUnits(bbox.height, AppUnitsPerCSSPixel()); - } else { - // Per the spec, non-replaced inline Elements will always have an empty - // content rect. - if (frame->IsFrameOfType(nsIFrame::eReplaced) || - !frame->IsFrameOfType(nsIFrame::eLineParticipant)) { - rect = frame->GetContentRectRelativeToSelf(); - } - } - } - - return rect; -} +*/ ResizeObservation::~ResizeObservation() { diff --git a/dom/base/ResizeObserver.h b/dom/base/ResizeObserver.h index 2f56c580f4..92a7ee08cc 100644 --- a/dom/base/ResizeObserver.h +++ b/dom/base/ResizeObserver.h @@ -6,7 +6,10 @@ #ifndef mozilla_dom_ResizeObserver_h #define mozilla_dom_ResizeObserver_h +#include "mozilla/AppUnits.h" +#include "mozilla/WritingModes.h" #include "mozilla/dom/ResizeObserverBinding.h" +#include "nsCoord.h" namespace mozilla { namespace dom { @@ -47,7 +50,7 @@ public: return mOwner; } - void Observe(Element* aTarget, ErrorResult& aRv); + void Observe(Element* aTarget, const ResizeObserverOptions& aOptions, ErrorResult& aRv); void Unobserve(Element* aTarget, ErrorResult& aRv); @@ -77,9 +80,10 @@ public: /* * Deliver the callback function in JavaScript for all active observations * and pass the sequence of ResizeObserverEntry so JavaScript can access them. - * The broadcast size of observations will be updated and mActiveTargets will - * be cleared. It also returns the shallowest depth of elements from active - * observations or UINT32_MAX if there is no any active observations. + * The active observations' mLastReportedSize fields will be updated, and + * mActiveTargets will be cleared. It also returns the shallowest depth of + * elements from active observations or numeric_limits::max() if + * there are not any active observations. */ uint32_t BroadcastActiveObservations(); @@ -92,6 +96,10 @@ protected: nsCOMPtr mOwner; RefPtr mCallback; nsTArray> mActiveTargets; + // The spec uses a list to store the skipped targets. However, it seems what + // we want is to check if there are any skipped targets (i.e. existence). + // Therefore, we use a boolean value to represent the existence of skipped + // targets. bool mHasSkippedTargets; // Combination of HashTable and LinkedList so we can iterate through @@ -153,14 +161,67 @@ public: return mContentRect; } - void SetContentRect(nsRect aRect); + /** + * Returns target's logical border-box size and content-box size as + * ResizeObserverSize. + */ + ResizeObserverSize* BorderBoxSize() const { + return mBorderBoxSize; + } + ResizeObserverSize* ContentBoxSize() const { + return mContentBoxSize; + } + + // Set borderBoxSize. + void SetBorderBoxSize(const nsSize& aSize); + // Set contentRect and contentBoxSize. + void SetContentRectAndSize(const nsSize& aSize); protected: ~ResizeObserverEntry(); nsCOMPtr mOwner; nsCOMPtr mTarget; + RefPtr mContentRect; + RefPtr mBorderBoxSize; + RefPtr mContentBoxSize; +}; + +class ResizeObserverSize final : public nsISupports, public nsWrapperCache { +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ResizeObserverSize) + + // Note: the unit of |aSize| is app unit, and we convert it into css pixel in + // the public JS APIs. + ResizeObserverSize(nsISupports* aOwner, const nsSize& aSize, + const WritingMode aWM) + : mOwner(aOwner), mSize(aWM, aSize), mWM(aWM) { + MOZ_ASSERT(mOwner, "Need a non-null owner"); + } + + nsISupports* GetParentObject() const { return mOwner; } + + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override { + return ResizeObserverSizeBinding::Wrap(aCx, this, aGivenProto); + } + + double InlineSize() const { + return NSAppUnitsToDoublePixels(mSize.ISize(mWM), AppUnitsPerCSSPixel()); + } + + double BlockSize() const { + return NSAppUnitsToDoublePixels(mSize.BSize(mWM), AppUnitsPerCSSPixel()); + } + +protected: + ~ResizeObserverSize() = default; + + nsCOMPtr mOwner; + const LogicalSize mSize; + const WritingMode mWM; }; /** @@ -177,11 +238,15 @@ public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ResizeObservation) - ResizeObservation(nsISupports* aOwner, Element* aTarget) + ResizeObservation(nsISupports* aOwner, + Element* aTarget, + ResizeObserverBoxOptions aBox, + const WritingMode aWM) : mOwner(aOwner) , mTarget(aTarget) - , mBroadcastWidth(0) - , mBroadcastHeight(0) + , mObservedBox(aBox) + , mLastReportedSize(aWM) + , mLastReportedWM(aWM) { MOZ_ASSERT(mOwner, "Need a non-null owner"); MOZ_ASSERT(mTarget, "Need a non-null target element"); @@ -208,32 +273,21 @@ public: return mTarget; } - nscoord BroadcastWidth() const - { - return mBroadcastWidth; - } - - nscoord BroadcastHeight() const - { - return mBroadcastHeight; + ResizeObserverBoxOptions BoxOptions() const + { + return mObservedBox; } /* - * Returns whether the observed target element size differs from current - * BroadcastWidth and BroadcastHeight + * Returns whether the observed target element size differs from the saved + * mLastReportedSize. */ bool IsActive() const; /* - * Update current BroadcastWidth and BroadcastHeight with size from aRect. + * Update current mLastReportedSize with size from aSize. */ - void UpdateBroadcastSize(nsRect aRect); - - /* - * Returns the target's rect in the form of nsRect. - * If the target is SVG, width and height are determined from bounding box. - */ - nsRect GetTargetRect() const; + void UpdateLastReportedSize(const nsSize& aSize); protected: ~ResizeObservation(); @@ -241,10 +295,14 @@ protected: nsCOMPtr mOwner; nsCOMPtr mTarget; - // Broadcast width and broadcast height are the latest recorded size - // of observed target. - nscoord mBroadcastWidth; - nscoord mBroadcastHeight; + const ResizeObserverBoxOptions mObservedBox; + + // The latest recorded size of observed target. + // Per the spec, observation.lastReportedSize should be entry.borderBoxSize + // or entry.contentBoxSize (i.e. logical size), instead of entry.contentRect + // (i.e. physical rect), so we store this as LogicalSize. + LogicalSize mLastReportedSize; + WritingMode mLastReportedWM; }; } // namespace dom diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 4e9ca75e3e..ddd32a4940 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -736,6 +736,11 @@ DOMInterfaces = { 'headerFile': 'mozilla/dom/ResizeObserver.h', }, +'ResizeObserverSize': { + 'nativeType': 'mozilla::dom::ResizeObserverSize', + 'headerFile': 'mozilla/dom/ResizeObserver.h', +}, + 'Response': { 'binaryNames': { 'headers': 'headers_' }, }, diff --git a/dom/webidl/ResizeObserver.webidl b/dom/webidl/ResizeObserver.webidl index 98700f53c6..c3ae93a810 100644 --- a/dom/webidl/ResizeObserver.webidl +++ b/dom/webidl/ResizeObserver.webidl @@ -7,12 +7,21 @@ * https://wicg.github.io/ResizeObserver/ */ +enum ResizeObserverBoxOptions { + "border-box", + "content-box" +}; + +dictionary ResizeObserverOptions { + ResizeObserverBoxOptions box = "content-box"; +}; + [Constructor(ResizeObserverCallback callback), Exposed=Window, Pref="layout.css.resizeobserver.enabled"] interface ResizeObserver { [Throws] - void observe(Element? target); + void observe(Element? target, optional ResizeObserverOptions options); [Throws] void unobserve(Element? target); void disconnect(); @@ -21,11 +30,18 @@ interface ResizeObserver { callback ResizeObserverCallback = void (sequence entries, ResizeObserver observer); [Constructor(Element? target), - ChromeOnly, Pref="layout.css.resizeobserver.enabled"] interface ResizeObserverEntry { readonly attribute Element target; readonly attribute DOMRectReadOnly? contentRect; + readonly attribute ResizeObserverSize borderBoxSize; + readonly attribute ResizeObserverSize contentBoxSize; +}; + +[Pref="layout.css.resizeobserver.enabled"] +interface ResizeObserverSize { + readonly attribute unrestricted double inlineSize; + readonly attribute unrestricted double blockSize; }; [Constructor(Element? target), @@ -33,7 +49,7 @@ interface ResizeObserverEntry { Pref="layout.css.resizeobserver.enabled"] interface ResizeObservation { readonly attribute Element target; - readonly attribute long broadcastWidth; - readonly attribute long broadcastHeight; +/* readonly attribute long broadcastWidth; + readonly attribute long broadcastHeight; */ boolean isActive(); };