tor-browser

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

PresShellWidgetListener.cpp (9877B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "PresShellWidgetListener.h"
      7 
      8 #include "WindowRenderer.h"
      9 #include "mozilla/BasicEvents.h"
     10 #include "mozilla/PresShell.h"
     11 #include "mozilla/StartupTimeline.h"
     12 #include "mozilla/StaticPrefs_layout.h"
     13 #include "mozilla/dom/BrowserParent.h"
     14 #include "mozilla/dom/Document.h"
     15 #include "mozilla/widget/Screen.h"
     16 #include "nsContentUtils.h"  // for nsAutoScriptBlocker
     17 #include "nsDeviceContext.h"
     18 #include "nsDocShell.h"
     19 #include "nsIFrame.h"
     20 #include "nsIWidget.h"
     21 #include "nsIWidgetListener.h"
     22 #include "nsLayoutUtils.h"
     23 #include "nsXULPopupManager.h"
     24 
     25 using namespace mozilla;
     26 using namespace mozilla::widget;
     27 
     28 static uint32_t gLastUserEventTime = 0;
     29 static void MaybeUpdateLastUserEventTime(WidgetGUIEvent* aEvent) {
     30  WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
     31  if ((mouseEvent &&
     32       // Ignore mouse events that we synthesize.
     33       mouseEvent->mReason == WidgetMouseEvent::eReal &&
     34       // Ignore mouse exit and enter (we'll get moves if the user
     35       // is really moving the mouse) since we get them when we
     36       // create and destroy widgets.
     37       mouseEvent->mMessage != eMouseExitFromWidget &&
     38       mouseEvent->mMessage != eMouseEnterIntoWidget) ||
     39      aEvent->HasKeyEventMessage() || aEvent->HasIMEEventMessage()) {
     40    gLastUserEventTime = PR_IntervalToMicroseconds(PR_IntervalNow());
     41  }
     42 }
     43 
     44 uint32_t PresShellWidgetListener::GetLastUserEventTime() {
     45  return gLastUserEventTime;
     46 }
     47 
     48 PresShellWidgetListener::PresShellWidgetListener(PresShell* aPs)
     49    : mPresShell(aPs) {
     50  MOZ_COUNT_CTOR(PresShellWidgetListener);
     51 }
     52 
     53 PresShellWidgetListener::~PresShellWidgetListener() {
     54  MOZ_COUNT_DTOR(PresShellWidgetListener);
     55 
     56  if (mPreviousWindow) {
     57    mPreviousWindow->SetPreviouslyAttachedWidgetListener(nullptr);
     58  }
     59 
     60  // Destroy and release the widget
     61  DetachWidget();
     62 }
     63 
     64 void PresShellWidgetListener::DetachWidget() {
     65  if (mWindow) {
     66    mWindow->SetAttachedWidgetListener(nullptr);
     67    mWindow = nullptr;
     68  }
     69 }
     70 
     71 // Attach to a top level widget and start receiving mirrored events.
     72 void PresShellWidgetListener::AttachToTopLevelWidget(nsIWidget* aWidget) {
     73  MOZ_ASSERT(aWidget, "null widget ptr");
     74  MOZ_ASSERT(!aWidget->GetWidgetListener() ||
     75                 aWidget->GetWidgetListener()->GetAppWindow(),
     76             "Expect a top level widget");
     77 
     78  /// XXXjimm This is a temporary workaround to an issue w/document
     79  // viewer (bug 513162).
     80  if (nsIWidgetListener* listener = aWidget->GetAttachedWidgetListener()) {
     81    if (auto* old = listener->GetAsPresShellWidgetListener()) {
     82      old->DetachFromTopLevelWidget();
     83    }
     84  }
     85 
     86  mWindow = aWidget;
     87 
     88  mWindow->SetAttachedWidgetListener(this);
     89  if (mWindow->GetWindowType() != WindowType::Invisible) {
     90    mWindow->AsyncEnableDragDrop(true);
     91  }
     92 }
     93 
     94 // Detach us from an attached widget.
     95 void PresShellWidgetListener::DetachFromTopLevelWidget() {
     96  MOZ_ASSERT(mWindow, "null mWindow for DetachFromTopLevelWidget!");
     97 
     98  mWindow->SetAttachedWidgetListener(nullptr);
     99  if (nsIWidgetListener* listener =
    100          mWindow->GetPreviouslyAttachedWidgetListener()) {
    101    if (auto* previousListener = listener->GetAsPresShellWidgetListener()) {
    102      // Ensure the listener doesn't think it's being used anymore
    103      previousListener->mPreviousWindow = nullptr;
    104    }
    105  }
    106 
    107  // If the new pres shell is paint suppressed then the window
    108  // will want to use us instead until that's done
    109  mWindow->SetPreviouslyAttachedWidgetListener(this);
    110  mPreviousWindow = std::move(mWindow);
    111  MOZ_ASSERT(!mWindow);
    112 }
    113 
    114 PresShell* PresShellWidgetListener::GetPresShell() { return mPresShell; }
    115 
    116 void PresShellWidgetListener::WindowResized(nsIWidget*,
    117                                            const LayoutDeviceIntSize& aSize) {
    118  RefPtr<PresShell> ps = GetPresShell();
    119  if (!ps) {
    120    return;
    121  }
    122 
    123  nsPresContext* pc = ps->GetPresContext();
    124  if (!pc) {
    125    return;
    126  }
    127 
    128  // ensure DPI is up-to-date, in case of window being opened and sized
    129  // on a non-default-dpi display (bug 829963)
    130  pc->DeviceContext()->CheckDPIChange();
    131  int32_t p2a = pc->AppUnitsPerDevPixel();
    132  if (auto* frame = ps->GetRootFrame()) {
    133    // Usually the resize would deal with this, but there are some cases (like
    134    // web-extension popups) where frames might already be correctly sized etc
    135    // due to a call to e.g. nsDocumentViewer::GetContentSize or so.
    136    frame->InvalidateFrame();
    137  }
    138  ps->SetLayoutViewportSize(LayoutDeviceIntSize::ToAppUnits(aSize, p2a),
    139                            /* aDelay = */ false);
    140 
    141  if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
    142    pm->AdjustPopupsOnWindowChange(ps);
    143  }
    144 }
    145 
    146 void PresShellWidgetListener::DynamicToolbarMaxHeightChanged(
    147    ScreenIntCoord aHeight) {
    148  MOZ_ASSERT(XRE_IsParentProcess(),
    149             "Should be only called for the browser parent process");
    150  CallOnAllRemoteChildren(
    151      [aHeight](dom::BrowserParent* aBrowserParent) -> CallState {
    152        aBrowserParent->DynamicToolbarMaxHeightChanged(aHeight);
    153        return CallState::Continue;
    154      });
    155 }
    156 
    157 void PresShellWidgetListener::DynamicToolbarOffsetChanged(
    158    ScreenIntCoord aOffset) {
    159  MOZ_ASSERT(XRE_IsParentProcess(),
    160             "Should be only called for the browser parent process");
    161  CallOnAllRemoteChildren(
    162      [aOffset](dom::BrowserParent* aBrowserParent) -> CallState {
    163        // Skip background tabs.
    164        if (!aBrowserParent->GetDocShellIsActive()) {
    165          return CallState::Continue;
    166        }
    167 
    168        aBrowserParent->DynamicToolbarOffsetChanged(aOffset);
    169        return CallState::Stop;
    170      });
    171 }
    172 
    173 void PresShellWidgetListener::KeyboardHeightChanged(ScreenIntCoord aHeight) {
    174  MOZ_ASSERT(XRE_IsParentProcess(),
    175             "Should be only called for the browser parent process");
    176 #ifdef MOZ_WIDGET_ANDROID
    177  CallOnAllRemoteChildren(
    178      [aHeight](dom::BrowserParent* aBrowserParent) -> CallState {
    179        // Skip background tabs.
    180        if (!aBrowserParent->GetDocShellIsActive()) {
    181          return CallState::Continue;
    182        }
    183 
    184        aBrowserParent->KeyboardHeightChanged(aHeight);
    185        return CallState::Stop;
    186      });
    187 #endif
    188 }
    189 
    190 void PresShellWidgetListener::AndroidPipModeChanged(bool aPipMode) {
    191  MOZ_ASSERT(XRE_IsParentProcess(),
    192             "Should be only called for the browser parent process");
    193 #ifdef MOZ_WIDGET_ANDROID
    194  CallOnAllRemoteChildren(
    195      [aPipMode](dom::BrowserParent* aBrowserParent) -> CallState {
    196        aBrowserParent->AndroidPipModeChanged(aPipMode);
    197        return CallState::Continue;
    198      });
    199 #endif
    200 }
    201 
    202 void PresShellWidgetListener::PaintWindow(nsIWidget* aWidget) {
    203  RefPtr ps = GetPresShell();
    204  if (!ps) {
    205    return;
    206  }
    207  RefPtr renderer = aWidget->GetWindowRenderer();
    208  if (!renderer->NeedsWidgetInvalidation()) {
    209    ps->PaintSynchronously();
    210    renderer->FlushRendering(wr::RenderReasons::WIDGET);
    211  } else {
    212    ps->SyncPaintFallback(ps->GetRootFrame(), renderer);
    213  }
    214  mozilla::StartupTimeline::RecordOnce(mozilla::StartupTimeline::FIRST_PAINT);
    215  ps->DidPaintWindow();
    216 }
    217 
    218 void PresShellWidgetListener::DidCompositeWindow(
    219    mozilla::layers::TransactionId aTransactionId,
    220    const TimeStamp& aCompositeStart, const TimeStamp& aCompositeEnd) {
    221  PresShell* presShell = GetPresShell();
    222  if (!presShell) {
    223    return;
    224  }
    225 
    226  nsAutoScriptBlocker scriptBlocker;
    227 
    228  nsPresContext* context = presShell->GetPresContext();
    229  nsRootPresContext* rootContext = context->GetRootPresContext();
    230  if (rootContext) {
    231    rootContext->NotifyDidPaintForSubtree(aTransactionId, aCompositeEnd);
    232  }
    233 
    234  mozilla::StartupTimeline::RecordOnce(mozilla::StartupTimeline::FIRST_PAINT2,
    235                                       aCompositeEnd);
    236 }
    237 
    238 nsEventStatus PresShellWidgetListener::HandleEvent(WidgetGUIEvent* aEvent) {
    239  MOZ_ASSERT(aEvent->mWidget, "null widget ptr");
    240 
    241  nsEventStatus result = nsEventStatus_eIgnore;
    242  MaybeUpdateLastUserEventTime(aEvent);
    243  if (RefPtr<PresShell> ps = GetPresShell()) {
    244    if (nsIFrame* root = ps->GetRootFrame()) {
    245      ps->HandleEvent(root, aEvent, false, &result);
    246    }
    247  }
    248  return result;
    249 }
    250 
    251 void PresShellWidgetListener::SafeAreaInsetsChanged(
    252    const LayoutDeviceIntMargin& aSafeAreaInsets) {
    253  PresShell* presShell = GetPresShell();
    254  if (!presShell) {
    255    return;
    256  }
    257 
    258  LayoutDeviceIntMargin windowSafeAreaInsets;
    259  const LayoutDeviceIntRect windowRect = mWindow->GetScreenBounds();
    260  if (nsCOMPtr<nsIScreen> screen = mWindow->GetWidgetScreen()) {
    261    windowSafeAreaInsets = nsContentUtils::GetWindowSafeAreaInsets(
    262        screen, aSafeAreaInsets, windowRect);
    263  }
    264 
    265  presShell->GetPresContext()->SetSafeAreaInsets(windowSafeAreaInsets);
    266 
    267  // https://github.com/w3c/csswg-drafts/issues/4670
    268  // Actually we don't set this value on sub document. This behaviour is
    269  // same as Blink.
    270  CallOnAllRemoteChildren(
    271      [windowSafeAreaInsets](dom::BrowserParent* aBrowserParent) -> CallState {
    272        (void)aBrowserParent->SendSafeAreaInsetsChanged(windowSafeAreaInsets);
    273        return CallState::Continue;
    274      });
    275 }
    276 
    277 bool PresShellWidgetListener::IsPrimaryFramePaintSuppressed() const {
    278  return StaticPrefs::layout_show_previous_page() && mPresShell &&
    279         mPresShell->IsPaintingSuppressed();
    280 }
    281 
    282 void PresShellWidgetListener::CallOnAllRemoteChildren(
    283    const std::function<CallState(dom::BrowserParent*)>& aCallback) {
    284  PresShell* presShell = GetPresShell();
    285  if (!presShell) {
    286    return;
    287  }
    288 
    289  dom::Document* document = presShell->GetDocument();
    290  if (!document) {
    291    return;
    292  }
    293 
    294  nsPIDOMWindowOuter* window = document->GetWindow();
    295  if (!window) {
    296    return;
    297  }
    298 
    299  nsContentUtils::CallOnAllRemoteChildren(window, aCallback);
    300 }