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 }