From aface9187854046e6ec60f07fd64b705677838d7 Mon Sep 17 00:00:00 2001 From: "roc+%cs.cmu.edu" Date: Mon, 7 Mar 2005 01:24:22 +0000 Subject: [PATCH] Bug 285038. Support offscreen rendering API in nsIViewManager and provide MOZ_FORCE_PAINT_AFTER_ONLOAD environment variable hack to dump loaded documents to image files. r+sr=bzbarsky --- layout/base/nsDocumentViewer.cpp | 118 ++++++++++++++++++++++++++++--- view/public/nsIViewManager.h | 21 ++++++ view/src/nsView.cpp | 19 +++-- view/src/nsView.h | 4 +- view/src/nsViewManager.cpp | 94 +++++++++++++++++++----- view/src/nsViewManager.h | 10 ++- 6 files changed, 232 insertions(+), 34 deletions(-) diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp index 5cb4ef535ca1..53f39384f249 100644 --- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -111,6 +111,7 @@ #ifdef MOZ_XUL #include "nsIXULDocument.h" #endif +#include "nsPrintfCString.h" #include "nsIClipboardHelper.h" @@ -359,6 +360,8 @@ private: nsresult GetPopupLinkNode(nsIDOMNode** aNode); nsresult GetPopupImageNode(nsIImageLoadingContent** aNode); + void DumpContentToPPM(const char* aFileName); + void PrepareToStartLoad(void); nsresult SyncParentSubDocMap(); @@ -845,6 +848,107 @@ DocumentViewerImpl::InitInternal(nsIWidget* aParentWidget, return rv; } +void +DocumentViewerImpl::DumpContentToPPM(const char* aFileName) +{ + mDocument->FlushPendingNotifications(Flush_Display); + + nsIScrollableView* scrollableView; + mViewManager->GetRootScrollableView(&scrollableView); + nsIView* view; + if (scrollableView) { + scrollableView->GetScrolledView(view); + } else { + mViewManager->GetRootView(view); + } + nsRect r = view->GetBounds() - view->GetPosition(); + float p2t = mPresContext->PixelsToTwips(); + // Limit the bitmap size to 5000x5000 + nscoord twipLimit = NSIntPixelsToTwips(5000, p2t); + if (r.height > twipLimit) + r.height = twipLimit; + if (r.width > twipLimit) + r.width = twipLimit; + + const char* status; + + if (r.IsEmpty()) { + status = "EMPTY"; + } else { + nsCOMPtr context; + nsresult rv = mViewManager->RenderOffscreen(view, r, + PR_FALSE, NS_RGB(255, 255, 255), + getter_AddRefs(context)); + + if (NS_FAILED(rv)) { + status = "FAILEDRENDER"; + } else { + nsIDrawingSurface* surface; + context->GetDrawingSurface(&surface); + if (!surface) { + status = "NOSURFACE"; + } else { + float t2p = mPresContext->TwipsToPixels(); + PRUint32 width = NSTwipsToIntPixels(view->GetBounds().width, t2p); + PRUint32 height = NSTwipsToIntPixels(view->GetBounds().height, t2p); + + PRUint8* data; + PRInt32 rowLen, rowSpan; + rv = surface->Lock(0, 0, width, height, (void**)&data, &rowSpan, &rowLen, + NS_LOCK_SURFACE_READ_ONLY); + if (NS_FAILED(rv)) { + status = "FAILEDLOCK"; + } else { + PRUint32 bytesPerPix = rowLen/width; + nsPixelFormat format; + surface->GetPixelFormat(&format); + + PRUint8* buf = new PRUint8[3*width]; + if (buf) { + FILE* f = fopen(aFileName, "wb"); + if (f) { + fprintf(f, "P6\n%d\n%d\n255\n", width, height); + for (PRUint32 i = 0; i < height; ++i) { + PRUint8* src = data + i*rowSpan; + PRUint8* dest = buf; + for (PRUint32 j = 0; j < width; ++j) { + /* v is the pixel value */ +#ifdef IS_BIG_ENDIAN + PRUint32 v = (src[0] << 24) | (src[1] << 16) || (src[2] << 8) | src[3]; + v >>= (32 - 8*bytesPerPix); +#else + PRUint32 v = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24); +#endif + dest[0] = ((v & format.mRedMask) >> format.mRedShift) << (8 - format.mRedCount); + dest[1] = ((v & format.mGreenMask) >> format.mGreenShift) << (8 - format.mGreenCount); + dest[2] = ((v & format.mBlueMask) >> format.mBlueShift) << (8 - format.mBlueCount); + src += bytesPerPix; + dest += 3; + } + fwrite(buf, 3, width, f); + } + fclose(f); + status = "OK"; + } + + delete[] buf; + } + surface->Unlock(); + } + context->DestroyDrawingSurface(surface); + } + } + } + + nsIURI *uri = mDocument->GetDocumentURI(); + nsCAutoString spec; + if (uri) { + uri->GetAsciiSpec(spec); + } + printf("GECKO: PAINT FORCED AFTER ONLOAD: %s %s (%s)\n", spec.get(), aFileName, status); + fflush(stdout); +} + // // LoadComplete(aStatus) // @@ -912,15 +1016,13 @@ DocumentViewerImpl::LoadComplete(nsresult aStatus) static PRBool forcePaint = PR_GetEnv("MOZ_FORCE_PAINT_AFTER_ONLOAD") != nsnull; + static PRUint32 index = 0; if (forcePaint) { - mDocument->FlushPendingNotifications(Flush_Display); - nsIURI *uri = mDocument->GetDocumentURI(); - nsCAutoString spec; - if (uri) { - uri->GetSpec(spec); - } - printf("GECKO: PAINT FORCED AFTER ONLOAD: %s\n", spec.get()); - fflush(stdout); + nsCAutoString name(PR_GetEnv("MOZ_FORCE_PAINT_AFTER_ONLOAD")); + name.AppendLiteral("-"); + ++index; + name.AppendInt(index); + DumpContentToPPM(name.get()); } #ifdef NS_PRINTING diff --git a/view/public/nsIViewManager.h b/view/public/nsIViewManager.h index d41f3073f5dc..f8f62bab466c 100644 --- a/view/public/nsIViewManager.h +++ b/view/public/nsIViewManager.h @@ -449,6 +449,27 @@ public: //XXXbz how is this different from UpdateView(NS_VMREFRESH_IMMEDIATE)? NS_IMETHOD Display(nsIView *aView, nscoord aX, nscoord aY, const nsRect& aClipRect) = 0; + /** + * Dump the specified view into a new offscreen rendering context. + * The view is treated as the display root and is not clipped by + * ancestor views, nor is ancestor or sibling content displayed even + * if it overlaps the view. + * @param aRect is the region to capture into the offscreen buffer, in the view's + * coordinate system + * @param aUntrusted set to PR_TRUE if the contents may be passed to malicious + * agents. E.g. we might choose not to paint the contents of sensitive widgets + * such as the file name in a file upload widget, and we might choose not + * to paint themes. + * @param aBackgroundColor a background color to render onto + * @param aRenderedContext gets set to a rendering context whose offscreen + * buffer can be locked to get the data. The buffer's size will be aRect's size. + * In all cases the caller must clean it up by calling + * cx->DestroyDrawingSurface(cx->GetDrawingSurface()). + */ + NS_IMETHOD RenderOffscreen(nsIView* aView, nsRect aRect, PRBool aUntrusted, + nscolor aBackgroundColor, + nsIRenderingContext** aRenderedContext) = 0; + /** * Add a listener to the view manager's composite listener list. * @param aListener - new listener diff --git a/view/src/nsView.cpp b/view/src/nsView.cpp index 34e024e7ae7e..1157fe30fc30 100644 --- a/view/src/nsView.cpp +++ b/view/src/nsView.cpp @@ -856,15 +856,20 @@ PRBool nsIView::ExternalIsRoot() const return nsIView::IsRoot(); } -// Returns PR_TRUE iff we found a placeholder parent edge on the way -static PRBool ApplyClipRect(const nsView* aView, nsRect* aRect, PRBool aFollowPlaceholders) +/** + * @param aStopAtView clipping from ancestors of this view will not be applied + * @return PR_TRUE iff we found a placeholder parent edge on the way up the view tree + * from aView to aStopAtView + */ +static PRBool ApplyClipRect(const nsView* aView, nsRect* aRect, PRBool aFollowPlaceholders, + nsIView* aStopAtView) { // this records the offset from the origin of the current aView // to the origin of the initial aView nsPoint offset(0, 0); PRBool lastViewIsFloating = aView->GetFloating(); PRBool foundPlaceholders = PR_FALSE; - while (PR_TRUE) { + while (aView != aStopAtView) { const nsView* parentView = aView->GetParent(); nsPoint offsetFromParent = aView->GetPosition(); @@ -874,7 +879,7 @@ static PRBool ApplyClipRect(const nsView* aView, nsRect* aRect, PRBool aFollowPl if (aFollowPlaceholders) { // correct offsetFromParent to account for the fact that we're // switching parentView to ZParent - // Note tha the common case is that parentView is an ancestor of + // Note that the common case is that parentView is an ancestor of // ZParent. const nsView* zParentChain; for (zParentChain = zParent; @@ -934,12 +939,12 @@ static PRBool ApplyClipRect(const nsView* aView, nsRect* aRect, PRBool aFollowPl return foundPlaceholders; } -nsRect nsView::GetClippedRect() +nsRect nsView::GetClippedRect(nsIView* aStopAtView) { nsRect clip = GetDimensions(); - PRBool foundPlaceholders = ApplyClipRect(this, &clip, PR_FALSE); + PRBool foundPlaceholders = ApplyClipRect(this, &clip, PR_FALSE, aStopAtView); if (foundPlaceholders && !clip.IsEmpty()) { - ApplyClipRect(this, &clip, PR_TRUE); + ApplyClipRect(this, &clip, PR_TRUE, aStopAtView); } return clip; } diff --git a/view/src/nsView.h b/view/src/nsView.h index d1f0c4263b0d..14eb620d97ef 100644 --- a/view/src/nsView.h +++ b/view/src/nsView.h @@ -226,8 +226,10 @@ public: /** * @return the view's dimensions after clipping by ancestors is applied * (the rect is relative to the view's origin) + * @param aStopAtView do not consider clipping imposed by views above this view + * on the ancestor chain */ - nsRect GetClippedRect(); + nsRect GetClippedRect(nsIView* aStopAtView = nsnull); // Helper function to get the view that's associated with a widget static nsView* GetViewFor(nsIWidget* aWidget); diff --git a/view/src/nsViewManager.cpp b/view/src/nsViewManager.cpp index c89084482404..b49fd98721e6 100644 --- a/view/src/nsViewManager.cpp +++ b/view/src/nsViewManager.cpp @@ -884,7 +884,8 @@ void nsViewManager::Refresh(nsView *aView, nsIRenderingContext *aContext, PLArenaPool displayArena; PL_INIT_ARENA_POOL(&displayArena, "displayArena", 1024); PRBool anyTransparentPixels - = BuildRenderingDisplayList(aView, damageRegion, &displayList, displayArena); + = BuildRenderingDisplayList(aView, damageRegion, &displayList, displayArena, + PR_FALSE, PR_FALSE); PRBool needBlending = PR_FALSE; for (PRInt32 i = 0; i < displayList.Count(); i++) { DisplayListElement2* element = NS_STATIC_CAST(DisplayListElement2*, displayList.ElementAt(i)); @@ -1288,14 +1289,18 @@ void nsViewManager::AddCoveringWidgetsToOpaqueRegion(nsRegion &aRgn, nsIDeviceCo } PRBool nsViewManager::BuildRenderingDisplayList(nsIView* aRootView, - const nsRegion& aRegion, nsVoidArray* aDisplayList, PLArenaPool &aPool) + const nsRegion& aRegion, nsVoidArray* aDisplayList, PLArenaPool &aPool, + PRBool aIgnoreCoveringWidgets, PRBool aIgnoreOutsideClipping) { BuildDisplayList(NS_STATIC_CAST(nsView*, aRootView), - aRegion.GetBounds(), PR_FALSE, PR_FALSE, aDisplayList, aPool); + aRegion.GetBounds(), PR_FALSE, aIgnoreOutsideClipping, + aDisplayList, aPool); nsRegion opaqueRgn; - AddCoveringWidgetsToOpaqueRegion(opaqueRgn, mContext, - NS_STATIC_CAST(nsView*, aRootView)); + if (!aIgnoreCoveringWidgets) { + AddCoveringWidgetsToOpaqueRegion(opaqueRgn, mContext, + NS_STATIC_CAST(nsView*, aRootView)); + } nsRect finalTransparentRect; OptimizeDisplayList(aDisplayList, aRegion, finalTransparentRect, opaqueRgn, PR_FALSE); @@ -2332,9 +2337,11 @@ static PRBool ComputePlaceholderContainment(nsView* aView) { aRect is the area in aView which we want to build a display list for. Set aEventProcesing when the list is required for event processing. - Set aCaptured if the event is being captured by the given view. + Set aCaptured if the event or painting is being captured by the given view so + only views that are descended from the given view are considered. */ -void nsViewManager::BuildDisplayList(nsView* aView, const nsRect& aRect, PRBool aEventProcessing, +void nsViewManager::BuildDisplayList(nsView* aView, const nsRect& aRect, + PRBool aEventProcessing, PRBool aCaptured, nsVoidArray* aDisplayList, PLArenaPool &aPool) { @@ -3325,7 +3332,7 @@ nsViewManager::CreateRenderingContext(nsView &aView) if (nsnull != win) { - mContext->CreateRenderingContext(&aView, cx); + mContext->CreateRenderingContext(par, cx); if (nsnull != cx) cx->Translate(ax, ay); @@ -3437,6 +3444,54 @@ NS_IMETHODIMP nsViewManager::GetRootScrollableView(nsIScrollableView **aScrollab return NS_OK; } +NS_IMETHODIMP nsViewManager::RenderOffscreen(nsIView* aView, nsRect aRect, + PRBool aUntrusted, + nscolor aBackgroundColor, + nsIRenderingContext** aRenderedContext) +{ + nsView* view = NS_STATIC_CAST(nsView*, aView); + + *aRenderedContext = nsnull; + + NS_ASSERTION(!aUntrusted, "We don't support untrusted yet"); + if (aUntrusted) + return NS_ERROR_NOT_IMPLEMENTED; + + nsCOMPtr tmpContext + = CreateRenderingContext(*view); + if (!tmpContext) + return NS_ERROR_FAILURE; + + nsRect bounds(nsPoint(0, 0), aRect.Size()); + nsIDrawingSurface* surface; + nsresult rv + = tmpContext->CreateDrawingSurface(bounds, NS_CREATEDRAWINGSURFACE_FOR_PIXEL_ACCESS, + surface); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + nsCOMPtr localcx; + rv = NewOffscreenContext(mContext, surface, aRect, getter_AddRefs(localcx)); + if (NS_FAILED(rv)) { + tmpContext->DestroyDrawingSurface(surface); + return NS_ERROR_FAILURE; + } + // clipping and translation is set by NewOffscreenContext + + localcx->SetColor(aBackgroundColor); + localcx->FillRect(aRect); + + nsAutoVoidArray displayList; + PLArenaPool displayArena; + PL_INIT_ARENA_POOL(&displayArena, "displayArena", 1024); + BuildRenderingDisplayList(view, nsRegion(aRect), &displayList, displayArena, + PR_TRUE, PR_TRUE); + RenderViews(view, *localcx, nsRegion(aRect), surface, displayList); + PL_FreeArenaPool(&displayArena); + PL_FinishArenaPool(&displayArena); + + localcx.swap(*aRenderedContext); +} + NS_IMETHODIMP nsViewManager::Display(nsIView* aView, nscoord aX, nscoord aY, const nsRect& aClipRect) { nsView *view = NS_STATIC_CAST(nsView*, aView); @@ -3470,7 +3525,8 @@ NS_IMETHODIMP nsViewManager::Display(nsIView* aView, nscoord aX, nscoord aY, con nsAutoVoidArray displayList; PLArenaPool displayArena; PL_INIT_ARENA_POOL(&displayArena, "displayArena", 1024); - BuildRenderingDisplayList(view, nsRegion(trect), &displayList, displayArena); + BuildRenderingDisplayList(view, nsRegion(trect), &displayList, displayArena, + PR_FALSE, PR_FALSE); RenderViews(view, *localcx, nsRegion(trect), PR_FALSE, displayList); PL_FreeArenaPool(&displayArena); PL_FinishArenaPool(&displayArena); @@ -3650,7 +3706,8 @@ PRBool nsViewManager::CreateDisplayList(nsView *aView, // Add POP first because the z-tree is in reverse order retval = AddToDisplayList(aView, aResult, bounds, bounds, - POP_FILTER, aX - aOriginX, aY - aOriginY, PR_TRUE, aPool); + POP_FILTER, aX - aOriginX, aY - aOriginY, PR_TRUE, aPool, + aTopView); if (retval) return retval; @@ -3667,7 +3724,8 @@ PRBool nsViewManager::CreateDisplayList(nsView *aView, // Add POP first because the z-tree is in reverse order retval = AddToDisplayList(aView, aResult, bounds, bounds, - POP_CLIP, aX - aOriginX, aY - aOriginY, PR_TRUE, aPool); + POP_CLIP, aX - aOriginX, aY - aOriginY, PR_TRUE, aPool, + aTopView); if (retval) return retval; @@ -3710,7 +3768,8 @@ PRBool nsViewManager::CreateDisplayList(nsView *aView, flags |= VIEW_TRANSPARENT; retval = AddToDisplayList(aView, aResult, bounds, irect, flags, aX - aOriginX, aY - aOriginY, - aEventProcessing && aTopView == aView, aPool); + aEventProcessing && aTopView == aView, aPool, + aTopView); // We're forcing AddToDisplayList to pick up the view only // during event processing, and only when aView is back at the // root of the tree of acceptable views (note that when event @@ -3735,7 +3794,8 @@ PRBool nsViewManager::CreateDisplayList(nsView *aView, bounds.y -= aOriginY; if (AddToDisplayList(aView, aResult, bounds, bounds, PUSH_CLIP, - aX - aOriginX, aY - aOriginY, PR_TRUE, aPool)) { + aX - aOriginX, aY - aOriginY, PR_TRUE, aPool, + aTopView)) { retval = PR_TRUE; } @@ -3750,7 +3810,8 @@ PRBool nsViewManager::CreateDisplayList(nsView *aView, bounds.y -= aOriginY; retval = AddToDisplayList(aView, aResult, bounds, bounds, - PUSH_FILTER, aX - aOriginX, aY - aOriginY, PR_TRUE, aPool); + PUSH_FILTER, aX - aOriginX, aY - aOriginY, PR_TRUE, aPool, + aTopView); if (retval) return retval; @@ -3766,9 +3827,10 @@ PRBool nsViewManager::AddToDisplayList(nsView *aView, DisplayZTreeNode* &aParent, nsRect &aClipRect, nsRect& aDirtyRect, PRUint32 aFlags, nscoord aAbsX, nscoord aAbsY, - PRBool aAssumeIntersection, PLArenaPool &aPool) + PRBool aAssumeIntersection, PLArenaPool &aPool, + nsIView* aStopClippingAtView) { - nsRect clipRect = aView->GetClippedRect(); + nsRect clipRect = aView->GetClippedRect(aStopClippingAtView); PRBool clipped = clipRect != aView->GetDimensions(); // get clipRect into the coordinate system of aView's parent. aAbsX and diff --git a/view/src/nsViewManager.h b/view/src/nsViewManager.h index 00dcbcd9d8e1..633430f59e46 100644 --- a/view/src/nsViewManager.h +++ b/view/src/nsViewManager.h @@ -231,6 +231,10 @@ public: NS_IMETHOD Display(nsIView *aView, nscoord aX, nscoord aY, const nsRect& aClipRect); + NS_IMETHOD RenderOffscreen(nsIView* aView, nsRect aRect, PRBool aUntrusted, + nscolor aBackgroundColor, + nsIRenderingContext** aRenderedContext); + NS_IMETHOD AddCompositeListener(nsICompositeListener *aListener); NS_IMETHOD RemoveCompositeListener(nsICompositeListener *aListener); @@ -286,7 +290,8 @@ private: */ void DefaultRefresh(nsView* aView, const nsRect* aRect); PRBool BuildRenderingDisplayList(nsIView* aRootView, - const nsRegion& aRegion, nsVoidArray* aDisplayList, PLArenaPool &aPool); + const nsRegion& aRegion, nsVoidArray* aDisplayList, PLArenaPool &aPool, + PRBool aIgnoreCoveringWidgets, PRBool aIgnoreOutsideClipping); void RenderViews(nsView *aRootView, nsIRenderingContext& aRC, const nsRegion& aRegion, nsIDrawingSurface* aRCSurface, const nsVoidArray& aDisplayList); @@ -320,7 +325,8 @@ private: PRBool AddToDisplayList(nsView *aView, DisplayZTreeNode* &aParent, nsRect &aClipRect, nsRect& aDirtyRect, PRUint32 aFlags, nscoord aAbsX, nscoord aAbsY, - PRBool aAssumeIntersection, PLArenaPool &aPool); + PRBool aAssumeIntersection, PLArenaPool &aPool, + nsIView* aStopClippingAtView); void OptimizeDisplayList(const nsVoidArray* aDisplayList, const nsRegion& aDirtyRegion, nsRect& aFinalTransparentRect, nsRegion& aOpaqueRgn, PRBool aTreatUniformAsOpaque);