1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/ResizeObserverController.h"
9 #include "mozilla/dom/BrowsingContext.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/dom/ErrorEvent.h"
12 #include "mozilla/dom/RootedDictionary.h"
13 #include "mozilla/PresShell.h"
14 #include "mozilla/Unused.h"
15 #include "nsPresContext.h"
16 #include "nsRefreshDriver.h"
19 namespace mozilla::dom
{
21 void ResizeObserverNotificationHelper::WillRefresh(TimeStamp aTime
) {
22 MOZ_DIAGNOSTIC_ASSERT(mOwner
, "Should've de-registered on-time!");
24 // Note that mOwner may be null / dead here.
27 nsRefreshDriver
* ResizeObserverNotificationHelper::GetRefreshDriver() const {
28 PresShell
* presShell
= mOwner
->GetPresShell();
29 if (MOZ_UNLIKELY(!presShell
)) {
33 nsPresContext
* presContext
= presShell
->GetPresContext();
34 if (MOZ_UNLIKELY(!presContext
)) {
38 return presContext
->RefreshDriver();
41 void ResizeObserverNotificationHelper::Register() {
46 nsRefreshDriver
* refreshDriver
= GetRefreshDriver();
48 // We maybe navigating away from this page or currently in an iframe with
49 // display: none. Just abort the Register(), no need to do notification.
53 refreshDriver
->AddRefreshObserver(this, FlushType::Display
, "ResizeObserver");
57 void ResizeObserverNotificationHelper::Unregister() {
62 nsRefreshDriver
* refreshDriver
= GetRefreshDriver();
65 "We should not leave a dangling reference to the observer around");
67 bool rv
= refreshDriver
->RemoveRefreshObserver(this, FlushType::Display
);
68 MOZ_DIAGNOSTIC_ASSERT(rv
, "Should remove the observer successfully");
74 ResizeObserverNotificationHelper::~ResizeObserverNotificationHelper() {
75 MOZ_RELEASE_ASSERT(!mRegistered
, "How can we die when registered?");
76 MOZ_RELEASE_ASSERT(!mOwner
, "Forgot to clear weak pointer?");
79 void ResizeObserverController::ShellDetachedFromDocument() {
80 mResizeObserverNotificationHelper
->Unregister();
83 static void FlushLayoutForWholeBrowsingContextTree(Document
& aDoc
) {
84 if (BrowsingContext
* bc
= aDoc
.GetBrowsingContext()) {
85 RefPtr
<BrowsingContext
> top
= bc
->Top();
86 top
->PreOrderWalk([](BrowsingContext
* aCur
) {
87 if (Document
* doc
= aCur
->GetExtantDocument()) {
88 doc
->FlushPendingNotifications(FlushType::Layout
);
92 // If there is no browsing context, we just flush this document itself.
93 aDoc
.FlushPendingNotifications(FlushType::Layout
);
97 void ResizeObserverController::Notify() {
98 if (mResizeObservers
.IsEmpty()) {
102 // We may call BroadcastAllActiveObservations(), which might cause mDocument
103 // to be destroyed (and the ResizeObserverController with it).
104 // e.g. if mDocument is in an iframe, and the observer JS removes it from the
105 // parent document and we trigger an unlucky GC/CC (or maybe if the observer
106 // JS navigates to a different URL). Or the author uses elem.offsetTop API,
107 // which could flush style + layout and make the document destroyed if we're
108 // inside an iframe that has suddenly become |display:none| via the author
109 // doing something in their ResizeObserver callback.
111 // Therefore, we ref-count mDocument here to make sure it and its members
112 // (e.g. mResizeObserverController, which is `this` pointer) are still alive
113 // in the rest of this function because after it goes up, `this` is possible
115 RefPtr
<Document
> doc(mDocument
);
117 uint32_t shallowestTargetDepth
= 0;
119 GatherAllActiveObservations(shallowestTargetDepth
);
121 while (HasAnyActiveObservations()) {
122 DebugOnly
<uint32_t> oldShallowestTargetDepth
= shallowestTargetDepth
;
123 shallowestTargetDepth
= BroadcastAllActiveObservations();
124 NS_ASSERTION(oldShallowestTargetDepth
< shallowestTargetDepth
,
125 "shallowestTargetDepth should be getting strictly deeper");
127 // Flush layout, so that any callback functions' style changes / resizes
128 // get a chance to take effect. The callback functions may do changes in its
129 // sub-documents or ancestors, so flushing layout for the whole browsing
130 // context tree makes sure we don't miss anyone.
131 FlushLayoutForWholeBrowsingContextTree(*doc
);
133 // To avoid infinite resize loop, we only gather all active observations
134 // that have the depth of observed target element more than current
135 // shallowestTargetDepth.
136 GatherAllActiveObservations(shallowestTargetDepth
);
139 mResizeObserverNotificationHelper
->Unregister();
141 if (HasAnySkippedObservations()) {
142 // Per spec, we deliver an error if the document has any skipped
143 // observations. Also, we re-register via ScheduleNotification().
144 RootedDictionary
<ErrorEventInit
> init(RootingCx());
146 init
.mMessage
.AssignLiteral(
147 "ResizeObserver loop completed with undelivered notifications.");
148 init
.mBubbles
= false;
149 init
.mCancelable
= false;
151 nsEventStatus status
= nsEventStatus_eIgnore
;
153 if (nsCOMPtr
<nsPIDOMWindowInner
> window
= doc
->GetInnerWindow()) {
154 nsCOMPtr
<nsIScriptGlobalObject
> sgo
= do_QueryInterface(window
);
157 if (NS_WARN_IF(sgo
->HandleScriptError(init
, &status
))) {
158 status
= nsEventStatus_eIgnore
;
161 // We don't fire error events at any global for non-window JS on the main
165 // We need to deliver pending notifications in next cycle.
166 ScheduleNotification();
170 void ResizeObserverController::GatherAllActiveObservations(uint32_t aDepth
) {
171 for (ResizeObserver
* observer
: mResizeObservers
) {
172 observer
->GatherActiveObservations(aDepth
);
176 uint32_t ResizeObserverController::BroadcastAllActiveObservations() {
177 uint32_t shallowestTargetDepth
= std::numeric_limits
<uint32_t>::max();
179 // Copy the observers as this invokes the callbacks and could register and
180 // unregister observers at will.
181 const auto observers
=
182 ToTArray
<nsTArray
<RefPtr
<ResizeObserver
>>>(mResizeObservers
);
183 for (auto& observer
: observers
) {
184 // MOZ_KnownLive because 'observers' is guaranteed to keep it
187 // This can go away once
188 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
189 uint32_t targetDepth
=
190 MOZ_KnownLive(observer
)->BroadcastActiveObservations();
191 if (targetDepth
< shallowestTargetDepth
) {
192 shallowestTargetDepth
= targetDepth
;
196 return shallowestTargetDepth
;
199 bool ResizeObserverController::HasAnyActiveObservations() const {
200 for (auto& observer
: mResizeObservers
) {
201 if (observer
->HasActiveObservations()) {
208 bool ResizeObserverController::HasAnySkippedObservations() const {
209 for (auto& observer
: mResizeObservers
) {
210 if (observer
->HasSkippedObservations()) {
217 void ResizeObserverController::ScheduleNotification() {
218 mResizeObserverNotificationHelper
->Register();
221 ResizeObserverController::~ResizeObserverController() {
223 !mResizeObserverNotificationHelper
->IsRegistered(),
224 "Nothing else should keep a reference to our helper when we go away");
225 mResizeObserverNotificationHelper
->DetachFromOwner();
228 void ResizeObserverController::AddSizeOfIncludingThis(
229 nsWindowSizes
& aSizes
) const {
230 MallocSizeOf mallocSizeOf
= aSizes
.mState
.mMallocSizeOf
;
231 size_t size
= mallocSizeOf(this);
232 size
+= mResizeObservers
.ShallowSizeOfExcludingThis(mallocSizeOf
);
233 // TODO(emilio): Measure the observers individually or something? They aren't
234 // really owned by us.
235 aSizes
.mDOMSizes
.mDOMResizeObserverControllerSize
+= size
;
238 } // namespace mozilla::dom