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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "VisualViewport.h"
9 #include "mozilla/EventDispatcher.h"
10 #include "mozilla/PresShell.h"
11 #include "mozilla/ToString.h"
12 #include "nsIScrollableFrame.h"
13 #include "nsIDocShell.h"
14 #include "nsPresContext.h"
15 #include "nsRefreshDriver.h"
16 #include "DocumentInlines.h"
18 static mozilla::LazyLogModule
sVvpLog("visualviewport");
19 #define VVP_LOG(...) MOZ_LOG(sVvpLog, LogLevel::Debug, (__VA_ARGS__))
21 using namespace mozilla
;
22 using namespace mozilla::dom
;
24 VisualViewport::VisualViewport(nsPIDOMWindowInner
* aWindow
)
25 : DOMEventTargetHelper(aWindow
) {}
27 VisualViewport::~VisualViewport() {
29 mResizeEvent
->Revoke();
33 mScrollEvent
->Revoke();
38 JSObject
* VisualViewport::WrapObject(JSContext
* aCx
,
39 JS::Handle
<JSObject
*> aGivenProto
) {
40 return VisualViewport_Binding::Wrap(aCx
, this, aGivenProto
);
44 void VisualViewport::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
45 EventMessage msg
= aVisitor
.mEvent
->mMessage
;
47 aVisitor
.mCanHandle
= true;
48 EventTarget
* parentTarget
= nullptr;
49 // Only our special internal events are allowed to escape the
50 // Visual Viewport and be dispatched further up the DOM tree.
51 if (msg
== eMozVisualScroll
|| msg
== eMozVisualResize
) {
52 if (nsPIDOMWindowInner
* win
= GetOwner()) {
53 if (Document
* doc
= win
->GetExtantDoc()) {
58 aVisitor
.SetParentTarget(parentTarget
, false);
61 CSSSize
VisualViewport::VisualViewportSize() const {
62 CSSSize size
= CSSSize(0, 0);
64 // Flush layout, as that may affect the answer below (e.g. scrollbars
65 // may have appeared, decreasing the available viewport size).
66 RefPtr
<const VisualViewport
> kungFuDeathGrip(this);
67 if (Document
* doc
= GetDocument()) {
68 doc
->FlushPendingNotifications(FlushType::Layout
);
71 // Fetch the pres shell after the layout flush, as it might have destroyed it.
72 if (PresShell
* presShell
= GetPresShell()) {
73 if (presShell
->IsVisualViewportSizeSet()) {
74 DynamicToolbarState state
= presShell
->GetDynamicToolbarState();
75 size
= CSSRect::FromAppUnits(
76 (state
== DynamicToolbarState::InTransition
||
77 state
== DynamicToolbarState::Collapsed
)
78 ? presShell
->GetVisualViewportSizeUpdatedByDynamicToolbar()
79 : presShell
->GetVisualViewportSize());
81 nsIScrollableFrame
* sf
= presShell
->GetRootScrollFrameAsScrollable();
83 size
= CSSRect::FromAppUnits(sf
->GetScrollPortRect().Size());
90 double VisualViewport::Width() const {
91 CSSSize size
= VisualViewportSize();
95 double VisualViewport::Height() const {
96 CSSSize size
= VisualViewportSize();
100 double VisualViewport::Scale() const {
102 if (PresShell
* presShell
= GetPresShell()) {
103 scale
= presShell
->GetResolution();
108 CSSPoint
VisualViewport::VisualViewportOffset() const {
109 CSSPoint offset
= CSSPoint(0, 0);
111 if (PresShell
* presShell
= GetPresShell()) {
112 offset
= CSSPoint::FromAppUnits(presShell
->GetVisualViewportOffset());
117 CSSPoint
VisualViewport::LayoutViewportOffset() const {
118 CSSPoint offset
= CSSPoint(0, 0);
120 if (PresShell
* presShell
= GetPresShell()) {
121 offset
= CSSPoint::FromAppUnits(presShell
->GetLayoutViewportOffset());
126 double VisualViewport::PageLeft() const { return VisualViewportOffset().X(); }
128 double VisualViewport::PageTop() const { return VisualViewportOffset().Y(); }
130 double VisualViewport::OffsetLeft() const {
131 return PageLeft() - LayoutViewportOffset().X();
134 double VisualViewport::OffsetTop() const {
135 return PageTop() - LayoutViewportOffset().Y();
138 Document
* VisualViewport::GetDocument() const {
139 nsCOMPtr
<nsPIDOMWindowInner
> window
= GetOwner();
144 nsIDocShell
* docShell
= window
->GetDocShell();
149 return docShell
->GetDocument();
152 PresShell
* VisualViewport::GetPresShell() const {
153 RefPtr
<Document
> document
= GetDocument();
154 return document
? document
->GetPresShell() : nullptr;
157 nsPresContext
* VisualViewport::GetPresContext() const {
158 RefPtr
<Document
> document
= GetDocument();
159 return document
? document
->GetPresContext() : nullptr;
162 /* ================= Resize event handling ================= */
164 void VisualViewport::PostResizeEvent() {
165 VVP_LOG("%p: PostResizeEvent (pre-existing: %d)\n", this, !!mResizeEvent
);
166 nsPresContext
* presContext
= GetPresContext();
167 if (mResizeEvent
&& mResizeEvent
->HasPresContext(presContext
)) {
171 // prescontext changed, so discard the old resize event and queue a new one
172 mResizeEvent
->Revoke();
173 mResizeEvent
= nullptr;
176 // The event constructor will register itself with the refresh driver.
178 mResizeEvent
= new VisualViewportResizeEvent(this, presContext
);
179 VVP_LOG("%p: PostResizeEvent, created new event\n", this);
183 VisualViewport::VisualViewportResizeEvent::VisualViewportResizeEvent(
184 VisualViewport
* aViewport
, nsPresContext
* aPresContext
)
185 : Runnable("VisualViewport::VisualViewportResizeEvent"),
186 mViewport(aViewport
),
187 mPresContext(aPresContext
) {
188 VVP_LOG("%p: Registering PostResize on %p %p\n", aViewport
, aPresContext
,
189 aPresContext
->RefreshDriver());
190 aPresContext
->RefreshDriver()->PostVisualViewportResizeEvent(this);
193 bool VisualViewport::VisualViewportResizeEvent::HasPresContext(
194 nsPresContext
* aContext
) const {
195 return mPresContext
.get() == aContext
;
198 void VisualViewport::VisualViewportResizeEvent::Revoke() {
200 mPresContext
= nullptr;
204 VisualViewport::VisualViewportResizeEvent::Run() {
206 mViewport
->FireResizeEvent();
211 void VisualViewport::FireResizeEvent() {
212 MOZ_ASSERT(mResizeEvent
);
213 mResizeEvent
->Revoke();
214 mResizeEvent
= nullptr;
216 VVP_LOG("%p, FireResizeEvent, fire mozvisualresize\n", this);
217 WidgetEvent
mozEvent(true, eMozVisualResize
);
218 mozEvent
.mFlags
.mOnlySystemGroupDispatch
= true;
219 EventDispatcher::Dispatch(this, GetPresContext(), &mozEvent
);
221 VVP_LOG("%p, FireResizeEvent, fire VisualViewport resize\n", this);
222 WidgetEvent
event(true, eResize
);
223 event
.mFlags
.mBubbles
= false;
224 event
.mFlags
.mCancelable
= false;
225 EventDispatcher::Dispatch(this, GetPresContext(), &event
);
228 /* ================= Scroll event handling ================= */
230 void VisualViewport::PostScrollEvent(const nsPoint
& aPrevVisualOffset
,
231 const nsPoint
& aPrevLayoutOffset
) {
232 VVP_LOG("%p: PostScrollEvent, prevRelativeOffset=%s (pre-existing: %d)\n",
233 this, ToString(aPrevVisualOffset
- aPrevLayoutOffset
).c_str(),
235 nsPresContext
* presContext
= GetPresContext();
236 if (mScrollEvent
&& mScrollEvent
->HasPresContext(presContext
)) {
241 // prescontext changed, so discard the old scroll event and queue a new one
242 mScrollEvent
->Revoke();
243 mScrollEvent
= nullptr;
246 // The event constructor will register itself with the refresh driver.
248 mScrollEvent
= new VisualViewportScrollEvent(
249 this, presContext
, aPrevVisualOffset
, aPrevLayoutOffset
);
250 VVP_LOG("%p: PostScrollEvent, created new event\n", this);
254 VisualViewport::VisualViewportScrollEvent::VisualViewportScrollEvent(
255 VisualViewport
* aViewport
, nsPresContext
* aPresContext
,
256 const nsPoint
& aPrevVisualOffset
, const nsPoint
& aPrevLayoutOffset
)
257 : Runnable("VisualViewport::VisualViewportScrollEvent"),
258 mViewport(aViewport
),
259 mPresContext(aPresContext
),
260 mPrevVisualOffset(aPrevVisualOffset
),
261 mPrevLayoutOffset(aPrevLayoutOffset
) {
262 VVP_LOG("%p: Registering PostScroll on %p %p\n", aViewport
, aPresContext
,
263 aPresContext
->RefreshDriver());
264 aPresContext
->RefreshDriver()->PostVisualViewportScrollEvent(this);
267 bool VisualViewport::VisualViewportScrollEvent::HasPresContext(
268 nsPresContext
* aContext
) const {
269 return mPresContext
.get() == aContext
;
272 void VisualViewport::VisualViewportScrollEvent::Revoke() {
274 mPresContext
= nullptr;
278 VisualViewport::VisualViewportScrollEvent::Run() {
280 mViewport
->FireScrollEvent();
285 void VisualViewport::FireScrollEvent() {
286 MOZ_ASSERT(mScrollEvent
);
287 nsPoint prevVisualOffset
= mScrollEvent
->PrevVisualOffset();
288 nsPoint prevLayoutOffset
= mScrollEvent
->PrevLayoutOffset();
289 mScrollEvent
->Revoke();
290 mScrollEvent
= nullptr;
292 if (PresShell
* presShell
= GetPresShell()) {
293 if (presShell
->GetVisualViewportOffset() != prevVisualOffset
) {
294 // The internal event will be fired whenever the visual viewport's
295 // *absolute* offset changed, i.e. relative to the page.
296 VVP_LOG("%p: FireScrollEvent, fire mozvisualscroll\n", this);
297 WidgetEvent
mozEvent(true, eMozVisualScroll
);
298 mozEvent
.mFlags
.mOnlySystemGroupDispatch
= true;
299 EventDispatcher::Dispatch(this, GetPresContext(), &mozEvent
);
302 // Check whether the relative visual viewport offset actually changed -
303 // maybe both visual and layout viewport scrolled together and there was no
305 nsPoint curRelativeOffset
=
306 presShell
->GetVisualViewportOffsetRelativeToLayoutViewport();
307 nsPoint prevRelativeOffset
= prevVisualOffset
- prevLayoutOffset
;
309 "%p: FireScrollEvent, curRelativeOffset %s, "
310 "prevRelativeOffset %s\n",
311 this, ToString(curRelativeOffset
).c_str(),
312 ToString(prevRelativeOffset
).c_str());
313 if (curRelativeOffset
!= prevRelativeOffset
) {
314 VVP_LOG("%p, FireScrollEvent, fire VisualViewport scroll\n", this);
315 WidgetGUIEvent
event(true, eScroll
, nullptr);
316 event
.mFlags
.mBubbles
= false;
317 event
.mFlags
.mCancelable
= false;
318 EventDispatcher::Dispatch(this, GetPresContext(), &event
);