tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }