diff options
Diffstat (limited to 'dom/base/VisualViewport.cpp')
-rw-r--r-- | dom/base/VisualViewport.cpp | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/dom/base/VisualViewport.cpp b/dom/base/VisualViewport.cpp new file mode 100644 index 0000000000..64e66a5ac4 --- /dev/null +++ b/dom/base/VisualViewport.cpp @@ -0,0 +1,331 @@ +/* -*- 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 "VisualViewport.h" + +#include "mozilla/EventDispatcher.h" +#include "mozilla/PresShell.h" +#include "mozilla/ToString.h" +#include "nsIScrollableFrame.h" +#include "nsIDocShell.h" +#include "nsPresContext.h" +#include "nsRefreshDriver.h" +#include "DocumentInlines.h" + +static mozilla::LazyLogModule sVvpLog("visualviewport"); +#define VVP_LOG(...) MOZ_LOG(sVvpLog, LogLevel::Debug, (__VA_ARGS__)) + +using namespace mozilla; +using namespace mozilla::dom; + +VisualViewport::VisualViewport(nsPIDOMWindowInner* aWindow) + : DOMEventTargetHelper(aWindow) {} + +VisualViewport::~VisualViewport() { + if (mResizeEvent) { + mResizeEvent->Revoke(); + } + + if (mScrollEvent) { + mScrollEvent->Revoke(); + } +} + +/* virtual */ +JSObject* VisualViewport::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return VisualViewport_Binding::Wrap(aCx, this, aGivenProto); +} + +/* virtual */ +void VisualViewport::GetEventTargetParent(EventChainPreVisitor& aVisitor) { + EventMessage msg = aVisitor.mEvent->mMessage; + + aVisitor.mCanHandle = true; + EventTarget* parentTarget = nullptr; + // Only our special internal events are allowed to escape the + // Visual Viewport and be dispatched further up the DOM tree. + if (msg == eMozVisualScroll || msg == eMozVisualResize) { + if (nsPIDOMWindowInner* win = GetOwner()) { + if (Document* doc = win->GetExtantDoc()) { + parentTarget = doc; + } + } + } + aVisitor.SetParentTarget(parentTarget, false); +} + +CSSSize VisualViewport::VisualViewportSize() const { + CSSSize size = CSSSize(0, 0); + + // Flush layout, as that may affect the answer below (e.g. scrollbars + // may have appeared, decreasing the available viewport size). + RefPtr<const VisualViewport> kungFuDeathGrip(this); + if (Document* doc = GetDocument()) { + doc->FlushPendingNotifications(FlushType::Layout); + } + + // Fetch the pres shell after the layout flush, as it might have destroyed it. + if (PresShell* presShell = GetPresShell()) { + if (presShell->IsVisualViewportSizeSet()) { + DynamicToolbarState state = presShell->GetDynamicToolbarState(); + size = CSSRect::FromAppUnits( + (state == DynamicToolbarState::InTransition || + state == DynamicToolbarState::Collapsed) + ? presShell->GetVisualViewportSizeUpdatedByDynamicToolbar() + : presShell->GetVisualViewportSize()); + } else { + nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable(); + if (sf) { + size = CSSRect::FromAppUnits(sf->GetScrollPortRect().Size()); + } + } + } + return size; +} + +double VisualViewport::Width() const { + CSSSize size = VisualViewportSize(); + return size.width; +} + +double VisualViewport::Height() const { + CSSSize size = VisualViewportSize(); + return size.height; +} + +double VisualViewport::Scale() const { + double scale = 1; + if (PresShell* presShell = GetPresShell()) { + scale = presShell->GetResolution(); + } + return scale; +} + +CSSPoint VisualViewport::VisualViewportOffset() const { + CSSPoint offset = CSSPoint(0, 0); + + if (PresShell* presShell = GetPresShell()) { + offset = CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset()); + } + return offset; +} + +CSSPoint VisualViewport::LayoutViewportOffset() const { + CSSPoint offset = CSSPoint(0, 0); + + if (PresShell* presShell = GetPresShell()) { + offset = CSSPoint::FromAppUnits(presShell->GetLayoutViewportOffset()); + } + return offset; +} + +double VisualViewport::PageLeft() const { return VisualViewportOffset().X(); } + +double VisualViewport::PageTop() const { return VisualViewportOffset().Y(); } + +double VisualViewport::OffsetLeft() const { + return PageLeft() - LayoutViewportOffset().X(); +} + +double VisualViewport::OffsetTop() const { + return PageTop() - LayoutViewportOffset().Y(); +} + +Document* VisualViewport::GetDocument() const { + nsCOMPtr<nsPIDOMWindowInner> window = GetOwner(); + if (!window) { + return nullptr; + } + + nsIDocShell* docShell = window->GetDocShell(); + if (!docShell) { + return nullptr; + } + + return docShell->GetDocument(); +} + +PresShell* VisualViewport::GetPresShell() const { + RefPtr<Document> document = GetDocument(); + return document ? document->GetPresShell() : nullptr; +} + +nsPresContext* VisualViewport::GetPresContext() const { + RefPtr<Document> document = GetDocument(); + return document ? document->GetPresContext() : nullptr; +} + +/* ================= Resize event handling ================= */ + +void VisualViewport::PostResizeEvent() { + VVP_LOG("%p: PostResizeEvent (pre-existing: %d)\n", this, !!mResizeEvent); + nsPresContext* presContext = GetPresContext(); + if (mResizeEvent && mResizeEvent->HasPresContext(presContext)) { + return; + } + if (mResizeEvent) { + // prescontext changed, so discard the old resize event and queue a new one + mResizeEvent->Revoke(); + mResizeEvent = nullptr; + } + + // The event constructor will register itself with the refresh driver. + if (presContext) { + mResizeEvent = new VisualViewportResizeEvent(this, presContext); + VVP_LOG("%p: PostResizeEvent, created new event\n", this); + } +} + +VisualViewport::VisualViewportResizeEvent::VisualViewportResizeEvent( + VisualViewport* aViewport, nsPresContext* aPresContext) + : Runnable("VisualViewport::VisualViewportResizeEvent"), + mViewport(aViewport), + mPresContext(aPresContext) { + VVP_LOG("%p: Registering PostResize on %p %p\n", aViewport, aPresContext, + aPresContext->RefreshDriver()); + aPresContext->RefreshDriver()->PostVisualViewportResizeEvent(this); +} + +bool VisualViewport::VisualViewportResizeEvent::HasPresContext( + nsPresContext* aContext) const { + return mPresContext.get() == aContext; +} + +void VisualViewport::VisualViewportResizeEvent::Revoke() { + mViewport = nullptr; + mPresContext = nullptr; +} + +// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398) +MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP +VisualViewport::VisualViewportResizeEvent::Run() { + if (RefPtr<VisualViewport> viewport = mViewport) { + viewport->FireResizeEvent(); + } + return NS_OK; +} + +void VisualViewport::FireResizeEvent() { + MOZ_ASSERT(mResizeEvent); + mResizeEvent->Revoke(); + mResizeEvent = nullptr; + + RefPtr<nsPresContext> presContext = GetPresContext(); + + VVP_LOG("%p, FireResizeEvent, fire mozvisualresize\n", this); + WidgetEvent mozEvent(true, eMozVisualResize); + mozEvent.mFlags.mOnlySystemGroupDispatch = true; + EventDispatcher::Dispatch(static_cast<EventTarget*>(this), presContext, + &mozEvent); + + VVP_LOG("%p, FireResizeEvent, fire VisualViewport resize\n", this); + WidgetEvent event(true, eResize); + event.mFlags.mBubbles = false; + event.mFlags.mCancelable = false; + EventDispatcher::Dispatch(static_cast<EventTarget*>(this), presContext, + &event); +} + +/* ================= Scroll event handling ================= */ + +void VisualViewport::PostScrollEvent(const nsPoint& aPrevVisualOffset, + const nsPoint& aPrevLayoutOffset) { + VVP_LOG("%p: PostScrollEvent, prevRelativeOffset=%s (pre-existing: %d)\n", + this, ToString(aPrevVisualOffset - aPrevLayoutOffset).c_str(), + !!mScrollEvent); + nsPresContext* presContext = GetPresContext(); + if (mScrollEvent && mScrollEvent->HasPresContext(presContext)) { + return; + } + + if (mScrollEvent) { + // prescontext changed, so discard the old scroll event and queue a new one + mScrollEvent->Revoke(); + mScrollEvent = nullptr; + } + + // The event constructor will register itself with the refresh driver. + if (presContext) { + mScrollEvent = new VisualViewportScrollEvent( + this, presContext, aPrevVisualOffset, aPrevLayoutOffset); + VVP_LOG("%p: PostScrollEvent, created new event\n", this); + } +} + +VisualViewport::VisualViewportScrollEvent::VisualViewportScrollEvent( + VisualViewport* aViewport, nsPresContext* aPresContext, + const nsPoint& aPrevVisualOffset, const nsPoint& aPrevLayoutOffset) + : Runnable("VisualViewport::VisualViewportScrollEvent"), + mViewport(aViewport), + mPresContext(aPresContext), + mPrevVisualOffset(aPrevVisualOffset), + mPrevLayoutOffset(aPrevLayoutOffset) { + VVP_LOG("%p: Registering PostScroll on %p %p\n", aViewport, aPresContext, + aPresContext->RefreshDriver()); + aPresContext->RefreshDriver()->PostVisualViewportScrollEvent(this); +} + +bool VisualViewport::VisualViewportScrollEvent::HasPresContext( + nsPresContext* aContext) const { + return mPresContext.get() == aContext; +} + +void VisualViewport::VisualViewportScrollEvent::Revoke() { + mViewport = nullptr; + mPresContext = nullptr; +} + +// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398) +MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP +VisualViewport::VisualViewportScrollEvent::Run() { + if (RefPtr<VisualViewport> viewport = mViewport) { + viewport->FireScrollEvent(); + } + return NS_OK; +} + +void VisualViewport::FireScrollEvent() { + MOZ_ASSERT(mScrollEvent); + nsPoint prevVisualOffset = mScrollEvent->PrevVisualOffset(); + nsPoint prevLayoutOffset = mScrollEvent->PrevLayoutOffset(); + mScrollEvent->Revoke(); + mScrollEvent = nullptr; + + if (RefPtr<PresShell> presShell = GetPresShell()) { + RefPtr<nsPresContext> presContext = GetPresContext(); + + if (presShell->GetVisualViewportOffset() != prevVisualOffset) { + // The internal event will be fired whenever the visual viewport's + // *absolute* offset changed, i.e. relative to the page. + VVP_LOG("%p: FireScrollEvent, fire mozvisualscroll\n", this); + WidgetEvent mozEvent(true, eMozVisualScroll); + mozEvent.mFlags.mOnlySystemGroupDispatch = true; + EventDispatcher::Dispatch(static_cast<EventTarget*>(this), presContext, + &mozEvent); + } + + // Check whether the relative visual viewport offset actually changed - + // maybe both visual and layout viewport scrolled together and there was no + // change after all. + nsPoint curRelativeOffset = + presShell->GetVisualViewportOffsetRelativeToLayoutViewport(); + nsPoint prevRelativeOffset = prevVisualOffset - prevLayoutOffset; + VVP_LOG( + "%p: FireScrollEvent, curRelativeOffset %s, " + "prevRelativeOffset %s\n", + this, ToString(curRelativeOffset).c_str(), + ToString(prevRelativeOffset).c_str()); + if (curRelativeOffset != prevRelativeOffset) { + VVP_LOG("%p, FireScrollEvent, fire VisualViewport scroll\n", this); + WidgetGUIEvent event(true, eScroll, nullptr); + event.mFlags.mBubbles = false; + event.mFlags.mCancelable = false; + EventDispatcher::Dispatch(static_cast<EventTarget*>(this), presContext, + &event); + } + } +} |