Issue #1643 - Part 3: Implement ResizeObserverController

This commit is contained in:
Moonchild 2020-09-16 18:21:14 +00:00 committed by roytam1
commit 96a75aa648
3 changed files with 365 additions and 0 deletions

View file

@ -0,0 +1,234 @@
/* -*- 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/ResizeObserverController.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ErrorEvent.h"
#include "nsIPresShell.h"
#include "nsPresContext.h"
namespace mozilla {
namespace dom {
void
ResizeObserverNotificationHelper::WillRefresh(TimeStamp aTime)
{
MOZ_ASSERT(mOwner, "Why is mOwner already dead when this RefreshObserver is still registered?");
if (mOwner) {
mOwner->Notify();
}
}
nsRefreshDriver*
ResizeObserverNotificationHelper::GetRefreshDriver() const
{
nsIPresShell* presShell = mOwner->GetShell();
if (MOZ_UNLIKELY(!presShell)) {
return nullptr;
}
nsPresContext* presContext = presShell->GetPresContext();
if (MOZ_UNLIKELY(!presContext)) {
return nullptr;
}
return presContext->RefreshDriver();
}
void
ResizeObserverNotificationHelper::Register()
{
if (mRegistered) {
return;
}
nsRefreshDriver* refreshDriver = GetRefreshDriver();
if (!refreshDriver) {
// We maybe navigating away from this page or currently in an iframe with
// display: none. Just abort the Register(), no need to do notification.
return;
}
refreshDriver->AddRefreshObserver(this, Flush_Display);
mRegistered = true;
}
void
ResizeObserverNotificationHelper::Unregister()
{
if (!mRegistered) {
return;
}
nsRefreshDriver* refreshDriver = GetRefreshDriver();
if (!refreshDriver) {
// We can't access RefreshDriver now. Just abort the Unregister().
return;
}
refreshDriver->RemoveRefreshObserver(this, Flush_Display);
mRegistered = false;
}
void
ResizeObserverNotificationHelper::Disconnect()
{
Unregister();
// Our owner is dying. Clear our pointer to it, in case we outlive it.
mOwner = nullptr;
}
ResizeObserverNotificationHelper::~ResizeObserverNotificationHelper()
{
Unregister();
}
void
ResizeObserverController::Traverse(nsCycleCollectionTraversalCallback& aCb)
{
ImplCycleCollectionTraverse(aCb, mResizeObservers, "mResizeObservers");
}
void
ResizeObserverController::Unlink()
{
mResizeObservers.Clear();
}
void
ResizeObserverController::AddResizeObserver(ResizeObserver* aObserver)
{
MOZ_ASSERT(aObserver, "AddResizeObserver() should never be called with "
"a null parameter");
mResizeObservers.AppendElement(aObserver);
}
void
ResizeObserverController::Notify()
{
if (mResizeObservers.IsEmpty()) {
return;
}
uint32_t shallowestTargetDepth = 0;
GatherAllActiveObservations(shallowestTargetDepth);
while (HasAnyActiveObservations()) {
DebugOnly<uint32_t> oldShallowestTargetDepth = shallowestTargetDepth;
shallowestTargetDepth = BroadcastAllActiveObservations();
NS_ASSERTION(oldShallowestTargetDepth < shallowestTargetDepth,
"shallowestTargetDepth should be getting strictly deeper");
// Flush layout, so that any callback functions' style changes / resizes
// get a chance to take effect.
mDocument->FlushPendingNotifications(Flush_Layout);
// To avoid infinite resize loop, we only gather all active observations
// that have the depth of observed target element more than current
// shallowestTargetDepth.
GatherAllActiveObservations(shallowestTargetDepth);
}
mResizeObserverNotificationHelper->Unregister();
// Per spec, we deliver an error if the document has any skipped observations.
if (HasAnySkippedObservations()) {
RootedDictionary<ErrorEventInit> init(RootingCx());
init.mMessage.AssignLiteral("ResizeObserver loop completed with undelivered"
" notifications.");
init.mCancelable = true;
init.mBubbles = true;
nsEventStatus status = nsEventStatus_eIgnore;
nsCOMPtr<nsPIDOMWindowInner> window =
mDocument->GetWindow()->GetCurrentInnerWindow();
if (window) {
nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(window);
MOZ_ASSERT(sgo);
if (NS_WARN_IF(NS_FAILED(sgo->HandleScriptError(init, &status)))) {
status = nsEventStatus_eIgnore;
}
} else {
// We don't fire error events at any global for non-window JS on the main
// thread.
}
// We need to deliver pending notifications in next cycle.
ScheduleNotification();
}
}
void
ResizeObserverController::GatherAllActiveObservations(uint32_t aDepth)
{
for (auto observer : mResizeObservers) {
observer->GatherActiveObservations(aDepth);
}
}
uint32_t
ResizeObserverController::BroadcastAllActiveObservations()
{
uint32_t shallowestTargetDepth = UINT32_MAX;
for (auto observer : mResizeObservers) {
uint32_t targetDepth = observer->BroadcastActiveObservations();
if (targetDepth < shallowestTargetDepth) {
shallowestTargetDepth = targetDepth;
}
}
return shallowestTargetDepth;
}
bool
ResizeObserverController::HasAnyActiveObservations() const
{
for (auto observer : mResizeObservers) {
if (observer->HasActiveObservations()) {
return true;
}
}
return false;
}
bool
ResizeObserverController::HasAnySkippedObservations() const
{
for (auto observer : mResizeObservers) {
if (observer->HasSkippedObservations()) {
return true;
}
}
return false;
}
void
ResizeObserverController::ScheduleNotification()
{
mResizeObserverNotificationHelper->Register();
}
nsIPresShell*
ResizeObserverController::GetShell() const
{
return mDocument->GetShell();
}
ResizeObserverController::~ResizeObserverController()
{
mResizeObserverNotificationHelper->Disconnect();
}
} // namespace dom
} // namespace mozilla

View file

@ -0,0 +1,129 @@
/* -*- 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_ResizeObserverController_h
#define mozilla_dom_ResizeObserverController_h
#include "mozilla/dom/ResizeObserver.h"
#include "mozilla/TimeStamp.h"
#include "nsRefreshDriver.h"
namespace mozilla {
namespace dom {
class ResizeObserverController;
/*
* ResizeObserverNotificationHelper will trigger ResizeObserver notifications
* by registering with the Refresh Driver.
*/
class ResizeObserverNotificationHelper final : public nsARefreshObserver
{
public:
NS_INLINE_DECL_REFCOUNTING(ResizeObserverNotificationHelper, override)
explicit ResizeObserverNotificationHelper(ResizeObserverController* aOwner)
: mOwner(aOwner)
, mRegistered(false)
{
MOZ_ASSERT(mOwner, "Need a non-null owner");
}
void WillRefresh(TimeStamp aTime) override;
nsRefreshDriver* GetRefreshDriver() const;
void Register();
void Unregister();
void Disconnect();
protected:
virtual ~ResizeObserverNotificationHelper();
ResizeObserverController* mOwner;
bool mRegistered;
};
/*
* ResizeObserverController contains the list of ResizeObservers and controls
* the flow of notification.
*/
class ResizeObserverController final
{
public:
explicit ResizeObserverController(nsIDocument* aDocument)
: mDocument(aDocument)
, mIsNotificationActive(false)
{
MOZ_ASSERT(mDocument, "Need a non-null document");
mResizeObserverNotificationHelper =
new ResizeObserverNotificationHelper(this);
}
// Methods for supporting cycle-collection
void Traverse(nsCycleCollectionTraversalCallback& aCb);
void Unlink();
void AddResizeObserver(ResizeObserver* aObserver);
/*
* Schedule the notification via ResizeObserverNotificationHelper refresh
* observer.
*/
void ScheduleNotification();
/*
* Notify all ResizeObservers by gathering and broadcasting all active
* observations.
*/
void Notify();
nsIPresShell* GetShell() const;
~ResizeObserverController();
private:
/*
* Calls GatherActiveObservations(aDepth) for all ResizeObservers in this
* controller. All observations in each ResizeObserver with element's depth
* more than aDepth will be gathered.
*/
void GatherAllActiveObservations(uint32_t aDepth);
/*
* Calls BroadcastActiveObservations() for all ResizeObservers in this
* controller. It also returns the shallowest depth of observed target
* elements from all ResizeObserver or UINT32_MAX if there is no any
* active obsevations at all.
*/
uint32_t BroadcastAllActiveObservations();
/*
* Returns whether there is any ResizeObserver that has active observations.
*/
bool HasAnyActiveObservations() const;
/*
* Returns whether there is any ResizeObserver that has skipped observations.
*/
bool HasAnySkippedObservations() const;
protected:
// Raw pointer is OK because mDocument strongly owns us & hence must outlive
// us.
nsIDocument* const mDocument;
RefPtr<ResizeObserverNotificationHelper> mResizeObserverNotificationHelper;
nsTArray<RefPtr<ResizeObserver>> mResizeObservers;
bool mIsNotificationActive;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_ResizeObserverController_h

View file

@ -205,6 +205,7 @@ EXPORTS.mozilla.dom += [
'Pose.h',
'ProcessGlobal.h',
'ResizeObserver.h',
'ResizeObserverController.h',
'ResponsiveImageSelector.h',
'SameProcessMessageQueue.h',
'ScreenOrientation.h',
@ -352,6 +353,7 @@ SOURCES += [
'PostMessageEvent.cpp',
'ProcessGlobal.cpp',
'ResizeObserver.cpp',
'ResizeObserverController.cpp',
'ResponsiveImageSelector.cpp',
'SameProcessMessageQueue.cpp',
'ScreenOrientation.cpp',