diff options
Diffstat (limited to 'dom/base/ResizeObserver.cpp')
-rw-r--r-- | dom/base/ResizeObserver.cpp | 304 |
1 files changed, 304 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
|