Bug 1272409 part 2: Add ResizeObserver webidl and implementation. r?dholbert draft
authorFariskhi Vidyan <farislab@gmail.com>
Wed, 14 Sep 2016 16:50:20 -0700
changeset 413824 7c161b464bae8d40cd6ba457a777c7dfb3bc8d2a
parent 413819 6392fe9e45ea71128e67152270c4b1f833b0fafe
child 413825 0848bd2349d335e6c581b0ded9bd6ffa5805ef80
push id29523
push userfarislab@gmail.com
push dateWed, 14 Sep 2016 23:58:19 +0000
reviewersdholbert
bugs1272409
milestone51.0a1
Bug 1272409 part 2: Add ResizeObserver webidl and implementation. r?dholbert MozReview-Commit-ID: A4NBuuKqKx2
dom/base/ResizeObserver.cpp
dom/base/ResizeObserver.h
dom/base/moz.build
dom/bindings/Bindings.conf
dom/webidl/ResizeObserver.webidl
dom/webidl/moz.build
modules/libpref/init/all.js
new file mode 100644
--- /dev/null
+++ b/dom/base/ResizeObserver.cpp
@@ -0,0 +1,301 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/dom/ResizeObserver.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObserver)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserver)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ResizeObserver)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ResizeObserver)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ResizeObserver)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservationMap)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ResizeObserver)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservationMap)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+already_AddRefed<ResizeObserver>
+ResizeObserver::Constructor(const GlobalObject& aGlobal,
+                            ResizeObserverCallback& aCb,
+                            ErrorResult& aRv)
+{
+  nsCOMPtr<nsPIDOMWindowInner> window =
+    do_QueryInterface(aGlobal.GetAsSupports());
+
+  if (!window) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIDocument> document = window->GetExtantDoc();
+
+  if (!document) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  RefPtr<ResizeObserver> observer = new ResizeObserver(window.forget(), aCb);
+  // TODO: Add the new ResizeObserver to document here in the later patch.
+
+  return observer.forget();
+}
+
+void
+ResizeObserver::Observe(Element* aTarget,
+                        ErrorResult& aRv)
+{
+  if (!aTarget) {
+    aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+    return;
+  }
+
+  RefPtr<ResizeObservation> observation;
+
+  if (!mObservationMap.Get(aTarget, getter_AddRefs(observation))) {
+    observation = new ResizeObservation(this, aTarget);
+
+    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.
+    // TODO: Implement the notification scheduling in the later patch.
+  }
+}
+
+void
+ResizeObserver::Unobserve(Element* aTarget,
+                          ErrorResult& aRv)
+{
+  if (!aTarget) {
+    aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+    return;
+  }
+
+  RefPtr<ResizeObservation> observation;
+
+  if (mObservationMap.Get(aTarget, getter_AddRefs(observation))) {
+    mObservationMap.Remove(aTarget);
+
+    MOZ_ASSERT(!mObservationList.isEmpty(),
+               "If ResizeObservation found for an element, observation list "
+               "must be not empty.");
+
+    observation->remove();
+  }
+}
+
+void
+ResizeObserver::Disconnect()
+{
+  mObservationMap.Clear();
+  mObservationList.clear();
+  mActiveTargets.Clear();
+}
+
+void
+ResizeObserver::GatherActiveObservations(uint32_t aDepth)
+{
+  mActiveTargets.Clear();
+  mHasSkippedTargets = false;
+
+  for (auto observation : mObservationList) {
+    if (observation->IsActive()) {
+      uint32_t targetDepth =
+        nsContentUtils::GetNodeDepth(observation->Target());
+
+      if (targetDepth > aDepth) {
+        mActiveTargets.AppendElement(observation);
+      } else {
+        mHasSkippedTargets = true;
+      }
+    }
+  }
+}
+
+bool
+ResizeObserver::HasActiveObservations() const
+{
+  return !mActiveTargets.IsEmpty();
+}
+
+bool
+ResizeObserver::HasSkippedObservations() const
+{
+  return mHasSkippedTargets;
+}
+
+uint32_t
+ResizeObserver::BroadcastActiveObservations()
+{
+  uint32_t shallowestTargetDepth = UINT32_MAX;
+
+  if (HasActiveObservations()) {
+    Sequence<OwningNonNull<ResizeObserverEntry>> entries;
+
+    for (auto observation : mActiveTargets) {
+      RefPtr<ResizeObserverEntry> entry =
+        new ResizeObserverEntry(this, observation->Target());
+
+      nsRect rect = observation->GetTargetRect();
+      entry->SetContentRect(rect);
+
+      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);
+
+      uint32_t targetDepth =
+        nsContentUtils::GetNodeDepth(observation->Target());
+
+      if (targetDepth < shallowestTargetDepth) {
+        shallowestTargetDepth = targetDepth;
+      }
+    }
+
+    mCallback->Call(this, entries, *this);
+    mActiveTargets.Clear();
+    mHasSkippedTargets = false;
+  }
+
+  return shallowestTargetDepth;
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObserverEntry)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserverEntry)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserverEntry)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObserverEntry,
+                                      mTarget, mContentRect,
+                                      mOwner)
+
+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(nsRect aRect)
+{
+  RefPtr<DOMRect> contentRect = new DOMRect(mTarget);
+  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);
+  mContentRect = contentRect.forget();
+}
+
+ResizeObserverEntry::~ResizeObserverEntry()
+{
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObservation)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObservation)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObservation)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObservation,
+                                      mTarget, mOwner)
+
+already_AddRefed<ResizeObservation>
+ResizeObservation::Constructor(const GlobalObject& aGlobal,
+                               Element* aTarget,
+                               ErrorResult& aRv)
+{
+  RefPtr<ResizeObservation> observation =
+    new ResizeObservation(aGlobal.GetAsSupports(), aTarget);
+  return observation.forget();
+}
+
+bool
+ResizeObservation::IsActive() const
+{
+  nsRect rect = GetTargetRect();
+  return (rect.width != mBroadcastWidth || rect.height != mBroadcastHeight);
+}
+
+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()
+{
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/ResizeObserver.h
@@ -0,0 +1,254 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/ResizeObserverBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * ResizeObserver interfaces and algorithms are based on
+ * https://wicg.github.io/ResizeObserver/#api
+ */
+class ResizeObserver final
+  : public nsISupports
+  , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ResizeObserver)
+
+  ResizeObserver(already_AddRefed<nsPIDOMWindowInner>&& aOwner,
+                 ResizeObserverCallback& aCb)
+    : mOwner(aOwner)
+    , mCallback(&aCb)
+  {
+    MOZ_ASSERT(mOwner, "Need a non-null owner window");
+  }
+
+  static already_AddRefed<ResizeObserver>
+  Constructor(const GlobalObject& aGlobal,
+              ResizeObserverCallback& aCb,
+              ErrorResult& aRv);
+
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override
+  {
+    return ResizeObserverBinding::Wrap(aCx, this, aGivenProto);
+  }
+
+  nsISupports* GetParentObject() const
+  {
+    return mOwner;
+  }
+
+  void Observe(Element* aTarget, ErrorResult& aRv);
+
+  void Unobserve(Element* aTarget, ErrorResult& aRv);
+
+  void Disconnect();
+
+  /*
+   * Gather all observations which have an observed target with size changed
+   * since last BroadcastActiveObservations() in this ResizeObserver.
+   * An observation will be skipped if the depth of its observed target is less
+   * or equal than aDepth. All gathered observations will be added to
+   * mActiveTargets.
+  */
+  void GatherActiveObservations(uint32_t aDepth);
+
+  /*
+   * Returns whether this ResizeObserver has any active observations
+   * since last GatherActiveObservations().
+  */
+  bool HasActiveObservations() const;
+
+  /*
+   * Returns whether this ResizeObserver has any skipped observations
+   * since last GatherActiveObservations().
+  */
+  bool HasSkippedObservations() const;
+
+  /*
+   * 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.
+  */
+  uint32_t BroadcastActiveObservations();
+
+protected:
+  ~ResizeObserver()
+  {
+    mObservationList.clear();
+  }
+
+  nsCOMPtr<nsPIDOMWindowInner> mOwner;
+  RefPtr<ResizeObserverCallback> mCallback;
+  nsTArray<RefPtr<ResizeObservation>> mActiveTargets;
+  bool mHasSkippedTargets;
+
+  // Combination of HashTable and LinkedList so we can iterate through
+  // the elements of HashTable in order of insertion time.
+  // Will be nice if we have our own data structure for this in the future.
+  nsRefPtrHashtable<nsPtrHashKey<Element>, ResizeObservation> mObservationMap;
+  LinkedList<ResizeObservation> mObservationList;
+};
+
+/**
+ * ResizeObserverEntry is the entry that contains the information for observed
+ * elements. This object is the one that visible to JavaScript in callback
+ * function that is fired by ResizeObserver.
+ */
+class ResizeObserverEntry final
+  : public nsISupports
+  , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ResizeObserverEntry)
+
+  ResizeObserverEntry(nsISupports* aOwner, Element* aTarget)
+    : mOwner(aOwner)
+    , mTarget(aTarget)
+  {
+    MOZ_ASSERT(mOwner, "Need a non-null owner");
+    MOZ_ASSERT(mTarget, "Need a non-null target element");
+  }
+
+  static already_AddRefed<ResizeObserverEntry>
+  Constructor(const GlobalObject& aGlobal,
+              Element* aTarget,
+              ErrorResult& aRv);
+
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override
+  {
+    return ResizeObserverEntryBinding::Wrap(aCx, this,
+                                            aGivenProto);
+  }
+
+  nsISupports* GetParentObject() const
+  {
+    return mOwner;
+  }
+
+  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* GetContentRect() const
+  {
+    return mContentRect;
+  }
+
+  void SetContentRect(nsRect aRect);
+
+protected:
+  ~ResizeObserverEntry();
+
+  nsCOMPtr<nsISupports> mOwner;
+  nsCOMPtr<Element> mTarget;
+  RefPtr<DOMRectReadOnly> mContentRect;
+};
+
+/**
+ * We use ResizeObservation to store and sync the size information of one
+ * observed element so we can decide whether an observation should be fired
+ * or not.
+ */
+class ResizeObservation final
+  : public nsISupports
+  , public nsWrapperCache
+  , public LinkedListElement<ResizeObservation>
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ResizeObservation)
+
+  ResizeObservation(nsISupports* aOwner, Element* aTarget)
+    : mOwner(aOwner)
+    , mTarget(aTarget)
+    , mBroadcastWidth(0)
+    , mBroadcastHeight(0)
+  {
+    MOZ_ASSERT(mOwner, "Need a non-null owner");
+    MOZ_ASSERT(mTarget, "Need a non-null target element");
+  }
+
+  static already_AddRefed<ResizeObservation>
+  Constructor(const GlobalObject& aGlobal,
+              Element* aTarget,
+              ErrorResult& aRv);
+
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override
+  {
+    return ResizeObservationBinding::Wrap(aCx, this, aGivenProto);
+  }
+
+  nsISupports* GetParentObject() const
+  {
+    return mOwner;
+  }
+
+  Element* Target() const
+  {
+    return mTarget;
+  }
+
+  nscoord BroadcastWidth() const
+  {
+    return mBroadcastWidth;
+  }
+
+  nscoord BroadcastHeight() const
+  {
+    return mBroadcastHeight;
+  }
+
+  /*
+   * Returns whether the observed target element size differs from current
+   * BroadcastWidth and BroadcastHeight
+  */
+  bool IsActive() const;
+
+  /*
+   * Update current BroadcastWidth and BroadcastHeight with size from aRect.
+  */
+  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;
+
+protected:
+  ~ResizeObservation();
+
+  nsCOMPtr<nsISupports> mOwner;
+  nsCOMPtr<Element> mTarget;
+
+  // Broadcast width and broadcast height are the latest recorded size
+  // of observed target.
+  nscoord mBroadcastWidth;
+  nscoord mBroadcastHeight;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ResizeObserver_h
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -186,16 +186,17 @@ EXPORTS.mozilla.dom += [
     'ImportManager.h',
     'Link.h',
     'NameSpaceConstants.h',
     'Navigator.h',
     'NodeInfo.h',
     'NodeInfoInlines.h',
     'NodeIterator.h',
     'ProcessGlobal.h',
+    'ResizeObserver.h',
     'ResponsiveImageSelector.h',
     'SameProcessMessageQueue.h',
     'ScreenOrientation.h',
     'ScriptSettings.h',
     'ShadowRoot.h',
     'StructuredCloneHolder.h',
     'StructuredCloneTags.h',
     'StyleSheetList.h',
@@ -321,16 +322,17 @@ UNIFIED_SOURCES += [
     'nsWindowMemoryReporter.cpp',
     'nsWindowRoot.cpp',
     'nsWrapperCache.cpp',
     'nsXHTMLContentSerializer.cpp',
     'nsXMLContentSerializer.cpp',
     'nsXMLNameSpaceMap.cpp',
     'PostMessageEvent.cpp',
     'ProcessGlobal.cpp',
+    'ResizeObserver.cpp',
     'ResponsiveImageSelector.cpp',
     'SameProcessMessageQueue.cpp',
     'ScreenOrientation.cpp',
     'ScriptSettings.cpp',
     'ShadowRoot.cpp',
     'StructuredCloneHolder.cpp',
     'StyleSheetList.cpp',
     'SubtleCrypto.cpp',
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -895,16 +895,31 @@ DOMInterfaces = {
 
 'Request': {
     'binaryNames': {
         'headers': 'headers_',
         'referrerPolicy': 'referrerPolicy_'
     },
 },
 
+'ResizeObservation': {
+    'nativeType': 'mozilla::dom::ResizeObservation',
+    'headerFile': 'mozilla/dom/ResizeObserver.h',
+},
+
+'ResizeObserver': {
+    'nativeType': 'mozilla::dom::ResizeObserver',
+    'headerFile': 'mozilla/dom/ResizeObserver.h',
+},
+
+'ResizeObserverEntry': {
+    'nativeType': 'mozilla::dom::ResizeObserverEntry',
+    'headerFile': 'mozilla/dom/ResizeObserver.h',
+},
+
 'Response': {
     'binaryNames': { 'headers': 'headers_' },
 },
 
 'RGBColor': {
     'nativeType': 'nsDOMCSSRGBColor',
 },
 
new file mode 100644
--- /dev/null
+++ b/dom/webidl/ResizeObserver.webidl
@@ -0,0 +1,39 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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://wicg.github.io/ResizeObserver/
+ */
+
+[Constructor(ResizeObserverCallback callback),
+ Exposed=Window,
+ Pref="layout.css.resizeobserver.enabled"]
+interface ResizeObserver {
+    [Throws]
+    void observe(Element? target);
+    [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;
+};
+
+[Constructor(Element? target),
+ ChromeOnly,
+ Pref="layout.css.resizeobserver.enabled"]
+interface ResizeObservation {
+    readonly attribute Element target;
+    readonly attribute long broadcastWidth;
+    readonly attribute long broadcastHeight;
+    boolean isActive();
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -400,16 +400,17 @@ WEBIDL_FILES = [
     'PushManager.webidl',
     'PushMessageData.webidl',
     'PushSubscription.webidl',
     'PushSubscriptionOptions.webidl',
     'RadioNodeList.webidl',
     'Range.webidl',
     'Rect.webidl',
     'Request.webidl',
+    'ResizeObserver.webidl',
     'ResourceStats.webidl',
     'ResourceStatsManager.webidl',
     'Response.webidl',
     'RGBColor.webidl',
     'RTCStatsReport.webidl',
     'Screen.webidl',
     'ScreenOrientation.webidl',
     'ScriptProcessorNode.webidl',
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2628,16 +2628,22 @@ pref("layout.css.font-loading-api.enable
 
 // Should stray control characters be rendered visibly?
 #ifdef RELEASE_BUILD
 pref("layout.css.control-characters.visible", false);
 #else
 pref("layout.css.control-characters.visible", true);
 #endif
 
+#ifdef NIGHTLY_BUILD
+pref("layout.css.resizeobserver.enabled", true);
+#else
+pref("layout.css.resizeobserver.enabled", false);
+#endif
+
 // pref for which side vertical scrollbars should be on
 // 0 = end-side in UI direction
 // 1 = end-side in document/content direction
 // 2 = right
 // 3 = left
 pref("layout.scrollbar.side", 0);
 
 // pref to stop overlay scrollbars from fading out, for testing purposes