Bug 1545239 - Update ResizeObserver and ResizeObserverEntry. r=smaug,dholbert
authorBoris Chiou <boris.chiou@gmail.com>
Wed, 08 May 2019 20:54:36 +0000
changeset 473154 dc3570e93c4150f913a20baa6ee42c109ab85d60
parent 473153 2e3522f77b0f686663145bdce20ffceef3909896
child 473155 01338a1e26b7d838ddd591fca9a7495e338c6c96
push id113065
push useropoprus@mozilla.com
push dateThu, 09 May 2019 03:46:59 +0000
treeherdermozilla-inbound@34a824c75b7b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, dholbert
bugs1545239
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1545239 - Update ResizeObserver and ResizeObserverEntry. r=smaug,dholbert In this patch, we support 1. content-box (default) 2. border-box And let ResizeObserverEntry expose these box sizes. Besides, we store the mLastReportedSize as the logical size. Differential Revision: https://phabricator.services.mozilla.com/D28737
dom/base/ResizeObserver.cpp
dom/base/ResizeObserver.h
dom/bindings/Bindings.conf
dom/tests/mochitest/general/test_interfaces.js
dom/webidl/ResizeObserver.webidl
testing/web-platform/meta/resize-observer/idlharness.window.js.ini
--- a/dom/base/ResizeObserver.cpp
+++ b/dom/base/ResizeObserver.cpp
@@ -39,51 +39,75 @@ static uint32_t GetNodeDepth(nsINode* aN
   // shadow boundary to calculate the node depth without the shadow root.
   while ((aNode = aNode->GetFlattenedTreeParentNode())) {
     ++depth;
   }
 
   return depth;
 }
 
+/**
+ * Returns |aTarget|'s size in the form of nsSize.
+ * If the target is SVG, width and height are determined from 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, SVG size is always its bounding box size no matter what
+    // box option you choose, because SVG elements do not use 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. Therefore, we always use the same trivially-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_IMPL_CYCLE_COLLECTION(ResizeObservation, mTarget)
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ResizeObservation, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ResizeObservation, Release)
 
 bool ResizeObservation::IsActive() const {
-  nsRect rect = GetTargetRect();
-  return (rect.width != mBroadcastWidth || rect.height != mBroadcastHeight);
-}
-
-void ResizeObservation::UpdateBroadcastSize(const nsSize& aSize) {
-  mBroadcastWidth = aSize.width;
-  mBroadcastHeight = aSize.height;
+  nsIFrame* frame = mTarget->GetPrimaryFrame();
+  const WritingMode wm = frame ? frame->GetWritingMode() : WritingMode();
+  const LogicalSize size(wm, GetTargetSize(mTarget, mObservedBox));
+  return mLastReportedSize.ISize(mLastReportedWM) != size.ISize(wm) ||
+         mLastReportedSize.BSize(mLastReportedWM) != size.BSize(wm);
 }
 
-nsRect ResizeObservation::GetTargetRect() const {
-  nsRect rect;
+void ResizeObservation::UpdateLastReportedSize(const nsSize& aSize) {
   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. Therefore, we don't set rect for non-replaced inline
-      // elements here, and their IsActive() will always return false.
-      if (frame->IsFrameOfType(nsIFrame::eReplaced) ||
-          !frame->IsFrameOfType(nsIFrame::eLineParticipant)) {
-        rect = frame->GetContentRectRelativeToSelf();
-      }
-    }
-  }
-
-  return rect;
+  mLastReportedWM = frame ? frame->GetWritingMode() : WritingMode();
+  mLastReportedSize = LogicalSize(mLastReportedWM, aSize);
 }
 
 // Only needed for refcounted objects.
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObserver, mOwner, mCallback,
                                       mActiveTargets, mObservationMap)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserver)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserver)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObserver)
@@ -110,30 +134,34 @@ already_AddRefed<ResizeObserver> ResizeO
   }
 
   RefPtr<ResizeObserver> observer = new ResizeObserver(window.forget(), aCb);
   document->AddResizeObserver(observer);
 
   return observer.forget();
 }
 
-void ResizeObserver::Observe(Element& aTarget, ErrorResult& aRv) {
+void ResizeObserver::Observe(Element& aTarget,
+                             const ResizeObserverOptions& aOptions,
+                             ErrorResult& aRv) {
   RefPtr<ResizeObservation> observation;
 
   if (mObservationMap.Get(&aTarget, getter_AddRefs(observation))) {
     // Already observed this target, so return.
     // Note: Based on the spec, we should unobserve it first. However, calling
     // Unobserve() will remove original ResizeObservation and then add a new
     // one, this may cause an unexpected result because ResizeObservation stores
     // the last reported size which should be kept to make sure IsActive()
     // returns the correct result.
     return;
   }
 
-  observation = new ResizeObservation(aTarget);
+  nsIFrame* frame = aTarget.GetPrimaryFrame();
+  observation = new ResizeObservation(
+      aTarget, aOptions.mBox, frame ? frame->GetWritingMode() : WritingMode());
 
   mObservationMap.Put(&aTarget, observation);
   mObservationList.insertBack(observation);
 
   // Per the spec, we need to trigger notification in event loop that
   // contains ResizeObserver observe call even when resize/reflow does
   // not happen.
   aTarget.OwnerDoc()->ScheduleResizeObserversNotification();
@@ -181,31 +209,42 @@ uint32_t ResizeObserver::BroadcastActive
 
   if (!HasActiveObservations()) {
     return shallowestTargetDepth;
   }
 
   Sequence<OwningNonNull<ResizeObserverEntry>> entries;
 
   for (auto& observation : mActiveTargets) {
-    RefPtr<ResizeObserverEntry> entry =
-        new ResizeObserverEntry(this, *observation->Target());
+    Element* target = observation->Target();
+    RefPtr<ResizeObserverEntry> entry = new ResizeObserverEntry(this, *target);
 
-    nsRect rect = observation->GetTargetRect();
-    entry->SetContentRect(rect);
-    // FIXME: Bug 1545239: Set borderBoxSize and contentBoxSize.
+    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.
       break;
     }
 
     // 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.Size());
+    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());
 
     if (targetDepth < shallowestTargetDepth) {
       shallowestTargetDepth = targetDepth;
     }
   }
 
@@ -213,40 +252,58 @@ uint32_t ResizeObserver::BroadcastActive
   callback->Call(this, entries, *this);
 
   mActiveTargets.Clear();
   mHasSkippedTargets = false;
 
   return shallowestTargetDepth;
 }
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObserverEntry, mTarget,
-                                      mContentRect, mOwner)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObserverEntry, mOwner, mTarget,
+                                      mContentRect, mBorderBoxSize,
+                                      mContentBoxSize)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserverEntry)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserverEntry)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObserverEntry)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 already_AddRefed<ResizeObserverEntry> ResizeObserverEntry::Constructor(
     const GlobalObject& aGlobal, Element& aTarget, ErrorResult& aRv) {
   RefPtr<ResizeObserverEntry> observerEntry =
       new ResizeObserverEntry(aGlobal.GetAsSupports(), aTarget);
   return observerEntry.forget();
 }
 
-void ResizeObserverEntry::SetContentRect(const nsRect& aRect) {
+void ResizeObserverEntry::SetBorderBoxSize(const nsSize& aSize) {
   nsIFrame* frame = mTarget->GetPrimaryFrame();
-  Maybe<nsMargin> padding = frame ? Some(frame->GetUsedPadding()) : Nothing();
+  const WritingMode wm = frame ? frame->GetWritingMode() : WritingMode();
+  mBorderBoxSize = new ResizeObserverSize(this, aSize, wm);
+}
 
-  // Per the spec, we need to include padding in contentRect of
-  // ResizeObserverEntry.
-  nsRect rect(padding ? padding->left : aRect.x,
-              padding ? padding->top : aRect.y, aRect.width, aRect.height);
+void ResizeObserverEntry::SetContentRectAndSize(const nsSize& aSize) {
+  nsIFrame* frame = mTarget->GetPrimaryFrame();
 
+  // 1. 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<DOMRect> contentRect = new DOMRect(mTarget);
   contentRect->SetLayoutRect(rect);
   mContentRect = contentRect.forget();
+
+  // 2. Update mContentBoxSize.
+  const WritingMode wm = frame ? frame->GetWritingMode() : WritingMode();
+  mContentBoxSize = new ResizeObserverSize(this, aSize, wm);
 }
 
+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
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/base/ResizeObserver.h
+++ b/dom/base/ResizeObserver.h
@@ -3,21 +3,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_ResizeObserver_h
 #define mozilla_dom_ResizeObserver_h
 
 #include "js/TypeDecls.h"
+#include "mozilla/AppUnits.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/LinkedList.h"
+#include "mozilla/WritingModes.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/ResizeObserverBinding.h"
+#include "nsCoord.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsRefPtrHashtable.h"
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace dom {
 
@@ -31,53 +34,54 @@ namespace dom {
 
 // For the internal implementation in ResizeObserver. Normally, this is owned by
 // ResizeObserver.
 class ResizeObservation final : public LinkedListElement<ResizeObservation> {
  public:
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ResizeObservation)
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ResizeObservation)
 
-  explicit ResizeObservation(Element& aTarget)
-      : mTarget(&aTarget), mBroadcastWidth(0), mBroadcastHeight(0) {
+  ResizeObservation(Element& aTarget, ResizeObserverBoxOptions aBox,
+                    const WritingMode aWM)
+      : mTarget(&aTarget),
+        mObservedBox(aBox),
+        // This starts us with a 0,0 last-reported-size:
+        mLastReportedSize(aWM),
+        mLastReportedWM(aWM) {
     MOZ_ASSERT(mTarget, "Need a non-null target element");
   }
 
   Element* Target() const { 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 the saved
-   * BroadcastWidth and BroadcastHeight.
+   * mLastReportedSize.
    */
   bool IsActive() const;
 
   /**
-   * Update current BroadcastWidth and BroadcastHeight with size from aSize.
+   * Update current mLastReportedSize with size from aSize.
    */
-  void UpdateBroadcastSize(const nsSize& aSize);
-
-  /**
-   * 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() = default;
 
   nsCOMPtr<Element> 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;
 };
 
 /**
  * ResizeObserver interfaces and algorithms are based on
  * https://drafts.csswg.org/resize-observer/#api
  */
 class ResizeObserver final : public nsISupports, public nsWrapperCache {
  public:
@@ -96,17 +100,18 @@ class ResizeObserver final : public nsIS
                        JS::Handle<JSObject*> aGivenProto) override {
     return ResizeObserver_Binding::Wrap(aCx, this, aGivenProto);
   }
 
   static already_AddRefed<ResizeObserver> Constructor(
       const GlobalObject& aGlobal, ResizeObserverCallback& aCb,
       ErrorResult& aRv);
 
-  void Observe(Element& target, ErrorResult& aRv);
+  void Observe(Element& aTarget, const ResizeObserverOptions& aOptions,
+               ErrorResult& aRv);
 
   void Unobserve(Element& target, ErrorResult& aRv);
 
   void Disconnect();
 
   /**
    * Gather all observations which have an observed target with size changed
    * since last BroadcastActiveObservations() in this ResizeObserver.
@@ -126,28 +131,33 @@ class ResizeObserver final : public nsIS
    * Returns whether this ResizeObserver has any skipped observations
    * since last GatherActiveObservations().
    */
   bool HasSkippedObservations() const { return mHasSkippedTargets; }
 
   /**
    * Invoke 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 are not 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<uint32_t>::max() if
+   * there are not any active observations.
    */
   MOZ_CAN_RUN_SCRIPT uint32_t BroadcastActiveObservations();
 
  protected:
   ~ResizeObserver() { mObservationList.clear(); }
 
   nsCOMPtr<nsPIDOMWindowInner> mOwner;
   RefPtr<ResizeObserverCallback> mCallback;
   nsTArray<RefPtr<ResizeObservation>> 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
   // the elements of HashTable in order of insertion time, so we can deliver
   // observations in the correct order
   // FIXME: it will be nice if we have our own data structure for this in the
   // future, and mObservationMap should be considered the "owning" storage for
   // the observations, so it'd be better to drop mObservationList later.
@@ -184,22 +194,71 @@ class ResizeObserverEntry final : public
   Element* Target() const { return mTarget; }
 
   /**
    * Returns the DOMRectReadOnly of target's content rect so it can be
    * accessed from JavaScript in callback function of ResizeObserver.
    */
   DOMRectReadOnly* ContentRect() const { return mContentRect; }
 
