Bug 1835684 [wpt PR 40289] - Update wpt metadata, a=testonly
[gecko.git] / view / nsView.cpp
blob3c6aac593afbc46c72b072fccff3ef75322c7dde
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/. */
6 #include "nsView.h"
8 #include "mozilla/Attributes.h"
9 #include "mozilla/BasicEvents.h"
10 #include "mozilla/DebugOnly.h"
11 #include "mozilla/IntegerPrintfMacros.h"
12 #include "mozilla/Likely.h"
13 #include "mozilla/Poison.h"
14 #include "mozilla/PresShell.h"
15 #include "mozilla/StaticPrefs_layout.h"
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/BrowserParent.h"
18 #include "mozilla/widget/Screen.h"
19 #include "nsIWidget.h"
20 #include "nsViewManager.h"
21 #include "nsIFrame.h"
22 #include "nsPresArena.h"
23 #include "nsXULPopupManager.h"
24 #include "nsIScreen.h"
25 #include "nsIWidgetListener.h"
26 #include "nsContentUtils.h" // for nsAutoScriptBlocker
27 #include "nsDocShell.h"
28 #include "nsLayoutUtils.h"
29 #include "mozilla/TimelineConsumers.h"
30 #include "mozilla/CompositeTimelineMarker.h"
31 #include "mozilla/StartupTimeline.h"
33 using namespace mozilla;
34 using namespace mozilla::widget;
36 nsView::nsView(nsViewManager* aViewManager, ViewVisibility aVisibility)
37 : mViewManager(aViewManager),
38 mParent(nullptr),
39 mNextSibling(nullptr),
40 mFirstChild(nullptr),
41 mFrame(nullptr),
42 mZIndex(0),
43 mVis(aVisibility),
44 mPosX(0),
45 mPosY(0),
46 mVFlags(0),
47 mWidgetIsTopLevel(false),
48 mForcedRepaint(false),
49 mNeedsWindowPropertiesSync(false) {
50 MOZ_COUNT_CTOR(nsView);
52 // Views should be transparent by default. Not being transparent is
53 // a promise that the view will paint all its pixels opaquely. Views
54 // should make this promise explicitly by calling
55 // SetViewContentTransparency.
58 void nsView::DropMouseGrabbing() {
59 if (mViewManager->GetPresShell()) {
60 PresShell::ClearMouseCaptureOnView(this);
64 nsView::~nsView() {
65 MOZ_COUNT_DTOR(nsView);
67 while (GetFirstChild()) {
68 nsView* child = GetFirstChild();
69 if (child->GetViewManager() == mViewManager) {
70 child->Destroy();
71 } else {
72 // just unhook it. Someone else will want to destroy this.
73 RemoveChild(child);
77 if (mViewManager) {
78 DropMouseGrabbing();
80 nsView* rootView = mViewManager->GetRootView();
82 if (rootView) {
83 // Root views can have parents!
84 if (mParent) {
85 mViewManager->RemoveChild(this);
88 if (rootView == this) {
89 // Inform the view manager that the root view has gone away...
90 mViewManager->SetRootView(nullptr);
92 } else if (mParent) {
93 mParent->RemoveChild(this);
96 mViewManager = nullptr;
97 } else if (mParent) {
98 mParent->RemoveChild(this);
101 if (mPreviousWindow) {
102 mPreviousWindow->SetPreviouslyAttachedWidgetListener(nullptr);
105 // Destroy and release the widget
106 DestroyWidget();
108 MOZ_RELEASE_ASSERT(!mFrame);
111 class DestroyWidgetRunnable : public Runnable {
112 public:
113 NS_DECL_NSIRUNNABLE
115 explicit DestroyWidgetRunnable(nsIWidget* aWidget)
116 : mozilla::Runnable("DestroyWidgetRunnable"), mWidget(aWidget) {}
118 private:
119 nsCOMPtr<nsIWidget> mWidget;
122 NS_IMETHODIMP DestroyWidgetRunnable::Run() {
123 mWidget->Destroy();
124 mWidget = nullptr;
125 return NS_OK;
128 void nsView::DestroyWidget() {
129 if (mWindow) {
130 // If we are not attached to a base window, we're going to tear down our
131 // widget here. However, if we're attached to somebody elses widget, we
132 // want to leave the widget alone: don't reset the client data or call
133 // Destroy. Just clear our event view ptr and free our reference to it.
134 if (mWidgetIsTopLevel) {
135 mWindow->SetAttachedWidgetListener(nullptr);
136 } else {
137 mWindow->SetWidgetListener(nullptr);
139 nsCOMPtr<nsIRunnable> widgetDestroyer =
140 new DestroyWidgetRunnable(mWindow);
142 // Don't leak if we happen to arrive here after the main thread
143 // has disappeared.
144 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
145 if (mainThread) {
146 mainThread->Dispatch(widgetDestroyer.forget(), NS_DISPATCH_NORMAL);
150 mWindow = nullptr;
154 nsView* nsView::GetViewFor(const nsIWidget* aWidget) {
155 MOZ_ASSERT(aWidget, "null widget ptr");
157 nsIWidgetListener* listener = aWidget->GetWidgetListener();
158 if (listener) {
159 if (nsView* view = listener->GetView()) {
160 return view;
164 listener = aWidget->GetAttachedWidgetListener();
165 return listener ? listener->GetView() : nullptr;
168 void nsView::Destroy() {
169 this->~nsView();
170 mozWritePoison(this, sizeof(*this));
171 nsView::operator delete(this);
174 void nsView::SetPosition(nscoord aX, nscoord aY) {
175 mDimBounds.MoveBy(aX - mPosX, aY - mPosY);
176 mPosX = aX;
177 mPosY = aY;
179 NS_ASSERTION(GetParent() || (aX == 0 && aY == 0),
180 "Don't try to move the root widget to something non-zero");
182 ResetWidgetBounds(true, false);
185 void nsView::ResetWidgetBounds(bool aRecurse, bool aForceSync) {
186 if (mWindow) {
187 if (!aForceSync) {
188 // Don't change widget geometry synchronously, since that can
189 // cause synchronous painting.
190 mViewManager->PostPendingUpdate();
191 } else {
192 DoResetWidgetBounds(false, true);
194 return;
197 if (aRecurse) {
198 // reposition any widgets under this view
199 for (nsView* v = GetFirstChild(); v; v = v->GetNextSibling()) {
200 v->ResetWidgetBounds(true, aForceSync);
205 bool nsView::IsEffectivelyVisible() {
206 for (nsView* v = this; v; v = v->mParent) {
207 if (v->GetVisibility() == ViewVisibility::Hide) return false;
209 return true;
212 // Cocoa and GTK round widget coordinates to the nearest global "display pixel"
213 // integer value. So we avoid fractional display pixel values by rounding to
214 // the nearest value that won't yield a fractional display pixel.
215 static LayoutDeviceIntRect MaybeRoundToDisplayPixels(
216 const LayoutDeviceIntRect& aRect, TransparencyMode aTransparency,
217 int32_t aRound) {
218 if (aRound == 1) {
219 return aRect;
222 // If the widget doesn't support transparency, we prefer truncating to
223 // ceiling, so that we don't have extra pixels not painted by our frame.
224 auto size = aTransparency == TransparencyMode::Opaque
225 ? aRect.Size().TruncatedToMultiple(aRound)
226 : aRect.Size().CeiledToMultiple(aRound);
227 Unused << NS_WARN_IF(aTransparency == TransparencyMode::Opaque &&
228 size != aRect.Size());
229 return {aRect.TopLeft().RoundedToMultiple(aRound), size};
232 LayoutDeviceIntRect nsView::CalcWidgetBounds(WindowType aType,
233 TransparencyMode aTransparency) {
234 int32_t p2a = mViewManager->AppUnitsPerDevPixel();
236 nsRect viewBounds(mDimBounds);
238 nsView* parent = GetParent();
239 nsIWidget* parentWidget = nullptr;
240 if (parent) {
241 nsPoint offset;
242 parentWidget = parent->GetNearestWidget(&offset, p2a);
243 // make viewBounds be relative to the parent widget, in appunits
244 viewBounds += offset;
246 if (parentWidget && aType == WindowType::Popup && IsEffectivelyVisible()) {
247 // put offset into screen coordinates. (based on client area origin)
248 LayoutDeviceIntPoint screenPoint = parentWidget->WidgetToScreenOffset();
249 viewBounds += nsPoint(NSIntPixelsToAppUnits(screenPoint.x, p2a),
250 NSIntPixelsToAppUnits(screenPoint.y, p2a));
254 // Compute widget bounds in device pixels
255 const LayoutDeviceIntRect newBounds = [&] {
256 // TODO(emilio): We should probably use outside pixels for transparent
257 // windows (not just popups) as well.
258 if (aType != WindowType::Popup) {
259 return LayoutDeviceIntRect::FromUnknownRect(
260 viewBounds.ToNearestPixels(p2a));
262 // We use outside pixels for transparent windows if possible, so that we
263 // don't truncate the contents. For opaque popups, we use nearest pixels
264 // which prevents having pixels not drawn by the frame.
265 const bool opaque = aTransparency == TransparencyMode::Opaque;
266 const auto idealBounds = LayoutDeviceIntRect::FromUnknownRect(
267 opaque ? viewBounds.ToNearestPixels(p2a)
268 : viewBounds.ToOutsidePixels(p2a));
270 nsIWidget* widget = parentWidget ? parentWidget : mWindow.get();
271 if (!widget) {
272 return idealBounds;
274 const int32_t round = widget->RoundsWidgetCoordinatesTo();
275 return MaybeRoundToDisplayPixels(idealBounds, aTransparency, round);
276 }();
278 // Compute where the top-left of our widget ended up relative to the parent
279 // widget, in appunits.
280 nsPoint roundedOffset(NSIntPixelsToAppUnits(newBounds.X(), p2a),
281 NSIntPixelsToAppUnits(newBounds.Y(), p2a));
283 // mViewToWidgetOffset is added to coordinates relative to the view origin
284 // to get coordinates relative to the widget.
285 // The view origin, relative to the parent widget, is at
286 // (mPosX,mPosY) - mDimBounds.TopLeft() + viewBounds.TopLeft().
287 // Our widget, relative to the parent widget, is roundedOffset.
288 mViewToWidgetOffset = nsPoint(mPosX, mPosY) - mDimBounds.TopLeft() +
289 viewBounds.TopLeft() - roundedOffset;
291 return newBounds;
294 LayoutDeviceIntRect nsView::RecalcWidgetBounds() {
295 MOZ_ASSERT(mWindow);
296 return CalcWidgetBounds(mWindow->GetWindowType(),
297 mWindow->GetTransparencyMode());
300 void nsView::DoResetWidgetBounds(bool aMoveOnly, bool aInvalidateChangedSize) {
301 // The geometry of a root view's widget is controlled externally,
302 // NOT by sizing or positioning the view
303 if (mViewManager->GetRootView() == this) {
304 return;
307 MOZ_ASSERT(mWindow, "Why was this called??");
309 // Hold this ref to make sure it stays alive.
310 nsCOMPtr<nsIWidget> widget = mWindow;
312 // Stash a copy of these and use them so we can handle this being deleted (say
313 // from sync painting/flushing from Show/Move/Resize on the widget).
314 LayoutDeviceIntRect newBounds;
316 WindowType type = widget->GetWindowType();
318 LayoutDeviceIntRect curBounds = widget->GetClientBounds();
319 bool invisiblePopup = type == WindowType::Popup &&
320 ((curBounds.IsEmpty() && mDimBounds.IsEmpty()) ||
321 mVis == ViewVisibility::Hide);
323 if (invisiblePopup) {
324 // We're going to hit the early exit below, avoid calling CalcWidgetBounds.
325 } else {
326 newBounds = CalcWidgetBounds(type, widget->GetTransparencyMode());
327 invisiblePopup = newBounds.IsEmpty();
330 bool curVisibility = widget->IsVisible();
331 bool newVisibility = !invisiblePopup && IsEffectivelyVisible();
332 if (curVisibility && !newVisibility) {
333 widget->Show(false);
336 if (invisiblePopup) {
337 // Don't manipulate empty or hidden popup widgets. For example there's no
338 // point moving hidden comboboxes around, or doing X server roundtrips
339 // to compute their true screen position. This could mean that
340 // WidgetToScreen operations on these widgets don't return up-to-date
341 // values, but popup positions aren't reliable anyway because of correction
342 // to be on or off-screen.
343 return;
346 // Apply the widget size constraints to newBounds.
347 widget->ConstrainSize(&newBounds.width, &newBounds.height);
349 bool changedPos = curBounds.TopLeft() != newBounds.TopLeft();
350 bool changedSize = curBounds.Size() != newBounds.Size();
352 // Child views are never attached to top level widgets, this is safe.
354 // Coordinates are converted to desktop pixels for window Move/Resize APIs,
355 // because of the potential for device-pixel coordinate spaces for mixed
356 // hidpi/lodpi screens to overlap each other and result in bad placement
357 // (bug 814434).
359 DesktopToLayoutDeviceScale scale = widget->GetDesktopToDeviceScaleByScreen();
361 DesktopRect deskRect = newBounds / scale;
362 if (changedPos) {
363 if (changedSize && !aMoveOnly) {
364 widget->ResizeClient(deskRect, aInvalidateChangedSize);
365 } else {
366 widget->MoveClient(deskRect.TopLeft());
368 } else {
369 if (changedSize && !aMoveOnly) {
370 widget->ResizeClient(deskRect.Size(), aInvalidateChangedSize);
371 } // else do nothing!
374 if (!curVisibility && newVisibility) {
375 widget->Show(true);
379 void nsView::SetDimensions(const nsRect& aRect, bool aPaint,
380 bool aResizeWidget) {
381 nsRect dims = aRect;
382 dims.MoveBy(mPosX, mPosY);
384 // Don't use nsRect's operator== here, since it returns true when
385 // both rects are empty even if they have different widths and we
386 // have cases where that sort of thing matters to us.
387 if (mDimBounds.TopLeft() == dims.TopLeft() &&
388 mDimBounds.Size() == dims.Size()) {
389 return;
392 mDimBounds = dims;
394 if (aResizeWidget) {
395 ResetWidgetBounds(false, false);
399 void nsView::NotifyEffectiveVisibilityChanged(bool aEffectivelyVisible) {
400 if (!aEffectivelyVisible) {
401 DropMouseGrabbing();
404 SetForcedRepaint(true);
406 if (nullptr != mWindow) {
407 ResetWidgetBounds(false, false);
410 for (nsView* child = mFirstChild; child; child = child->mNextSibling) {
411 if (child->mVis == ViewVisibility::Hide) {
412 // It was effectively hidden and still is
413 continue;
415 // Our child is visible if we are
416 child->NotifyEffectiveVisibilityChanged(aEffectivelyVisible);
420 void nsView::SetVisibility(ViewVisibility aVisibility) {
421 mVis = aVisibility;
422 NotifyEffectiveVisibilityChanged(IsEffectivelyVisible());
425 void nsView::SetFloating(bool aFloatingView) {
426 if (aFloatingView)
427 mVFlags |= NS_VIEW_FLAG_FLOATING;
428 else
429 mVFlags &= ~NS_VIEW_FLAG_FLOATING;
432 void nsView::InvalidateHierarchy() {
433 if (mViewManager->GetRootView() == this) mViewManager->InvalidateHierarchy();
435 for (nsView* child = mFirstChild; child; child = child->GetNextSibling())
436 child->InvalidateHierarchy();
439 void nsView::InsertChild(nsView* aChild, nsView* aSibling) {
440 MOZ_ASSERT(nullptr != aChild, "null ptr");
442 if (nullptr != aChild) {
443 if (nullptr != aSibling) {
444 #ifdef DEBUG
445 NS_ASSERTION(aSibling->GetParent() == this,
446 "tried to insert view with invalid sibling");
447 #endif
448 // insert after sibling
449 aChild->SetNextSibling(aSibling->GetNextSibling());
450 aSibling->SetNextSibling(aChild);
451 } else {
452 aChild->SetNextSibling(mFirstChild);
453 mFirstChild = aChild;
455 aChild->SetParent(this);
457 // If we just inserted a root view, then update the RootViewManager
458 // on all view managers in the new subtree.
460 nsViewManager* vm = aChild->GetViewManager();
461 if (vm->GetRootView() == aChild) {
462 aChild->InvalidateHierarchy();
467 void nsView::RemoveChild(nsView* child) {
468 MOZ_ASSERT(nullptr != child, "null ptr");
470 if (nullptr != child) {
471 nsView* prevKid = nullptr;
472 nsView* kid = mFirstChild;
473 DebugOnly<bool> found = false;
474 while (nullptr != kid) {
475 if (kid == child) {
476 if (nullptr != prevKid) {
477 prevKid->SetNextSibling(kid->GetNextSibling());
478 } else {
479 mFirstChild = kid->GetNextSibling();
481 child->SetParent(nullptr);
482 found = true;
483 break;
485 prevKid = kid;
486 kid = kid->GetNextSibling();
488 NS_ASSERTION(found, "tried to remove non child");
490 // If we just removed a root view, then update the RootViewManager
491 // on all view managers in the removed subtree.
493 nsViewManager* vm = child->GetViewManager();
494 if (vm->GetRootView() == child) {
495 child->InvalidateHierarchy();
500 // Native widgets ultimately just can't deal with the awesome power of
501 // CSS2 z-index. However, we set the z-index on the widget anyway
502 // because in many simple common cases the widgets do end up in the
503 // right order. We set each widget's z-index to the z-index of the
504 // nearest ancestor that has non-auto z-index.
505 static void UpdateNativeWidgetZIndexes(nsView* aView, int32_t aZIndex) {
506 if (aView->HasWidget()) {
507 nsIWidget* widget = aView->GetWidget();
508 if (widget->GetZIndex() != aZIndex) {
509 widget->SetZIndex(aZIndex);
511 } else {
512 for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
513 if (v->GetZIndexIsAuto()) {
514 UpdateNativeWidgetZIndexes(v, aZIndex);
520 static int32_t FindNonAutoZIndex(nsView* aView) {
521 while (aView) {
522 if (!aView->GetZIndexIsAuto()) {
523 return aView->GetZIndex();
525 aView = aView->GetParent();
527 return 0;
530 struct DefaultWidgetInitData : public widget::InitData {
531 DefaultWidgetInitData() : widget::InitData() {
532 mWindowType = WindowType::Child;
533 mClipChildren = true;
534 mClipSiblings = true;
538 nsresult nsView::CreateWidget(widget::InitData* aWidgetInitData,
539 bool aEnableDragDrop, bool aResetVisibility) {
540 AssertNoWindow();
541 MOZ_ASSERT(
542 !aWidgetInitData || aWidgetInitData->mWindowType != WindowType::Popup,
543 "Use CreateWidgetForPopup");
545 DefaultWidgetInitData defaultInitData;
546 aWidgetInitData = aWidgetInitData ? aWidgetInitData : &defaultInitData;
547 LayoutDeviceIntRect trect = CalcWidgetBounds(
548 aWidgetInitData->mWindowType, aWidgetInitData->mTransparencyMode);
550 nsIWidget* parentWidget =
551 GetParent() ? GetParent()->GetNearestWidget(nullptr) : nullptr;
552 if (!parentWidget) {
553 NS_ERROR("nsView::CreateWidget without suitable parent widget??");
554 return NS_ERROR_FAILURE;
557 // XXX: using aForceUseIWidgetParent=true to preserve previous
558 // semantics. It's not clear that it's actually needed.
559 mWindow = parentWidget->CreateChild(trect, aWidgetInitData, true);
560 if (!mWindow) {
561 return NS_ERROR_FAILURE;
564 InitializeWindow(aEnableDragDrop, aResetVisibility);
566 return NS_OK;
569 nsresult nsView::CreateWidgetForParent(nsIWidget* aParentWidget,
570 widget::InitData* aWidgetInitData,
571 bool aEnableDragDrop,
572 bool aResetVisibility) {
573 AssertNoWindow();
574 MOZ_ASSERT(
575 !aWidgetInitData || aWidgetInitData->mWindowType != WindowType::Popup,
576 "Use CreateWidgetForPopup");
577 MOZ_ASSERT(aParentWidget, "Parent widget required");
579 DefaultWidgetInitData defaultInitData;
580 aWidgetInitData = aWidgetInitData ? aWidgetInitData : &defaultInitData;
582 LayoutDeviceIntRect trect = CalcWidgetBounds(
583 aWidgetInitData->mWindowType, aWidgetInitData->mTransparencyMode);
585 mWindow = aParentWidget->CreateChild(trect, aWidgetInitData);
586 if (!mWindow) {
587 return NS_ERROR_FAILURE;
590 InitializeWindow(aEnableDragDrop, aResetVisibility);
592 return NS_OK;
595 nsresult nsView::CreateWidgetForPopup(widget::InitData* aWidgetInitData,
596 nsIWidget* aParentWidget) {
597 AssertNoWindow();
598 MOZ_ASSERT(aWidgetInitData, "Widget init data required");
599 MOZ_ASSERT(aWidgetInitData->mWindowType == WindowType::Popup,
600 "Use one of the other CreateWidget methods");
602 LayoutDeviceIntRect trect = CalcWidgetBounds(
603 aWidgetInitData->mWindowType, aWidgetInitData->mTransparencyMode);
605 // XXX/cjones: having these two separate creation cases seems ... um
606 // ... unnecessary, but it's the way the old code did it. Please
607 // unify them by first finding a suitable parent nsIWidget, then
608 // getting rid of aForceUseIWidgetParent.
609 if (aParentWidget) {
610 // XXX: using aForceUseIWidgetParent=true to preserve previous
611 // semantics. It's not clear that it's actually needed.
612 mWindow = aParentWidget->CreateChild(trect, aWidgetInitData, true);
613 } else {
614 nsIWidget* nearestParent =
615 GetParent() ? GetParent()->GetNearestWidget(nullptr) : nullptr;
616 if (!nearestParent) {
617 // Without a parent, we can't make a popup. This can happen
618 // when printing
619 return NS_ERROR_FAILURE;
622 mWindow = nearestParent->CreateChild(trect, aWidgetInitData);
624 if (!mWindow) {
625 return NS_ERROR_FAILURE;
628 InitializeWindow(/* aEnableDragDrop = */ true, /* aResetVisibility = */ true);
630 return NS_OK;
633 void nsView::InitializeWindow(bool aEnableDragDrop, bool aResetVisibility) {
634 MOZ_ASSERT(mWindow, "Must have a window to initialize");
636 mWindow->SetWidgetListener(this);
638 if (aEnableDragDrop) {
639 mWindow->EnableDragDrop(true);
642 // propagate the z-index to the widget.
643 UpdateNativeWidgetZIndexes(this, FindNonAutoZIndex(this));
645 // make sure visibility state is accurate
647 if (aResetVisibility) {
648 SetVisibility(GetVisibility());
652 void nsView::SetNeedsWindowPropertiesSync() {
653 mNeedsWindowPropertiesSync = true;
654 if (mViewManager) {
655 mViewManager->PostPendingUpdate();
659 // Attach to a top level widget and start receiving mirrored events.
660 nsresult nsView::AttachToTopLevelWidget(nsIWidget* aWidget) {
661 MOZ_ASSERT(nullptr != aWidget, "null widget ptr");
663 /// XXXjimm This is a temporary workaround to an issue w/document
664 // viewer (bug 513162).
665 nsIWidgetListener* listener = aWidget->GetAttachedWidgetListener();
666 if (listener) {
667 nsView* oldView = listener->GetView();
668 if (oldView) {
669 oldView->DetachFromTopLevelWidget();
673 // Note, the previous device context will be released. Detaching
674 // will not restore the old one.
675 aWidget->AttachViewToTopLevel(!nsIWidget::UsePuppetWidgets());
677 mWindow = aWidget;
679 mWindow->SetAttachedWidgetListener(this);
680 if (mWindow->GetWindowType() != WindowType::Invisible) {
681 nsresult rv = mWindow->AsyncEnableDragDrop(true);
682 NS_ENSURE_SUCCESS(rv, rv);
684 mWidgetIsTopLevel = true;
686 // Refresh the view bounds
687 RecalcWidgetBounds();
688 return NS_OK;
691 // Detach this view from an attached widget.
692 nsresult nsView::DetachFromTopLevelWidget() {
693 MOZ_ASSERT(mWidgetIsTopLevel, "Not attached currently!");
694 MOZ_ASSERT(mWindow, "null mWindow for DetachFromTopLevelWidget!");
696 mWindow->SetAttachedWidgetListener(nullptr);
697 nsIWidgetListener* listener = mWindow->GetPreviouslyAttachedWidgetListener();
699 if (listener && listener->GetView()) {
700 // Ensure the listener doesn't think it's being used anymore
701 listener->GetView()->SetPreviousWidget(nullptr);
704 // If the new view's frame is paint suppressed then the window
705 // will want to use us instead until that's done
706 mWindow->SetPreviouslyAttachedWidgetListener(this);
708 mPreviousWindow = mWindow;
709 mWindow = nullptr;
711 mWidgetIsTopLevel = false;
713 return NS_OK;
716 void nsView::SetZIndex(bool aAuto, int32_t aZIndex) {
717 bool oldIsAuto = GetZIndexIsAuto();
718 mVFlags = (mVFlags & ~NS_VIEW_FLAG_AUTO_ZINDEX) |
719 (aAuto ? NS_VIEW_FLAG_AUTO_ZINDEX : 0);
720 mZIndex = aZIndex;
722 if (HasWidget() || !oldIsAuto || !aAuto) {
723 UpdateNativeWidgetZIndexes(this, FindNonAutoZIndex(this));
727 void nsView::AssertNoWindow() {
728 // XXX: it would be nice to make this a strong assert
729 if (MOZ_UNLIKELY(mWindow)) {
730 NS_ERROR("We already have a window for this view? BAD");
731 mWindow->SetWidgetListener(nullptr);
732 mWindow->Destroy();
733 mWindow = nullptr;
738 // internal window creation functions
740 void nsView::AttachWidgetEventHandler(nsIWidget* aWidget) {
741 #ifdef DEBUG
742 NS_ASSERTION(!aWidget->GetWidgetListener(), "Already have a widget listener");
743 #endif
745 aWidget->SetWidgetListener(this);
748 void nsView::DetachWidgetEventHandler(nsIWidget* aWidget) {
749 NS_ASSERTION(!aWidget->GetWidgetListener() ||
750 aWidget->GetWidgetListener()->GetView() == this,
751 "Wrong view");
752 aWidget->SetWidgetListener(nullptr);
755 #ifdef DEBUG
756 void nsView::List(FILE* out, int32_t aIndent) const {
757 int32_t i;
758 for (i = aIndent; --i >= 0;) fputs(" ", out);
759 fprintf(out, "%p ", (void*)this);
760 if (nullptr != mWindow) {
761 nscoord p2a = mViewManager->AppUnitsPerDevPixel();
762 LayoutDeviceIntRect rect = mWindow->GetClientBounds();
763 nsRect windowBounds = LayoutDeviceIntRect::ToAppUnits(rect, p2a);
764 rect = mWindow->GetBounds();
765 nsRect nonclientBounds = LayoutDeviceIntRect::ToAppUnits(rect, p2a);
766 nsrefcnt widgetRefCnt = mWindow.get()->AddRef() - 1;
767 mWindow.get()->Release();
768 int32_t Z = mWindow->GetZIndex();
769 fprintf(out, "(widget=%p[%" PRIuPTR "] z=%d pos={%d,%d,%d,%d}) ",
770 (void*)mWindow, widgetRefCnt, Z, nonclientBounds.X(),
771 nonclientBounds.Y(), windowBounds.Width(), windowBounds.Height());
773 nsRect brect = GetBounds();
774 fprintf(out, "{%d,%d,%d,%d} @ %d,%d", brect.X(), brect.Y(), brect.Width(),
775 brect.Height(), mPosX, mPosY);
776 fprintf(out, " flags=%x z=%d vis=%d frame=%p <\n", mVFlags, mZIndex,
777 int(mVis), mFrame);
778 for (nsView* kid = mFirstChild; kid; kid = kid->GetNextSibling()) {
779 NS_ASSERTION(kid->GetParent() == this, "incorrect parent");
780 kid->List(out, aIndent + 1);
782 for (i = aIndent; --i >= 0;) fputs(" ", out);
783 fputs(">\n", out);
785 #endif // DEBUG
787 nsPoint nsView::GetOffsetTo(const nsView* aOther) const {
788 return GetOffsetTo(aOther, GetViewManager()->AppUnitsPerDevPixel());
791 nsPoint nsView::GetOffsetTo(const nsView* aOther, const int32_t aAPD) const {
792 MOZ_ASSERT(GetParent() || !aOther || aOther->GetParent() || this == aOther,
793 "caller of (outer) GetOffsetTo must not pass unrelated views");
794 // We accumulate the final result in offset
795 nsPoint offset(0, 0);
796 // The offset currently accumulated at the current APD
797 nsPoint docOffset(0, 0);
798 const nsView* v = this;
799 nsViewManager* currVM = v->GetViewManager();
800 int32_t currAPD = currVM->AppUnitsPerDevPixel();
801 const nsView* root = nullptr;
802 for (; v != aOther && v; root = v, v = v->GetParent()) {
803 nsViewManager* newVM = v->GetViewManager();
804 if (newVM != currVM) {
805 int32_t newAPD = newVM->AppUnitsPerDevPixel();
806 if (newAPD != currAPD) {
807 offset += docOffset.ScaleToOtherAppUnits(currAPD, aAPD);
808 docOffset.x = docOffset.y = 0;
809 currAPD = newAPD;
811 currVM = newVM;
813 docOffset += v->GetPosition();
815 offset += docOffset.ScaleToOtherAppUnits(currAPD, aAPD);
817 if (v != aOther) {
818 // Looks like aOther wasn't an ancestor of |this|. So now we have
819 // the root-VM-relative position of |this| in |offset|. Get the
820 // root-VM-relative position of aOther and subtract it.
821 nsPoint negOffset = aOther->GetOffsetTo(root, aAPD);
822 offset -= negOffset;
825 return offset;
828 nsPoint nsView::GetOffsetToWidget(nsIWidget* aWidget) const {
829 nsPoint pt;
830 // Get the view for widget
831 nsView* widgetView = GetViewFor(aWidget);
832 if (!widgetView) {
833 return pt;
836 // Get the offset to the widget view in the widget view's APD
837 // We get the offset in the widget view's APD first and then convert to our
838 // APD afterwards so that we can include the widget view's ViewToWidgetOffset
839 // in the sum in its native APD, and then convert the whole thing to our APD
840 // so that we don't have to convert the APD of the relatively small
841 // ViewToWidgetOffset by itself with a potentially large relative rounding
842 // error.
843 pt = -widgetView->GetOffsetTo(this);
844 // Add in the offset to the widget.
845 pt += widgetView->ViewToWidgetOffset();
847 // Convert to our appunits.
848 int32_t widgetAPD = widgetView->GetViewManager()->AppUnitsPerDevPixel();
849 int32_t ourAPD = GetViewManager()->AppUnitsPerDevPixel();
850 pt = pt.ScaleToOtherAppUnits(widgetAPD, ourAPD);
851 return pt;
854 nsIWidget* nsView::GetNearestWidget(nsPoint* aOffset) const {
855 return GetNearestWidget(aOffset, GetViewManager()->AppUnitsPerDevPixel());
858 nsIWidget* nsView::GetNearestWidget(nsPoint* aOffset,
859 const int32_t aAPD) const {
860 // aOffset is based on the view's position, which ignores any chrome on
861 // attached parent widgets.
863 // We accumulate the final result in pt
864 nsPoint pt(0, 0);
865 // The offset currently accumulated at the current APD
866 nsPoint docPt(0, 0);
867 const nsView* v = this;
868 nsViewManager* currVM = v->GetViewManager();
869 int32_t currAPD = currVM->AppUnitsPerDevPixel();
870 for (; v && !v->HasWidget(); v = v->GetParent()) {
871 nsViewManager* newVM = v->GetViewManager();
872 if (newVM != currVM) {
873 int32_t newAPD = newVM->AppUnitsPerDevPixel();
874 if (newAPD != currAPD) {
875 pt += docPt.ScaleToOtherAppUnits(currAPD, aAPD);
876 docPt.x = docPt.y = 0;
877 currAPD = newAPD;
879 currVM = newVM;
881 docPt += v->GetPosition();
883 if (!v) {
884 if (aOffset) {
885 pt += docPt.ScaleToOtherAppUnits(currAPD, aAPD);
886 *aOffset = pt;
888 return nullptr;
891 // pt is now the offset from v's origin to this view's origin.
892 // We add the ViewToWidgetOffset to get the offset to the widget.
893 if (aOffset) {
894 docPt += v->ViewToWidgetOffset();
895 pt += docPt.ScaleToOtherAppUnits(currAPD, aAPD);
896 *aOffset = pt;
898 return v->GetWidget();
901 bool nsView::IsRoot() const {
902 NS_ASSERTION(mViewManager != nullptr,
903 " View manager is null in nsView::IsRoot()");
904 return mViewManager->GetRootView() == this;
907 nsRect nsView::GetBoundsInParentUnits() const {
908 nsView* parent = GetParent();
909 nsViewManager* VM = GetViewManager();
910 if (this != VM->GetRootView() || !parent) {
911 return mDimBounds;
913 int32_t ourAPD = VM->AppUnitsPerDevPixel();
914 int32_t parentAPD = parent->GetViewManager()->AppUnitsPerDevPixel();
915 return mDimBounds.ScaleToOtherAppUnitsRoundOut(ourAPD, parentAPD);
918 nsPoint nsView::ConvertFromParentCoords(nsPoint aPt) const {
919 const nsView* parent = GetParent();
920 if (parent) {
921 aPt = aPt.ScaleToOtherAppUnits(
922 parent->GetViewManager()->AppUnitsPerDevPixel(),
923 GetViewManager()->AppUnitsPerDevPixel());
925 aPt -= GetPosition();
926 return aPt;
929 static bool IsPopupWidget(nsIWidget* aWidget) {
930 return aWidget->GetWindowType() == WindowType::Popup;
933 PresShell* nsView::GetPresShell() { return GetViewManager()->GetPresShell(); }
935 bool nsView::WindowMoved(nsIWidget* aWidget, int32_t x, int32_t y,
936 ByMoveToRect aByMoveToRect) {
937 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
938 if (pm && IsPopupWidget(aWidget)) {
939 pm->PopupMoved(mFrame, LayoutDeviceIntPoint(x, y),
940 aByMoveToRect == ByMoveToRect::Yes);
941 return true;
944 return false;
947 bool nsView::WindowResized(nsIWidget* aWidget, int32_t aWidth,
948 int32_t aHeight) {
949 // The root view may not be set if this is the resize associated with
950 // window creation
951 SetForcedRepaint(true);
952 if (this == mViewManager->GetRootView()) {
953 RefPtr<nsDeviceContext> devContext = mViewManager->GetDeviceContext();
954 // ensure DPI is up-to-date, in case of window being opened and sized
955 // on a non-default-dpi display (bug 829963)
956 devContext->CheckDPIChange();
957 int32_t p2a = devContext->AppUnitsPerDevPixel();
958 if (auto* frame = GetFrame()) {
959 // Usually the resize would deal with this, but there are some cases (like
960 // web-extension popups) where frames might already be correctly sized etc
961 // due to a call to e.g. nsDocumentViewer::GetContentSize or so.
962 frame->InvalidateFrame();
965 mViewManager->SetWindowDimensions(NSIntPixelsToAppUnits(aWidth, p2a),
966 NSIntPixelsToAppUnits(aHeight, p2a));
968 if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
969 PresShell* presShell = mViewManager->GetPresShell();
970 if (presShell && presShell->GetDocument()) {
971 pm->AdjustPopupsOnWindowChange(presShell);
975 return true;
977 if (IsPopupWidget(aWidget)) {
978 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
979 if (pm) {
980 pm->PopupResized(mFrame, LayoutDeviceIntSize(aWidth, aHeight));
981 return true;
985 return false;
988 #if defined(MOZ_WIDGET_ANDROID)
989 void nsView::DynamicToolbarMaxHeightChanged(ScreenIntCoord aHeight) {
990 MOZ_ASSERT(XRE_IsParentProcess(),
991 "Should be only called for the browser parent process");
992 MOZ_ASSERT(this == mViewManager->GetRootView(),
993 "Should be called for the root view");
995 PresShell* presShell = mViewManager->GetPresShell();
996 if (!presShell) {
997 return;
1000 dom::Document* document = presShell->GetDocument();
1001 if (!document) {
1002 return;
1005 nsPIDOMWindowOuter* window = document->GetWindow();
1006 if (!window) {
1007 return;
1010 nsContentUtils::CallOnAllRemoteChildren(
1011 window, [&aHeight](dom::BrowserParent* aBrowserParent) -> CallState {
1012 aBrowserParent->DynamicToolbarMaxHeightChanged(aHeight);
1013 return CallState::Continue;
1017 void nsView::DynamicToolbarOffsetChanged(ScreenIntCoord aOffset) {
1018 MOZ_ASSERT(XRE_IsParentProcess(),
1019 "Should be only called for the browser parent process");
1020 MOZ_ASSERT(this == mViewManager->GetRootView(),
1021 "Should be called for the root view");
1023 PresShell* presShell = mViewManager->GetPresShell();
1024 if (!presShell) {
1025 return;
1028 dom::Document* document = presShell->GetDocument();
1029 if (!document) {
1030 return;
1033 nsPIDOMWindowOuter* window = document->GetWindow();
1034 if (!window) {
1035 return;
1038 nsContentUtils::CallOnAllRemoteChildren(
1039 window, [&aOffset](dom::BrowserParent* aBrowserParent) -> CallState {
1040 // Skip background tabs.
1041 if (!aBrowserParent->GetDocShellIsActive()) {
1042 return CallState::Continue;
1045 aBrowserParent->DynamicToolbarOffsetChanged(aOffset);
1046 return CallState::Stop;
1049 #endif
1051 bool nsView::RequestWindowClose(nsIWidget* aWidget) {
1052 if (mFrame && IsPopupWidget(aWidget) && mFrame->IsMenuPopupFrame()) {
1053 if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
1054 pm->HidePopup(mFrame->GetContent()->AsElement(),
1055 {HidePopupOption::DeselectMenu});
1056 return true;
1060 return false;
1063 void nsView::WillPaintWindow(nsIWidget* aWidget) {
1064 RefPtr<nsViewManager> vm = mViewManager;
1065 vm->WillPaintWindow(aWidget);
1068 bool nsView::PaintWindow(nsIWidget* aWidget, LayoutDeviceIntRegion aRegion) {
1069 NS_ASSERTION(this == nsView::GetViewFor(aWidget), "wrong view for widget?");
1071 RefPtr<nsViewManager> vm = mViewManager;
1072 bool result = vm->PaintWindow(aWidget, aRegion);
1073 return result;
1076 void nsView::DidPaintWindow() {
1077 RefPtr<nsViewManager> vm = mViewManager;
1078 vm->DidPaintWindow();
1081 void nsView::DidCompositeWindow(mozilla::layers::TransactionId aTransactionId,
1082 const TimeStamp& aCompositeStart,
1083 const TimeStamp& aCompositeEnd) {
1084 PresShell* presShell = mViewManager->GetPresShell();
1085 if (!presShell) {
1086 return;
1089 nsAutoScriptBlocker scriptBlocker;
1091 nsPresContext* context = presShell->GetPresContext();
1092 nsRootPresContext* rootContext = context->GetRootPresContext();
1093 if (rootContext) {
1094 rootContext->NotifyDidPaintForSubtree(aTransactionId, aCompositeEnd);
1097 mozilla::StartupTimeline::RecordOnce(mozilla::StartupTimeline::FIRST_PAINT2,
1098 aCompositeEnd);
1100 // If the two timestamps are identical, this was likely a fake composite
1101 // event which wouldn't be terribly useful to display.
1102 if (aCompositeStart == aCompositeEnd) {
1103 return;
1106 nsIDocShell* docShell = context->GetDocShell();
1108 if (TimelineConsumers::HasConsumer(docShell)) {
1109 TimelineConsumers::AddMarkerForDocShell(
1110 docShell, MakeUnique<CompositeTimelineMarker>(
1111 aCompositeStart, MarkerTracingType::START));
1112 TimelineConsumers::AddMarkerForDocShell(
1113 docShell, MakeUnique<CompositeTimelineMarker>(aCompositeEnd,
1114 MarkerTracingType::END));
1118 void nsView::RequestRepaint() {
1119 PresShell* presShell = mViewManager->GetPresShell();
1120 if (presShell) {
1121 presShell->ScheduleViewManagerFlush();
1125 bool nsView::ShouldNotBeVisible() {
1126 if (mFrame && mFrame->IsMenuPopupFrame()) {
1127 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1128 return !pm || !pm->IsPopupOpen(mFrame->GetContent()->AsElement());
1131 return false;
1134 nsEventStatus nsView::HandleEvent(WidgetGUIEvent* aEvent,
1135 bool aUseAttachedEvents) {
1136 MOZ_ASSERT(nullptr != aEvent->mWidget, "null widget ptr");
1138 nsEventStatus result = nsEventStatus_eIgnore;
1139 nsView* view;
1140 if (aUseAttachedEvents) {
1141 nsIWidgetListener* listener = aEvent->mWidget->GetAttachedWidgetListener();
1142 view = listener ? listener->GetView() : nullptr;
1143 } else {
1144 view = GetViewFor(aEvent->mWidget);
1147 if (view) {
1148 RefPtr<nsViewManager> vm = view->GetViewManager();
1149 vm->DispatchEvent(aEvent, view, &result);
1152 return result;
1155 void nsView::SafeAreaInsetsChanged(const ScreenIntMargin& aSafeAreaInsets) {
1156 if (!IsRoot()) {
1157 return;
1160 PresShell* presShell = mViewManager->GetPresShell();
1161 if (!presShell) {
1162 return;
1165 ScreenIntMargin windowSafeAreaInsets;
1166 LayoutDeviceIntRect windowRect = mWindow->GetScreenBounds();
1167 nsCOMPtr<nsIScreen> screen = mWindow->GetWidgetScreen();
1168 if (screen) {
1169 windowSafeAreaInsets = nsContentUtils::GetWindowSafeAreaInsets(
1170 screen, aSafeAreaInsets, windowRect);
1173 presShell->GetPresContext()->SetSafeAreaInsets(windowSafeAreaInsets);
1175 // https://github.com/w3c/csswg-drafts/issues/4670
1176 // Actually we don't set this value on sub document. This behaviour is
1177 // same as Blink.
1179 dom::Document* document = presShell->GetDocument();
1180 if (!document) {
1181 return;
1184 nsPIDOMWindowOuter* window = document->GetWindow();
1185 if (!window) {
1186 return;
1189 nsContentUtils::CallOnAllRemoteChildren(
1190 window,
1191 [windowSafeAreaInsets](dom::BrowserParent* aBrowserParent) -> CallState {
1192 Unused << aBrowserParent->SendSafeAreaInsetsChanged(
1193 windowSafeAreaInsets);
1194 return CallState::Continue;
1198 bool nsView::IsPrimaryFramePaintSuppressed() {
1199 return StaticPrefs::layout_show_previous_page() && mFrame &&
1200 mFrame->PresShell()->IsPaintingSuppressed();