tor-browser

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

ZoomConstraintsClient.cpp (10931B)


      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 "ZoomConstraintsClient.h"
      8 
      9 #include <inttypes.h>
     10 
     11 #include "UnitTransforms.h"
     12 #include "Units.h"
     13 #include "mozilla/Preferences.h"
     14 #include "mozilla/PresShell.h"
     15 #include "mozilla/ScrollContainerFrame.h"
     16 #include "mozilla/StaticPrefs_apz.h"
     17 #include "mozilla/dom/Document.h"
     18 #include "mozilla/dom/Event.h"
     19 #include "mozilla/layers/APZCCallbackHelper.h"
     20 #include "mozilla/layers/ScrollableLayerGuid.h"
     21 #include "mozilla/layers/ZoomConstraints.h"
     22 #include "nsIFrame.h"
     23 #include "nsLayoutUtils.h"
     24 #include "nsPoint.h"
     25 #include "nsViewportInfo.h"
     26 
     27 static mozilla::LazyLogModule sApzZoomLog("apz.zoom");
     28 #define ZCC_LOG(...) MOZ_LOG(sApzZoomLog, LogLevel::Debug, (__VA_ARGS__))
     29 
     30 NS_IMPL_ISUPPORTS(ZoomConstraintsClient, nsIDOMEventListener, nsIObserver)
     31 
     32 #define DOM_META_ADDED u"DOMMetaAdded"_ns
     33 #define DOM_META_CHANGED u"DOMMetaChanged"_ns
     34 #define FULLSCREEN_CHANGED u"fullscreenchange"_ns
     35 #define BEFORE_FIRST_PAINT "before-first-paint"_ns
     36 #define COMPOSITOR_REINITIALIZED "compositor-reinitialized"_ns
     37 #define NS_PREF_CHANGED "nsPref:changed"_ns
     38 
     39 using namespace mozilla;
     40 using namespace mozilla::dom;
     41 using namespace mozilla::layers;
     42 
     43 ZoomConstraintsClient::ZoomConstraintsClient()
     44    : mDocument(nullptr),
     45      mPresShell(nullptr),
     46      mZoomConstraints(false, false, CSSToParentLayerScale(1.f),
     47                       CSSToParentLayerScale(1.f)) {}
     48 
     49 ZoomConstraintsClient::~ZoomConstraintsClient() = default;
     50 
     51 static nsIWidget* GetWidget(PresShell* aPresShell) {
     52  if (!aPresShell) {
     53    return nullptr;
     54  }
     55  if (nsIFrame* rootFrame = aPresShell->GetRootFrame()) {
     56 #if defined(MOZ_WIDGET_ANDROID)
     57    // On Android in cases of about:XX pages loaded in the browser parent
     58    // process we need to return the nearest widget since it's the widget owning
     59    // an IAPZCTreeManager to communicate with the APZCTreeManager for the
     60    // browser.
     61    // In bug 1648427 we will apply this code to desktops as well to make
     62    // about pages zoomable on desktops, but it will be involving more works,
     63    // see https://bugzilla.mozilla.org/show_bug.cgi?id=1648427#c7 .
     64    return rootFrame->GetNearestWidget();
     65 #else
     66    return rootFrame->GetOwnWidget();
     67 #endif
     68  }
     69  return nullptr;
     70 }
     71 
     72 void ZoomConstraintsClient::Destroy() {
     73  if (!(mPresShell && mDocument)) {
     74    return;
     75  }
     76 
     77  ZCC_LOG("Destroying %p\n", this);
     78 
     79  if (mEventTarget) {
     80    mEventTarget->RemoveEventListener(DOM_META_ADDED, this, false);
     81    mEventTarget->RemoveEventListener(DOM_META_CHANGED, this, false);
     82    mEventTarget->RemoveSystemEventListener(FULLSCREEN_CHANGED, this, false);
     83    mEventTarget = nullptr;
     84  }
     85 
     86  nsCOMPtr<nsIObserverService> observerService =
     87      mozilla::services::GetObserverService();
     88  if (observerService) {
     89    observerService->RemoveObserver(this, BEFORE_FIRST_PAINT.Data());
     90    observerService->RemoveObserver(this, COMPOSITOR_REINITIALIZED.Data());
     91  }
     92 
     93  Preferences::RemoveObserver(this, "browser.ui.zoom.force-user-scalable");
     94 
     95  if (mGuid) {
     96    if (nsIWidget* widget = GetWidget(mPresShell)) {
     97      ZCC_LOG("Sending null constraints in %p for { %u, %" PRIu64 " }\n", this,
     98              mGuid->mPresShellId, mGuid->mScrollId);
     99      widget->UpdateZoomConstraints(mGuid->mPresShellId, mGuid->mScrollId,
    100                                    Nothing());
    101      mGuid = Nothing();
    102    }
    103  }
    104 
    105  mDocument = nullptr;
    106  mPresShell = nullptr;
    107 }
    108 
    109 void ZoomConstraintsClient::Init(PresShell* aPresShell, Document* aDocument) {
    110  if (!(aPresShell && aDocument)) {
    111    return;
    112  }
    113 
    114  mPresShell = aPresShell;
    115  mDocument = aDocument;
    116 
    117  if (nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow()) {
    118    mEventTarget = window->GetParentTarget();
    119  }
    120  if (mEventTarget) {
    121    mEventTarget->AddEventListener(DOM_META_ADDED, this, false);
    122    mEventTarget->AddEventListener(DOM_META_CHANGED, this, false);
    123    mEventTarget->AddSystemEventListener(FULLSCREEN_CHANGED, this, false);
    124  }
    125 
    126  nsCOMPtr<nsIObserverService> observerService =
    127      mozilla::services::GetObserverService();
    128  if (observerService) {
    129    observerService->AddObserver(this, BEFORE_FIRST_PAINT.Data(), false);
    130    observerService->AddObserver(this, COMPOSITOR_REINITIALIZED.Data(), false);
    131  }
    132 
    133  Preferences::AddStrongObserver(this, "browser.ui.zoom.force-user-scalable");
    134 }
    135 
    136 NS_IMETHODIMP
    137 ZoomConstraintsClient::HandleEvent(dom::Event* event) {
    138  nsAutoString type;
    139  event->GetType(type);
    140 
    141  if (type.Equals(DOM_META_ADDED)) {
    142    ZCC_LOG("Got a dom-meta-added event in %p\n", this);
    143    RefreshZoomConstraints();
    144  } else if (type.Equals(DOM_META_CHANGED)) {
    145    ZCC_LOG("Got a dom-meta-changed event in %p\n", this);
    146    RefreshZoomConstraints();
    147  } else if (type.Equals(FULLSCREEN_CHANGED)) {
    148    ZCC_LOG("Got a fullscreen-change event in %p\n", this);
    149    RefreshZoomConstraints();
    150  }
    151 
    152  return NS_OK;
    153 }
    154 
    155 NS_IMETHODIMP
    156 ZoomConstraintsClient::Observe(nsISupports* aSubject, const char* aTopic,
    157                               const char16_t* aData) {
    158  if (SameCOMIdentity(aSubject, ToSupports(mDocument)) &&
    159      BEFORE_FIRST_PAINT.EqualsASCII(aTopic)) {
    160    ZCC_LOG("Got a before-first-paint event in %p\n", this);
    161    RefreshZoomConstraints();
    162  } else if (COMPOSITOR_REINITIALIZED.EqualsASCII(aTopic)) {
    163    ZCC_LOG("Got a compositor-reinitialized notification in %p\n", this);
    164    RefreshZoomConstraints();
    165  } else if (NS_PREF_CHANGED.EqualsASCII(aTopic)) {
    166    ZCC_LOG("Got a pref-change event in %p\n", this);
    167    // We need to run this later because all the pref change listeners need
    168    // to execute before we can be guaranteed that
    169    // StaticPrefs::browser_ui_zoom_force_user_scalable() returns the updated
    170    // value.
    171 
    172    RefPtr<nsRunnableMethod<ZoomConstraintsClient>> event =
    173        NewRunnableMethod("ZoomConstraintsClient::RefreshZoomConstraints", this,
    174                          &ZoomConstraintsClient::RefreshZoomConstraints);
    175    mDocument->Dispatch(event.forget());
    176  }
    177  return NS_OK;
    178 }
    179 
    180 void ZoomConstraintsClient::ScreenSizeChanged() {
    181  ZCC_LOG("Got a screen-size change notification in %p\n", this);
    182  RefreshZoomConstraints();
    183 }
    184 
    185 static mozilla::layers::ZoomConstraints ComputeZoomConstraintsFromViewportInfo(
    186    const nsViewportInfo& aViewportInfo, Document* aDocument) {
    187  mozilla::layers::ZoomConstraints constraints;
    188  constraints.mAllowZoom = aViewportInfo.IsZoomAllowed() &&
    189                           nsLayoutUtils::AllowZoomingForDocument(aDocument);
    190  constraints.mAllowDoubleTapZoom =
    191      constraints.mAllowZoom && StaticPrefs::apz_allow_double_tap_zooming();
    192  if (constraints.mAllowZoom) {
    193    constraints.mMinZoom.scale = aViewportInfo.GetMinZoom().scale;
    194    constraints.mMaxZoom.scale = aViewportInfo.GetMaxZoom().scale;
    195  } else {
    196    constraints.mMinZoom.scale = aViewportInfo.GetDefaultZoom().scale;
    197    constraints.mMaxZoom.scale = aViewportInfo.GetDefaultZoom().scale;
    198  }
    199  return constraints;
    200 }
    201 
    202 void ZoomConstraintsClient::RefreshZoomConstraints() {
    203  mZoomConstraints = ZoomConstraints(false, false, CSSToParentLayerScale(1.f),
    204                                     CSSToParentLayerScale(1.f));
    205 
    206  nsIWidget* widget = GetWidget(mPresShell);
    207  if (!widget) {
    208    return;
    209  }
    210 
    211  // Ignore documents which has been removed from the doc shell.
    212  if (!mDocument->IsActive()) {
    213    return;
    214  }
    215 
    216  uint32_t presShellId = 0;
    217  ScrollableLayerGuid::ViewID viewId = ScrollableLayerGuid::NULL_SCROLL_ID;
    218  bool scrollIdentifiersValid =
    219      APZCCallbackHelper::GetOrCreateScrollIdentifiers(
    220          mDocument->GetDocumentElement(), &presShellId, &viewId);
    221  if (!scrollIdentifiersValid) {
    222    return;
    223  }
    224 
    225  LayoutDeviceIntSize screenSize;
    226  if (!nsLayoutUtils::GetDocumentViewerSize(mPresShell->GetPresContext(),
    227                                            screenSize)) {
    228    return;
    229  }
    230 
    231  nsViewportInfo viewportInfo = mDocument->GetViewportInfo(ViewAs<ScreenPixel>(
    232      screenSize, PixelCastJustification::LayoutDeviceIsScreenForBounds));
    233 
    234  mZoomConstraints =
    235      ComputeZoomConstraintsFromViewportInfo(viewportInfo, mDocument);
    236 
    237  if (mDocument->Fullscreen()) {
    238    ZCC_LOG("%p is in fullscreen, disallowing zooming\n", this);
    239    mZoomConstraints.mAllowZoom = false;
    240    mZoomConstraints.mAllowDoubleTapZoom = false;
    241  }
    242 
    243  if (mDocument->IsStaticDocument()) {
    244    ZCC_LOG("%p is in print or print preview, disallowing double tap zooming\n",
    245            this);
    246    mZoomConstraints.mAllowDoubleTapZoom = false;
    247  }
    248 
    249  if (nsContentUtils::IsPDFJS(mDocument->GetPrincipal())) {
    250    ZCC_LOG("%p is pdf.js viewer, disallowing double tap zooming\n", this);
    251    mZoomConstraints.mAllowDoubleTapZoom = false;
    252  }
    253 
    254  // On macOS the OS can send us a double tap zoom event from the touchpad and
    255  // there are no touch screen macOS devices so we never wait to see if a second
    256  // tap is coming so we can always allow double tap zooming on mac. We need
    257  // this because otherwise the width check usually disables it.
    258  bool allow_double_tap_always = false;
    259 #ifdef XP_MACOSX
    260  allow_double_tap_always =
    261      StaticPrefs::apz_mac_enable_double_tap_zoom_touchpad_gesture();
    262 #endif
    263  if (!allow_double_tap_always && mZoomConstraints.mAllowDoubleTapZoom) {
    264    // If the CSS viewport is narrower than the screen (i.e. width <=
    265    // device-width) then we disable double-tap-to-zoom behaviour.
    266    CSSToLayoutDeviceScale scale =
    267        mPresShell->GetPresContext()->CSSToDevPixelScale();
    268    if ((viewportInfo.GetSize() * scale).width <= screenSize.width) {
    269      mZoomConstraints.mAllowDoubleTapZoom = false;
    270    }
    271  }
    272 
    273  // We only ever create a ZoomConstraintsClient for an RCD, so the RSF of
    274  // the presShell must be the RCD-RSF (if it exists).
    275  MOZ_ASSERT(mPresShell->GetPresContext()->IsRootContentDocumentCrossProcess());
    276  if (ScrollContainerFrame* rcdrsf =
    277          mPresShell->GetRootScrollContainerFrame()) {
    278    ZCC_LOG("Notifying RCD-RSF that it is zoomable: %d\n",
    279            mZoomConstraints.mAllowZoom);
    280    rcdrsf->SetZoomableByAPZ(mZoomConstraints.mAllowZoom);
    281  }
    282 
    283  ScrollableLayerGuid newGuid(LayersId{0}, presShellId, viewId);
    284  if (mGuid && mGuid.value() != newGuid) {
    285    ZCC_LOG("Clearing old constraints in %p for { %u, %" PRIu64 " }\n", this,
    286            mGuid->mPresShellId, mGuid->mScrollId);
    287    // If the guid changes, send a message to clear the old one
    288    widget->UpdateZoomConstraints(mGuid->mPresShellId, mGuid->mScrollId,
    289                                  Nothing());
    290  }
    291  mGuid = Some(newGuid);
    292  ZCC_LOG("Sending constraints %s in %p for { %u, %" PRIu64 " }\n",
    293          ToString(mZoomConstraints).c_str(), this, presShellId, viewId);
    294  widget->UpdateZoomConstraints(presShellId, viewId, Some(mZoomConstraints));
    295 }