-  void SetContentRect(const 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() = default;
 
   nsCOMPtr<nsISupports> mOwner;
   nsCOMPtr<Element> mTarget;
+
   RefPtr<DOMRectReadOnly> mContentRect;
+  RefPtr<ResizeObserverSize> mBorderBoxSize;
+  RefPtr<ResizeObserverSize> 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<JSObject*> aGivenProto) override {
+    return ResizeObserverSize_Binding::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<nsISupports> mOwner;
+  const LogicalSize mSize;
+  const WritingMode mWM;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_ResizeObserver_h
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -728,16 +728,21 @@ DOMInterfaces = {
     'implicitJSContext': [ 'arrayBuffer', 'blob', 'formData', 'json', 'text' ],
 },
 
 'ResizeObserverEntry': {
     'nativeType': 'mozilla::dom::ResizeObserverEntry',
     'headerFile': 'mozilla/dom/ResizeObserver.h',
 },
 
+'ResizeObserverSize': {
+    'nativeType': 'mozilla::dom::ResizeObserverSize',
+    'headerFile': 'mozilla/dom/ResizeObserver.h',
+},
+
 'Response': {
     'binaryNames': { 'headers': 'headers_' },
     'implicitJSContext': [ 'arrayBuffer', 'blob', 'formData', 'json', 'text',
                            'clone', 'cloneUnfiltered' ],
 },
 
 'RTCDataChannel': {
     'nativeType': 'nsDOMDataChannel',
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -831,16 +831,20 @@ var interfaceNamesInGlobalScope =
     {name: "ReportBody", insecureContext: true, nightly: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "ReportingObserver", insecureContext: true, nightly: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "Request", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "ResizeObserver", insecureContext: true, nightly: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "ResizeObserverEntry", insecureContext: true, nightly: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "ResizeObserverSize", insecureContext: true, nightly: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "Response", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "RTCCertificate", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "RTCDataChannel", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "RTCDataChannelEvent", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/webidl/ResizeObserver.webidl
+++ b/dom/webidl/ResizeObserver.webidl
@@ -2,30 +2,44 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * The origin of this IDL file is
  * https://drafts.csswg.org/resize-observer/
  */
 
+enum ResizeObserverBoxOptions {
+    "border-box",
+    "content-box"
+};
+
+dictionary ResizeObserverOptions {
+    ResizeObserverBoxOptions box = "content-box";
+};
+
 [Constructor(ResizeObserverCallback callback),
  Exposed=Window,
  Pref="layout.css.resizeobserver.enabled"]
 interface ResizeObserver {
-    // Bug 1545239: Add "optional ResizeObserverOptions" in observe.
     [Throws]
-    void observe(Element target);
+    void observe(Element target, optional ResizeObserverOptions options);
     [Throws]
     void unobserve(Element target);
     void disconnect();
 };
 
 callback ResizeObserverCallback = void (sequence<ResizeObserverEntry> entries, ResizeObserver observer);
 
 [Constructor(Element target),
- ChromeOnly,
  Pref="layout.css.resizeobserver.enabled"]
 interface ResizeObserverEntry {
     readonly attribute Element target;
     readonly attribute DOMRectReadOnly contentRect;
-    // Bug 1545239: Add borderBoxSize and contentBoxSize attributes.
+    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;
+};
--- a/testing/web-platform/meta/resize-observer/idlharness.window.js.ini
+++ b/testing/web-platform/meta/resize-observer/idlharness.window.js.ini
@@ -34,51 +34,15 @@
   [ResizeObservation interface: existence and properties of interface object]
     expected: FAIL
     bug: https://github.com/w3c/csswg-drafts/issues/3839
 
   [ResizeObservation interface: attribute broadcastHeight]
     expected: FAIL
     bug: https://github.com/w3c/csswg-drafts/issues/3839
 
-  [ResizeObserverEntry interface: attribute borderBoxSize]
-    expected: FAIL
-
-  [ResizeObserverSize interface: existence and properties of interface prototype object's @@unscopables property]
-    expected: FAIL
-
-  [ResizeObserverSize interface object length]
-    expected: FAIL
-
   [ResizeObservation interface: attribute lastReportedSize]
     expected: FAIL
     bug: https://github.com/w3c/csswg-drafts/issues/3839
 
   [ResizeObservation interface: attribute observedBox]
     expected: FAIL
     bug: https://github.com/w3c/csswg-drafts/issues/3839
-
-  [ResizeObserverSize interface: attribute inlineSize]
-    expected: FAIL
-
-  [ResizeObserverSize interface object name]
-    expected: FAIL
-
-  [ResizeObserverSize interface: existence and properties of interface prototype object]
-    expected: FAIL
-
-  [ResizeObserverEntry interface: entry must inherit property "contentBoxSize" with the proper type]
-    expected: FAIL
-
-  [ResizeObserverEntry interface: attribute contentBoxSize]
-    expected: FAIL
-
-  [ResizeObserverSize interface: attribute blockSize]
-    expected: FAIL
-
-  [ResizeObserverEntry interface: entry must inherit property "borderBoxSize" with the proper type]
-    expected: FAIL
-
-  [ResizeObserverSize interface: existence and properties of interface object]
-    expected: FAIL
-
-  [ResizeObserverSize interface: existence and properties of interface prototype object's "constructor" property]
-    expected: FAIL