зеркало из https://github.com/mozilla/gecko-dev.git
239 строки
8.0 KiB
C++
239 строки
8.0 KiB
C++
/* -*- 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/BrowsingContext.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "mozilla/dom/ErrorEvent.h"
|
|
#include "mozilla/dom/RootedDictionary.h"
|
|
#include "mozilla/PresShell.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "nsPresContext.h"
|
|
#include "nsRefreshDriver.h"
|
|
#include <limits>
|
|
|
|
namespace mozilla::dom {
|
|
|
|
void ResizeObserverNotificationHelper::WillRefresh(TimeStamp aTime) {
|
|
MOZ_DIAGNOSTIC_ASSERT(mOwner, "Should've de-registered on-time!");
|
|
mOwner->Notify();
|
|
// Note that mOwner may be null / dead here.
|
|
}
|
|
|
|
nsRefreshDriver* ResizeObserverNotificationHelper::GetRefreshDriver() const {
|
|
PresShell* presShell = mOwner->GetPresShell();
|
|
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, FlushType::Display, "ResizeObserver");
|
|
mRegistered = true;
|
|
}
|
|
|
|
void ResizeObserverNotificationHelper::Unregister() {
|
|
if (!mRegistered) {
|
|
return;
|
|
}
|
|
|
|
nsRefreshDriver* refreshDriver = GetRefreshDriver();
|
|
MOZ_RELEASE_ASSERT(
|
|
refreshDriver,
|
|
"We should not leave a dangling reference to the observer around");
|
|
|
|
bool rv = refreshDriver->RemoveRefreshObserver(this, FlushType::Display);
|
|
MOZ_DIAGNOSTIC_ASSERT(rv, "Should remove the observer successfully");
|
|
Unused << rv;
|
|
|
|
mRegistered = false;
|
|
}
|
|
|
|
ResizeObserverNotificationHelper::~ResizeObserverNotificationHelper() {
|
|
MOZ_RELEASE_ASSERT(!mRegistered, "How can we die when registered?");
|
|
MOZ_RELEASE_ASSERT(!mOwner, "Forgot to clear weak pointer?");
|
|
}
|
|
|
|
void ResizeObserverController::ShellDetachedFromDocument() {
|
|
mResizeObserverNotificationHelper->Unregister();
|
|
}
|
|
|
|
static void FlushLayoutForWholeBrowsingContextTree(Document& aDoc) {
|
|
if (BrowsingContext* bc = aDoc.GetBrowsingContext()) {
|
|
RefPtr<BrowsingContext> top = bc->Top();
|
|
top->PreOrderWalk([](BrowsingContext* aCur) {
|
|
if (Document* doc = aCur->GetExtantDocument()) {
|
|
doc->FlushPendingNotifications(FlushType::Layout);
|
|
}
|
|
});
|
|
} else {
|
|
// If there is no browsing context, we just flush this document itself.
|
|
aDoc.FlushPendingNotifications(FlushType::Layout);
|
|
}
|
|
}
|
|
|
|
void ResizeObserverController::Notify() {
|
|
mResizeObserverNotificationHelper->Unregister();
|
|
if (mResizeObservers.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// We may call BroadcastAllActiveObservations(), which might cause mDocument
|
|
// to be destroyed (and the ResizeObserverController with it).
|
|
// e.g. if mDocument is in an iframe, and the observer JS removes it from the
|
|
// parent document and we trigger an unlucky GC/CC (or maybe if the observer
|
|
// JS navigates to a different URL). Or the author uses elem.offsetTop API,
|
|
// which could flush style + layout and make the document destroyed if we're
|
|
// inside an iframe that has suddenly become |display:none| via the author
|
|
// doing something in their ResizeObserver callback.
|
|
//
|
|
// Therefore, we ref-count mDocument here to make sure it and its members
|
|
// (e.g. mResizeObserverController, which is `this` pointer) are still alive
|
|
// in the rest of this function because after it goes up, `this` is possible
|
|
// deleted.
|
|
RefPtr<Document> doc(mDocument);
|
|
|
|
uint32_t shallowestTargetDepth = 0;
|
|
while (true) {
|
|
// Flush layout, so that any callback functions' style changes / resizes
|
|
// get a chance to take effect. The callback functions may do changes in its
|
|
// sub-documents or ancestors, so flushing layout for the whole browsing
|
|
// context tree makes sure we don't miss anyone.
|
|
FlushLayoutForWholeBrowsingContextTree(*doc);
|
|
|
|
// 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);
|
|
|
|
if (!HasAnyActiveObservations()) {
|
|
break;
|
|
}
|
|
|
|
DebugOnly<uint32_t> oldShallowestTargetDepth = shallowestTargetDepth;
|
|
shallowestTargetDepth = BroadcastAllActiveObservations();
|
|
NS_ASSERTION(oldShallowestTargetDepth < shallowestTargetDepth,
|
|
"shallowestTargetDepth should be getting strictly deeper");
|
|
}
|
|
|
|
if (HasAnySkippedObservations()) {
|
|
// Per spec, we deliver an error if the document has any skipped
|
|
// observations. Also, we re-register via ScheduleNotification().
|
|
RootedDictionary<ErrorEventInit> init(RootingCx());
|
|
|
|
init.mMessage.AssignLiteral(
|
|
"ResizeObserver loop completed with undelivered notifications.");
|
|
init.mBubbles = false;
|
|
init.mCancelable = false;
|
|
|
|
nsEventStatus status = nsEventStatus_eIgnore;
|
|
|
|
if (nsCOMPtr<nsPIDOMWindowInner> window = doc->GetInnerWindow()) {
|
|
nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(window);
|
|
MOZ_ASSERT(sgo);
|
|
|
|
if (NS_WARN_IF(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 (ResizeObserver* observer : mResizeObservers) {
|
|
observer->GatherActiveObservations(aDepth);
|
|
}
|
|
}
|
|
|
|
uint32_t ResizeObserverController::BroadcastAllActiveObservations() {
|
|
uint32_t shallowestTargetDepth = std::numeric_limits<uint32_t>::max();
|
|
|
|
// Copy the observers as this invokes the callbacks and could register and
|
|
// unregister observers at will.
|
|
const auto observers =
|
|
ToTArray<nsTArray<RefPtr<ResizeObserver>>>(mResizeObservers);
|
|
for (auto& observer : observers) {
|
|
// MOZ_KnownLive because 'observers' is guaranteed to keep it
|
|
// alive.
|
|
//
|
|
// This can go away once
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
|
|
uint32_t targetDepth =
|
|
MOZ_KnownLive(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();
|
|
}
|
|
|
|
ResizeObserverController::~ResizeObserverController() {
|
|
MOZ_RELEASE_ASSERT(
|
|
!mResizeObserverNotificationHelper->IsRegistered(),
|
|
"Nothing else should keep a reference to our helper when we go away");
|
|
mResizeObserverNotificationHelper->DetachFromOwner();
|
|
}
|
|
|
|
void ResizeObserverController::AddSizeOfIncludingThis(
|
|
nsWindowSizes& aSizes) const {
|
|
MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf;
|
|
size_t size = mallocSizeOf(this);
|
|
size += mResizeObservers.ShallowSizeOfExcludingThis(mallocSizeOf);
|
|
// TODO(emilio): Measure the observers individually or something? They aren't
|
|
// really owned by us.
|
|
aSizes.mDOMSizes.mDOMResizeObserverControllerSize += size;
|
|
}
|
|
|
|
} // namespace mozilla::dom
|