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/. */
6 
7 #include "VisualViewport.h"
8 
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"
17 
18 static mozilla::LazyLogModule sVvpLog("visualviewport");
19 #define VVP_LOG(...) MOZ_LOG(sVvpLog, LogLevel::Debug, (__VA_ARGS__))
20 
21 using namespace mozilla;
22 using namespace mozilla::dom;
23 
VisualViewport(nsPIDOMWindowInner * aWindow)24 VisualViewport::VisualViewport(nsPIDOMWindowInner* aWindow)
25     : DOMEventTargetHelper(aWindow) {}
26 
~VisualViewport()27 VisualViewport::~VisualViewport() {
28   if (mResizeEvent) {
29     mResizeEvent->Revoke();
30   }
31 
32   if (mScrollEvent) {
33     mScrollEvent->Revoke();
34   }
35 }
36 
37 /* virtual */
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)38 JSObject* VisualViewport::WrapObject(JSContext* aCx,
39                                      JS::Handle<JSObject*> aGivenProto) {
40   return VisualViewport_Binding::Wrap(aCx, this, aGivenProto);
41 }
42 
43 /* virtual */
GetEventTargetParent(EventChainPreVisitor & aVisitor)44 void VisualViewport::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
45   EventMessage msg = aVisitor.mEvent->mMessage;
46 
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()) {
54         parentTarget = doc;
55       }
56     }
57   }
58   aVisitor.SetParentTarget(parentTarget, false);
59 }
60 
VisualViewportSize() const61 CSSSize VisualViewport::VisualViewportSize() const {
62   CSSSize size = CSSSize(0, 0);
63 
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);
69   }
70 
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());
80     } else {
81       nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
82       if (sf) {
83         size = CSSRect::FromAppUnits(sf->GetScrollPortRect().Size());
84       }
85     }
86   }
87   return size;
88 }
89 
Width() const90 double VisualViewport::Width() const {
91   CSSSize size = VisualViewportSize();
92   return size.width;
93 }
94 
Height() const95 double VisualViewport::Height() const {
96   CSSSize size = VisualViewportSize();
97   return size.height;
98 }
99 
Scale() const100 double VisualViewport::Scale() const {
101   double scale = 1;
102   if (PresShell* presShell = GetPresShell()) {
103     scale = presShell->GetResolution();
104   }
105   return scale;
106 }
107 
VisualViewportOffset() const108 CSSPoint VisualViewport::VisualViewportOffset() const {
109   CSSPoint offset = CSSPoint(0, 0);
110 
111   if (PresShell* presShell = GetPresShell()) {
112     offset = CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset());
113   }
114   return offset;
115 }
116 
LayoutViewportOffset() const117 CSSPoint VisualViewport::LayoutViewportOffset() const {
118   CSSPoint offset = CSSPoint(0, 0);
119 
120   if (PresShell* presShell = GetPresShell()) {
121     offset = CSSPoint::FromAppUnits(presShell->GetLayoutViewportOffset());
122   }
123   return offset;
124 }
125 
PageLeft() const126 double VisualViewport::PageLeft() const { return VisualViewportOffset().X(); }
127 
PageTop() const128 double VisualViewport::PageTop() const { return VisualViewportOffset().Y(); }
129 
OffsetLeft() const130 double VisualViewport::OffsetLeft() const {
131   return PageLeft() - LayoutViewportOffset().X();
132 }
133 
OffsetTop() const134 double VisualViewport::OffsetTop() const {
135   return PageTop() - LayoutViewportOffset().Y();
136 }
137 
GetDocument() const138 Document* VisualViewport::GetDocument() const {
139   nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
140   if (!window) {
141     return nullptr;
142   }
143 
144   nsIDocShell* docShell = window->GetDocShell();
145   if (!docShell) {
146     return nullptr;
147   }
148 
149   return docShell->GetDocument();
150 }
151 
GetPresShell() const152 PresShell* VisualViewport::GetPresShell() const {
153   RefPtr<Document> document = GetDocument();
154   return document ? document->GetPresShell() : nullptr;
155 }
156 
GetPresContext() const157 nsPresContext* VisualViewport::GetPresContext() const {
158   RefPtr<Document> document = GetDocument();
159   return document ? document->GetPresContext() : nullptr;
160 }
161 
162 /* ================= Resize event handling ================= */
163 
PostResizeEvent()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)) {
168     return;
169   }
170   if (mResizeEvent) {
171     // prescontext changed, so discard the old resize event and queue a new one
172     mResizeEvent->Revoke();
173     mResizeEvent = nullptr;
174   }
175 
176   // The event constructor will register itself with the refresh driver.
177   if (presContext) {
178     mResizeEvent = new VisualViewportResizeEvent(this, presContext);
179     VVP_LOG("%p: PostResizeEvent, created new event\n", this);
180   }
181 }
182 
VisualViewportResizeEvent(VisualViewport * aViewport,nsPresContext * aPresContext)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);
191 }
192 
HasPresContext(nsPresContext * aContext) const193 bool VisualViewport::VisualViewportResizeEvent::HasPresContext(
194     nsPresContext* aContext) const {
195   return mPresContext.get() == aContext;
196 }
197 
Revoke()198 void VisualViewport::VisualViewportResizeEvent::Revoke() {
199   mViewport = nullptr;
200   mPresContext = nullptr;
201 }
202 
203 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
204 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
Run()205 VisualViewport::VisualViewportResizeEvent::Run() {
206   if (RefPtr<VisualViewport> viewport = mViewport) {
207     viewport->FireResizeEvent();
208   }
209   return NS_OK;
210 }
211 
FireResizeEvent()212 void VisualViewport::FireResizeEvent() {
213   MOZ_ASSERT(mResizeEvent);
214   mResizeEvent->Revoke();
215   mResizeEvent = nullptr;
216 
217   RefPtr<nsPresContext> presContext = GetPresContext();
218 
219   VVP_LOG("%p, FireResizeEvent, fire mozvisualresize\n", this);
220   WidgetEvent mozEvent(true, eMozVisualResize);
221   mozEvent.mFlags.mOnlySystemGroupDispatch = true;
222   EventDispatcher::Dispatch(this, presContext, &mozEvent);
223 
224   VVP_LOG("%p, FireResizeEvent, fire VisualViewport resize\n", this);
225   WidgetEvent event(true, eResize);
226   event.mFlags.mBubbles = false;
227   event.mFlags.mCancelable = false;
228   EventDispatcher::Dispatch(this, presContext, &event);
229 }
230 
231 /* ================= Scroll event handling ================= */
232 
PostScrollEvent(const nsPoint & aPrevVisualOffset,const nsPoint & aPrevLayoutOffset)233 void VisualViewport::PostScrollEvent(const nsPoint& aPrevVisualOffset,
234                                      const nsPoint& aPrevLayoutOffset) {
235   VVP_LOG("%p: PostScrollEvent, prevRelativeOffset=%s (pre-existing: %d)\n",
236           this, ToString(aPrevVisualOffset - aPrevLayoutOffset).c_str(),
237           !!mScrollEvent);
238   nsPresContext* presContext = GetPresContext();
239   if (mScrollEvent && mScrollEvent->HasPresContext(presContext)) {
240     return;
241   }
242 
243   if (mScrollEvent) {
244     // prescontext changed, so discard the old scroll event and queue a new one
245     mScrollEvent->Revoke();
246     mScrollEvent = nullptr;
247   }
248 
249   // The event constructor will register itself with the refresh driver.
250   if (presContext) {
251     mScrollEvent = new VisualViewportScrollEvent(
252         this, presContext, aPrevVisualOffset, aPrevLayoutOffset);
253     VVP_LOG("%p: PostScrollEvent, created new event\n", this);
254   }
255 }
256 
VisualViewportScrollEvent(VisualViewport * aViewport,nsPresContext * aPresContext,const nsPoint & aPrevVisualOffset,const nsPoint & aPrevLayoutOffset)257 VisualViewport::VisualViewportScrollEvent::VisualViewportScrollEvent(
258     VisualViewport* aViewport, nsPresContext* aPresContext,
259     const nsPoint& aPrevVisualOffset, const nsPoint& aPrevLayoutOffset)
260     : Runnable("VisualViewport::VisualViewportScrollEvent"),
261       mViewport(aViewport),
262       mPresContext(aPresContext),
263       mPrevVisualOffset(aPrevVisualOffset),
264       mPrevLayoutOffset(aPrevLayoutOffset) {
265   VVP_LOG("%p: Registering PostScroll on %p %p\n", aViewport, aPresContext,
266           aPresContext->RefreshDriver());
267   aPresContext->RefreshDriver()->PostVisualViewportScrollEvent(this);
268 }
269 
HasPresContext(nsPresContext * aContext) const270 bool VisualViewport::VisualViewportScrollEvent::HasPresContext(
271     nsPresContext* aContext) const {
272   return mPresContext.get() == aContext;
273 }
274 
Revoke()275 void VisualViewport::VisualViewportScrollEvent::Revoke() {
276   mViewport = nullptr;
277   mPresContext = nullptr;
278 }
279 
280 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
281 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
Run()282 VisualViewport::VisualViewportScrollEvent::Run() {
283   if (RefPtr<VisualViewport> viewport = mViewport) {
284     viewport->FireScrollEvent();
285   }
286   return NS_OK;
287 }
288 
FireScrollEvent()289 void VisualViewport::FireScrollEvent() {
290   MOZ_ASSERT(mScrollEvent);
291   nsPoint prevVisualOffset = mScrollEvent->PrevVisualOffset();
292   nsPoint prevLayoutOffset = mScrollEvent->PrevLayoutOffset();
293   mScrollEvent->Revoke();
294   mScrollEvent = nullptr;
295 
296   if (RefPtr<PresShell> presShell = GetPresShell()) {
297     RefPtr<nsPresContext> presContext = GetPresContext();
298 
299     if (presShell->GetVisualViewportOffset() != prevVisualOffset) {
300       // The internal event will be fired whenever the visual viewport's
301       // *absolute* offset changed, i.e. relative to the page.
302       VVP_LOG("%p: FireScrollEvent, fire mozvisualscroll\n", this);
303       WidgetEvent mozEvent(true, eMozVisualScroll);
304       mozEvent.mFlags.mOnlySystemGroupDispatch = true;
305       EventDispatcher::Dispatch(this, presContext, &mozEvent);
306     }
307 
308     // Check whether the relative visual viewport offset actually changed -
309     // maybe both visual and layout viewport scrolled together and there was no
310     // change after all.
311     nsPoint curRelativeOffset =
312         presShell->GetVisualViewportOffsetRelativeToLayoutViewport();
313     nsPoint prevRelativeOffset = prevVisualOffset - prevLayoutOffset;
314     VVP_LOG(
315         "%p: FireScrollEvent, curRelativeOffset %s, "
316         "prevRelativeOffset %s\n",
317         this, ToString(curRelativeOffset).c_str(),
318         ToString(prevRelativeOffset).c_str());
319     if (curRelativeOffset != prevRelativeOffset) {
320       VVP_LOG("%p, FireScrollEvent, fire VisualViewport scroll\n", this);
321       WidgetGUIEvent event(true, eScroll, nullptr);
322       event.mFlags.mBubbles = false;
323       event.mFlags.mCancelable = false;
324       EventDispatcher::Dispatch(this, presContext, &event);
325     }
326   }
327 }
328