summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--dom/base/ResizeObserver.cpp304
-rw-r--r--dom/base/ResizeObserver.h254
-rwxr-xr-xdom/base/moz.build2
-rw-r--r--dom/bindings/Bindings.conf15
-rw-r--r--dom/webidl/ResizeObserver.webidl39
-rw-r--r--dom/webidl/moz.build1
-rw-r--r--modules/libpref/init/all.js3
7 files changed, 618 insertions, 0 deletions
diff --git a/dom/base/ResizeObserver.cpp b/dom/base/ResizeObserver.cpp
new file mode 100644
index 000000000..aefcddd9d
--- /dev/null
+++ b/dom/base/ResizeObserver.cpp
@@ -0,0 +1,304 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#include "mozilla/dom/ResizeObserver.h"
+
+#include "mozilla/dom/DOMRect.h"
+#include "nsContentUtils.h"
+#include "nsIFrame.h"
+#include "nsSVGUtils.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(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
diff --git a/dom/base/ResizeObserver.h b/dom/base/ResizeObserver.h
new file mode 100644
index 000000000..2f56c580f
--- /dev/null
+++ b/dom/base/ResizeObserver.h
@@ -0,0 +1,254 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#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
+
diff --git a/dom/base/moz.build b/dom/base/moz.build
index 1de49424b..a0b9808da 100755
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -202,6 +202,7 @@ EXPORTS.mozilla.dom += [
'PartialSHistory.h',
'Pose.h',
'ProcessGlobal.h',
+ 'ResizeObserver.h',
'ResponsiveImageSelector.h',
'SameProcessMessageQueue.h',
'ScreenOrientation.h',
@@ -347,6 +348,7 @@ SOURCES += [
'Pose.cpp',
'PostMessageEvent.cpp',
'ProcessGlobal.cpp',
+ 'ResizeObserver.cpp',
'ResponsiveImageSelector.cpp',
'SameProcessMessageQueue.cpp',
'ScreenOrientation.cpp',
diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf
index 941e65eff..f08094617 100644
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -717,6 +717,21 @@ DOMInterfaces = {
},
},
+'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_' },
},
diff --git a/dom/webidl/ResizeObserver.webidl b/dom/webidl/ResizeObserver.webidl
new file mode 100644
index 000000000..98700f53c
--- /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();
+};
diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build
index 687e502fa..5699e7721 100644
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -365,6 +365,7 @@ WEBIDL_FILES = [
'Range.webidl',
'Rect.webidl',
'Request.webidl',
+ 'ResizeObserver.webidl',
'Response.webidl',
'RGBColor.webidl',
'RTCStatsReport.webidl',
diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
index e31b92fce..bed6d0d01 100644
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2655,6 +2655,9 @@ pref("layout.css.font-loading-api.enabled", true);
// Should stray control characters be rendered visibly?
pref("layout.css.control-characters.visible", false);
+// Is support for ResizeObservers enabled?
+pref("layout.css.resizeobserver.enabled", true);
+
// pref for which side vertical scrollbars should be on
// 0 = end-side in UI direction
// 1 = end-side in document/content direction