/* -*- 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::Constructor(const GlobalObject& aGlobal, ResizeObserverCallback& aCb, ErrorResult& aRv) { nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); if (!window) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsCOMPtr document = window->GetExtantDoc(); if (!document) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } RefPtr observer = new ResizeObserver(window.forget(), aCb); document->AddResizeObserver(observer); return observer.forget(); } void ResizeObserver::Observe(Element* aTarget, ErrorResult& aRv) { if (!aTarget) { aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); return; } RefPtr 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. aTarget->OwnerDoc()->ScheduleResizeObserversNotification(); } } void ResizeObserver::Unobserve(Element* aTarget, ErrorResult& aRv) { if (!aTarget) { aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); return; } RefPtr 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> entries; for (auto observation : mActiveTargets) { RefPtr 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::Constructor(const GlobalObject& aGlobal, Element* aTarget, ErrorResult& aRv) { RefPtr observerEntry = new ResizeObserverEntry(aGlobal.GetAsSupports(), aTarget); return observerEntry.forget(); } void ResizeObserverEntry::SetContentRect(nsRect aRect) { RefPtr 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::Constructor(const GlobalObject& aGlobal, Element* aTarget, ErrorResult& aRv) { RefPtr 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