VisualViewport.cpp (9312B)
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 "DocumentInlines.h" 10 #include "mozilla/EventDispatcher.h" 11 #include "mozilla/PresShell.h" 12 #include "mozilla/ScrollContainerFrame.h" 13 #include "mozilla/ToString.h" 14 #include "nsGlobalWindowInner.h" 15 #include "nsIDocShell.h" 16 #include "nsPresContext.h" 17 #include "nsRefreshDriver.h" 18 19 static mozilla::LazyLogModule sVvpLog("visualviewport"); 20 #define VVP_LOG(...) MOZ_LOG(sVvpLog, LogLevel::Debug, (__VA_ARGS__)) 21 22 using namespace mozilla; 23 using namespace mozilla::dom; 24 25 VisualViewport::VisualViewport(nsPIDOMWindowInner* aWindow) 26 : DOMEventTargetHelper(aWindow) {} 27 28 VisualViewport::~VisualViewport() { 29 if (mScrollEvent) { 30 mScrollEvent->Revoke(); 31 } 32 } 33 34 /* virtual */ 35 JSObject* VisualViewport::WrapObject(JSContext* aCx, 36 JS::Handle<JSObject*> aGivenProto) { 37 return VisualViewport_Binding::Wrap(aCx, this, aGivenProto); 38 } 39 40 /* virtual */ 41 void VisualViewport::GetEventTargetParent(EventChainPreVisitor& aVisitor) { 42 EventMessage msg = aVisitor.mEvent->mMessage; 43 44 aVisitor.mCanHandle = true; 45 EventTarget* parentTarget = nullptr; 46 // Only our special internal events are allowed to escape the 47 // Visual Viewport and be dispatched further up the DOM tree. 48 if (msg == eMozVisualScroll || msg == eMozVisualResize) { 49 if (nsPIDOMWindowInner* win = GetOwnerWindow()) { 50 if (Document* doc = win->GetExtantDoc()) { 51 parentTarget = doc; 52 } 53 } 54 } 55 aVisitor.SetParentTarget(parentTarget, false); 56 } 57 58 CSSSize VisualViewport::VisualViewportSize() const { 59 CSSSize size = CSSSize(0, 0); 60 61 // Flush layout, as that may affect the answer below (e.g. scrollbars 62 // may have appeared, decreasing the available viewport size). 63 RefPtr<const VisualViewport> kungFuDeathGrip(this); 64 if (Document* doc = GetDocument()) { 65 doc->FlushPendingNotifications(FlushType::Layout); 66 } 67 68 // Fetch the pres shell after the layout flush, as it might have destroyed it. 69 if (PresShell* presShell = GetPresShell()) { 70 if (presShell->IsVisualViewportSizeSet()) { 71 DynamicToolbarState state = presShell->GetDynamicToolbarState(); 72 size = CSSRect::FromAppUnits( 73 (state == DynamicToolbarState::InTransition || 74 state == DynamicToolbarState::Collapsed) 75 ? presShell->GetVisualViewportSizeUpdatedByDynamicToolbar() 76 : presShell->GetVisualViewportSize()); 77 } else { 78 ScrollContainerFrame* sf = presShell->GetRootScrollContainerFrame(); 79 if (sf) { 80 size = CSSRect::FromAppUnits(sf->GetScrollPortRect().Size()); 81 } 82 } 83 } 84 return size; 85 } 86 87 double VisualViewport::Width() const { 88 CSSSize size = VisualViewportSize(); 89 return size.width; 90 } 91 92 double VisualViewport::Height() const { 93 CSSSize size = VisualViewportSize(); 94 return size.height; 95 } 96 97 double VisualViewport::Scale() const { 98 double scale = 1; 99 if (PresShell* presShell = GetPresShell()) { 100 scale = presShell->GetResolution(); 101 } 102 return scale; 103 } 104 105 CSSPoint VisualViewport::VisualViewportOffset() const { 106 CSSPoint offset = CSSPoint(0, 0); 107 108 if (PresShell* presShell = GetPresShell()) { 109 offset = CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset()); 110 } 111 return offset; 112 } 113 114 CSSPoint VisualViewport::LayoutViewportOffset() const { 115 CSSPoint offset = CSSPoint(0, 0); 116 117 if (PresShell* presShell = GetPresShell()) { 118 offset = CSSPoint::FromAppUnits(presShell->GetLayoutViewportOffset()); 119 } 120 return offset; 121 } 122 123 double VisualViewport::PageLeft() const { return VisualViewportOffset().X(); } 124 125 double VisualViewport::PageTop() const { return VisualViewportOffset().Y(); } 126 127 double VisualViewport::OffsetLeft() const { 128 return PageLeft() - LayoutViewportOffset().X(); 129 } 130 131 double VisualViewport::OffsetTop() const { 132 return PageTop() - LayoutViewportOffset().Y(); 133 } 134 135 Document* VisualViewport::GetDocument() const { 136 nsCOMPtr<nsPIDOMWindowInner> window = GetOwnerWindow(); 137 if (!window) { 138 return nullptr; 139 } 140 141 nsIDocShell* docShell = window->GetDocShell(); 142 if (!docShell) { 143 return nullptr; 144 } 145 146 return docShell->GetDocument(); 147 } 148 149 PresShell* VisualViewport::GetPresShell() const { 150 RefPtr<Document> document = GetDocument(); 151 return document ? document->GetPresShell() : nullptr; 152 } 153 154 nsPresContext* VisualViewport::GetPresContext() const { 155 RefPtr<Document> document = GetDocument(); 156 return document ? document->GetPresContext() : nullptr; 157 } 158 159 /* ================= Resize event handling ================= */ 160 161 void VisualViewport::PostResizeEvent() { 162 VVP_LOG("%p: PostResizeEvent", this); 163 if (PresShell* ps = GetPresShell()) { 164 ps->ScheduleResizeEventIfNeeded(PresShell::ResizeEventKind::Visual); 165 } 166 } 167 168 void VisualViewport::FireResizeEvent() { 169 RefPtr<nsPresContext> presContext = GetPresContext(); 170 171 VVP_LOG("%p, FireResizeEvent, fire mozvisualresize\n", this); 172 WidgetEvent mozEvent(true, eMozVisualResize); 173 mozEvent.mFlags.mOnlySystemGroupDispatch = true; 174 EventDispatcher::Dispatch(this, presContext, &mozEvent); 175 176 VVP_LOG("%p, FireResizeEvent, fire VisualViewport resize\n", this); 177 WidgetEvent event(true, eResize); 178 event.mFlags.mBubbles = false; 179 event.mFlags.mCancelable = false; 180 EventDispatcher::Dispatch(this, presContext, &event); 181 } 182 183 /* ================= Scroll event handling ================= */ 184 185 void VisualViewport::PostScrollEvent(const nsPoint& aPrevVisualOffset, 186 const nsPoint& aPrevLayoutOffset) { 187 VVP_LOG("%p: PostScrollEvent, prevRelativeOffset=%s (pre-existing: %d)\n", 188 this, ToString(aPrevVisualOffset - aPrevLayoutOffset).c_str(), 189 !!mScrollEvent); 190 nsPresContext* presContext = GetPresContext(); 191 if (mScrollEvent && mScrollEvent->HasPresContext(presContext)) { 192 return; 193 } 194 195 if (mScrollEvent) { 196 // prescontext changed, so discard the old scroll event and queue a new one 197 mScrollEvent->Revoke(); 198 mScrollEvent = nullptr; 199 } 200 201 // The event constructor will register itself with the refresh driver. 202 if (presContext) { 203 mScrollEvent = new VisualViewportScrollEvent( 204 this, presContext, aPrevVisualOffset, aPrevLayoutOffset); 205 VVP_LOG("%p: PostScrollEvent, created new event\n", this); 206 } 207 } 208 209 VisualViewport::VisualViewportScrollEvent::VisualViewportScrollEvent( 210 VisualViewport* aViewport, nsPresContext* aPresContext, 211 const nsPoint& aPrevVisualOffset, const nsPoint& aPrevLayoutOffset) 212 : Runnable("VisualViewport::VisualViewportScrollEvent"), 213 mViewport(aViewport), 214 mPresContext(aPresContext), 215 mPrevVisualOffset(aPrevVisualOffset), 216 mPrevLayoutOffset(aPrevLayoutOffset) { 217 VVP_LOG("%p: Registering PostScroll on %p %p\n", aViewport, aPresContext, 218 aPresContext->RefreshDriver()); 219 aPresContext->PresShell()->PostScrollEvent(this); 220 } 221 222 bool VisualViewport::VisualViewportScrollEvent::HasPresContext( 223 nsPresContext* aContext) const { 224 return mPresContext.get() == aContext; 225 } 226 227 void VisualViewport::VisualViewportScrollEvent::Revoke() { 228 mViewport = nullptr; 229 mPresContext = nullptr; 230 } 231 232 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398) 233 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP 234 VisualViewport::VisualViewportScrollEvent::Run() { 235 if (RefPtr<VisualViewport> viewport = mViewport) { 236 viewport->FireScrollEvent(); 237 } 238 return NS_OK; 239 } 240 241 void VisualViewport::FireScrollEvent() { 242 MOZ_ASSERT(mScrollEvent); 243 nsPoint prevVisualOffset = mScrollEvent->PrevVisualOffset(); 244 nsPoint prevLayoutOffset = mScrollEvent->PrevLayoutOffset(); 245 mScrollEvent->Revoke(); 246 mScrollEvent = nullptr; 247 248 if (RefPtr<PresShell> presShell = GetPresShell()) { 249 RefPtr<nsPresContext> presContext = GetPresContext(); 250 251 if (presShell->GetVisualViewportOffset() != prevVisualOffset) { 252 // The internal event will be fired whenever the visual viewport's 253 // *absolute* offset changed, i.e. relative to the page. 254 VVP_LOG("%p: FireScrollEvent, fire mozvisualscroll\n", this); 255 WidgetEvent mozEvent(true, eMozVisualScroll); 256 mozEvent.mFlags.mOnlySystemGroupDispatch = true; 257 EventDispatcher::Dispatch(this, presContext, &mozEvent); 258 } 259 260 // Check whether the relative visual viewport offset actually changed - 261 // maybe both visual and layout viewport scrolled together and there was no 262 // change after all. 263 nsPoint curRelativeOffset = 264 presShell->GetVisualViewportOffsetRelativeToLayoutViewport(); 265 nsPoint prevRelativeOffset = prevVisualOffset - prevLayoutOffset; 266 VVP_LOG( 267 "%p: FireScrollEvent, curRelativeOffset %s, " 268 "prevRelativeOffset %s\n", 269 this, ToString(curRelativeOffset).c_str(), 270 ToString(prevRelativeOffset).c_str()); 271 if (curRelativeOffset != prevRelativeOffset) { 272 VVP_LOG("%p, FireScrollEvent, fire VisualViewport scroll\n", this); 273 WidgetGUIEvent event(true, eScroll, nullptr); 274 event.mFlags.mBubbles = false; 275 event.mFlags.mCancelable = false; 276 EventDispatcher::Dispatch(this, presContext, &event); 277 } 278 } 279 }