diff options
Diffstat (limited to 'view/nsViewManager.cpp')
-rw-r--r-- | view/nsViewManager.cpp | 1185 |
1 files changed, 1185 insertions, 0 deletions
diff --git a/view/nsViewManager.cpp b/view/nsViewManager.cpp new file mode 100644 index 000000000..e41f005b2 --- /dev/null +++ b/view/nsViewManager.cpp @@ -0,0 +1,1185 @@ +/* -*- 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/. */ + +#define PL_ARENA_CONST_ALIGN_MASK (sizeof(void*)-1) +#include "plarena.h" + +#include "nsAutoPtr.h" +#include "nsViewManager.h" +#include "nsGfxCIID.h" +#include "nsView.h" +#include "nsCOMPtr.h" +#include "mozilla/MouseEvents.h" +#include "nsRegion.h" +#include "nsCOMArray.h" +#include "nsIPluginWidget.h" +#include "nsXULPopupManager.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "mozilla/StartupTimeline.h" +#include "GeckoProfiler.h" +#include "nsRefreshDriver.h" +#include "mozilla/Preferences.h" +#include "nsContentUtils.h" // for nsAutoScriptBlocker +#include "nsLayoutUtils.h" +#include "Layers.h" +#include "gfxPlatform.h" +#include "gfxPrefs.h" +#include "nsIDocument.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 +nsTArray<nsViewManager*>* nsViewManager::gViewManagers = nullptr; +uint32_t nsViewManager::gLastUserEventTime = 0; + +nsViewManager::nsViewManager() + : mDelayedResize(NSCOORD_NONE, NSCOORD_NONE) +{ + mRootViewManager = this; + if (gViewManagers == nullptr) { + // Create an array to hold a list of view managers + gViewManagers = new nsTArray<nsViewManager*>; + } + + gViewManagers->AppendElement(this); + + // NOTE: we use a zeroing operator new, so all data members are + // assumed to be cleared here. + mHasPendingWidgetGeometryChanges = false; + mRecursiveRefreshPending = false; +} + +nsViewManager::~nsViewManager() +{ + if (mRootView) { + // Destroy any remaining views + mRootView->Destroy(); + mRootView = nullptr; + } + + if (!IsRootVM()) { + // We have a strong ref to mRootViewManager + NS_RELEASE(mRootViewManager); + } + + 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 + delete gViewManagers; + 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) +{ + NS_PRECONDITION(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, + nsViewVisibility aVisibilityFlag) +{ + nsView *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) +{ + NS_PRECONDITION(!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)) { + // Don't resize the widget. It is already being set elsewhere. + mRootView->SetDimensions(newDim, true, false); + if (mPresShell) + mPresShell->ResizeReflow(aWidth, aHeight, oldDim.width, oldDim.height); + } +} + +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(false); + } + mDelayedResize.SizeTo(NSCOORD_NONE, NSCOORD_NONE); + DoSetWindowDimensions(aWidth, aHeight); + } else { + mDelayedResize.SizeTo(aWidth, aHeight); + if (mPresShell && mPresShell->GetDocument()) { + nsIDocument* doc = mPresShell->GetDocument(); + doc->SetNeedStyleFlush(); + doc->SetNeedLayoutFlush(); + } + } + } +} + +void +nsViewManager::FlushDelayedResize(bool aDoReflow) +{ + if (mDelayedResize != nsSize(NSCOORD_NONE, NSCOORD_NONE)) { + if (aDoReflow) { + DoSetWindowDimensions(mDelayedResize.width, mDelayedResize.height); + mDelayedResize.SizeTo(NSCOORD_NONE, NSCOORD_NONE); + } else if (mPresShell && !mPresShell->GetIsViewportOverridden()) { + nsPresContext* presContext = mPresShell->GetPresContext(); + if (presContext) { + presContext->SetVisibleArea(nsRect(nsPoint(0, 0), mDelayedResize)); + } + } + } +} + +// 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, except for plugins. + nsIWidget* widget = displayRoot->GetWidget(); + if (widget && widget->WindowType() == eWindowType_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; + } + + // damageRegion is the damaged area, in twips, relative to the view origin + nsRegion damageRegion = aRegion.ToAppUnits(AppUnitsPerDevPixel()); + + // move region from widget coordinates into view coordinates + damageRegion.MoveBy(-aView->ViewToWidgetOffset()); + + if (damageRegion.IsEmpty()) { +#ifdef DEBUG_roc + nsRect viewRect = aView->GetDimensions(); + nsRect damageRect = damageRegion.GetBounds(); + printf_stderr("XXX Damage rectangle (%d,%d,%d,%d) does not intersect the widget's view (%d,%d,%d,%d)!\n", + damageRect.x, damageRect.y, damageRect.width, damageRect.height, + viewRect.x, viewRect.y, viewRect.width, viewRect.height); +#endif + 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 (mPresShell) { +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr("--COMPOSITE-- %p\n", mPresShell); + } +#endif + uint32_t paintFlags = nsIPresShell::PAINT_COMPOSITE; + LayerManager *manager = widget->GetLayerManager(); + if (!manager->NeedsWidgetInvalidation()) { + manager->FlushRendering(); + } else { + mPresShell->Paint(aView, damageRegion, + paintFlags); + } +#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; + } + + nsCOMPtr<nsIPresShell> rootShell(mPresShell); + nsTArray<nsCOMPtr<nsIWidget> > 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 (nsIPresShell* ps = vm->GetPresShell()) { + ps->SyncWindowProperties(view); + } + } + } + } + view = nsView::GetViewFor(widgets[i]); + if (view) { + view->ResetWidgetBounds(false, true); + } + } + if (rootShell->GetViewManager() != this) { + return; // presentation might have been torn down + } + if (aFlushDirtyRegion) { + profiler_tracing("Paint", "DisplayList", TRACING_INTERVAL_START); + nsAutoScriptBlocker scriptBlocker; + SetPainting(true); + for (uint32_t i = 0; i < widgets.Length(); ++i) { + nsIWidget* widget = widgets[i]; + nsView* view = nsView::GetViewFor(widget); + if (view) { + view->GetViewManager()->ProcessPendingUpdatesPaint(widget); + } + } + SetPainting(false); + profiler_tracing("Paint", "DisplayList", TRACING_INTERVAL_END); + } +} + +void +nsViewManager::ProcessPendingUpdatesRecurse(nsView* aView, + nsTArray<nsCOMPtr<nsIWidget> >& 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(true); + } + } + 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 (mPresShell) { +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr("---- PAINT START ----PresShell(%p), nsView(%p), nsIWidget(%p)\n", + mPresShell, view, aWidget); + } +#endif + + mPresShell->Paint(view, nsRegion(), nsIPresShell::PAINT_LAYERS); + 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); + + // If we draw the frame counter we need to make sure we invalidate the area + // for it to make it on screen + if (gfxPrefs::DrawFrameCounter()) { + nsRect counterBounds = ToAppUnits(gfxPlatform::FrameCounterBounds(), AppUnitsPerDevPixel()); + r.OrWith(counterBounds); + } + + 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(); + if (!dirtyRegion) + return; + + 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; + } + + // Update all child widgets with the damage. In the process, + // accumulate the union of all the child widget areas, or at least + // some subset of that. + nsRegion children; + if (widget->GetTransparencyMode() != eTransparencyTransparent) { + for (nsIWidget* childWidget = widget->GetFirstChild(); + childWidget; + childWidget = childWidget->GetNextSibling()) { + nsView* view = nsView::GetViewFor(childWidget); + NS_ASSERTION(view != aWidgetView, "will recur infinitely"); + nsWindowType type = childWidget->WindowType(); + if (view && childWidget->IsVisible() && type != eWindowType_popup) { + NS_ASSERTION(childWidget->IsPlugin(), + "Only plugin or popup widgets can be children!"); + + // We do not need to invalidate in plugin widgets, but we should + // exclude them from the invalidation region IF we're not on + // Mac. On Mac we need to draw under plugin widgets, because + // plugin widgets are basically invisible +#ifndef XP_MACOSX + // GetBounds should compensate for chrome on a toplevel widget + LayoutDeviceIntRect bounds = childWidget->GetBounds(); + + nsTArray<LayoutDeviceIntRect> clipRects; + childWidget->GetWindowClipRegion(&clipRects); + for (uint32_t i = 0; i < clipRects.Length(); ++i) { + nsRect rr = LayoutDeviceIntRect::ToAppUnits( + clipRects[i] + bounds.TopLeft(), AppUnitsPerDevPixel()); + children.Or(children, rr - aWidgetView->ViewToWidgetOffset()); + children.SimplifyInward(20); + } +#endif + } + } + } + + nsRegion leftOver; + leftOver.Sub(aDamagedRegion, children); + + if (!leftOver.IsEmpty()) { + for (auto iter = leftOver.RectIter(); !iter.Done(); iter.Next()) { + LayoutDeviceIntRect bounds = ViewToWidget(aWidgetView, iter.Get()); + widget->Invalidate(bounds); + } + } +} + +static bool +ShouldIgnoreInvalidation(nsViewManager* aVM) +{ + while (aVM) { + nsIPresShell* shell = aVM->GetPresShell(); + if (!shell || shell->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) +{ + NS_PRECONDITION(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) +{ + RefPtr<nsIWidget> widget(aWidget); + if (widget) { + nsView* view = nsView::GetViewFor(widget); + LayerManager* manager = widget->GetLayerManager(); + if (view && + (view->ForcedRepaint() || !manager->NeedsWidgetInvalidation())) { + ProcessPendingUpdates(); + // Re-get the view pointer here since the ProcessPendingUpdates might have + // destroyed it during CallWillPaintOnObservers. + view = nsView::GetViewFor(widget); + if (view) { + view->SetForcedRepaint(false); + } + } + } + + nsCOMPtr<nsIPresShell> shell = mPresShell; + if (shell) { + shell->WillPaintWindow(); + } +} + +bool nsViewManager::PaintWindow(nsIWidget* aWidget, + 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() +{ + nsCOMPtr<nsIPresShell> shell = mPresShell; + if (shell) { + shell->DidPaintWindow(); + } +} + +void +nsViewManager::DispatchEvent(WidgetGUIEvent *aEvent, + nsView* aView, + nsEventStatus* aStatus) +{ + PROFILER_LABEL("nsViewManager", "DispatchEvent", + js::ProfileEntry::Category::EVENTS); + + 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() || + aEvent->mMessage == ePluginInputEvent) { + 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() || + aEvent->IsNonRetargetedNativeEventDelivererForPlugin() || + aEvent->HasPluginActivationEventMessage())) { + 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. + nsCOMPtr<nsIPresShell> shell = view->GetViewManager()->GetPresShell(); + if (shell) { + shell->HandleEvent(frame, aEvent, false, aStatus); + return; + } + } + + *aStatus = nsEventStatus_eIgnore; +} + +// Recursively reparent widgets if necessary + +void nsViewManager::ReparentChildWidgets(nsView* aView, nsIWidget *aNewWidget) +{ + NS_PRECONDITION(aNewWidget, ""); + + 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) { +#ifdef DEBUG + nsresult rv = +#endif + widget->SetParent(aNewWidget); + NS_ASSERTION(NS_SUCCEEDED(rv), "SetParent failed!"); + } + } 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) +{ + NS_PRECONDITION(aParent, "Must have a parent"); + NS_PRECONDITION(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) +{ + NS_PRECONDITION(nullptr != aParent, "null ptr"); + NS_PRECONDITION(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::InsertChild(nsView *aParent, nsView *aChild, int32_t aZIndex) +{ + // no-one really calls this with anything other than aZIndex == 0 on a fresh view + // XXX this method should simply be eliminated and its callers redirected to the real method + SetViewZIndex(aChild, false, aZIndex); + InsertChild(aParent, aChild, nullptr, 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, nsViewVisibility 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; + } else if (aView->GetParent() == nullptr) { + return false; + } else { + 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!"); +} + +void +nsViewManager::GetRootWidget(nsIWidget **aWidget) +{ + if (!mRootView) { + *aWidget = nullptr; + return; + } + if (mRootView->HasWidget()) { + *aWidget = mRootView->GetWidget(); + NS_ADDREF(*aWidget); + return; + } + if (mRootView->GetParent()) { + mRootView->GetParent()->GetViewManager()->GetRootWidget(aWidget); + return; + } + *aWidget = 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()) { + 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); + } +} + +void +nsViewManager::UpdateWidgetGeometry() +{ + if (!IsRootVM()) { + RootViewManager()->UpdateWidgetGeometry(); + return; + } + + if (mHasPendingWidgetGeometryChanges) { + mHasPendingWidgetGeometryChanges = false; + RefPtr<nsViewManager> strongThis(this); + ProcessPendingUpdatesForView(mRootView, false); + } +} + +void +nsViewManager::CallWillPaintOnObservers() +{ + NS_PRECONDITION(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()) { + nsCOMPtr<nsIPresShell> shell = vm->GetPresShell(); + if (shell) { + shell->WillPaint(); + } + } + } + } +} + +void +nsViewManager::GetLastUserEventTime(uint32_t& aTime) +{ + aTime = gLastUserEventTime; +} + +void +nsViewManager::InvalidateHierarchy() +{ + if (mRootView) { + if (!IsRootVM()) { + NS_RELEASE(mRootViewManager); + } + nsView *parent = mRootView->GetParent(); + if (parent) { + mRootViewManager = parent->GetViewManager()->RootViewManager(); + NS_ADDREF(mRootViewManager); + NS_ASSERTION(mRootViewManager != this, + "Root view had a parent, but it has the same view manager"); + } else { + mRootViewManager = this; + } + } +} + |