Bug 1272409 - Part 2: Add ResizeObserver webidl and implementation. r=dholbert,smaug
authorFariskhi Vidyan <farislab@gmail.com>
Fri, 26 Apr 2019 20:29:14 +0000
changeset 530408 b634a26d0c2ed0efe2e7739b24aafcbf5820c058
parent 530407 2f3cfdbcb9003bb1cc33456181bf4206740caad9
child 530409 0b7728e6fcda89443d3d580fe108f74a193c3b1c
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert, smaug
bugs1272409
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 1272409 - Part 2: Add ResizeObserver webidl and implementation. r=dholbert,smaug This implements the first version of spec, so the webidl file doesn't match the current spec and we will fix them in the follow-up bugs. i.e. 1. The default observer box is content-box. 2. `ResizeObserverBoxOptions`, `ResizeObserverOptions`, and `ResizeObserverSize` are not included in `ResizeObserver.webidl`. 3. `ResizeObserverEntry` doesn't have `borderBoxSize` and `contentBoxSize` attributes. Depends on D27615 Differential Revision: https://phabricator.services.mozilla.com/D27616
dom/base/ResizeObserver.cpp
dom/base/ResizeObserver.h
dom/base/moz.build
dom/bindings/Bindings.conf
dom/tests/mochitest/general/test_interfaces.js
dom/webidl/ResizeObserver.webidl
dom/webidl/moz.build
modules/libpref/init/StaticPrefList.h
new file mode 100644
--- /dev/null
+++ b/dom/base/ResizeObserver.cpp
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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"
+
+#include "mozilla/dom/Document.h"
+#include "nsContentUtils.h"
+#include "nsSVGUtils.h"
+#include <limits>
+
+namespace mozilla {
+namespace dom {
+
+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;
+}
+
+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. 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;
+}
+
+// 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)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_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;
+  }
+
+  Document* 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) {
+  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);
+
+  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) {
+  RefPtr<ResizeObservation> observation;
+  if (!mObservationMap.Remove(&aTarget, getter_AddRefs(observation))) {
+    return;
+  }
+
+  MOZ_ASSERT(!mObservationList.isEmpty(),
+             "If ResizeObservation found for an element, observation list "
+             "must be not empty.");
+  observation->remove();
+}
+
+void ResizeObserver::Disconnect() {
+  mObservationList.clear();
+  mObservationMap.Clear();
+  mActiveTargets.Clear();
+}
+
+void ResizeObserver::GatherActiveObservations(uint32_t aDepth) {
+  mActiveTargets.Clear();
+  mHasSkippedTargets = false;
+
+  for (auto observation : mObservationList) {
+    if (!observation->IsActive()) {
+      continue;
+    }
+
+    uint32_t targetDepth = nsContentUtils::GetNodeDepth(observation->Target());
+
+    if (targetDepth > aDepth) {
+      mActiveTargets.AppendElement(observation);
+    } else {
+      mHasSkippedTargets = true;
+    }
+  }
+}
+
+uint32_t ResizeObserver::BroadcastActiveObservations() {
+  uint32_t shallowestTargetDepth = std::numeric_limits<uint32_t>::max();
+
+  if (!HasActiveObservations()) {
+    return shallowestTargetDepth;
+  }
+
+  Sequence<OwningNonNull<ResizeObserverEntry>> entries;
+
+  for (auto& observation : mActiveTargets) {
+    RefPtr<ResizeObserverEntry> entry =
+        new ResizeObserverEntry(this, *observation->Target());
+
+    nsRect rect = observation->GetTargetRect();
+    entry->SetContentRect(rect);
+    // FIXME: Bug 1545239: Set borderBoxSize and 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());
+
+    uint32_t targetDepth = nsContentUtils::GetNodeDepth(observation->Target());
+
+    if (targetDepth < shallowestTargetDepth) {
+      shallowestTargetDepth = targetDepth;
+    }
+  }
+
+  RefPtr<ResizeObserverCallback> callback(mCallback);
+  callback->Call(this, entries, *this);
+
+  mActiveTargets.Clear();
+  mHasSkippedTargets = false;
+
+  return shallowestTargetDepth;
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObserverEntry, mTarget,
+                                      mContentRect, mOwner)
+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) {
+  nsIFrame* frame = mTarget->GetPrimaryFrame();
+  Maybe<nsMargin> padding = frame ? Some(frame->GetUsedPadding()) : Nothing();
+
+  // 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);
+
+  RefPtr<DOMRect> contentRect = new DOMRect(mTarget);
+  contentRect->SetLayoutRect(rect);
+  mContentRect = contentRect.forget();
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/ResizeObserver.h
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ResizeObserverBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class Element;
+
+}  // namespace dom
+}  // namespace mozilla
+
+namespace mozilla {
+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) {
+    MOZ_ASSERT(mTarget, "Need a non-null target element");
+  }
+
+  Element* Target() const { return mTarget; }
+
+  nscoord BroadcastWidth() const { return mBroadcastWidth; }
+
+  nscoord BroadcastHeight() const { return mBroadcastHeight; }
+
+  /**
+   * Returns whether the observed target element size differs from the saved
+   * BroadcastWidth and BroadcastHeight.
+   */
+  bool IsActive() const;
+
+  /**
+   * Update current BroadcastWidth and BroadcastHeight 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;
+
+ protected:
+  ~ResizeObservation() = default;
+
+  nsCOMPtr<Element> mTarget;
+
+  // Broadcast width and broadcast height are the latest recorded size
+  // of observed target.
+  nscoord mBroadcastWidth;
+  nscoord mBroadcastHeight;
+};
+
+/**
+ * ResizeObserver interfaces and algorithms are based on
+ * https://drafts.csswg.org/resize-observer/#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");
+  }
+
+  nsISupports* GetParentObject() const { return mOwner; }
+
+  JSObject* WrapObject(JSContext* aCx,
+                       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 Unobserve(Element& target, 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 { return !mActiveTargets.IsEmpty(); }
+
+  /**
+   * 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.
+   */
+  MOZ_CAN_RUN_SCRIPT 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, 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.
+  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's 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");
+  }
+
+  nsISupports* GetParentObject() const { return mOwner; }
+
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override {
+    return ResizeObserverEntry_Binding::Wrap(aCx, this, aGivenProto);
+  }
+
+  static already_AddRefed<ResizeObserverEntry> Constructor(
+      const GlobalObject& global, Element& target, ErrorResult& aRv);
+
+  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);
+
+ protected:
+  ~ResizeObserverEntry() = default;
+
+  nsCOMPtr<nsISupports> mOwner;
+  nsCOMPtr<Element> mTarget;
+  RefPtr<DOMRectReadOnly> mContentRect;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_ResizeObserver_h
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -211,16 +211,17 @@ EXPORTS.mozilla.dom += [
     'PlacesEvent.h',
     'PlacesObservers.h',
     'PlacesVisit.h',
     'PlacesWeakCallbackWrapper.h',
     'PopupBlocker.h',
     'Pose.h',
     'PostMessageEvent.h',
     'ProcessMessageManager.h',
+    'ResizeObserver.h',
     'ResponsiveImageSelector.h',
     'SameProcessMessageQueue.h',
     'ScreenLuminance.h',
     'ScreenOrientation.h',
     'Selection.h',
     'ShadowIncludingTreeIterator.h',
     'ShadowRoot.h',
     'StructuredCloneBlob.h',
@@ -376,16 +377,17 @@ UNIFIED_SOURCES += [
     'nsXHTMLContentSerializer.cpp',
     'nsXMLContentSerializer.cpp',
     'ParentProcessMessageManager.cpp',
     'PopupBlocker.cpp',
     'Pose.cpp',
     'PostMessageEvent.cpp',
     'ProcessMessageManager.cpp',
     'RemoteOuterWindowProxy.cpp',
+    'ResizeObserver.cpp',
     'ResponsiveImageSelector.cpp',
     'SameProcessMessageQueue.cpp',
     'ScreenLuminance.cpp',
     'ScreenOrientation.cpp',
     'ScriptableContentIterator.cpp',
     'Selection.cpp',
     'SelectionChangeEventDispatcher.cpp',
     'ShadowRoot.cpp',
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -723,16 +723,21 @@ DOMInterfaces = {
 'Request': {
     'binaryNames': {
         'headers': 'headers_',
         'referrerPolicy': 'referrerPolicy_'
     },
     'implicitJSContext': [ 'arrayBuffer', 'blob', 'formData', 'json', 'text' ],
 },
 
+'ResizeObserverEntry': {
+    'nativeType': 'mozilla::dom::ResizeObserverEntry',
+    '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
@@ -829,16 +829,18 @@ var interfaceNamesInGlobalScope =
     {name: "Report", insecureContext: true, nightly: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {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},
+// 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!
new file mode 100644
--- /dev/null
+++ b/dom/webidl/ResizeObserver.webidl
@@ -0,0 +1,31 @@
+/* -*- 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://drafts.csswg.org/resize-observer/
+ */
+
+[Constructor(ResizeObserverCallback callback),
+ Exposed=Window,
+ Pref="layout.css.resizeobserver.enabled"]
+interface ResizeObserver {
+    // Bug 1545239: Add "optional ResizeObserverOptions" in observe.
+    [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;
+    // Bug 1545239: Add borderBoxSize and contentBoxSize attributes.
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -743,16 +743,17 @@ WEBIDL_FILES = [
     'PushManager.webidl',
     'PushMessageData.webidl',
     'PushSubscription.webidl',
     'PushSubscriptionOptions.webidl',
     'RadioNodeList.webidl',
     'Range.webidl',
     'Reporting.webidl',
     'Request.webidl',
+    'ResizeObserver.webidl',
     'Response.webidl',
     'RTCStatsReport.webidl',
     'Screen.webidl',
     'ScreenOrientation.webidl',
     'ScriptProcessorNode.webidl',
     'ScrollAreaEvent.webidl',
     'Selection.webidl',
     'ServiceWorker.webidl',
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1149,16 +1149,28 @@ VARCACHE_PREF(
 
 // Are shared memory User Agent style sheets enabled?
 VARCACHE_PREF(
   "layout.css.shared-memory-ua-sheets.enabled",
    layout_css_shared_memory_ua_sheets_enabled,
   bool, false
 )
 
+#ifdef NIGHTLY_BUILD
+# define PREF_VALUE true
+#else
+# define PREF_VALUE false
+#endif
+VARCACHE_PREF(
+  "layout.css.resizeobserver.enabled",
+   layout_css_resizeobserver_enabled,
+  bool, PREF_VALUE
+)
+#undef PREF_VALUE
+
 // Pref to control whether arrow-panel animations are enabled or not.
 // Transitions are currently disabled on Linux due to rendering issues on
 // certain configurations.
 #ifdef MOZ_WIDGET_GTK
 #define PREF_VALUE false
 #else
 #define PREF_VALUE true
 #endif