diff options
Diffstat (limited to '')
-rw-r--r-- | view/crashtests/281743-1.html | 16 | ||||
-rw-r--r-- | view/crashtests/323497-1.html | 17 | ||||
-rw-r--r-- | view/crashtests/382756-1.xhtml | 12 | ||||
-rw-r--r-- | view/crashtests/38589-1.xhtml | 10 | ||||
-rw-r--r-- | view/crashtests/387745-1.svg | 11 | ||||
-rw-r--r-- | view/crashtests/399852.html | 17 | ||||
-rw-r--r-- | view/crashtests/429315-1.html | 12 | ||||
-rw-r--r-- | view/crashtests/507563-1.html | 1 | ||||
-rw-r--r-- | view/crashtests/64049-1.html | 14 | ||||
-rw-r--r-- | view/crashtests/crashtests.list | 9 | ||||
-rw-r--r-- | view/moz.build | 26 | ||||
-rw-r--r-- | view/nsView.cpp | 1201 | ||||
-rw-r--r-- | view/nsView.h | 568 | ||||
-rw-r--r-- | view/nsViewManager.cpp | 999 | ||||
-rw-r--r-- | view/nsViewManager.h | 454 |
15 files changed, 3367 insertions, 0 deletions
diff --git a/view/crashtests/281743-1.html b/view/crashtests/281743-1.html new file mode 100644 index 0000000000..c381052c4f --- /dev/null +++ b/view/crashtests/281743-1.html @@ -0,0 +1,16 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> +<head> +<title>Testcase bug 281743</title> +<style type="text/css"> +#header { position: fixed; top: 0; left: 0; z-index: 100; } +#mainmenu { position: fixed; top: 0; left: 0; z-index: 200; } +</style> +<script type="text/javascript"> +window.onload = function(e) { + var e = document.getElementsByTagName("style")[0]; + e.disabled = true; + e.disabled = false; +} +</script> +</head><body><div id="page"><div id="header"></div><div id="mainmenu"></div></div></body></html> diff --git a/view/crashtests/323497-1.html b/view/crashtests/323497-1.html new file mode 100644 index 0000000000..450069571e --- /dev/null +++ b/view/crashtests/323497-1.html @@ -0,0 +1,17 @@ +<html><head><style> +body { + display: inline; + opacity: 0.8; +} +</style> + + +</head> + +<body> + +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nullam est nunc, eleifend et, auctor eget, bibendum vel, sapien. Curabitur orci justo, ullamcorper in, ultrices id, consectetuer eget, metus. Vestibulum ornare rutrum quam. Nullam nisl. In tincidunt, sapien vitae fringilla posuere, est ante ultricies dolor, ultricies molestie eros diam id nisi. Praesent consequat velit. Nullam interdum, dui nec laoreet venenatis, felis libero pharetra dolor, ut dictum pede lorem vel est. In hac habitasse platea dictumst. Phasellus mattis eros eu libero. Suspendisse nonummy. Nullam ultricies eros eget tortor scelerisque feugiat. Aliquam erat volutpat. Integer neque. Cras a eros sit amet purus semper accumsan. Cras adipiscing porta augue. Nunc feugiat, turpis eu vehicula ultrices, augue nisl hendrerit diam, in porttitor augue sapien sit amet tortor. Ut eu tortor quis mauris posuere cursus. Donec molestie orci vitae eros. Nulla augue augue, tincidunt quis, fermentum a, rhoncus et, lectus. + +<select><option>a</option></select> + +</body></html>
\ No newline at end of file diff --git a/view/crashtests/382756-1.xhtml b/view/crashtests/382756-1.xhtml new file mode 100644 index 0000000000..34960fbf69 --- /dev/null +++ b/view/crashtests/382756-1.xhtml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<tree rows="6"> + <treechildren style="-moz-appearance: checkbox;"> + </treechildren> +</tree> + +</window> diff --git a/view/crashtests/38589-1.xhtml b/view/crashtests/38589-1.xhtml new file mode 100644 index 0000000000..69bd472bb7 --- /dev/null +++ b/view/crashtests/38589-1.xhtml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <scrollbox> + <stack> + <box style="margin-top: 50px; margin-left: 50px; opacity: 0.3;">p </box> + </stack> + </scrollbox> +</window> diff --git a/view/crashtests/387745-1.svg b/view/crashtests/387745-1.svg new file mode 100644 index 0000000000..a8236f4e36 --- /dev/null +++ b/view/crashtests/387745-1.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<g> + <foreignObject width="500" height="500"> + <div xmlns="http://www.w3.org/1999/xhtml"> + <select><option>A</option></select> + </div> + </foreignObject> +</g> + +</svg> diff --git a/view/crashtests/399852.html b/view/crashtests/399852.html new file mode 100644 index 0000000000..aa31f7a8a8 --- /dev/null +++ b/view/crashtests/399852.html @@ -0,0 +1,17 @@ +<html> +<head> +<script>
+function doe2() {
+document.body.setAttribute('style', 'overflow:scroll; direction: rtl;');
+}
+setTimeout(doe2,500); +</script> + +</head> +<body> +<span style="position: fixed;">
+<iframe onload="window.frames[0].focus()"></iframe> +</span> +<li style=" position: fixed;" contenteditable="true"></li> +</body> +</html>
\ No newline at end of file diff --git a/view/crashtests/429315-1.html b/view/crashtests/429315-1.html new file mode 100644 index 0000000000..6b03b34883 --- /dev/null +++ b/view/crashtests/429315-1.html @@ -0,0 +1,12 @@ +<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<SPAN>
+<SPAN style="POSITION: relative">
+<BR>
+<BR>
+<select>
+</select>
+</BODY>
+</HTML>
diff --git a/view/crashtests/507563-1.html b/view/crashtests/507563-1.html new file mode 100644 index 0000000000..58472885f8 --- /dev/null +++ b/view/crashtests/507563-1.html @@ -0,0 +1 @@ +<html style="-moz-transform:rotate(5deg);width:max-content"><div style="letter-spacing:-4.59pt;overflow:auto;white-space:pre;text-shadow:-125396in -1in blue;column-count:15;text-indent:4.64%;direction:rtl"> diff --git a/view/crashtests/64049-1.html b/view/crashtests/64049-1.html new file mode 100644 index 0000000000..ef7f0da39a --- /dev/null +++ b/view/crashtests/64049-1.html @@ -0,0 +1,14 @@ +<html>
+<head>
+ <title>Testcase</title>
+</head>
+
+
+<body >
+
+
+
+<iframe id="page" width=2000000 height=2000000 src="data:text/html,Q"></iframe>
+
+</body>
+</html>
\ No newline at end of file diff --git a/view/crashtests/crashtests.list b/view/crashtests/crashtests.list new file mode 100644 index 0000000000..aab94d6c7b --- /dev/null +++ b/view/crashtests/crashtests.list @@ -0,0 +1,9 @@ +load chrome://reftest/content/crashtests/view/crashtests/38589-1.xhtml +load 64049-1.html +load 281743-1.html +load 323497-1.html +skip-if(Android) load chrome://reftest/content/crashtests/view/crashtests/382756-1.xhtml # No <tree> support on Android +load 387745-1.svg +load 399852.html +load 429315-1.html +load 507563-1.html diff --git a/view/moz.build b/view/moz.build new file mode 100644 index 0000000000..80cd6b0931 --- /dev/null +++ b/view/moz.build @@ -0,0 +1,26 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Core", "Web Painting") + +EXPORTS += [ + "nsView.h", + "nsViewManager.h", +] + +UNIFIED_SOURCES += [ + "nsView.cpp", + "nsViewManager.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "/gfx/cairo/cairo/src", +] diff --git a/view/nsView.cpp b/view/nsView.cpp new file mode 100644 index 0000000000..3c6aac593a --- /dev/null +++ b/view/nsView.cpp @@ -0,0 +1,1201 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsView.h" + +#include "mozilla/Attributes.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Likely.h" +#include "mozilla/Poison.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/widget/Screen.h" +#include "nsIWidget.h" +#include "nsViewManager.h" +#include "nsIFrame.h" +#include "nsPresArena.h" +#include "nsXULPopupManager.h" +#include "nsIScreen.h" +#include "nsIWidgetListener.h" +#include "nsContentUtils.h" // for nsAutoScriptBlocker +#include "nsDocShell.h" +#include "nsLayoutUtils.h" +#include "mozilla/TimelineConsumers.h" +#include "mozilla/CompositeTimelineMarker.h" +#include "mozilla/StartupTimeline.h" + +using namespace mozilla; +using namespace mozilla::widget; + +nsView::nsView(nsViewManager* aViewManager, ViewVisibility aVisibility) + : mViewManager(aViewManager), + mParent(nullptr), + mNextSibling(nullptr), + mFirstChild(nullptr), + mFrame(nullptr), + mZIndex(0), + mVis(aVisibility), + mPosX(0), + mPosY(0), + mVFlags(0), + mWidgetIsTopLevel(false), + mForcedRepaint(false), + mNeedsWindowPropertiesSync(false) { + MOZ_COUNT_CTOR(nsView); + + // Views should be transparent by default. Not being transparent is + // a promise that the view will paint all its pixels opaquely. Views + // should make this promise explicitly by calling + // SetViewContentTransparency. +} + +void nsView::DropMouseGrabbing() { + if (mViewManager->GetPresShell()) { + PresShell::ClearMouseCaptureOnView(this); + } +} + +nsView::~nsView() { + MOZ_COUNT_DTOR(nsView); + + while (GetFirstChild()) { + nsView* child = GetFirstChild(); + if (child->GetViewManager() == mViewManager) { + child->Destroy(); + } else { + // just unhook it. Someone else will want to destroy this. + RemoveChild(child); + } + } + + if (mViewManager) { + DropMouseGrabbing(); + + nsView* rootView = mViewManager->GetRootView(); + + if (rootView) { + // Root views can have parents! + if (mParent) { + mViewManager->RemoveChild(this); + } + + if (rootView == this) { + // Inform the view manager that the root view has gone away... + mViewManager->SetRootView(nullptr); + } + } else if (mParent) { + mParent->RemoveChild(this); + } + + mViewManager = nullptr; + } else if (mParent) { + mParent->RemoveChild(this); + } + + if (mPreviousWindow) { + mPreviousWindow->SetPreviouslyAttachedWidgetListener(nullptr); + } + + // Destroy and release the widget + DestroyWidget(); + + MOZ_RELEASE_ASSERT(!mFrame); +} + +class DestroyWidgetRunnable : public Runnable { + public: + NS_DECL_NSIRUNNABLE + + explicit DestroyWidgetRunnable(nsIWidget* aWidget) + : mozilla::Runnable("DestroyWidgetRunnable"), mWidget(aWidget) {} + + private: + nsCOMPtr<nsIWidget> mWidget; +}; + +NS_IMETHODIMP DestroyWidgetRunnable::Run() { + mWidget->Destroy(); + mWidget = nullptr; + return NS_OK; +} + +void nsView::DestroyWidget() { + if (mWindow) { + // If we are not attached to a base window, we're going to tear down our + // widget here. However, if we're attached to somebody elses widget, we + // want to leave the widget alone: don't reset the client data or call + // Destroy. Just clear our event view ptr and free our reference to it. + if (mWidgetIsTopLevel) { + mWindow->SetAttachedWidgetListener(nullptr); + } else { + mWindow->SetWidgetListener(nullptr); + + nsCOMPtr<nsIRunnable> widgetDestroyer = + new DestroyWidgetRunnable(mWindow); + + // Don't leak if we happen to arrive here after the main thread + // has disappeared. + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); + if (mainThread) { + mainThread->Dispatch(widgetDestroyer.forget(), NS_DISPATCH_NORMAL); + } + } + + mWindow = nullptr; + } +} + +nsView* nsView::GetViewFor(const nsIWidget* aWidget) { + MOZ_ASSERT(aWidget, "null widget ptr"); + + nsIWidgetListener* listener = aWidget->GetWidgetListener(); + if (listener) { + if (nsView* view = listener->GetView()) { + return view; + } + } + + listener = aWidget->GetAttachedWidgetListener(); + return listener ? listener->GetView() : nullptr; +} + +void nsView::Destroy() { + this->~nsView(); + mozWritePoison(this, sizeof(*this)); + nsView::operator delete(this); +} + +void nsView::SetPosition(nscoord aX, nscoord aY) { + mDimBounds.MoveBy(aX - mPosX, aY - mPosY); + mPosX = aX; + mPosY = aY; + + NS_ASSERTION(GetParent() || (aX == 0 && aY == 0), + "Don't try to move the root widget to something non-zero"); + + ResetWidgetBounds(true, false); +} + +void nsView::ResetWidgetBounds(bool aRecurse, bool aForceSync) { + if (mWindow) { + if (!aForceSync) { + // Don't change widget geometry synchronously, since that can + // cause synchronous painting. + mViewManager->PostPendingUpdate(); + } else { + DoResetWidgetBounds(false, true); + } + return; + } + + if (aRecurse) { + // reposition any widgets under this view + for (nsView* v = GetFirstChild(); v; v = v->GetNextSibling()) { + v->ResetWidgetBounds(true, aForceSync); + } + } +} + +bool nsView::IsEffectivelyVisible() { + for (nsView* v = this; v; v = v->mParent) { + if (v->GetVisibility() == ViewVisibility::Hide) return false; + } + return true; +} + +// Cocoa and GTK round widget coordinates to the nearest global "display pixel" +// integer value. So we avoid fractional display pixel values by rounding to +// the nearest value that won't yield a fractional display pixel. +static LayoutDeviceIntRect MaybeRoundToDisplayPixels( + const LayoutDeviceIntRect& aRect, TransparencyMode aTransparency, + int32_t aRound) { + if (aRound == 1) { + return aRect; + } + + // If the widget doesn't support transparency, we prefer truncating to + // ceiling, so that we don't have extra pixels not painted by our frame. + auto size = aTransparency == TransparencyMode::Opaque + ? aRect.Size().TruncatedToMultiple(aRound) + : aRect.Size().CeiledToMultiple(aRound); + Unused << NS_WARN_IF(aTransparency == TransparencyMode::Opaque && + size != aRect.Size()); + return {aRect.TopLeft().RoundedToMultiple(aRound), size}; +} + +LayoutDeviceIntRect nsView::CalcWidgetBounds(WindowType aType, + TransparencyMode aTransparency) { + int32_t p2a = mViewManager->AppUnitsPerDevPixel(); + + nsRect viewBounds(mDimBounds); + + nsView* parent = GetParent(); + nsIWidget* parentWidget = nullptr; + if (parent) { + nsPoint offset; + parentWidget = parent->GetNearestWidget(&offset, p2a); + // make viewBounds be relative to the parent widget, in appunits + viewBounds += offset; + + if (parentWidget && aType == WindowType::Popup && IsEffectivelyVisible()) { + // put offset into screen coordinates. (based on client area origin) + LayoutDeviceIntPoint screenPoint = parentWidget->WidgetToScreenOffset(); + viewBounds += nsPoint(NSIntPixelsToAppUnits(screenPoint.x, p2a), + NSIntPixelsToAppUnits(screenPoint.y, p2a)); + } + } + + // Compute widget bounds in device pixels + const LayoutDeviceIntRect newBounds = [&] { + // TODO(emilio): We should probably use outside pixels for transparent + // windows (not just popups) as well. + if (aType != WindowType::Popup) { + return LayoutDeviceIntRect::FromUnknownRect( + viewBounds.ToNearestPixels(p2a)); + } + // We use outside pixels for transparent windows if possible, so that we + // don't truncate the contents. For opaque popups, we use nearest pixels + // which prevents having pixels not drawn by the frame. + const bool opaque = aTransparency == TransparencyMode::Opaque; + const auto idealBounds = LayoutDeviceIntRect::FromUnknownRect( + opaque ? viewBounds.ToNearestPixels(p2a) + : viewBounds.ToOutsidePixels(p2a)); + + nsIWidget* widget = parentWidget ? parentWidget : mWindow.get(); + if (!widget) { + return idealBounds; + } + const int32_t round = widget->RoundsWidgetCoordinatesTo(); + return MaybeRoundToDisplayPixels(idealBounds, aTransparency, round); + }(); + + // Compute where the top-left of our widget ended up relative to the parent + // widget, in appunits. + nsPoint roundedOffset(NSIntPixelsToAppUnits(newBounds.X(), p2a), + NSIntPixelsToAppUnits(newBounds.Y(), p2a)); + + // mViewToWidgetOffset is added to coordinates relative to the view origin + // to get coordinates relative to the widget. + // The view origin, relative to the parent widget, is at + // (mPosX,mPosY) - mDimBounds.TopLeft() + viewBounds.TopLeft(). + // Our widget, relative to the parent widget, is roundedOffset. + mViewToWidgetOffset = nsPoint(mPosX, mPosY) - mDimBounds.TopLeft() + + viewBounds.TopLeft() - roundedOffset; + + return newBounds; +} + +LayoutDeviceIntRect nsView::RecalcWidgetBounds() { + MOZ_ASSERT(mWindow); + return CalcWidgetBounds(mWindow->GetWindowType(), + mWindow->GetTransparencyMode()); +} + +void nsView::DoResetWidgetBounds(bool aMoveOnly, bool aInvalidateChangedSize) { + // The geometry of a root view's widget is controlled externally, + // NOT by sizing or positioning the view + if (mViewManager->GetRootView() == this) { + return; + } + + MOZ_ASSERT(mWindow, "Why was this called??"); + + // Hold this ref to make sure it stays alive. + nsCOMPtr<nsIWidget> widget = mWindow; + + // Stash a copy of these and use them so we can handle this being deleted (say + // from sync painting/flushing from Show/Move/Resize on the widget). + LayoutDeviceIntRect newBounds; + + WindowType type = widget->GetWindowType(); + + LayoutDeviceIntRect curBounds = widget->GetClientBounds(); + bool invisiblePopup = type == WindowType::Popup && + ((curBounds.IsEmpty() && mDimBounds.IsEmpty()) || + mVis == ViewVisibility::Hide); + + if (invisiblePopup) { + // We're going to hit the early exit below, avoid calling CalcWidgetBounds. + } else { + newBounds = CalcWidgetBounds(type, widget->GetTransparencyMode()); + invisiblePopup = newBounds.IsEmpty(); + } + + bool curVisibility = widget->IsVisible(); + bool newVisibility = !invisiblePopup && IsEffectivelyVisible(); + if (curVisibility && !newVisibility) { + widget->Show(false); + } + + if (invisiblePopup) { + // Don't manipulate empty or hidden popup widgets. For example there's no + // point moving hidden comboboxes around, or doing X server roundtrips + // to compute their true screen position. This could mean that + // WidgetToScreen operations on these widgets don't return up-to-date + // values, but popup positions aren't reliable anyway because of correction + // to be on or off-screen. + return; + } + + // Apply the widget size constraints to newBounds. + widget->ConstrainSize(&newBounds.width, &newBounds.height); + + bool changedPos = curBounds.TopLeft() != newBounds.TopLeft(); + bool changedSize = curBounds.Size() != newBounds.Size(); + + // Child views are never attached to top level widgets, this is safe. + + // Coordinates are converted to desktop pixels for window Move/Resize APIs, + // because of the potential for device-pixel coordinate spaces for mixed + // hidpi/lodpi screens to overlap each other and result in bad placement + // (bug 814434). + + DesktopToLayoutDeviceScale scale = widget->GetDesktopToDeviceScaleByScreen(); + + DesktopRect deskRect = newBounds / scale; + if (changedPos) { + if (changedSize && !aMoveOnly) { + widget->ResizeClient(deskRect, aInvalidateChangedSize); + } else { + widget->MoveClient(deskRect.TopLeft()); + } + } else { + if (changedSize && !aMoveOnly) { + widget->ResizeClient(deskRect.Size(), aInvalidateChangedSize); + } // else do nothing! + } + + if (!curVisibility && newVisibility) { + widget->Show(true); + } +} + +void nsView::SetDimensions(const nsRect& aRect, bool aPaint, + bool aResizeWidget) { + nsRect dims = aRect; + dims.MoveBy(mPosX, mPosY); + + // Don't use nsRect's operator== here, since it returns true when + // both rects are empty even if they have different widths and we + // have cases where that sort of thing matters to us. + if (mDimBounds.TopLeft() == dims.TopLeft() && + mDimBounds.Size() == dims.Size()) { + return; + } + + mDimBounds = dims; + + if (aResizeWidget) { + ResetWidgetBounds(false, false); + } +} + +void nsView::NotifyEffectiveVisibilityChanged(bool aEffectivelyVisible) { + if (!aEffectivelyVisible) { + DropMouseGrabbing(); + } + + SetForcedRepaint(true); + + if (nullptr != mWindow) { + ResetWidgetBounds(false, false); + } + + for (nsView* child = mFirstChild; child; child = child->mNextSibling) { + if (child->mVis == ViewVisibility::Hide) { + // It was effectively hidden and still is + continue; + } + // Our child is visible if we are + child->NotifyEffectiveVisibilityChanged(aEffectivelyVisible); + } +} + +void nsView::SetVisibility(ViewVisibility aVisibility) { + mVis = aVisibility; + NotifyEffectiveVisibilityChanged(IsEffectivelyVisible()); +} + +void nsView::SetFloating(bool aFloatingView) { + if (aFloatingView) + mVFlags |= NS_VIEW_FLAG_FLOATING; + else + mVFlags &= ~NS_VIEW_FLAG_FLOATING; +} + +void nsView::InvalidateHierarchy() { + if (mViewManager->GetRootView() == this) mViewManager->InvalidateHierarchy(); + + for (nsView* child = mFirstChild; child; child = child->GetNextSibling()) + child->InvalidateHierarchy(); +} + +void nsView::InsertChild(nsView* aChild, nsView* aSibling) { + MOZ_ASSERT(nullptr != aChild, "null ptr"); + + if (nullptr != aChild) { + if (nullptr != aSibling) { +#ifdef DEBUG + NS_ASSERTION(aSibling->GetParent() == this, + "tried to insert view with invalid sibling"); +#endif + // insert after sibling + aChild->SetNextSibling(aSibling->GetNextSibling()); + aSibling->SetNextSibling(aChild); + } else { + aChild->SetNextSibling(mFirstChild); + mFirstChild = aChild; + } + aChild->SetParent(this); + + // If we just inserted a root view, then update the RootViewManager + // on all view managers in the new subtree. + + nsViewManager* vm = aChild->GetViewManager(); + if (vm->GetRootView() == aChild) { + aChild->InvalidateHierarchy(); + } + } +} + +void nsView::RemoveChild(nsView* child) { + MOZ_ASSERT(nullptr != child, "null ptr"); + + if (nullptr != child) { + nsView* prevKid = nullptr; + nsView* kid = mFirstChild; + DebugOnly<bool> found = false; + while (nullptr != kid) { + if (kid == child) { + if (nullptr != prevKid) { + prevKid->SetNextSibling(kid->GetNextSibling()); + } else { + mFirstChild = kid->GetNextSibling(); + } + child->SetParent(nullptr); + found = true; + break; + } + prevKid = kid; + kid = kid->GetNextSibling(); + } + NS_ASSERTION(found, "tried to remove non child"); + + // If we just removed a root view, then update the RootViewManager + // on all view managers in the removed subtree. + + nsViewManager* vm = child->GetViewManager(); + if (vm->GetRootView() == child) { + child->InvalidateHierarchy(); + } + } +} + +// Native widgets ultimately just can't deal with the awesome power of +// CSS2 z-index. However, we set the z-index on the widget anyway +// because in many simple common cases the widgets do end up in the +// right order. We set each widget's z-index to the z-index of the +// nearest ancestor that has non-auto z-index. +static void UpdateNativeWidgetZIndexes(nsView* aView, int32_t aZIndex) { + if (aView->HasWidget()) { + nsIWidget* widget = aView->GetWidget(); + if (widget->GetZIndex() != aZIndex) { + widget->SetZIndex(aZIndex); + } + } else { + for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) { + if (v->GetZIndexIsAuto()) { + UpdateNativeWidgetZIndexes(v, aZIndex); + } + } + } +} + +static int32_t FindNonAutoZIndex(nsView* aView) { + while (aView) { + if (!aView->GetZIndexIsAuto()) { + return aView->GetZIndex(); + } + aView = aView->GetParent(); + } + return 0; +} + +struct DefaultWidgetInitData : public widget::InitData { + DefaultWidgetInitData() : widget::InitData() { + mWindowType = WindowType::Child; + mClipChildren = true; + mClipSiblings = true; + } +}; + +nsresult nsView::CreateWidget(widget::InitData* aWidgetInitData, + bool aEnableDragDrop, bool aResetVisibility) { + AssertNoWindow(); + MOZ_ASSERT( + !aWidgetInitData || aWidgetInitData->mWindowType != WindowType::Popup, + "Use CreateWidgetForPopup"); + + DefaultWidgetInitData defaultInitData; + aWidgetInitData = aWidgetInitData ? aWidgetInitData : &defaultInitData; + LayoutDeviceIntRect trect = CalcWidgetBounds( + aWidgetInitData->mWindowType, aWidgetInitData->mTransparencyMode); + + nsIWidget* parentWidget = + GetParent() ? GetParent()->GetNearestWidget(nullptr) : nullptr; + if (!parentWidget) { + NS_ERROR("nsView::CreateWidget without suitable parent widget??"); + return NS_ERROR_FAILURE; + } + + // XXX: using aForceUseIWidgetParent=true to preserve previous + // semantics. It's not clear that it's actually needed. + mWindow = parentWidget->CreateChild(trect, aWidgetInitData, true); + if (!mWindow) { + return NS_ERROR_FAILURE; + } + + InitializeWindow(aEnableDragDrop, aResetVisibility); + + return NS_OK; +} + +nsresult nsView::CreateWidgetForParent(nsIWidget* aParentWidget, + widget::InitData* aWidgetInitData, + bool aEnableDragDrop, + bool aResetVisibility) { + AssertNoWindow(); + MOZ_ASSERT( + !aWidgetInitData || aWidgetInitData->mWindowType != WindowType::Popup, + "Use CreateWidgetForPopup"); + MOZ_ASSERT(aParentWidget, "Parent widget required"); + + DefaultWidgetInitData defaultInitData; + aWidgetInitData = aWidgetInitData ? aWidgetInitData : &defaultInitData; + + LayoutDeviceIntRect trect = CalcWidgetBounds( + aWidgetInitData->mWindowType, aWidgetInitData->mTransparencyMode); + + mWindow = aParentWidget->CreateChild(trect, aWidgetInitData); + if (!mWindow) { + return NS_ERROR_FAILURE; + } + + InitializeWindow(aEnableDragDrop, aResetVisibility); + + return NS_OK; +} + +nsresult nsView::CreateWidgetForPopup(widget::InitData* aWidgetInitData, + nsIWidget* aParentWidget) { + AssertNoWindow(); + MOZ_ASSERT(aWidgetInitData, "Widget init data required"); + MOZ_ASSERT(aWidgetInitData->mWindowType == WindowType::Popup, + "Use one of the other CreateWidget methods"); + + LayoutDeviceIntRect trect = CalcWidgetBounds( + aWidgetInitData->mWindowType, aWidgetInitData->mTransparencyMode); + + // XXX/cjones: having these two separate creation cases seems ... um + // ... unnecessary, but it's the way the old code did it. Please + // unify them by first finding a suitable parent nsIWidget, then + // getting rid of aForceUseIWidgetParent. + if (aParentWidget) { + // XXX: using aForceUseIWidgetParent=true to preserve previous + // semantics. It's not clear that it's actually needed. + mWindow = aParentWidget->CreateChild(trect, aWidgetInitData, true); + } else { + nsIWidget* nearestParent = + GetParent() ? GetParent()->GetNearestWidget(nullptr) : nullptr; + if (!nearestParent) { + // Without a parent, we can't make a popup. This can happen + // when printing + return NS_ERROR_FAILURE; + } + + mWindow = nearestParent->CreateChild(trect, aWidgetInitData); + } + if (!mWindow) { + return NS_ERROR_FAILURE; + } + + InitializeWindow(/* aEnableDragDrop = */ true, /* aResetVisibility = */ true); + + return NS_OK; +} + +void nsView::InitializeWindow(bool aEnableDragDrop, bool aResetVisibility) { + MOZ_ASSERT(mWindow, "Must have a window to initialize"); + + mWindow->SetWidgetListener(this); + + if (aEnableDragDrop) { + mWindow->EnableDragDrop(true); + } + + // propagate the z-index to the widget. + UpdateNativeWidgetZIndexes(this, FindNonAutoZIndex(this)); + + // make sure visibility state is accurate + + if (aResetVisibility) { + SetVisibility(GetVisibility()); + } +} + +void nsView::SetNeedsWindowPropertiesSync() { + mNeedsWindowPropertiesSync = true; + if (mViewManager) { + mViewManager->PostPendingUpdate(); + } +} + +// Attach to a top level widget and start receiving mirrored events. +nsresult nsView::AttachToTopLevelWidget(nsIWidget* aWidget) { + MOZ_ASSERT(nullptr != aWidget, "null widget ptr"); + + /// XXXjimm This is a temporary workaround to an issue w/document + // viewer (bug 513162). + nsIWidgetListener* listener = aWidget->GetAttachedWidgetListener(); + if (listener) { + nsView* oldView = listener->GetView(); + if (oldView) { + oldView->DetachFromTopLevelWidget(); + } + } + + // Note, the previous device context will be released. Detaching + // will not restore the old one. + aWidget->AttachViewToTopLevel(!nsIWidget::UsePuppetWidgets()); + + mWindow = aWidget; + + mWindow->SetAttachedWidgetListener(this); + if (mWindow->GetWindowType() != WindowType::Invisible) { + nsresult rv = mWindow->AsyncEnableDragDrop(true); + NS_ENSURE_SUCCESS(rv, rv); + } + mWidgetIsTopLevel = true; + + // Refresh the view bounds + RecalcWidgetBounds(); + return NS_OK; +} + +// Detach this view from an attached widget. +nsresult nsView::DetachFromTopLevelWidget() { + MOZ_ASSERT(mWidgetIsTopLevel, "Not attached currently!"); + MOZ_ASSERT(mWindow, "null mWindow for DetachFromTopLevelWidget!"); + + mWindow->SetAttachedWidgetListener(nullptr); + nsIWidgetListener* listener = mWindow->GetPreviouslyAttachedWidgetListener(); + + if (listener && listener->GetView()) { + // Ensure the listener doesn't think it's being used anymore + listener->GetView()->SetPreviousWidget(nullptr); + } + + // If the new view's frame is paint suppressed then the window + // will want to use us instead until that's done + mWindow->SetPreviouslyAttachedWidgetListener(this); + + mPreviousWindow = mWindow; + mWindow = nullptr; + + mWidgetIsTopLevel = false; + + return NS_OK; +} + +void nsView::SetZIndex(bool aAuto, int32_t aZIndex) { + bool oldIsAuto = GetZIndexIsAuto(); + mVFlags = (mVFlags & ~NS_VIEW_FLAG_AUTO_ZINDEX) | + (aAuto ? NS_VIEW_FLAG_AUTO_ZINDEX : 0); + mZIndex = aZIndex; + + if (HasWidget() || !oldIsAuto || !aAuto) { + UpdateNativeWidgetZIndexes(this, FindNonAutoZIndex(this)); + } +} + +void nsView::AssertNoWindow() { + // XXX: it would be nice to make this a strong assert + if (MOZ_UNLIKELY(mWindow)) { + NS_ERROR("We already have a window for this view? BAD"); + mWindow->SetWidgetListener(nullptr); + mWindow->Destroy(); + mWindow = nullptr; + } +} + +// +// internal window creation functions +// +void nsView::AttachWidgetEventHandler(nsIWidget* aWidget) { +#ifdef DEBUG + NS_ASSERTION(!aWidget->GetWidgetListener(), "Already have a widget listener"); +#endif + + aWidget->SetWidgetListener(this); +} + +void nsView::DetachWidgetEventHandler(nsIWidget* aWidget) { + NS_ASSERTION(!aWidget->GetWidgetListener() || + aWidget->GetWidgetListener()->GetView() == this, + "Wrong view"); + aWidget->SetWidgetListener(nullptr); +} + +#ifdef DEBUG +void nsView::List(FILE* out, int32_t aIndent) const { + int32_t i; + for (i = aIndent; --i >= 0;) fputs(" ", out); + fprintf(out, "%p ", (void*)this); + if (nullptr != mWindow) { + nscoord p2a = mViewManager->AppUnitsPerDevPixel(); + LayoutDeviceIntRect rect = mWindow->GetClientBounds(); + nsRect windowBounds = LayoutDeviceIntRect::ToAppUnits(rect, p2a); + rect = mWindow->GetBounds(); + nsRect nonclientBounds = LayoutDeviceIntRect::ToAppUnits(rect, p2a); + nsrefcnt widgetRefCnt = mWindow.get()->AddRef() - 1; + mWindow.get()->Release(); + int32_t Z = mWindow->GetZIndex(); + fprintf(out, "(widget=%p[%" PRIuPTR "] z=%d pos={%d,%d,%d,%d}) ", + (void*)mWindow, widgetRefCnt, Z, nonclientBounds.X(), + nonclientBounds.Y(), windowBounds.Width(), windowBounds.Height()); + } + nsRect brect = GetBounds(); + fprintf(out, "{%d,%d,%d,%d} @ %d,%d", brect.X(), brect.Y(), brect.Width(), + brect.Height(), mPosX, mPosY); + fprintf(out, " flags=%x z=%d vis=%d frame=%p <\n", mVFlags, mZIndex, + int(mVis), mFrame); + for (nsView* kid = mFirstChild; kid; kid = kid->GetNextSibling()) { + NS_ASSERTION(kid->GetParent() == this, "incorrect parent"); + kid->List(out, aIndent + 1); + } + for (i = aIndent; --i >= 0;) fputs(" ", out); + fputs(">\n", out); +} +#endif // DEBUG + +nsPoint nsView::GetOffsetTo(const nsView* aOther) const { + return GetOffsetTo(aOther, GetViewManager()->AppUnitsPerDevPixel()); +} + +nsPoint nsView::GetOffsetTo(const nsView* aOther, const int32_t aAPD) const { + MOZ_ASSERT(GetParent() || !aOther || aOther->GetParent() || this == aOther, + "caller of (outer) GetOffsetTo must not pass unrelated views"); + // We accumulate the final result in offset + nsPoint offset(0, 0); + // The offset currently accumulated at the current APD + nsPoint docOffset(0, 0); + const nsView* v = this; + nsViewManager* currVM = v->GetViewManager(); + int32_t currAPD = currVM->AppUnitsPerDevPixel(); + const nsView* root = nullptr; + for (; v != aOther && v; root = v, v = v->GetParent()) { + nsViewManager* newVM = v->GetViewManager(); + if (newVM != currVM) { + int32_t newAPD = newVM->AppUnitsPerDevPixel(); + if (newAPD != currAPD) { + offset += docOffset.ScaleToOtherAppUnits(currAPD, aAPD); + docOffset.x = docOffset.y = 0; + currAPD = newAPD; + } + currVM = newVM; + } + docOffset += v->GetPosition(); + } + offset += docOffset.ScaleToOtherAppUnits(currAPD, aAPD); + + if (v != aOther) { + // Looks like aOther wasn't an ancestor of |this|. So now we have + // the root-VM-relative position of |this| in |offset|. Get the + // root-VM-relative position of aOther and subtract it. + nsPoint negOffset = aOther->GetOffsetTo(root, aAPD); + offset -= negOffset; + } + + return offset; +} + +nsPoint nsView::GetOffsetToWidget(nsIWidget* aWidget) const { + nsPoint pt; + // Get the view for widget + nsView* widgetView = GetViewFor(aWidget); + if (!widgetView) { + return pt; + } + + // Get the offset to the widget view in the widget view's APD + // We get the offset in the widget view's APD first and then convert to our + // APD afterwards so that we can include the widget view's ViewToWidgetOffset + // in the sum in its native APD, and then convert the whole thing to our APD + // so that we don't have to convert the APD of the relatively small + // ViewToWidgetOffset by itself with a potentially large relative rounding + // error. + pt = -widgetView->GetOffsetTo(this); + // Add in the offset to the widget. + pt += widgetView->ViewToWidgetOffset(); + + // Convert to our appunits. + int32_t widgetAPD = widgetView->GetViewManager()->AppUnitsPerDevPixel(); + int32_t ourAPD = GetViewManager()->AppUnitsPerDevPixel(); + pt = pt.ScaleToOtherAppUnits(widgetAPD, ourAPD); + return pt; +} + +nsIWidget* nsView::GetNearestWidget(nsPoint* aOffset) const { + return GetNearestWidget(aOffset, GetViewManager()->AppUnitsPerDevPixel()); +} + +nsIWidget* nsView::GetNearestWidget(nsPoint* aOffset, + const int32_t aAPD) const { + // aOffset is based on the view's position, which ignores any chrome on + // attached parent widgets. + + // We accumulate the final result in pt + nsPoint pt(0, 0); + // The offset currently accumulated at the current APD + nsPoint docPt(0, 0); + const nsView* v = this; + nsViewManager* currVM = v->GetViewManager(); + int32_t currAPD = currVM->AppUnitsPerDevPixel(); + for (; v && !v->HasWidget(); v = v->GetParent()) { + nsViewManager* newVM = v->GetViewManager(); + if (newVM != currVM) { + int32_t newAPD = newVM->AppUnitsPerDevPixel(); + if (newAPD != currAPD) { + pt += docPt.ScaleToOtherAppUnits(currAPD, aAPD); + docPt.x = docPt.y = 0; + currAPD = newAPD; + } + currVM = newVM; + } + docPt += v->GetPosition(); + } + if (!v) { + if (aOffset) { + pt += docPt.ScaleToOtherAppUnits(currAPD, aAPD); + *aOffset = pt; + } + return nullptr; + } + + // pt is now the offset from v's origin to this view's origin. + // We add the ViewToWidgetOffset to get the offset to the widget. + if (aOffset) { + docPt += v->ViewToWidgetOffset(); + pt += docPt.ScaleToOtherAppUnits(currAPD, aAPD); + *aOffset = pt; + } + return v->GetWidget(); +} + +bool nsView::IsRoot() const { + NS_ASSERTION(mViewManager != nullptr, + " View manager is null in nsView::IsRoot()"); + return mViewManager->GetRootView() == this; +} + +nsRect nsView::GetBoundsInParentUnits() const { + nsView* parent = GetParent(); + nsViewManager* VM = GetViewManager(); + if (this != VM->GetRootView() || !parent) { + return mDimBounds; + } + int32_t ourAPD = VM->AppUnitsPerDevPixel(); + int32_t parentAPD = parent->GetViewManager()->AppUnitsPerDevPixel(); + return mDimBounds.ScaleToOtherAppUnitsRoundOut(ourAPD, parentAPD); +} + +nsPoint nsView::ConvertFromParentCoords(nsPoint aPt) const { + const nsView* parent = GetParent(); + if (parent) { + aPt = aPt.ScaleToOtherAppUnits( + parent->GetViewManager()->AppUnitsPerDevPixel(), + GetViewManager()->AppUnitsPerDevPixel()); + } + aPt -= GetPosition(); + return aPt; +} + +static bool IsPopupWidget(nsIWidget* aWidget) { + return aWidget->GetWindowType() == WindowType::Popup; +} + +PresShell* nsView::GetPresShell() { return GetViewManager()->GetPresShell(); } + +bool nsView::WindowMoved(nsIWidget* aWidget, int32_t x, int32_t y, + ByMoveToRect aByMoveToRect) { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && IsPopupWidget(aWidget)) { + pm->PopupMoved(mFrame, LayoutDeviceIntPoint(x, y), + aByMoveToRect == ByMoveToRect::Yes); + return true; + } + + return false; +} + +bool nsView::WindowResized(nsIWidget* aWidget, int32_t aWidth, + int32_t aHeight) { + // The root view may not be set if this is the resize associated with + // window creation + SetForcedRepaint(true); + if (this == mViewManager->GetRootView()) { + RefPtr<nsDeviceContext> devContext = mViewManager->GetDeviceContext(); + // ensure DPI is up-to-date, in case of window being opened and sized + // on a non-default-dpi display (bug 829963) + devContext->CheckDPIChange(); + int32_t p2a = devContext->AppUnitsPerDevPixel(); + if (auto* frame = GetFrame()) { + // Usually the resize would deal with this, but there are some cases (like + // web-extension popups) where frames might already be correctly sized etc + // due to a call to e.g. nsDocumentViewer::GetContentSize or so. + frame->InvalidateFrame(); + } + + mViewManager->SetWindowDimensions(NSIntPixelsToAppUnits(aWidth, p2a), + NSIntPixelsToAppUnits(aHeight, p2a)); + + if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { + PresShell* presShell = mViewManager->GetPresShell(); + if (presShell && presShell->GetDocument()) { + pm->AdjustPopupsOnWindowChange(presShell); + } + } + + return true; + } + if (IsPopupWidget(aWidget)) { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + pm->PopupResized(mFrame, LayoutDeviceIntSize(aWidth, aHeight)); + return true; + } + } + + return false; +} + +#if defined(MOZ_WIDGET_ANDROID) +void nsView::DynamicToolbarMaxHeightChanged(ScreenIntCoord aHeight) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Should be only called for the browser parent process"); + MOZ_ASSERT(this == mViewManager->GetRootView(), + "Should be called for the root view"); + + PresShell* presShell = mViewManager->GetPresShell(); + if (!presShell) { + return; + } + + dom::Document* document = presShell->GetDocument(); + if (!document) { + return; + } + + nsPIDOMWindowOuter* window = document->GetWindow(); + if (!window) { + return; + } + + nsContentUtils::CallOnAllRemoteChildren( + window, [&aHeight](dom::BrowserParent* aBrowserParent) -> CallState { + aBrowserParent->DynamicToolbarMaxHeightChanged(aHeight); + return CallState::Continue; + }); +} + +void nsView::DynamicToolbarOffsetChanged(ScreenIntCoord aOffset) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Should be only called for the browser parent process"); + MOZ_ASSERT(this == mViewManager->GetRootView(), + "Should be called for the root view"); + + PresShell* presShell = mViewManager->GetPresShell(); + if (!presShell) { + return; + } + + dom::Document* document = presShell->GetDocument(); + if (!document) { + return; + } + + nsPIDOMWindowOuter* window = document->GetWindow(); + if (!window) { + return; + } + + nsContentUtils::CallOnAllRemoteChildren( + window, [&aOffset](dom::BrowserParent* aBrowserParent) -> CallState { + // Skip background tabs. + if (!aBrowserParent->GetDocShellIsActive()) { + return CallState::Continue; + } + + aBrowserParent->DynamicToolbarOffsetChanged(aOffset); + return CallState::Stop; + }); +} +#endif + +bool nsView::RequestWindowClose(nsIWidget* aWidget) { + if (mFrame && IsPopupWidget(aWidget) && mFrame->IsMenuPopupFrame()) { + if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { + pm->HidePopup(mFrame->GetContent()->AsElement(), + {HidePopupOption::DeselectMenu}); + return true; + } + } + + return false; +} + +void nsView::WillPaintWindow(nsIWidget* aWidget) { + RefPtr<nsViewManager> vm = mViewManager; + vm->WillPaintWindow(aWidget); +} + +bool nsView::PaintWindow(nsIWidget* aWidget, LayoutDeviceIntRegion aRegion) { + NS_ASSERTION(this == nsView::GetViewFor(aWidget), "wrong view for widget?"); + + RefPtr<nsViewManager> vm = mViewManager; + bool result = vm->PaintWindow(aWidget, aRegion); + return result; +} + +void nsView::DidPaintWindow() { + RefPtr<nsViewManager> vm = mViewManager; + vm->DidPaintWindow(); +} + +void nsView::DidCompositeWindow(mozilla::layers::TransactionId aTransactionId, + const TimeStamp& aCompositeStart, + const TimeStamp& aCompositeEnd) { + PresShell* presShell = mViewManager->GetPresShell(); + if (!presShell) { + return; + } + + nsAutoScriptBlocker scriptBlocker; + + nsPresContext* context = presShell->GetPresContext(); + nsRootPresContext* rootContext = context->GetRootPresContext(); + if (rootContext) { + rootContext->NotifyDidPaintForSubtree(aTransactionId, aCompositeEnd); + } + + mozilla::StartupTimeline::RecordOnce(mozilla::StartupTimeline::FIRST_PAINT2, + aCompositeEnd); + + // If the two timestamps are identical, this was likely a fake composite + // event which wouldn't be terribly useful to display. + if (aCompositeStart == aCompositeEnd) { + return; + } + + nsIDocShell* docShell = context->GetDocShell(); + + if (TimelineConsumers::HasConsumer(docShell)) { + TimelineConsumers::AddMarkerForDocShell( + docShell, MakeUnique<CompositeTimelineMarker>( + aCompositeStart, MarkerTracingType::START)); + TimelineConsumers::AddMarkerForDocShell( + docShell, MakeUnique<CompositeTimelineMarker>(aCompositeEnd, + MarkerTracingType::END)); + } +} + +void nsView::RequestRepaint() { + PresShell* presShell = mViewManager->GetPresShell(); + if (presShell) { + presShell->ScheduleViewManagerFlush(); + } +} + +bool nsView::ShouldNotBeVisible() { + if (mFrame && mFrame->IsMenuPopupFrame()) { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + return !pm || !pm->IsPopupOpen(mFrame->GetContent()->AsElement()); + } + + return false; +} + +nsEventStatus nsView::HandleEvent(WidgetGUIEvent* aEvent, + bool aUseAttachedEvents) { + MOZ_ASSERT(nullptr != aEvent->mWidget, "null widget ptr"); + + nsEventStatus result = nsEventStatus_eIgnore; + nsView* view; + if (aUseAttachedEvents) { + nsIWidgetListener* listener = aEvent->mWidget->GetAttachedWidgetListener(); + view = listener ? listener->GetView() : nullptr; + } else { + view = GetViewFor(aEvent->mWidget); + } + + if (view) { + RefPtr<nsViewManager> vm = view->GetViewManager(); + vm->DispatchEvent(aEvent, view, &result); + } + + return result; +} + +void nsView::SafeAreaInsetsChanged(const ScreenIntMargin& aSafeAreaInsets) { + if (!IsRoot()) { + return; + } + + PresShell* presShell = mViewManager->GetPresShell(); + if (!presShell) { + return; + } + + ScreenIntMargin windowSafeAreaInsets; + LayoutDeviceIntRect windowRect = mWindow->GetScreenBounds(); + nsCOMPtr<nsIScreen> screen = mWindow->GetWidgetScreen(); + if (screen) { + windowSafeAreaInsets = nsContentUtils::GetWindowSafeAreaInsets( + screen, aSafeAreaInsets, windowRect); + } + + presShell->GetPresContext()->SetSafeAreaInsets(windowSafeAreaInsets); + + // https://github.com/w3c/csswg-drafts/issues/4670 + // Actually we don't set this value on sub document. This behaviour is + // same as Blink. + + dom::Document* document = presShell->GetDocument(); + if (!document) { + return; + } + + nsPIDOMWindowOuter* window = document->GetWindow(); + if (!window) { + return; + } + + nsContentUtils::CallOnAllRemoteChildren( + window, + [windowSafeAreaInsets](dom::BrowserParent* aBrowserParent) -> CallState { + Unused << aBrowserParent->SendSafeAreaInsetsChanged( + windowSafeAreaInsets); + return CallState::Continue; + }); +} + +bool nsView::IsPrimaryFramePaintSuppressed() { + return StaticPrefs::layout_show_previous_page() && mFrame && + mFrame->PresShell()->IsPaintingSuppressed(); +} diff --git a/view/nsView.h b/view/nsView.h new file mode 100644 index 0000000000..832c6358ad --- /dev/null +++ b/view/nsView.h @@ -0,0 +1,568 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsView_h__ +#define nsView_h__ + +#include "nsCoord.h" +#include "nsRect.h" +#include "nsPoint.h" +#include "nsRegion.h" +#include "nsCRT.h" +#include "nsCOMPtr.h" +#include "nsIWidgetListener.h" +#include "Units.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" +#include "mozilla/UniquePtr.h" + +class nsViewManager; +class nsIWidget; +class nsIFrame; + +namespace mozilla { +class PresShell; +namespace widget { +struct InitData; +enum class TransparencyMode : uint8_t; +enum class WindowType : uint8_t; +} // namespace widget +} // namespace mozilla + +/** + * nsView's serve two main purposes: 1) a bridge between nsIFrame's and + * nsIWidget's, 2) linking the frame tree of a(n) (in-process) subdocument with + * its parent document frame tree. Historically views were used for more things, + * but their role has been reduced, and could be reduced to nothing in the + * future (bug 337801 tracks removing views). Views are generally associated + * with a frame. A view that does not have a frame is called an anonymous view. + * Some frames also have associated widgets (think os level windows). If a frame + * has a widget it must also have a view, but not all frames with views will + * have widgets. + * + * Only four types of frames can have a view: root frames (ViewportFrame), + * subdocument frames (nsSubDocumentFrame), + * menu popup frames (nsMenuPopupFrame), and list control frames + * (nsListControlFrame). Root frames and subdocument frames have views to link + * the two documents together (the frame trees do not link up otherwise). + * Menu popup frames, and list control frames have views because + * they (sometimes) need to create widgets. + * Menu popup frames handles xul popups, which is anything + * where we need content to go over top the main window at an os level. List + * control frames handle select popups/dropdowns in non-e10s mode. + * + * The term "root view" refers to the root view of a document. Just like root + * frames, root views can have parent views. Only the root view of the root + * document in the process will not have a parent. + * + * All views are created by their frames except root views. Root views are + * special. Root views are created in nsDocumentViewer::MakeWindow before the + * root frame is created, so the root view will not have a frame very early in + * document creation. + * + * Subdocument frames have an anonymous (no frame associated + * with it) inner view that is a child of their "outer" view. + * + * On a subdocument frame the inner view serves as the parent of the + * root view of the subdocument. The outer and inner view of the subdocument + * frame belong to the subdocument frame and hence to the parent document. The + * root view of the subdocument belongs to the subdocument. + * nsLayoutUtils::GetCrossDocParentFrame and nsPresContext::GetParentPresContext + * depend on this view structure and are the main way that we traverse across + * the document boundary in layout. + * + * When the load of a new document is started in the subdocument, the creation + * of the new subdocument and destruction of the old subdocument are not + * linked. (This creation and destruction is handled in nsDocumentViewer.cpp.) + * This means that the old and new document will both exist at the same time + * during the loading of the new document. During this period the inner view of + * the subdocument parent will be the parent of two root views. This means that + * during this period there is a choice for which subdocument we draw, + * nsSubDocumentFrame::GetSubdocumentPresShellForPainting is what makes that + * choice. Note that this might not be a totally free choice, ie there might be + * hidden dependencies and bugs if the way we choose is changed. + * + * One thing that is special about the root view of a chrome window is that + * instead of creating a widget for the view, they can "attach" to the + * existing widget that was created by appshell code or something else. (see + * nsDocumentViewer::ShouldAttachToTopLevel) + */ + +// Enumerated type to indicate the visibility of a layer. +// hide - the layer is not shown. +// show - the layer is shown irrespective of the visibility of +// the layer's parent. +enum class ViewVisibility : uint8_t { Hide = 0, Show = 1 }; + +// Public view flags + +// Indicates that the view is using auto z-indexing +#define NS_VIEW_FLAG_AUTO_ZINDEX 0x0004 + +// Indicates that the view is a floating view. +#define NS_VIEW_FLAG_FLOATING 0x0008 + +//---------------------------------------------------------------------- + +/** + * View interface + * + * Views are NOT reference counted. Use the Destroy() member function to + * destroy a view. + * + * The lifetime of the view hierarchy is bounded by the lifetime of the + * view manager that owns the views. + * + * Most of the methods here are read-only. To set the corresponding properties + * of a view, go through nsViewManager. + */ + +class nsView final : public nsIWidgetListener { + public: + friend class nsViewManager; + + typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect; + typedef mozilla::LayoutDeviceIntRegion LayoutDeviceIntRegion; + + void operator delete(void* ptr) { ::operator delete(ptr); } + + /** + * Get the view manager which "owns" the view. + * This method might require some expensive traversal work in the future. If + * you can get the view manager from somewhere else, do that instead. + * @result the view manager + */ + nsViewManager* GetViewManager() const { return mViewManager; } + + /** + * Find the view for the given widget, if there is one. + * @return the view the widget belongs to, or null if the widget doesn't + * belong to any view. + */ + static nsView* GetViewFor(const nsIWidget* aWidget); + + /** + * Destroy the view. + * + * The view destroys its child views, and destroys and releases its + * widget (if it has one). + * + * Also informs the view manager that the view is destroyed by calling + * SetRootView(NULL) if the view is the root view and calling RemoveChild() + * otherwise. + */ + void Destroy(); + + /** + * Called to get the position of a view. + * The specified coordinates are relative to the parent view's origin, but + * are in appunits of this. + * This is the (0, 0) origin of the coordinate space established by this view. + * @param x out parameter for x position + * @param y out parameter for y position + */ + nsPoint GetPosition() const { + NS_ASSERTION(!IsRoot() || (mPosX == 0 && mPosY == 0), + "root views should always have explicit position of (0,0)"); + return nsPoint(mPosX, mPosY); + } + + /** + * Called to get the dimensions and position of the view's bounds. + * The view's bounds (x,y) are relative to the origin of the parent view, but + * are in appunits of this. + * The view's bounds (x,y) might not be the same as the view's position, + * if the view has content above or to the left of its origin. + * @param aBounds out parameter for bounds + */ + nsRect GetBounds() const { return mDimBounds; } + + /** + * The bounds of this view relative to this view. So this is the same as + * GetBounds except this is relative to this view instead of the parent view. + */ + nsRect GetDimensions() const { + nsRect r = mDimBounds; + r.MoveBy(-mPosX, -mPosY); + return r; + } + + /** + * Get the offset between the coordinate systems of |this| and aOther. + * Adding the return value to a point in the coordinate system of |this| + * will transform the point to the coordinate system of aOther. + * + * The offset is expressed in appunits of |this|. So if you are getting the + * offset between views in different documents that might have different + * appunits per devpixel ratios you need to be careful how you use the + * result. + * + * If aOther is null, this will return the offset of |this| from the + * root of the viewmanager tree. + * + * This function is fastest when aOther is an ancestor of |this|. + * + * NOTE: this actually returns the offset from aOther to |this|, but + * that offset is added to transform _coordinates_ from |this| to aOther. + */ + nsPoint GetOffsetTo(const nsView* aOther) const; + + /** + * Get the offset between the origin of |this| and the origin of aWidget. + * Adding the return value to a point in the coordinate system of |this| + * will transform the point to the coordinate system of aWidget. + * + * The offset is expressed in appunits of |this|. + */ + nsPoint GetOffsetToWidget(nsIWidget* aWidget) const; + + /** + * Takes a point aPt that is in the coordinate system of |this|'s parent view + * and converts it to be in the coordinate system of |this| taking into + * account the offset and any app unit per dev pixel ratio differences. + */ + nsPoint ConvertFromParentCoords(nsPoint aPt) const; + + /** + * Called to query the visibility state of a view. + * @result current visibility state + */ + ViewVisibility GetVisibility() const { return mVis; } + + /** + * Get whether the view "floats" above all other views, + * which tells the compositor not to consider higher views in + * the view hierarchy that would geometrically intersect with + * this view. This is a hack, but it fixes some problems with + * views that need to be drawn in front of all other views. + * @result true if the view floats, false otherwise. + */ + bool GetFloating() const { return (mVFlags & NS_VIEW_FLAG_FLOATING) != 0; } + + /** + * Called to query the parent of the view. + * @result view's parent + */ + nsView* GetParent() const { return mParent; } + + /** + * The view's first child is the child which is earliest in document order. + * @result first child + */ + nsView* GetFirstChild() const { return mFirstChild; } + + /** + * Called to query the next sibling of the view. + * @result view's next sibling + */ + nsView* GetNextSibling() const { return mNextSibling; } + + /** + * Set the view's frame. + */ + void SetFrame(nsIFrame* aRootFrame) { mFrame = aRootFrame; } + + /** + * Retrieve the view's frame. + */ + nsIFrame* GetFrame() const { return mFrame; } + + /** + * Get the nearest widget in this view or a parent of this view and + * the offset from the widget's origin to this view's origin + * @param aOffset - if non-null the offset from this view's origin to the + * widget's origin (usually positive) expressed in appunits of this will be + * returned in aOffset. + * @return the widget closest to this view; can be null because some view + * trees don't have widgets at all (e.g., printing), but if any view in the + * view tree has a widget, then it's safe to assume this will not return null + */ + nsIWidget* GetNearestWidget(nsPoint* aOffset) const; + + /** + * Create a widget to associate with this view. This variant of + * CreateWidget*() will look around in the view hierarchy for an + * appropriate parent widget for the view. + * + * @param aWidgetInitData data used to initialize this view's widget before + * its create is called. + * @return error status + */ + nsresult CreateWidget(mozilla::widget::InitData* aWidgetInitData = nullptr, + bool aEnableDragDrop = true, + bool aResetVisibility = true); + + /** + * Create a widget for this view with an explicit parent widget. + * |aParentWidget| must be nonnull. The other params are the same + * as for |CreateWidget()|. + */ + nsresult CreateWidgetForParent(nsIWidget* aParentWidget, + mozilla::widget::InitData* = nullptr, + bool aEnableDragDrop = true, + bool aResetVisibility = true); + + /** + * Create a popup widget for this view. Pass |aParentWidget| to + * explicitly set the popup's parent. If it's not passed, the view + * hierarchy will be searched for an appropriate parent widget. The + * other params are the same as for |CreateWidget()|, except that + * |aWidgetInitData| must be nonnull. + */ + nsresult CreateWidgetForPopup(mozilla::widget::InitData*, + nsIWidget* aParentWidget = nullptr); + + /** + * Destroys the associated widget for this view. If this method is + * not called explicitly, the widget when be destroyed when its + * view gets destroyed. + */ + void DestroyWidget(); + + /** + * Attach/detach a top level widget from this view. When attached, the view + * updates the widget's device context and allows the view to begin receiving + * gecko events. The underlying base window associated with the widget will + * continues to receive events it expects. + * + * An attached widget will not be destroyed when the view is destroyed, + * allowing the recycling of a single top level widget over multiple views. + * + * @param aWidget The widget to attach to / detach from. + */ + nsresult AttachToTopLevelWidget(nsIWidget* aWidget); + nsresult DetachFromTopLevelWidget(); + + /** + * Returns a flag indicating whether the view owns it's widget + * or is attached to an existing top level widget. + */ + bool IsAttachedToTopLevel() const { return mWidgetIsTopLevel; } + + /** + * In 4.0, the "cutout" nature of a view is queryable. + * If we believe that all cutout view have a native widget, this + * could be a replacement. + * @param aWidget out parameter for widget that this view contains, + * or nullptr if there is none. + */ + nsIWidget* GetWidget() const { return mWindow; } + + /** + * The widget which we have attached a listener to can also have a "previous" + * listener set on it. This is to keep track of the last nsView when + * navigating to a new one so that we can continue to paint that if the new + * one isn't ready yet. + */ + void SetPreviousWidget(nsIWidget* aWidget) { mPreviousWindow = aWidget; } + + /** + * Returns true if the view has a widget associated with it. + */ + bool HasWidget() const { return mWindow != nullptr; } + + void SetForcedRepaint(bool aForceRepaint) { mForcedRepaint = aForceRepaint; } + + void SetNeedsWindowPropertiesSync(); + + /** + * Make aWidget direct its events to this view. + * The caller must call DetachWidgetEventHandler before this view + * is destroyed. + */ + void AttachWidgetEventHandler(nsIWidget* aWidget); + /** + * Stop aWidget directing its events to this view. + */ + void DetachWidgetEventHandler(nsIWidget* aWidget); + +#ifdef DEBUG + /** + * Output debug info to FILE + * @param out output file handle + * @param aIndent indentation depth + * NOTE: virtual so that debugging tools not linked into gklayout can access + * it + */ + virtual void List(FILE* out, int32_t aIndent = 0) const; +#endif // DEBUG + + /** + * @result true iff this is the root view for its view manager + */ + bool IsRoot() const; + + LayoutDeviceIntRect CalcWidgetBounds(mozilla::widget::WindowType, + mozilla::widget::TransparencyMode); + + LayoutDeviceIntRect RecalcWidgetBounds(); + + // This is an app unit offset to add when converting view coordinates to + // widget coordinates. It is the offset in view coordinates from widget + // origin (unlike views, widgets can't extend above or to the left of their + // origin) to view origin expressed in appunits of this. + nsPoint ViewToWidgetOffset() const { return mViewToWidgetOffset; } + + /** + * Called to indicate that the position of the view has been changed. + * The specified coordinates are in the parent view's coordinate space. + * @param x new x position + * @param y new y position + */ + void SetPosition(nscoord aX, nscoord aY); + + /** + * Called to indicate that the z-index of a view has been changed. + * The z-index is relative to all siblings of the view. + * @param aAuto Indicate that the z-index of a view is "auto". An "auto" + * z-index means that the view does not define a new stacking + * context, which means that the z-indicies of the view's + * children are relative to the view's siblings. + * @param zindex new z depth + */ + void SetZIndex(bool aAuto, int32_t aZIndex); + bool GetZIndexIsAuto() const { + return (mVFlags & NS_VIEW_FLAG_AUTO_ZINDEX) != 0; + } + int32_t GetZIndex() const { return mZIndex; } + + void SetParent(nsView* aParent) { mParent = aParent; } + void SetNextSibling(nsView* aSibling) { + NS_ASSERTION(aSibling != this, "Can't be our own sibling!"); + mNextSibling = aSibling; + } + + nsRegion& GetDirtyRegion() { + if (!mDirtyRegion) { + NS_ASSERTION(!mParent || GetFloating(), + "Only display roots should have dirty regions"); + mDirtyRegion = mozilla::MakeUnique<nsRegion>(); + } + return *mDirtyRegion; + } + + // nsIWidgetListener + virtual mozilla::PresShell* GetPresShell() override; + virtual nsView* GetView() override { return this; } + virtual bool WindowMoved(nsIWidget* aWidget, int32_t x, int32_t y, + ByMoveToRect) override; + virtual bool WindowResized(nsIWidget* aWidget, int32_t aWidth, + int32_t aHeight) override; +#if defined(MOZ_WIDGET_ANDROID) + virtual void DynamicToolbarMaxHeightChanged( + mozilla::ScreenIntCoord aHeight) override; + virtual void DynamicToolbarOffsetChanged( + mozilla::ScreenIntCoord aOffset) override; +#endif + virtual bool RequestWindowClose(nsIWidget* aWidget) override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual void WillPaintWindow(nsIWidget* aWidget) override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual bool PaintWindow(nsIWidget* aWidget, + LayoutDeviceIntRegion aRegion) override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual void DidPaintWindow() override; + virtual void DidCompositeWindow( + mozilla::layers::TransactionId aTransactionId, + const mozilla::TimeStamp& aCompositeStart, + const mozilla::TimeStamp& aCompositeEnd) override; + virtual void RequestRepaint() override; + virtual bool ShouldNotBeVisible() override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual nsEventStatus HandleEvent(mozilla::WidgetGUIEvent* aEvent, + bool aUseAttachedEvents) override; + virtual void SafeAreaInsetsChanged(const mozilla::ScreenIntMargin&) override; + + virtual ~nsView(); + + nsPoint GetOffsetTo(const nsView* aOther, const int32_t aAPD) const; + nsIWidget* GetNearestWidget(nsPoint* aOffset, const int32_t aAPD) const; + + bool IsPrimaryFramePaintSuppressed(); + + private: + explicit nsView(nsViewManager* = nullptr, + ViewVisibility = ViewVisibility::Show); + + bool ForcedRepaint() { return mForcedRepaint; } + + // Do the actual work of ResetWidgetBounds, unconditionally. Don't + // call this method if we have no widget. + void DoResetWidgetBounds(bool aMoveOnly, bool aInvalidateChangedSize); + void InitializeWindow(bool aEnableDragDrop, bool aResetVisibility); + + bool IsEffectivelyVisible(); + + /** + * Called to indicate that the dimensions of the view have been changed. + * The x and y coordinates may be < 0, indicating that the view extends above + * or to the left of its origin position. The term 'dimensions' indicates it + * is relative to this view. + */ + void SetDimensions(const nsRect& aRect, bool aPaint = true, + bool aResizeWidget = true); + + /** + * Called to indicate that the visibility of a view has been + * changed. + * @param visibility new visibility state + */ + void SetVisibility(ViewVisibility visibility); + + /** + * Set/Get whether the view "floats" above all other views, + * which tells the compositor not to consider higher views in + * the view hierarchy that would geometrically intersect with + * this view. This is a hack, but it fixes some problems with + * views that need to be drawn in front of all other views. + * @result true if the view floats, false otherwise. + */ + void SetFloating(bool aFloatingView); + + // Helper function to get mouse grabbing off this view (by moving it to the + // parent, if we can) + void DropMouseGrabbing(); + + // Same as GetBounds but converts to parent appunits if they are different. + nsRect GetBoundsInParentUnits() const; + + bool HasNonEmptyDirtyRegion() { + return mDirtyRegion && !mDirtyRegion->IsEmpty(); + } + + void InsertChild(nsView* aChild, nsView* aSibling); + void RemoveChild(nsView* aChild); + + void ResetWidgetBounds(bool aRecurse, bool aForceSync); + void AssertNoWindow(); + + void NotifyEffectiveVisibilityChanged(bool aEffectivelyVisible); + + // Update the cached RootViewManager for all view manager descendents. + void InvalidateHierarchy(); + + nsViewManager* mViewManager; + nsView* mParent; + nsCOMPtr<nsIWidget> mWindow; + nsCOMPtr<nsIWidget> mPreviousWindow; + nsView* mNextSibling; + nsView* mFirstChild; + nsIFrame* mFrame; + mozilla::UniquePtr<nsRegion> mDirtyRegion; + int32_t mZIndex; + ViewVisibility mVis; + // position relative our parent view origin but in our appunits + nscoord mPosX, mPosY; + // relative to parent, but in our appunits + nsRect mDimBounds; + // in our appunits + nsPoint mViewToWidgetOffset; + uint32_t mVFlags; + bool mWidgetIsTopLevel; + bool mForcedRepaint; + bool mNeedsWindowPropertiesSync; +}; + +#endif diff --git a/view/nsViewManager.cpp b/view/nsViewManager.cpp new file mode 100644 index 0000000000..842237f4db --- /dev/null +++ b/view/nsViewManager.cpp @@ -0,0 +1,999 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsViewManager.h" + +#include "mozilla/MouseEvents.h" +#include "mozilla/PresShell.h" +#include "mozilla/PresShellInlines.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/StartupTimeline.h" +#include "mozilla/dom/Document.h" +#include "nsGfxCIID.h" +#include "nsView.h" +#include "nsCOMPtr.h" +#include "nsRegion.h" +#include "nsCOMArray.h" +#include "nsXULPopupManager.h" +#include "nsPresContext.h" +#include "nsRefreshDriver.h" +#include "nsContentUtils.h" // for nsAutoScriptBlocker +#include "nsLayoutUtils.h" +#include "gfxPlatform.h" +#include "WindowRenderer.h" + +/** + XXX TODO XXX + + DeCOMify newly private methods + Optimize view storage +*/ + +/** + A note about platform assumptions: + + We assume that a widget is z-ordered on top of its parent. + + We do NOT assume anything about the relative z-ordering of sibling widgets. + Even though we ask for a specific z-order, we don't assume that widget + z-ordering actually works. +*/ + +using namespace mozilla; +using namespace mozilla::layers; + +#define NSCOORD_NONE INT32_MIN + +#undef DEBUG_MOUSE_LOCATION + +// Weakly held references to all of the view managers +StaticAutoPtr<nsTArray<nsViewManager*>> nsViewManager::gViewManagers; +uint32_t nsViewManager::gLastUserEventTime = 0; + +nsViewManager::nsViewManager() + : mPresShell(nullptr), + mDelayedResize(NSCOORD_NONE, NSCOORD_NONE), + mRootView(nullptr), + mRefreshDisableCount(0), + mPainting(false), + mRecursiveRefreshPending(false), + mHasPendingWidgetGeometryChanges(false) { + if (gViewManagers == nullptr) { + // Create an array to hold a list of view managers + gViewManagers = new nsTArray<nsViewManager*>; + } + + gViewManagers->AppendElement(this); +} + +nsViewManager::~nsViewManager() { + if (mRootView) { + // Destroy any remaining views + mRootView->Destroy(); + mRootView = nullptr; + } + + mRootViewManager = nullptr; + + NS_ASSERTION(gViewManagers != nullptr, "About to use null gViewManagers"); + +#ifdef DEBUG + bool removed = +#endif + gViewManagers->RemoveElement(this); + NS_ASSERTION( + removed, + "Viewmanager instance was not in the global list of viewmanagers"); + + if (gViewManagers->IsEmpty()) { + // There aren't any more view managers so + // release the global array of view managers + gViewManagers = nullptr; + } + + MOZ_RELEASE_ASSERT(!mPresShell, + "Releasing nsViewManager without having called Destroy on " + "the PresShell!"); +} + +// We don't hold a reference to the presentation context because it +// holds a reference to us. +nsresult nsViewManager::Init(nsDeviceContext* aContext) { + MOZ_ASSERT(nullptr != aContext, "null ptr"); + + if (nullptr == aContext) { + return NS_ERROR_NULL_POINTER; + } + if (nullptr != mContext) { + return NS_ERROR_ALREADY_INITIALIZED; + } + mContext = aContext; + + return NS_OK; +} + +nsView* nsViewManager::CreateView(const nsRect& aBounds, nsView* aParent, + ViewVisibility aVisibilityFlag) { + auto* v = new nsView(this, aVisibilityFlag); + v->SetParent(aParent); + v->SetPosition(aBounds.X(), aBounds.Y()); + nsRect dim(0, 0, aBounds.Width(), aBounds.Height()); + v->SetDimensions(dim, false); + return v; +} + +void nsViewManager::SetRootView(nsView* aView) { + MOZ_ASSERT(!aView || aView->GetViewManager() == this, + "Unexpected viewmanager on root view"); + + // Do NOT destroy the current root view. It's the caller's responsibility + // to destroy it + mRootView = aView; + + if (mRootView) { + nsView* parent = mRootView->GetParent(); + if (parent) { + // Calling InsertChild on |parent| will InvalidateHierarchy() on us, so + // no need to set mRootViewManager ourselves here. + parent->InsertChild(mRootView, nullptr); + } else { + InvalidateHierarchy(); + } + + mRootView->SetZIndex(false, 0); + } + // Else don't touch mRootViewManager +} + +void nsViewManager::GetWindowDimensions(nscoord* aWidth, nscoord* aHeight) { + if (nullptr != mRootView) { + if (mDelayedResize == nsSize(NSCOORD_NONE, NSCOORD_NONE)) { + nsRect dim = mRootView->GetDimensions(); + *aWidth = dim.Width(); + *aHeight = dim.Height(); + } else { + *aWidth = mDelayedResize.width; + *aHeight = mDelayedResize.height; + } + } else { + *aWidth = 0; + *aHeight = 0; + } +} + +void nsViewManager::DoSetWindowDimensions(nscoord aWidth, nscoord aHeight) { + nsRect oldDim = mRootView->GetDimensions(); + nsRect newDim(0, 0, aWidth, aHeight); + // We care about resizes even when one dimension is already zero. + if (oldDim.IsEqualEdges(newDim)) { + return; + } + // Don't resize the widget. It is already being set elsewhere. + mRootView->SetDimensions(newDim, true, false); + if (RefPtr<PresShell> presShell = mPresShell) { + presShell->ResizeReflow(aWidth, aHeight); + } +} + +bool nsViewManager::ShouldDelayResize() const { + MOZ_ASSERT(mRootView); + if (!mRootView->IsEffectivelyVisible() || !mPresShell || + !mPresShell->IsVisible()) { + return true; + } + if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) { + if (rd->IsResizeSuppressed()) { + return true; + } + } + return false; +} + +void nsViewManager::SetWindowDimensions(nscoord aWidth, nscoord aHeight, + bool aDelayResize) { + if (mRootView) { + if (!ShouldDelayResize() && !aDelayResize) { + if (mDelayedResize != nsSize(NSCOORD_NONE, NSCOORD_NONE) && + mDelayedResize != nsSize(aWidth, aHeight)) { + // We have a delayed resize; that now obsolete size may already have + // been flushed to the PresContext so we need to update the PresContext + // with the new size because if the new size is exactly the same as the + // root view's current size then DoSetWindowDimensions will not + // request a resize reflow (which would correct it). See bug 617076. + mDelayedResize = nsSize(aWidth, aHeight); + FlushDelayedResize(); + } + mDelayedResize.SizeTo(NSCOORD_NONE, NSCOORD_NONE); + DoSetWindowDimensions(aWidth, aHeight); + } else { + mDelayedResize.SizeTo(aWidth, aHeight); + if (mPresShell) { + mPresShell->SetNeedStyleFlush(); + mPresShell->SetNeedLayoutFlush(); + } + } + } +} + +void nsViewManager::FlushDelayedResize() { + if (mDelayedResize != nsSize(NSCOORD_NONE, NSCOORD_NONE)) { + DoSetWindowDimensions(mDelayedResize.width, mDelayedResize.height); + mDelayedResize.SizeTo(NSCOORD_NONE, NSCOORD_NONE); + } +} + +// Convert aIn from being relative to and in appunits of aFromView, to being +// relative to and in appunits of aToView. +static nsRegion ConvertRegionBetweenViews(const nsRegion& aIn, + nsView* aFromView, nsView* aToView) { + nsRegion out = aIn; + out.MoveBy(aFromView->GetOffsetTo(aToView)); + out = out.ScaleToOtherAppUnitsRoundOut( + aFromView->GetViewManager()->AppUnitsPerDevPixel(), + aToView->GetViewManager()->AppUnitsPerDevPixel()); + return out; +} + +nsView* nsViewManager::GetDisplayRootFor(nsView* aView) { + nsView* displayRoot = aView; + for (;;) { + nsView* displayParent = displayRoot->GetParent(); + if (!displayParent) return displayRoot; + + if (displayRoot->GetFloating() && !displayParent->GetFloating()) + return displayRoot; + + // If we have a combobox dropdown popup within a panel popup, both the view + // for the dropdown popup and its parent will be floating, so we need to + // distinguish this situation. We do this by looking for a widget. Any view + // with a widget is a display root. + nsIWidget* widget = displayRoot->GetWidget(); + if (widget && widget->GetWindowType() == widget::WindowType::Popup) { + NS_ASSERTION(displayRoot->GetFloating() && displayParent->GetFloating(), + "this should only happen with floating views that have " + "floating parents"); + return displayRoot; + } + + displayRoot = displayParent; + } +} + +/** + aRegion is given in device coordinates!! + aContext may be null, in which case layers should be used for + rendering. +*/ +void nsViewManager::Refresh(nsView* aView, + const LayoutDeviceIntRegion& aRegion) { + NS_ASSERTION(aView->GetViewManager() == this, "wrong view manager"); + + if (mPresShell && mPresShell->IsNeverPainting()) { + return; + } + + if (aRegion.IsEmpty()) { + return; + } + + nsIWidget* widget = aView->GetWidget(); + if (!widget) { + return; + } + + NS_ASSERTION(!IsPainting(), "recursive painting not permitted"); + if (IsPainting()) { + RootViewManager()->mRecursiveRefreshPending = true; + return; + } + + { + nsAutoScriptBlocker scriptBlocker; + SetPainting(true); + + NS_ASSERTION(GetDisplayRootFor(aView) == aView, + "Widgets that we paint must all be display roots"); + + if (RefPtr<PresShell> presShell = mPresShell) { +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr("--COMPOSITE-- %p\n", presShell.get()); + } +#endif + WindowRenderer* renderer = widget->GetWindowRenderer(); + if (!renderer->NeedsWidgetInvalidation()) { + renderer->FlushRendering(wr::RenderReasons::WIDGET); + } else { + presShell->SyncPaintFallback(aView); + } +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr("--ENDCOMPOSITE--\n"); + } +#endif + mozilla::StartupTimeline::RecordOnce( + mozilla::StartupTimeline::FIRST_PAINT); + } + + SetPainting(false); + } + + if (RootViewManager()->mRecursiveRefreshPending) { + RootViewManager()->mRecursiveRefreshPending = false; + InvalidateAllViews(); + } +} + +void nsViewManager::ProcessPendingUpdatesForView(nsView* aView, + bool aFlushDirtyRegion) { + NS_ASSERTION(IsRootVM(), "Updates will be missed"); + if (!aView) { + return; + } + + RefPtr<PresShell> rootPresShell = mPresShell; + AutoTArray<nsCOMPtr<nsIWidget>, 1> widgets; + aView->GetViewManager()->ProcessPendingUpdatesRecurse(aView, widgets); + for (uint32_t i = 0; i < widgets.Length(); ++i) { + nsView* view = nsView::GetViewFor(widgets[i]); + if (view) { + if (view->mNeedsWindowPropertiesSync) { + view->mNeedsWindowPropertiesSync = false; + if (nsViewManager* vm = view->GetViewManager()) { + if (PresShell* presShell = vm->GetPresShell()) { + presShell->SyncWindowProperties(/* aSync */ true); + } + } + } + } + view = nsView::GetViewFor(widgets[i]); + if (view) { + view->ResetWidgetBounds(false, true); + } + } + if (rootPresShell->GetViewManager() != this) { + return; // presentation might have been torn down + } + if (aFlushDirtyRegion) { + nsAutoScriptBlocker scriptBlocker; + SetPainting(true); + for (uint32_t i = 0; i < widgets.Length(); ++i) { + nsIWidget* widget = widgets[i]; + nsView* view = nsView::GetViewFor(widget); + if (view) { + RefPtr<nsViewManager> viewManager = view->GetViewManager(); + viewManager->ProcessPendingUpdatesPaint(MOZ_KnownLive(widget)); + } + } + SetPainting(false); + } +} + +void nsViewManager::ProcessPendingUpdatesRecurse( + nsView* aView, AutoTArray<nsCOMPtr<nsIWidget>, 1>& aWidgets) { + if (mPresShell && mPresShell->IsNeverPainting()) { + return; + } + + for (nsView* childView = aView->GetFirstChild(); childView; + childView = childView->GetNextSibling()) { + childView->GetViewManager()->ProcessPendingUpdatesRecurse(childView, + aWidgets); + } + + nsIWidget* widget = aView->GetWidget(); + if (widget) { + aWidgets.AppendElement(widget); + } else { + FlushDirtyRegionToWidget(aView); + } +} + +void nsViewManager::ProcessPendingUpdatesPaint(nsIWidget* aWidget) { + if (aWidget->NeedsPaint()) { + // If an ancestor widget was hidden and then shown, we could + // have a delayed resize to handle. + for (RefPtr<nsViewManager> vm = this; vm; + vm = vm->mRootView->GetParent() + ? vm->mRootView->GetParent()->GetViewManager() + : nullptr) { + if (vm->mDelayedResize != nsSize(NSCOORD_NONE, NSCOORD_NONE) && + vm->mRootView->IsEffectivelyVisible() && vm->mPresShell && + vm->mPresShell->IsVisible()) { + vm->FlushDelayedResize(); + } + } + nsView* view = nsView::GetViewFor(aWidget); + + if (!view) { + NS_ERROR("FlushDelayedResize destroyed the nsView?"); + return; + } + + nsIWidgetListener* previousListener = + aWidget->GetPreviouslyAttachedWidgetListener(); + + if (previousListener && previousListener != view && + view->IsPrimaryFramePaintSuppressed()) { + return; + } + + if (RefPtr<PresShell> presShell = mPresShell) { +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr( + "---- PAINT START ----PresShell(%p), nsView(%p), nsIWidget(%p)\n", + presShell.get(), view, aWidget); + } +#endif + + presShell->PaintAndRequestComposite(view, PaintFlags::None); + view->SetForcedRepaint(false); + +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr("---- PAINT END ----\n"); + } +#endif + } + } + FlushDirtyRegionToWidget(nsView::GetViewFor(aWidget)); +} + +void nsViewManager::FlushDirtyRegionToWidget(nsView* aView) { + NS_ASSERTION(aView->GetViewManager() == this, + "FlushDirtyRegionToWidget called on view we don't own"); + + if (!aView->HasNonEmptyDirtyRegion()) { + return; + } + + nsRegion& dirtyRegion = aView->GetDirtyRegion(); + nsView* nearestViewWithWidget = aView; + while (!nearestViewWithWidget->HasWidget() && + nearestViewWithWidget->GetParent()) { + nearestViewWithWidget = nearestViewWithWidget->GetParent(); + } + nsRegion r = + ConvertRegionBetweenViews(dirtyRegion, aView, nearestViewWithWidget); + + nsViewManager* widgetVM = nearestViewWithWidget->GetViewManager(); + widgetVM->InvalidateWidgetArea(nearestViewWithWidget, r); + dirtyRegion.SetEmpty(); +} + +void nsViewManager::InvalidateView(nsView* aView) { + // Mark the entire view as damaged + InvalidateView(aView, aView->GetDimensions()); +} + +static void AddDirtyRegion(nsView* aView, const nsRegion& aDamagedRegion) { + nsRegion& dirtyRegion = aView->GetDirtyRegion(); + dirtyRegion.Or(dirtyRegion, aDamagedRegion); + dirtyRegion.SimplifyOutward(8); +} + +void nsViewManager::PostPendingUpdate() { + nsViewManager* rootVM = RootViewManager(); + rootVM->mHasPendingWidgetGeometryChanges = true; + if (rootVM->mPresShell) { + rootVM->mPresShell->ScheduleViewManagerFlush(); + } +} + +/** + * @param aDamagedRegion this region, relative to aWidgetView, is invalidated in + * every widget child of aWidgetView, plus aWidgetView's own widget + */ +void nsViewManager::InvalidateWidgetArea(nsView* aWidgetView, + const nsRegion& aDamagedRegion) { + NS_ASSERTION(aWidgetView->GetViewManager() == this, + "InvalidateWidgetArea called on view we don't own"); + nsIWidget* widget = aWidgetView->GetWidget(); + +#if 0 + nsRect dbgBounds = aDamagedRegion.GetBounds(); + printf("InvalidateWidgetArea view:%X (%d) widget:%X region: %d, %d, %d, %d\n", + aWidgetView, aWidgetView->IsAttachedToTopLevel(), + widget, dbgBounds.x, dbgBounds.y, dbgBounds.width, dbgBounds.height); +#endif + + // If the widget is hidden, it don't cover nothing + if (widget && !widget->IsVisible()) { + return; + } + + if (!widget) { + // The root view or a scrolling view might not have a widget + // (for example, during printing). We get here when we scroll + // during printing to show selected options in a listbox, for example. + return; + } + + if (!aDamagedRegion.IsEmpty()) { + for (auto iter = aDamagedRegion.RectIter(); !iter.Done(); iter.Next()) { + LayoutDeviceIntRect bounds = ViewToWidget(aWidgetView, iter.Get()); + widget->Invalidate(bounds); + } + } +} + +static bool ShouldIgnoreInvalidation(nsViewManager* aVM) { + while (aVM) { + PresShell* presShell = aVM->GetPresShell(); + if (!presShell || presShell->ShouldIgnoreInvalidation()) { + return true; + } + nsView* view = aVM->GetRootView()->GetParent(); + aVM = view ? view->GetViewManager() : nullptr; + } + return false; +} + +void nsViewManager::InvalidateView(nsView* aView, const nsRect& aRect) { + // If painting is suppressed in the presshell or an ancestor drop all + // invalidates, it will invalidate everything when it unsuppresses. + if (ShouldIgnoreInvalidation(this)) { + return; + } + + InvalidateViewNoSuppression(aView, aRect); +} + +void nsViewManager::InvalidateViewNoSuppression(nsView* aView, + const nsRect& aRect) { + MOZ_ASSERT(nullptr != aView, "null view"); + + NS_ASSERTION(aView->GetViewManager() == this, + "InvalidateViewNoSuppression called on view we don't own"); + + nsRect damagedRect(aRect); + if (damagedRect.IsEmpty()) { + return; + } + + nsView* displayRoot = GetDisplayRootFor(aView); + nsViewManager* displayRootVM = displayRoot->GetViewManager(); + // Propagate the update to the displayRoot, since iframes, for example, + // can overlap each other and be translucent. So we have to possibly + // invalidate our rect in each of the widgets we have lying about. + damagedRect.MoveBy(aView->GetOffsetTo(displayRoot)); + int32_t rootAPD = displayRootVM->AppUnitsPerDevPixel(); + int32_t APD = AppUnitsPerDevPixel(); + damagedRect = damagedRect.ScaleToOtherAppUnitsRoundOut(APD, rootAPD); + + // accumulate this rectangle in the view's dirty region, so we can + // process it later. + AddDirtyRegion(displayRoot, nsRegion(damagedRect)); +} + +void nsViewManager::InvalidateAllViews() { + if (RootViewManager() != this) { + return RootViewManager()->InvalidateAllViews(); + } + + InvalidateViews(mRootView); +} + +void nsViewManager::InvalidateViews(nsView* aView) { + // Invalidate this view. + InvalidateView(aView); + + // Invalidate all children as well. + nsView* childView = aView->GetFirstChild(); + while (nullptr != childView) { + childView->GetViewManager()->InvalidateViews(childView); + childView = childView->GetNextSibling(); + } +} + +void nsViewManager::WillPaintWindow(nsIWidget* aWidget) { + if (aWidget) { + nsView* view = nsView::GetViewFor(aWidget); + WindowRenderer* renderer = aWidget->GetWindowRenderer(); + if (view && + (view->ForcedRepaint() || !renderer->NeedsWidgetInvalidation())) { + ProcessPendingUpdates(); + // Re-get the view pointer here since the ProcessPendingUpdates might have + // destroyed it during CallWillPaintOnObservers. + view = nsView::GetViewFor(aWidget); + if (view) { + view->SetForcedRepaint(false); + } + } + } +} + +bool nsViewManager::PaintWindow(nsIWidget* aWidget, + const LayoutDeviceIntRegion& aRegion) { + if (!aWidget || !mContext) return false; + + NS_ASSERTION( + IsPaintingAllowed(), + "shouldn't be receiving paint events while painting is disallowed!"); + + // Get the view pointer here since NS_WILL_PAINT might have + // destroyed it during CallWillPaintOnObservers (bug 378273). + nsView* view = nsView::GetViewFor(aWidget); + if (view && !aRegion.IsEmpty()) { + Refresh(view, aRegion); + } + + return true; +} + +void nsViewManager::DidPaintWindow() { + if (RefPtr<PresShell> presShell = mPresShell) { + presShell->DidPaintWindow(); + } +} + +void nsViewManager::DispatchEvent(WidgetGUIEvent* aEvent, nsView* aView, + nsEventStatus* aStatus) { + AUTO_PROFILER_LABEL("nsViewManager::DispatchEvent", OTHER); + + WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); + if ((mouseEvent && + // Ignore mouse events that we synthesize. + mouseEvent->mReason == WidgetMouseEvent::eReal && + // Ignore mouse exit and enter (we'll get moves if the user + // is really moving the mouse) since we get them when we + // create and destroy widgets. + mouseEvent->mMessage != eMouseExitFromWidget && + mouseEvent->mMessage != eMouseEnterIntoWidget) || + aEvent->HasKeyEventMessage() || aEvent->HasIMEEventMessage()) { + gLastUserEventTime = PR_IntervalToMicroseconds(PR_IntervalNow()); + } + + // Find the view whose coordinates system we're in. + nsView* view = aView; + bool dispatchUsingCoordinates = aEvent->IsUsingCoordinates(); + if (dispatchUsingCoordinates) { + // Will dispatch using coordinates. Pretty bogus but it's consistent + // with what presshell does. + view = GetDisplayRootFor(view); + } + + // If the view has no frame, look for a view that does. + nsIFrame* frame = view->GetFrame(); + if (!frame && (dispatchUsingCoordinates || aEvent->HasKeyEventMessage() || + aEvent->IsIMERelatedEvent())) { + while (view && !view->GetFrame()) { + view = view->GetParent(); + } + + if (view) { + frame = view->GetFrame(); + } + } + + if (nullptr != frame) { + // Hold a refcount to the presshell. The continued existence of the + // presshell will delay deletion of this view hierarchy should the event + // want to cause its destruction in, say, some JavaScript event handler. + if (RefPtr<PresShell> presShell = view->GetViewManager()->GetPresShell()) { + presShell->HandleEvent(frame, aEvent, false, aStatus); + return; + } + } + + *aStatus = nsEventStatus_eIgnore; +} + +// Recursively reparent widgets if necessary + +void nsViewManager::ReparentChildWidgets(nsView* aView, nsIWidget* aNewWidget) { + MOZ_ASSERT(aNewWidget, "null widget"); + + if (aView->HasWidget()) { + // Check to see if the parent widget is the + // same as the new parent. If not then reparent + // the widget, otherwise there is nothing more + // to do for the view and its descendants + nsIWidget* widget = aView->GetWidget(); + nsIWidget* parentWidget = widget->GetParent(); + if (parentWidget) { + // Child widget + if (parentWidget != aNewWidget) { + widget->SetParent(aNewWidget); + } + } else { + // Toplevel widget (popup, dialog, etc) + widget->ReparentNativeWidget(aNewWidget); + } + return; + } + + // Need to check each of the views children to see + // if they have a widget and reparent it. + + for (nsView* kid = aView->GetFirstChild(); kid; kid = kid->GetNextSibling()) { + ReparentChildWidgets(kid, aNewWidget); + } +} + +// Reparent a view and its descendant views widgets if necessary + +void nsViewManager::ReparentWidgets(nsView* aView, nsView* aParent) { + MOZ_ASSERT(aParent, "Must have a parent"); + MOZ_ASSERT(aView, "Must have a view"); + + // Quickly determine whether the view has pre-existing children or a + // widget. In most cases the view will not have any pre-existing + // children when this is called. Only in the case + // where a view has been reparented by removing it from + // a reinserting it into a new location in the view hierarchy do we + // have to consider reparenting the existing widgets for the view and + // it's descendants. + if (aView->HasWidget() || aView->GetFirstChild()) { + nsIWidget* parentWidget = aParent->GetNearestWidget(nullptr); + if (parentWidget) { + ReparentChildWidgets(aView, parentWidget); + return; + } + NS_WARNING("Can not find a widget for the parent view"); + } +} + +void nsViewManager::InsertChild(nsView* aParent, nsView* aChild, + nsView* aSibling, bool aAfter) { + MOZ_ASSERT(nullptr != aParent, "null ptr"); + MOZ_ASSERT(nullptr != aChild, "null ptr"); + NS_ASSERTION(aSibling == nullptr || aSibling->GetParent() == aParent, + "tried to insert view with invalid sibling"); + NS_ASSERTION(!IsViewInserted(aChild), + "tried to insert an already-inserted view"); + + if ((nullptr != aParent) && (nullptr != aChild)) { + // if aAfter is set, we will insert the child after 'prev' (i.e. after 'kid' + // in document order, otherwise after 'kid' (i.e. before 'kid' in document + // order). + + if (nullptr == aSibling) { + if (aAfter) { + // insert at end of document order, i.e., before first view + // this is the common case, by far + aParent->InsertChild(aChild, nullptr); + ReparentWidgets(aChild, aParent); + } else { + // insert at beginning of document order, i.e., after last view + nsView* kid = aParent->GetFirstChild(); + nsView* prev = nullptr; + while (kid) { + prev = kid; + kid = kid->GetNextSibling(); + } + // prev is last view or null if there are no children + aParent->InsertChild(aChild, prev); + ReparentWidgets(aChild, aParent); + } + } else { + nsView* kid = aParent->GetFirstChild(); + nsView* prev = nullptr; + while (kid && aSibling != kid) { + // get the next sibling view + prev = kid; + kid = kid->GetNextSibling(); + } + NS_ASSERTION(kid != nullptr, "couldn't find sibling in child list"); + if (aAfter) { + // insert after 'kid' in document order, i.e. before in view order + aParent->InsertChild(aChild, prev); + ReparentWidgets(aChild, aParent); + } else { + // insert before 'kid' in document order, i.e. after in view order + aParent->InsertChild(aChild, kid); + ReparentWidgets(aChild, aParent); + } + } + + // if the parent view is marked as "floating", make the newly added view + // float as well. + if (aParent->GetFloating()) aChild->SetFloating(true); + } +} + +void nsViewManager::RemoveChild(nsView* aChild) { + NS_ASSERTION(aChild, "aChild must not be null"); + + nsView* parent = aChild->GetParent(); + + if (nullptr != parent) { + NS_ASSERTION( + aChild->GetViewManager() == this || parent->GetViewManager() == this, + "wrong view manager"); + parent->RemoveChild(aChild); + } +} + +void nsViewManager::MoveViewTo(nsView* aView, nscoord aX, nscoord aY) { + NS_ASSERTION(aView->GetViewManager() == this, "wrong view manager"); + aView->SetPosition(aX, aY); +} + +void nsViewManager::ResizeView(nsView* aView, const nsRect& aRect, + bool aRepaintExposedAreaOnly) { + NS_ASSERTION(aView->GetViewManager() == this, "wrong view manager"); + + nsRect oldDimensions = aView->GetDimensions(); + if (!oldDimensions.IsEqualEdges(aRect)) { + aView->SetDimensions(aRect, true); + } + + // Note that if layout resizes the view and the view has a custom clip + // region set, then we expect layout to update the clip region too. Thus + // in the case where mClipRect has been optimized away to just be a null + // pointer, and this resize is implicitly changing the clip rect, it's OK + // because layout will change it back again if necessary. +} + +void nsViewManager::SetViewFloating(nsView* aView, bool aFloating) { + NS_ASSERTION(!(nullptr == aView), "no view"); + + aView->SetFloating(aFloating); +} + +void nsViewManager::SetViewVisibility(nsView* aView, ViewVisibility aVisible) { + NS_ASSERTION(aView->GetViewManager() == this, "wrong view manager"); + + if (aVisible != aView->GetVisibility()) { + aView->SetVisibility(aVisible); + } +} + +bool nsViewManager::IsViewInserted(nsView* aView) { + if (mRootView == aView) { + return true; + } + if (aView->GetParent() == nullptr) { + return false; + } + nsView* view = aView->GetParent()->GetFirstChild(); + while (view != nullptr) { + if (view == aView) { + return true; + } + view = view->GetNextSibling(); + } + return false; +} + +void nsViewManager::SetViewZIndex(nsView* aView, bool aAutoZIndex, + int32_t aZIndex) { + NS_ASSERTION((aView != nullptr), "no view"); + + // don't allow the root view's z-index to be changed. It should always be + // zero. This could be removed and replaced with a style rule, or just removed + // altogether, with interesting consequences + if (aView == mRootView) { + return; + } + + if (aAutoZIndex) { + aZIndex = 0; + } + + aView->SetZIndex(aAutoZIndex, aZIndex); +} + +nsViewManager* nsViewManager::IncrementDisableRefreshCount() { + if (!IsRootVM()) { + return RootViewManager()->IncrementDisableRefreshCount(); + } + + ++mRefreshDisableCount; + + return this; +} + +void nsViewManager::DecrementDisableRefreshCount() { + NS_ASSERTION(IsRootVM(), "Should only be called on root"); + --mRefreshDisableCount; + NS_ASSERTION(mRefreshDisableCount >= 0, "Invalid refresh disable count!"); +} + +nsIWidget* nsViewManager::GetRootWidget() const { + if (!mRootView) { + return nullptr; + } + if (mRootView->HasWidget()) { + return mRootView->GetWidget(); + } + if (mRootView->GetParent()) { + return mRootView->GetParent()->GetViewManager()->GetRootWidget(); + } + return nullptr; +} + +LayoutDeviceIntRect nsViewManager::ViewToWidget(nsView* aView, + const nsRect& aRect) const { + NS_ASSERTION(aView->GetViewManager() == this, "wrong view manager"); + + // account for the view's origin not lining up with the widget's + nsRect rect = aRect + aView->ViewToWidgetOffset(); + + // finally, convert to device coordinates. + return LayoutDeviceIntRect::FromUnknownRect( + rect.ToOutsidePixels(AppUnitsPerDevPixel())); +} + +void nsViewManager::IsPainting(bool& aIsPainting) { + aIsPainting = IsPainting(); +} + +void nsViewManager::ProcessPendingUpdates() { + if (!IsRootVM()) { + RefPtr<nsViewManager> rootViewManager = RootViewManager(); + rootViewManager->ProcessPendingUpdates(); + return; + } + + // Flush things like reflows by calling WillPaint on observer presShells. + if (mPresShell) { + mPresShell->GetPresContext()->RefreshDriver()->RevokeViewManagerFlush(); + + RefPtr<nsViewManager> strongThis(this); + CallWillPaintOnObservers(); + + ProcessPendingUpdatesForView(mRootView, true); + if (mPresShell) { + if (nsPresContext* pc = mPresShell->GetPresContext()) { + pc->RefreshDriver()->ClearHasScheduleFlush(); + } + } + } +} + +void nsViewManager::UpdateWidgetGeometry() { + if (!IsRootVM()) { + RefPtr<nsViewManager> rootViewManager = RootViewManager(); + rootViewManager->UpdateWidgetGeometry(); + return; + } + + if (mHasPendingWidgetGeometryChanges) { + mHasPendingWidgetGeometryChanges = false; + ProcessPendingUpdatesForView(mRootView, false); + } +} + +void nsViewManager::CallWillPaintOnObservers() { + MOZ_ASSERT(IsRootVM(), "Must be root VM for this to be called!"); + + if (NS_WARN_IF(!gViewManagers)) { + return; + } + + uint32_t index; + for (index = 0; index < gViewManagers->Length(); index++) { + nsViewManager* vm = gViewManagers->ElementAt(index); + if (vm->RootViewManager() == this) { + // One of our kids. + if (vm->mRootView && vm->mRootView->IsEffectivelyVisible()) { + if (RefPtr<PresShell> presShell = vm->GetPresShell()) { + presShell->WillPaint(); + } + } + } + } +} + +void nsViewManager::GetLastUserEventTime(uint32_t& aTime) { + aTime = gLastUserEventTime; +} + +void nsViewManager::InvalidateHierarchy() { + if (mRootView) { + mRootViewManager = nullptr; + nsView* parent = mRootView->GetParent(); + if (parent) { + mRootViewManager = parent->GetViewManager()->RootViewManager(); + NS_ASSERTION(mRootViewManager != this, + "Root view had a parent, but it has the same view manager"); + } + // else, we are implicitly our own root view manager. + } +} diff --git a/view/nsViewManager.h b/view/nsViewManager.h new file mode 100644 index 0000000000..32b090d06b --- /dev/null +++ b/view/nsViewManager.h @@ -0,0 +1,454 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsViewManager_h___ +#define nsViewManager_h___ + +#include "nscore.h" +#include "nsView.h" +#include "nsCOMPtr.h" +#include "nsCRT.h" +#include "nsTArray.h" +#include "nsDeviceContext.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/EventForwards.h" + +class nsIWidget; +struct nsRect; +class nsRegion; +class nsDeviceContext; + +namespace mozilla { +class PresShell; +} // namespace mozilla + +class nsViewManager final { + ~nsViewManager(); + + public: + friend class nsView; + + typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect; + typedef mozilla::LayoutDeviceIntRegion LayoutDeviceIntRegion; + + NS_INLINE_DECL_REFCOUNTING(nsViewManager) + + nsViewManager(); + + /** + * Initialize the ViewManager + * Note: this instance does not hold a reference to the presshell + * because it holds a reference to this instance. + * @result The result of the initialization, NS_OK if no errors + */ + nsresult Init(nsDeviceContext* aContext); + + /** + * Create an ordinary view + * @param aBounds initial bounds for view + * XXX We should eliminate this parameter; you can set the bounds + * after CreateView + * @param aParent intended parent for view. this is not actually set in the + * nsView through this method. it is only used by the initialization + * code to walk up the view tree, if necessary, to find resources. + * XXX We should eliminate this parameter! + * @param aVisibilityFlag initial visibility state of view + * XXX We should eliminate this parameter; you can set it after + * CreateView + * @result The new view. Never null. + */ + nsView* CreateView(const nsRect& aBounds, nsView* aParent, + ViewVisibility aVisibilityFlag = ViewVisibility::Show); + + /** + * Get the root of the view tree. + * @result the root view + */ + nsView* GetRootView() { return mRootView; } + + /** + * Set the root of the view tree. Does not destroy the current root view. + * aView may have a parent view managed by a different view manager. + * aView may have a widget (anything but printing) or may not (printing). + * @param aView view to set as root + */ + void SetRootView(nsView* aView); + + /** + * Get the dimensions of the root window. The dimensions are in + * twips + * @param aWidth out parameter for width of window in twips + * @param aHeight out parameter for height of window in twips + */ + void GetWindowDimensions(nscoord* aWidth, nscoord* aHeight); + + /** + * Set the dimensions of the root window. + * Called if the root window is resized. The dimensions are in + * twips + * @param aWidth of window in twips + * @param aHeight of window in twips + */ + void SetWindowDimensions(nscoord aWidth, nscoord aHeight, + bool aDelayResize = false); + + /** + * Do any resizes that are pending. + */ + void FlushDelayedResize(); + + /** + * Called to inform the view manager that the entire area of a view + * is dirty and needs to be redrawn. + * @param aView view to paint. should be root view + */ + void InvalidateView(nsView* aView); + + /** + * Called to inform the view manager that some portion of a view is dirty and + * needs to be redrawn. The rect passed in should be in the view's coordinate + * space. Does not check for paint suppression. + * @param aView view to paint. should be root view + * @param rect rect to mark as damaged + */ + void InvalidateViewNoSuppression(nsView* aView, const nsRect& aRect); + + /** + * Called to inform the view manager that it should invalidate all views. + */ + void InvalidateAllViews(); + + /** + * Called to dispatch an event to the appropriate view. Often called + * as a result of receiving a mouse or keyboard event from the widget + * event system. + * @param aEvent event to dispatch + * @param aViewTarget dispatch the event to this view + * @param aStatus event handling status + */ + MOZ_CAN_RUN_SCRIPT + void DispatchEvent(mozilla::WidgetGUIEvent* aEvent, nsView* aViewTarget, + nsEventStatus* aStatus); + + /** + * Given a parent view, insert another view as its child. + * aSibling and aAbove control the "document order" for the insertion. + * If aSibling is null, the view is inserted at the end of the document order + * if aAfter is true, otherwise it is inserted at the beginning. + * If aSibling is non-null, then if aAfter is true, the view is inserted + * after the sibling in document order (appearing above the sibling unless + * overriden by z-order). + * If it is false, the view is inserted before the sibling. + * The view manager generates the appopriate dirty regions. + * @param aParent parent view + * @param aChild child view + * @param aSibling sibling view + * @param aAfter after or before in the document order + */ + void InsertChild(nsView* aParent, nsView* aChild, nsView* aSibling, + bool aAfter); + + /** + * Remove a specific child view from its parent. This will NOT remove its + * placeholder if there is one. The view manager generates the appropriate + * dirty regions. + * @param aParent parent view + * @param aChild child view + */ + void RemoveChild(nsView* aChild); + + /** + * Move a view to the specified position, provided in parent coordinates. + * The new position is the (0, 0) origin for the view's coordinate system. + * The view's bounds may extend above or to the left of this point. + * The view manager generates the appropriate dirty regions. + * @param aView view to move + * @param aX x value for new view position + * @param aY y value for new view position + */ + void MoveViewTo(nsView* aView, nscoord aX, nscoord aY); + + /** + * Resize a view. In addition to setting the width and height, you can + * set the x and y of its bounds relative to its position. Negative x and y + * will let the view extend above and to the left of the (0,0) point in its + * coordinate system. + * The view manager generates the appropriate dirty regions. + * @param aView view to move + * @param the new bounds relative to the current position + * @param RepaintExposedAreaOnly + * if true Repaint only the expanded or contracted region, + * if false Repaint the union of the old and new rectangles. + */ + void ResizeView(nsView* aView, const nsRect& aRect, + bool aRepaintExposedAreaOnly = false); + + /** + * Set the visibility of a view. Hidden views have the effect of hiding + * their descendants as well. This does not affect painting, so layout + * is responsible for ensuring that content in hidden views is not + * painted nor handling events. It does affect the visibility of widgets; + * if a view is hidden, descendant views with widgets have their widgets + * hidden. + * The view manager generates the appropriate dirty regions. + * @param aView view to change visibility state of + * @param visible new visibility state + */ + void SetViewVisibility(nsView* aView, ViewVisibility aVisible); + + /** + * Set the z-index of a view. Positive z-indices mean that a view + * is above its parent in z-order. Negative z-indices mean that a + * view is below its parent. + * The view manager generates the appropriate dirty regions. + * @param aAutoZIndex indicate that the z-index of a view is "auto". An + * "auto" z-index means that the view does not define a new stacking + * context, which means that the z-indicies of the view's children are + * relative to the view's siblings. + * @param aView view to change z depth of + * @param aZindex explicit z depth + */ + void SetViewZIndex(nsView* aView, bool aAutoZIndex, int32_t aZindex); + + /** + * Set whether the view "floats" above all other views, + * which tells the compositor not to consider higher views in + * the view hierarchy that would geometrically intersect with + * this view. This is a hack, but it fixes some problems with + * views that need to be drawn in front of all other views. + */ + void SetViewFloating(nsView* aView, bool aFloatingView); + + /** + * Set the presshell associated with this manager + * @param aPresShell - new presshell + */ + void SetPresShell(mozilla::PresShell* aPresShell) { mPresShell = aPresShell; } + + /** + * Get the pres shell associated with this manager + */ + mozilla::PresShell* GetPresShell() const { return mPresShell; } + + /** + * Get the device context associated with this manager + */ + nsDeviceContext* GetDeviceContext() const { return mContext; } + + /** + * A stack class for disallowing changes that would enter painting. For + * example, popup widgets shouldn't be resized during reflow, since doing so + * might cause synchronous painting inside reflow which is forbidden. + * While refresh is disabled, widget geometry changes are deferred and will + * be handled later, either from the refresh driver or from an NS_WILL_PAINT + * event. + * We don't want to defer widget geometry changes all the time. Resizing a + * popup from script doesn't need to be deferred, for example, especially + * since popup widget geometry is observable from script and expected to + * update synchronously. + */ + class MOZ_STACK_CLASS AutoDisableRefresh { + public: + explicit AutoDisableRefresh(nsViewManager* aVM) { + if (aVM) { + mRootVM = aVM->IncrementDisableRefreshCount(); + } + } + ~AutoDisableRefresh() { + if (mRootVM) { + mRootVM->DecrementDisableRefreshCount(); + } + } + + private: + AutoDisableRefresh(const AutoDisableRefresh& aOther); + const AutoDisableRefresh& operator=(const AutoDisableRefresh& aOther); + + RefPtr<nsViewManager> mRootVM; + }; + + private: + friend class AutoDisableRefresh; + + nsViewManager* IncrementDisableRefreshCount(); + void DecrementDisableRefreshCount(); + + public: + /** + * Retrieve the widget at the root of the nearest enclosing + * view manager whose root view has a widget. + */ + nsIWidget* GetRootWidget() const; + + /** + * Indicate whether the viewmanager is currently painting + * + * @param aPainting true if the viewmanager is painting + * false otherwise + */ + void IsPainting(bool& aIsPainting); + + /** + * Retrieve the time of the last user event. User events + * include mouse and keyboard events. The viewmanager + * saves the time of the last user event. + * + * @param aTime Last user event time in microseconds + */ + void GetLastUserEventTime(uint32_t& aTime); + + /** + * Find the nearest display root view for the view aView. This is the view for + * the nearest enclosing popup or the root view for the root document. + */ + static nsView* GetDisplayRootFor(nsView* aView); + + /** + * Flush the accumulated dirty region to the widget and update widget + * geometry. + */ + MOZ_CAN_RUN_SCRIPT void ProcessPendingUpdates(); + + /** + * Just update widget geometry without flushing the dirty region + */ + MOZ_CAN_RUN_SCRIPT void UpdateWidgetGeometry(); + + int32_t AppUnitsPerDevPixel() const { + return mContext->AppUnitsPerDevPixel(); + } + + private: + static uint32_t gLastUserEventTime; + + /* Update the cached RootViewManager pointer on this view manager. */ + void InvalidateHierarchy(); + void FlushPendingInvalidates(); + + MOZ_CAN_RUN_SCRIPT + void ProcessPendingUpdatesForView(nsView* aView, + bool aFlushDirtyRegion = true); + void ProcessPendingUpdatesRecurse( + nsView* aView, AutoTArray<nsCOMPtr<nsIWidget>, 1>& aWidgets); + MOZ_CAN_RUN_SCRIPT + void ProcessPendingUpdatesPaint(nsIWidget* aWidget); + + void FlushDirtyRegionToWidget(nsView* aView); + /** + * Call WillPaint() on all view observers under this vm root. + */ + MOZ_CAN_RUN_SCRIPT_BOUNDARY void CallWillPaintOnObservers(); + void ReparentChildWidgets(nsView* aView, nsIWidget* aNewWidget); + void ReparentWidgets(nsView* aView, nsView* aParent); + void InvalidateWidgetArea(nsView* aWidgetView, + const nsRegion& aDamagedRegion); + + void InvalidateViews(nsView* aView); + + // aView is the view for aWidget and aRegion is relative to aWidget. + MOZ_CAN_RUN_SCRIPT + void Refresh(nsView* aView, const LayoutDeviceIntRegion& aRegion); + + // Utilities + + bool IsViewInserted(nsView* aView); + + /** + * Intersects aRect with aView's bounds and then transforms it from aView's + * coordinate system to the coordinate system of the widget attached to + * aView. + */ + LayoutDeviceIntRect ViewToWidget(nsView* aView, const nsRect& aRect) const; + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + void DoSetWindowDimensions(nscoord aWidth, nscoord aHeight); + bool ShouldDelayResize() const; + + bool IsPainting() const { return RootViewManager()->mPainting; } + + void SetPainting(bool aPainting) { RootViewManager()->mPainting = aPainting; } + + void InvalidateView(nsView* aView, const nsRect& aRect); + + nsViewManager* RootViewManager() const { + return mRootViewManager ? mRootViewManager.get() + : const_cast<nsViewManager*>(this); + } + bool IsRootVM() const { return !mRootViewManager; } + + // Whether synchronous painting is allowed at the moment. For example, + // widget geometry changes can cause synchronous painting, so they need to + // be deferred while refresh is disabled. + bool IsPaintingAllowed() { + return RootViewManager()->mRefreshDisableCount == 0; + } + + MOZ_CAN_RUN_SCRIPT void WillPaintWindow(nsIWidget* aWidget); + MOZ_CAN_RUN_SCRIPT + bool PaintWindow(nsIWidget* aWidget, const LayoutDeviceIntRegion& aRegion); + MOZ_CAN_RUN_SCRIPT void DidPaintWindow(); + + // Call this when you need to let the viewmanager know that it now has + // pending updates. + void PostPendingUpdate(); + + RefPtr<nsDeviceContext> mContext; + mozilla::PresShell* mPresShell; + + // The size for a resize that we delayed until the root view becomes + // visible again. + nsSize mDelayedResize; + + nsView* mRootView; + + // mRootViewManager is a strong reference to the root view manager, unless + // |this| is the root, in which case mRootViewManager is null. Callers + // should use RootViewManager() (which handles that case) rather than using + // mRootViewManager directly. + RefPtr<nsViewManager> mRootViewManager; + + // The following members should not be accessed directly except by + // the root view manager. Some have accessor functions to enforce + // this, as noted. + + int32_t mRefreshDisableCount; + // Use IsPainting() and SetPainting() to access mPainting. + bool mPainting; + bool mRecursiveRefreshPending; + bool mHasPendingWidgetGeometryChanges; + + // from here to public should be static and locked... MMP + + // list of view managers + static mozilla::StaticAutoPtr<nsTArray<nsViewManager*>> gViewManagers; +}; + +/** + Invalidation model: + + 1) Callers call into the view manager and ask it to invalidate a view. + + 2) The view manager finds the "right" widget for the view, henceforth called + the root widget. + + 3) The view manager traverses descendants of the root widget and for each + one that needs invalidation stores the rect to invalidate on the widget's + view (batching). + + 4) The dirty region is flushed to the right widget when + ProcessPendingUpdates is called from the RefreshDriver. + + It's important to note that widgets associated to views outside this view + manager can end up being invalidated during step 3. Therefore, the end of a + view update batch really needs to traverse the entire view tree, to ensure + that those invalidates happen. + + To cope with this, invalidation processing and should only happen on the + root viewmanager. +*/ + +#endif // nsViewManager_h___ |