Bug 700240 - Print display lists to a file. r=roc,bjacob
authorMatt Woodrow <mwoodrow@mozilla.com>
Thu, 01 Mar 2012 21:26:09 +1300
changeset 90947 ec9be13d5fad1e78c066ad6e51248672cb84b8da
parent 90946 4392f9d56d2eb020fb3d181bf413cbfc546a1eda
child 90948 2bf1f0b762b2f8bb31cbc922cb954bf75d7689a4
push idunknown
push userunknown
push dateunknown
reviewersroc, bjacob
bugs700240
milestone13.0a1
Bug 700240 - Print display lists to a file. r=roc,bjacob
gfx/gl/GLContext.cpp
gfx/gl/GLContext.h
gfx/gl/GLContextSymbols.h
gfx/layers/Layers.cpp
gfx/layers/Layers.h
gfx/layers/opengl/ContainerLayerOGL.cpp
gfx/layers/opengl/LayerManagerOGL.cpp
gfx/layers/opengl/LayerManagerOGL.h
gfx/layers/opengl/ThebesLayerOGL.cpp
gfx/thebes/gfxASurface.cpp
gfx/thebes/gfxASurface.h
gfx/thebes/gfxUtils.cpp
gfx/thebes/gfxUtils.h
layout/base/FrameLayerBuilder.cpp
layout/base/FrameLayerBuilder.h
layout/base/nsDisplayList.h
layout/base/nsLayoutDebugger.cpp
layout/base/nsLayoutUtils.cpp
layout/generic/nsFrame.h
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -248,16 +248,17 @@ GLContext::InitWithPrefix(const char *pr
         { (PRFuncPtr*) &mSymbols.fGetBooleanv, { "GetBooleanv", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetBufferParameteriv, { "GetBufferParameteriv", "GetBufferParameterivARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetError, { "GetError", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetProgramiv, { "GetProgramiv", "GetProgramivARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetProgramInfoLog, { "GetProgramInfoLog", "GetProgramInfoLogARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fTexParameteri, { "TexParameteri", NULL } },
         { (PRFuncPtr*) &mSymbols.fTexParameterf, { "TexParameterf", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetString, { "GetString", NULL } },
+        { (PRFuncPtr*) &mSymbols.fGetTexLevelParameteriv, { "GetTexLevelParameteriv", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetTexParameterfv, { "GetTexParameterfv", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetTexParameteriv, { "GetTexParameteriv", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetUniformfv, { "GetUniformfv", "GetUniformfvARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetUniformiv, { "GetUniformiv", "GetUniformivARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetUniformLocation, { "GetUniformLocation", "GetUniformLocationARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetVertexAttribfv, { "GetVertexAttribfv", "GetVertexAttribfvARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetVertexAttribiv, { "GetVertexAttribiv", "GetVertexAttribivARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fHint, { "Hint", NULL } },
@@ -489,16 +490,23 @@ GLContext::InitWithPrefix(const char *pr
                     { (PRFuncPtr*) &mSymbols.fRenderbufferStorageMultisample, { "RenderbufferStorageMultisample", "RenderbufferStorageMultisampleEXT", "RenderbufferStorageMultisampleANGLE", NULL } },
                     { NULL, { NULL } },
             };
             if (!LoadSymbols(&auxSymbols[0], trygl, prefix)) {
                 NS_RUNTIMEABORT("GL supports framebuffer_multisample without supplying glRenderbufferStorageMultisample");
                 mInitialized = false;
             }
         }
+       
+        // Load developer symbols, don't fail if we can't find them.
+        SymLoadStruct auxSymbols[] = {
+                { (PRFuncPtr*) &mSymbols.fGetTexImage, { "GetTexImage", NULL } },
+                { NULL, { NULL } },
+        };
+        LoadSymbols(&auxSymbols[0], trygl, prefix);
     }
 
     if (mInitialized) {
         GLint v[4];
 
         fGetIntegerv(LOCAL_GL_SCISSOR_BOX, v);
         mScissorStack.AppendElement(nsIntRect(v[0], v[1], v[2], v[3]));
 
@@ -1593,20 +1601,83 @@ GLContext::MarkDestroyed()
     fDeleteProgram(mBlitProgram);
     mBlitProgram = 0;
     fDeleteFramebuffers(1, &mBlitFramebuffer);
     mBlitFramebuffer = 0;
 
     mSymbols.Zero();
 }
 
+static void SwapRAndBComponents(gfxImageSurface* aSurf)
+{
+  gfxIntSize size = aSurf->GetSize();
+  for (int j = 0; j < size.height; ++j) {
+    PRUint32 *row = (PRUint32*) (aSurf->Data() + aSurf->Stride() * j);
+    for (int i = 0; i < size.width; ++i) {
+      *row = (*row & 0xff00ff00) | ((*row & 0xff) << 16) | ((*row & 0xff0000) >> 16);
+      row++;
+    }
+  }
+}
+
+static already_AddRefed<gfxImageSurface> YInvertImageSurface(gfxImageSurface* aSurf)
+{
+  gfxIntSize size = aSurf->GetSize();
+  nsRefPtr<gfxImageSurface> temp = new gfxImageSurface(size, aSurf->Format());
+  nsRefPtr<gfxContext> ctx = new gfxContext(temp);
+  ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
+  ctx->Scale(1.0, -1.0);
+  ctx->Translate(-gfxPoint(0.0, size.height));
+  ctx->SetSource(aSurf);
+  ctx->Paint();
+  return temp.forget();
+}
+
+already_AddRefed<gfxImageSurface>
+GLContext::GetTexImage(GLuint aTexture, bool aYInvert, ShaderProgramType aShader)
+{
+    MakeCurrent();
+    fFinish();
+    fActiveTexture(LOCAL_GL_TEXTURE0);
+    fBindTexture(LOCAL_GL_TEXTURE_2D, aTexture);
+
+    gfxIntSize size;
+    fGetTexLevelParameteriv(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_TEXTURE_WIDTH, &size.width);
+    fGetTexLevelParameteriv(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_TEXTURE_HEIGHT, &size.height);
+    
+    nsRefPtr<gfxImageSurface> surf = new gfxImageSurface(size, gfxASurface::ImageFormatARGB32);
+    if (!surf || surf->CairoStatus()) {
+        return NULL;
+    }
+
+    PRUint32 currentPackAlignment = 0;
+    fGetIntegerv(LOCAL_GL_PACK_ALIGNMENT, (GLint*)&currentPackAlignment);
+    if (currentPackAlignment != 4) {
+        fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 4);
+    }
+    fGetTexImage(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, surf->Data());
+    if (currentPackAlignment != 4) {
+        fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, currentPackAlignment);
+    }
+   
+    if (aShader == RGBALayerProgramType || aShader == RGBXLayerProgramType) {
+      SwapRAndBComponents(surf);
+    }
+
+    if (aYInvert) {
+      surf = YInvertImageSurface(surf);
+    }
+    return surf.forget();
+}
+
 already_AddRefed<gfxImageSurface>
 GLContext::ReadTextureImage(GLuint aTexture,
                             const gfxIntSize& aSize,
-                            GLenum aTextureFormat)
+                            GLenum aTextureFormat,
+                            bool aYInvert)
 {
     MakeCurrent();
 
     nsRefPtr<gfxImageSurface> isurf;
 
     GLint oldrb, oldfb, oldprog, oldPackAlignment;
     GLint success;
 
@@ -1659,16 +1730,18 @@ GLContext::ReadTextureImage(GLuint aText
     {
         goto cleanup;
     }
 
     vs = fCreateShader(LOCAL_GL_VERTEX_SHADER);
     fs = fCreateShader(LOCAL_GL_FRAGMENT_SHADER);
     fShaderSource(vs, 1, (const GLchar**) &vShader, NULL);
     fShaderSource(fs, 1, (const GLchar**) &fShader, NULL);
+    fCompileShader(vs);
+    fCompileShader(fs);
     prog = fCreateProgram();
     fAttachShader(prog, vs);
     fAttachShader(prog, fs);
     fBindAttribLocation(prog, 0, "aVertex");
     fBindAttribLocation(prog, 1, "aTexCoord");
     fLinkProgram(prog);
 
     fGetProgramiv(prog, LOCAL_GL_LINK_STATUS, &success);
@@ -1702,19 +1775,25 @@ GLContext::ReadTextureImage(GLuint aText
 
     if (oldPackAlignment != 4)
         fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 4);
 
     fReadPixels(0, 0, aSize.width, aSize.height,
                 LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE,
                 isurf->Data());
 
+    SwapRAndBComponents(isurf);
+
     if (oldPackAlignment != 4)
         fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, oldPackAlignment);
 
+    if (aYInvert) {
+      isurf = YInvertImageSurface(isurf);
+    }
+
  cleanup:
     // note that deleting 0 has no effect in any of these calls
     fDeleteRenderbuffers(1, &rb);
     fDeleteFramebuffers(1, &fb);
     fDeleteShader(vs);
     fDeleteShader(fs);
     fDeleteProgram(prog);
 
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -1228,17 +1228,20 @@ public:
      * If GL_LUMINANCE is given as the format, a ImageFormatA8 surface is returned.
      *
      * THIS IS EXPENSIVE.  It is ridiculously expensive.  Only do this
      * if you absolutely positively must, and never in any performance
      * critical path.
      */
     already_AddRefed<gfxImageSurface> ReadTextureImage(GLuint aTexture,
                                                        const gfxIntSize& aSize,
-                                                       GLenum aTextureFormat);
+                                                       GLenum aTextureFormat,
+                                                       bool aYInvert = false);
+
+    already_AddRefed<gfxImageSurface> GetTexImage(GLuint aTexture, bool aYInvert, ShaderProgramType aShader);
 
     /**
      * Call ReadPixels into an existing gfxImageSurface for the given bounds.
      * The image surface must be using image format RGBA32 or RGB24.
      */
     void THEBES_API ReadPixelsIntoImageSurface(GLint aX, GLint aY,
                                     GLsizei aWidth, GLsizei aHeight,
                                     gfxImageSurface *aDest);
@@ -2117,16 +2120,32 @@ public:
 
     const GLubyte* fGetString(GLenum name) {
         BEFORE_GL_CALL;
         const GLubyte *result = mSymbols.fGetString(name);
         AFTER_GL_CALL;
         return result;
     }
 
+    void fGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *img) {
+        if (!mSymbols.fGetTexImage) {
+          return;
+        }
+        BEFORE_GL_CALL;
+        mSymbols.fGetTexImage(target, level, format, type, img);
+        AFTER_GL_CALL;
+    };
+
+    void fGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params)
+    {  
+        BEFORE_GL_CALL;
+        mSymbols.fGetTexLevelParameteriv(target, level, pname, params);
+        AFTER_GL_CALL;
+    }
+
     void fGetTexParameterfv(GLenum target, GLenum pname, const GLfloat *params) {
         BEFORE_GL_CALL;
         mSymbols.fGetTexParameterfv(target, pname, params);
         AFTER_GL_CALL;
     }
 
     void fGetTexParameteriv(GLenum target, GLenum pname, const GLint *params) {
         BEFORE_GL_CALL;
--- a/gfx/gl/GLContextSymbols.h
+++ b/gfx/gl/GLContextSymbols.h
@@ -150,16 +150,20 @@ struct GLContextSymbols
     typedef void (GLAPIENTRY * PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
     PFNGLGETPROGRAMINFOLOGPROC fGetProgramInfoLog;
     typedef void (GLAPIENTRY * PFNGLTEXPARAMETERIPROC) (GLenum target, GLenum pname, GLint param);
     PFNGLTEXPARAMETERIPROC fTexParameteri;
     typedef void (GLAPIENTRY * PFNGLTEXPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat param);
     PFNGLTEXPARAMETERFPROC fTexParameterf;
     typedef GLubyte* (GLAPIENTRY * PFNGLGETSTRINGPROC) (GLenum);
     PFNGLGETSTRINGPROC fGetString;
+    typedef void (GLAPIENTRY * PFNGLGETTEXIMAGEPROC) (GLenum target, GLint level, GLenum format, GLenum type, GLvoid* image);
+    PFNGLGETTEXIMAGEPROC fGetTexImage;
+    typedef void (GLAPIENTRY * PFNGLGETTEXLEVELPARAMETERIVPROC) (GLenum target, GLint level, GLenum pname, GLint *params);
+    PFNGLGETTEXLEVELPARAMETERIVPROC fGetTexLevelParameteriv;
     typedef void (GLAPIENTRY * PFNGLGETTEXPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params);
     PFNGLGETTEXPARAMETERFVPROC fGetTexParameterfv;
     typedef void (GLAPIENTRY * PFNGLGETTEXPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params);
     PFNGLGETTEXPARAMETERIVPROC fGetTexParameteriv;
     typedef void (GLAPIENTRY * PFNGLGETUNIFORMFVPROC) (GLuint program, GLint location, GLfloat* params);
     PFNGLGETUNIFORMFVPROC fGetUniformfv;
     typedef void (GLAPIENTRY * PFNGLGETUNIFORMIVPROC) (GLuint program, GLint location, GLint* params);
     PFNGLGETUNIFORMIVPROC fGetUniformiv;
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -453,16 +453,20 @@ ContainerLayer::DefaultComputeEffectiveT
   gfx3DMatrix idealTransform = GetLocalTransform()*aTransformToSurface;
   idealTransform.ProjectTo2D();
   mEffectiveTransform = SnapTransform(idealTransform, gfxRect(0, 0, 0, 0), &residual);
 
   bool useIntermediateSurface;
   float opacity = GetEffectiveOpacity();
   if (opacity != 1.0f && HasMultipleChildren()) {
     useIntermediateSurface = true;
+#ifdef MOZ_DUMP_PAINTING
+  } else if (gfxUtils::sDumpPainting) {
+    useIntermediateSurface = true;
+#endif
   } else {
     useIntermediateSurface = false;
     gfxMatrix contTransform;
     if (!mEffectiveTransform.Is2D(&contTransform) ||
 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
         !contTransform.PreservesAxisAlignedRectangles()) {
 #else
         contTransform.HasNonIntegerTranslation()) {
@@ -547,27 +551,70 @@ LayerManager::StopFrameTimeRecording()
 }
 
 
 
 #ifdef MOZ_LAYERS_HAVE_LOG
 
 static nsACString& PrintInfo(nsACString& aTo, ShadowLayer* aShadowLayer);
 
+#ifdef MOZ_DUMP_PAINTING
+template <typename T>
+void WriteSnapshotLinkToDumpFile(T* aObj, FILE* aFile)
+{
+  nsCString string(aObj->Name());
+  string.Append("-");
+  string.AppendInt((PRUint64)aObj);
+  fprintf(aFile, "href=\"javascript:ViewImage('%s')\"", string.BeginReading());
+}
+
+template <typename T>
+void WriteSnapshotToDumpFile_internal(T* aObj, gfxASurface* aSurf)
+{
+  nsCString string(aObj->Name());
+  string.Append("-");
+  string.AppendInt((PRUint64)aObj);
+  fprintf(gfxUtils::sDumpPaintFile, "array[\"%s\"]=\"", string.BeginReading());
+  aSurf->DumpAsDataURL(gfxUtils::sDumpPaintFile);
+  fprintf(gfxUtils::sDumpPaintFile, "\";");
+}
+
+void WriteSnapshotToDumpFile(Layer* aLayer, gfxASurface* aSurf)
+{
+  WriteSnapshotToDumpFile_internal(aLayer, aSurf);
+}
+
+void WriteSnapshotToDumpFile(LayerManager* aManager, gfxASurface* aSurf)
+{
+  WriteSnapshotToDumpFile_internal(aManager, aSurf);
+}
+#endif
+
 void
 Layer::Dump(FILE* aFile, const char* aPrefix)
 {
+  fprintf(aFile, "<li><a id=\"%p\" ", this);
+#ifdef MOZ_DUMP_PAINTING
+  if (GetType() == TYPE_CONTAINER || GetType() == TYPE_THEBES) {
+    WriteSnapshotLinkToDumpFile(this, aFile);
+  }
+#endif
+  fprintf(aFile, ">");
   DumpSelf(aFile, aPrefix);
+  fprintf(aFile, "</a>");
 
   if (Layer* kid = GetFirstChild()) {
     nsCAutoString pfx(aPrefix);
     pfx += "  ";
+    fprintf(aFile, "<ul>");
     kid->Dump(aFile, pfx.get());
+    fprintf(aFile, "</ul>");
   }
 
+  fprintf(aFile, "</li>");
   if (Layer* next = GetNextSibling())
     next->Dump(aFile, aPrefix);
 }
 
 void
 Layer::DumpSelf(FILE* aFile, const char* aPrefix)
 {
   nsCAutoString str;
@@ -709,27 +756,37 @@ ReadbackLayer::PrintInfo(nsACString& aTo
 
 //--------------------------------------------------
 // LayerManager
 
 void
 LayerManager::Dump(FILE* aFile, const char* aPrefix)
 {
   FILE* file = FILEOrDefault(aFile);
-
+ 
+  fprintf(file, "<ul><li><a ");
+#ifdef MOZ_DUMP_PAINTING
+  WriteSnapshotLinkToDumpFile(this, aFile);
+#endif
+  fprintf(file, ">");
   DumpSelf(file, aPrefix);
+#ifdef MOZ_DUMP_PAINTING
+  fprintf(aFile, "</a>");
+#endif
 
   nsCAutoString pfx(aPrefix);
   pfx += "  ";
   if (!GetRoot()) {
-    fprintf(file, "%s(null)", pfx.get());
+    fprintf(file, "%s(null)</li></ul>", pfx.get());
     return;
   }
-
+ 
+  fprintf(file, "<ul>");
   GetRoot()->Dump(file, pfx.get());
+  fprintf(file, "</ul></li></ul>");
 }
 
 void
 LayerManager::DumpSelf(FILE* aFile, const char* aPrefix)
 {
   nsCAutoString str;
   PrintInfo(str, aPrefix);
   fprintf(FILEOrDefault(aFile), "%s\n", str.get());
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -1340,12 +1340,17 @@ protected:
   void* mCallbackData;
   gfxPattern::GraphicsFilter mFilter;
   /**
    * Set to true in Updated(), cleared during a transaction.
    */
   bool mDirty;
 };
 
+#ifdef MOZ_DUMP_PAINTING
+void WriteSnapshotToDumpFile(Layer* aLayer, gfxASurface* aSurf);
+void WriteSnapshotToDumpFile(LayerManager* aManager, gfxASurface* aSurf);
+#endif
+
 }
 }
 
 #endif /* GFX_LAYERS_H */
--- a/gfx/layers/opengl/ContainerLayerOGL.cpp
+++ b/gfx/layers/opengl/ContainerLayerOGL.cpp
@@ -251,16 +251,24 @@ ContainerRender(Container* aContainer,
 
     layerToRender->RenderLayer(frameBuffer, childOffset);
     aContainer->gl()->MakeCurrent();
   }
 
 
   if (needsFramebuffer) {
     // Unbind the current framebuffer and rebind the previous one.
+#ifdef MOZ_DUMP_PAINTING
+    if (gfxUtils::sDumpPainting) {
+      nsRefPtr<gfxImageSurface> surf = 
+        aContainer->gl()->GetTexImage(containerSurface, true, aManager->GetFBOLayerProgramType());
+
+      WriteSnapshotToDumpFile(aContainer, surf);
+    }
+#endif
     
     // Restore the viewport
     aContainer->gl()->PopViewportRect();
     nsIntRect viewport = aContainer->gl()->ViewportRect();
     aManager->SetupPipeline(viewport.width, viewport.height,
                             LayerManagerOGL::ApplyWorldTransform);
     aContainer->gl()->PopScissorRect();
 
--- a/gfx/layers/opengl/LayerManagerOGL.cpp
+++ b/gfx/layers/opengl/LayerManagerOGL.cpp
@@ -49,16 +49,18 @@
 #include "ColorLayerOGL.h"
 #include "CanvasLayerOGL.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Preferences.h"
 
 #include "LayerManagerOGLShaders.h"
 
 #include "gfxContext.h"
+#include "gfxUtils.h"
+#include "gfxPlatform.h"
 #include "nsIWidget.h"
 
 #include "GLContext.h"
 #include "GLContextProvider.h"
 
 #include "nsIServiceManager.h"
 #include "nsIConsoleService.h"
 
@@ -774,18 +776,30 @@ LayerManagerOGL::Render()
   mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT);
 
   // Render our layers.
   RootLayer()->RenderLayer(mGLContext->IsDoubleBuffered() ? 0 : mBackBufferFBO,
                            nsIntPoint(0, 0));
 
   mWidget->DrawWindowOverlay(this, rect);
 
+#ifdef MOZ_DUMP_PAINTING
+  if (gfxUtils::sDumpPainting) {
+    nsIntRect rect;
+    mWidget->GetBounds(rect);
+    nsRefPtr<gfxASurface> surf = gfxPlatform::GetPlatform()->CreateOffscreenSurface(rect.Size(), gfxASurface::CONTENT_COLOR_ALPHA);
+    nsRefPtr<gfxContext> ctx = new gfxContext(surf);
+    CopyToTarget(ctx);
+
+    WriteSnapshotToDumpFile(this, surf);
+  }
+#endif
+
   if (mTarget) {
-    CopyToTarget();
+    CopyToTarget(mTarget);
     mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
     return;
   }
 
   if (sDrawFPS) {
     mFPS.DrawFPS(mGLContext, GetCopy2DProgram());
   }
 
@@ -981,17 +995,17 @@ LayerManagerOGL::SetupBackBuffer(int aWi
     NS_RUNTIMEABORT(msg.get());
   }
 
   mBackBufferSize.width = aWidth;
   mBackBufferSize.height = aHeight;
 }
 
 void
-LayerManagerOGL::CopyToTarget()
+LayerManagerOGL::CopyToTarget(gfxContext *aTarget)
 {
   nsIntRect rect;
   mWidget->GetBounds(rect);
   GLint width = rect.width;
   GLint height = rect.height;
 
   if ((PRInt64(width) * PRInt64(height) * PRInt64(4)) > PR_INT32_MAX) {
     NS_ERROR("Widget size too big - integer overflow!");
@@ -1045,21 +1059,21 @@ LayerManagerOGL::CopyToTarget()
       PRUint32 *row = (PRUint32*) (imageSurface->Data() + imageSurface->Stride() * j);
       for (int i = 0; i < width; ++i) {
         *row = (*row & 0xff00ff00) | ((*row & 0xff) << 16) | ((*row & 0xff0000) >> 16);
         row++;
       }
     }
   }
 
-  mTarget->SetOperator(gfxContext::OPERATOR_SOURCE);
-  mTarget->Scale(1.0, -1.0);
-  mTarget->Translate(-gfxPoint(0.0, height));
-  mTarget->SetSource(imageSurface);
-  mTarget->Paint();
+  aTarget->SetOperator(gfxContext::OPERATOR_SOURCE);
+  aTarget->Scale(1.0, -1.0);
+  aTarget->Translate(-gfxPoint(0.0, height));
+  aTarget->SetSource(imageSurface);
+  aTarget->Paint();
 }
 
 LayerManagerOGL::ProgramType LayerManagerOGL::sLayerProgramTypes[] = {
   gl::RGBALayerProgramType,
   gl::BGRALayerProgramType,
   gl::RGBXLayerProgramType,
   gl::BGRXLayerProgramType,
   gl::RGBARectLayerProgramType,
--- a/gfx/layers/opengl/LayerManagerOGL.h
+++ b/gfx/layers/opengl/LayerManagerOGL.h
@@ -231,19 +231,23 @@ public:
   CopyProgram *GetCopy2DProgram() {
     return static_cast<CopyProgram*>(mPrograms[gl::Copy2DProgramType]);
   }
   CopyProgram *GetCopy2DRectProgram() {
     return static_cast<CopyProgram*>(mPrograms[gl::Copy2DRectProgramType]);
   }
 
   ColorTextureLayerProgram *GetFBOLayerProgram() {
+    return static_cast<ColorTextureLayerProgram*>(mPrograms[GetFBOLayerProgramType()]);
+  }
+
+  gl::ShaderProgramType GetFBOLayerProgramType() {
     if (mFBOTextureTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB)
-      return static_cast<ColorTextureLayerProgram*>(mPrograms[gl::RGBARectLayerProgramType]);
-    return static_cast<ColorTextureLayerProgram*>(mPrograms[gl::RGBALayerProgramType]);
+      return gl::RGBARectLayerProgramType;
+    return gl::RGBALayerProgramType;
   }
 
   GLContext *gl() const { return mGLContext; }
 
   DrawThebesLayerCallback GetThebesLayerCallback() const
   { return mThebesLayerCallback; }
 
   void* GetThebesLayerCallbackData() const
@@ -446,17 +450,17 @@ private:
   /**
    * Setup a backbuffer of the given dimensions.
    */
   void SetupBackBuffer(int aWidth, int aHeight);
 
   /**
    * Copies the content of our backbuffer to the set transaction target.
    */
-  void CopyToTarget();
+  void CopyToTarget(gfxContext *aTarget);
 
   /**
    * Updates all layer programs with a new projection matrix.
    *
    * XXX we need a way to be able to delay setting this until
    * the program is actually used.  Maybe a DelayedSetUniform
    * on Program, that will delay the set until the next Activate?
    *
--- a/gfx/layers/opengl/ThebesLayerOGL.cpp
+++ b/gfx/layers/opengl/ThebesLayerOGL.cpp
@@ -140,16 +140,25 @@ ThebesLayerBufferOGL::RenderTo(const nsI
   if (mTexImage->InUpdate()) {
     mTexImage->EndUpdate();
   }
 
   if (mTexImageOnWhite && mTexImageOnWhite->InUpdate()) {
     mTexImageOnWhite->EndUpdate();
   }
 
+#ifdef MOZ_DUMP_PAINTING
+  if (gfxUtils::sDumpPainting) {
+    nsRefPtr<gfxImageSurface> surf = 
+      gl()->GetTexImage(mTexImage->GetTextureID(), false, mTexImage->GetShaderProgramType());
+    
+    WriteSnapshotToDumpFile(mLayer, surf);
+  }
+#endif
+
   PRInt32 passes = mTexImageOnWhite ? 2 : 1;
   for (PRInt32 pass = 1; pass <= passes; ++pass) {
     LayerProgram *program;
 
     if (passes == 2) {
       ComponentAlphaTextureLayerProgram *alphaProgram;
       if (pass == 1) {
         alphaProgram = aManager->GetComponentAlphaPass1LayerProgram();
--- a/gfx/thebes/gfxASurface.cpp
+++ b/gfx/thebes/gfxASurface.cpp
@@ -711,19 +711,26 @@ gfxASurface::WriteAsPNG(const char* aFil
       WriteAsPNG_internal(file, true);
       fclose(file);
     } else {
       NS_WARNING("Failed to create file!\n");
     }
 }
     
 void 
-gfxASurface::DumpAsDataURL() 
+gfxASurface::DumpAsDataURL(FILE* aOutput) 
 { 
+  WriteAsPNG_internal(aOutput, false);
+}
+
+void
+gfxASurface::PrintAsDataURL()
+{
   WriteAsPNG_internal(stdout, false);
+  fprintf(stdout, "\n");
 }
 
 void 
 gfxASurface::CopyAsDataURL() 
 { 
   WriteAsPNG_internal(nsnull, false);
 }
 
@@ -771,17 +778,16 @@ gfxASurface::WriteAsPNG_internal(FILE* a
   if (!encoder) {
     PRInt32 w = NS_MIN(size.width, 8);
     PRInt32 h = NS_MIN(size.height, 8);
     printf("Could not create encoder. Printing %dx%d pixels.\n", w, h);
     for (PRInt32 y = 0; y < h; ++y) {
       for (PRInt32 x = 0; x < w; ++x) {
         printf("%x ", reinterpret_cast<PRUint32*>(imgsurf->Data())[y*imgsurf->Stride()+ x]);
       }
-      printf("\n");
     }
     return;
   }
 
   nsresult rv = encoder->InitFromData(imgsurf->Data(),
                                       size.width * size.height * 4, 
                                       size.width, 
                                       size.height, 
@@ -841,17 +847,16 @@ gfxASurface::WriteAsPNG_internal(FILE* a
   if (!encodedImg) // not sure why this would fail
     return;
 
   nsCString string("data:image/png;base64,");
   string.Append(encodedImg);
 
   if (aFile) {
     fprintf(aFile, "%s", string.BeginReading());
-    fprintf(aFile, "\n");
   } else {
     nsCOMPtr<nsIClipboardHelper> clipboard(do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv));
     if (clipboard) {
       clipboard->CopyString(NS_ConvertASCIItoUTF16(string));
     }
   }
 
   PR_Free(encodedImg);
--- a/gfx/thebes/gfxASurface.h
+++ b/gfx/thebes/gfxASurface.h
@@ -253,19 +253,24 @@ public:
      */
 
     /**
      * Writes a binary PNG file.
      */
     void WriteAsPNG(const char* aFile);
 
     /**
+     * Write as a PNG encoded Data URL to a file.
+     */
+    void DumpAsDataURL(FILE* aOutput = stdout);
+
+    /**
      * Write as a PNG encoded Data URL to stdout.
      */
-    void DumpAsDataURL();
+    void PrintAsDataURL();
 
     /**
      * Copy a PNG encoded Data URL to the clipboard.
      */
     void CopyAsDataURL();
     
     void WriteAsPNG_internal(FILE* aFile, bool aBinary);
 #endif
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -687,9 +687,13 @@ gfxUtils::CopyAsDataURL(DrawTarget* aDT)
   aDT->Flush();
   nsRefPtr<gfxASurface> surf = gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(aDT);
   if (surf) {
     surf->CopyAsDataURL();
   } else {
     NS_WARNING("Failed to get Thebes surface!");
   }
 }
+
+bool gfxUtils::sDumpPainting = getenv("MOZ_DUMP_PAINT_LIST") != 0;
+bool gfxUtils::sDumpPaintingToFile = getenv("MOZ_DUMP_PAINT_TO_FILE") != 0;
+FILE *gfxUtils::sDumpPaintFile = NULL;
 #endif
--- a/gfx/thebes/gfxUtils.h
+++ b/gfx/thebes/gfxUtils.h
@@ -162,12 +162,17 @@ public:
      * Write as a PNG encoded Data URL to stdout.
      */
     static void DumpAsDataURL(mozilla::gfx::DrawTarget* aDT);
 
     /**
      * Copy a PNG encoded Data URL to the clipboard.
      */
     static void CopyAsDataURL(mozilla::gfx::DrawTarget* aDT);
+
+    static bool sDumpPainting;
+    static bool sDumpPaintingToFile;
+    static FILE* sDumpPaintFile;
 #endif
 };
 
+
 #endif
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -1307,40 +1307,77 @@ ContainerState::FindThebesLayerFor(nsDis
     thebesLayerData = mThebesLayerDataStack[lowestUsableLayerWithScrolledRoot];
     layer = thebesLayerData->mLayer;
   }
 
   thebesLayerData->Accumulate(this, aItem, aVisibleRect, aDrawRect, aClip);
   return layer.forget();
 }
 
+#ifdef MOZ_DUMP_PAINTING
+static void
+DumpPaintedImage(nsDisplayItem* aItem, gfxASurface* aSurf)
+{
+  nsCString string(aItem->Name());
+  string.Append("-");
+  string.AppendInt((PRUint64)aItem);
+  fprintf(gfxUtils::sDumpPaintFile, "array[\"%s\"]=\"", string.BeginReading());
+  aSurf->DumpAsDataURL(gfxUtils::sDumpPaintFile);
+  fprintf(gfxUtils::sDumpPaintFile, "\";");
+}
+#endif
+
 static void
 PaintInactiveLayer(nsDisplayListBuilder* aBuilder,
                    nsDisplayItem* aItem,
                    gfxContext* aContext)
 {
   // This item has an inactive layer. Render it to a ThebesLayer
   // using a temporary BasicLayerManager.
+  PRInt32 appUnitsPerDevPixel = AppUnitsPerDevPixel(aItem);
+  nsIntRect itemVisibleRect =
+    aItem->GetVisibleRect().ToOutsidePixels(appUnitsPerDevPixel);
+
+  nsRefPtr<gfxContext> context = aContext;
+#ifdef MOZ_DUMP_PAINTING
+  nsRefPtr<gfxASurface> surf; 
+  if (gfxUtils::sDumpPainting) {
+    surf = gfxPlatform::GetPlatform()->CreateOffscreenSurface(itemVisibleRect.Size(), 
+                                                              gfxASurface::CONTENT_COLOR_ALPHA);
+    surf->SetDeviceOffset(-itemVisibleRect.TopLeft());
+    context = new gfxContext(surf);
+  }
+#endif
+
   nsRefPtr<BasicLayerManager> tempManager = new BasicLayerManager();
-  tempManager->BeginTransactionWithTarget(aContext);
+  tempManager->BeginTransactionWithTarget(context);
   nsRefPtr<Layer> layer =
     aItem->BuildLayer(aBuilder, tempManager, FrameLayerBuilder::ContainerParameters());
   if (!layer) {
     tempManager->EndTransaction(nsnull, nsnull);
     return;
   }
-  PRInt32 appUnitsPerDevPixel = AppUnitsPerDevPixel(aItem);
-  nsIntRect itemVisibleRect =
-    aItem->GetVisibleRect().ToOutsidePixels(appUnitsPerDevPixel);
   RestrictVisibleRegionForLayer(layer, itemVisibleRect);
-
+  
   tempManager->SetRoot(layer);
   aBuilder->LayerBuilder()->WillEndTransaction(tempManager);
   tempManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer, aBuilder);
   aBuilder->LayerBuilder()->DidEndTransaction(tempManager);
+ 
+#ifdef MOZ_DUMP_PAINTING
+  if (gfxUtils::sDumpPainting) {
+    DumpPaintedImage(aItem, surf);
+  
+    surf->SetDeviceOffset(gfxPoint(0, 0));
+    aContext->SetSource(surf, itemVisibleRect.TopLeft());
+    aContext->Rectangle(itemVisibleRect);
+    aContext->Fill();
+    aItem->SetPainted();
+  }
+#endif
 }
 
 /*
  * Iterate through the non-clip items in aList and its descendants.
  * For each item we compute the effective clip rect. Each item is assigned
  * to a layer. We invalidate the areas in ThebesLayers where an item
  * has moved from one ThebesLayer to another. Also,
  * aState->mInvalidThebesContent is invalidated in every ThebesLayer.
@@ -1997,16 +2034,42 @@ FrameLayerBuilder::GetDedicatedLayer(nsI
           !layer->HasUserData(&gImageLayerUserData) &&
           !layer->HasUserData(&gThebesDisplayItemLayerUserData))
         return layer;
     }
   }
   return nsnull;
 }
 
+#ifdef MOZ_DUMP_PAINTING
+static void DebugPaintItem(nsRenderingContext* aDest, nsDisplayItem *aItem, nsDisplayListBuilder* aBuilder)
+{
+  nsRect appUnitBounds = aItem->GetBounds(aBuilder);
+  gfxRect bounds(appUnitBounds.x, appUnitBounds.y, appUnitBounds.width, appUnitBounds.height);
+  bounds.ScaleInverse(aDest->AppUnitsPerDevPixel());
+
+  nsRefPtr<gfxASurface> surf = 
+    gfxPlatform::GetPlatform()->CreateOffscreenSurface(gfxIntSize(bounds.width, bounds.height), 
+                                                       gfxASurface::CONTENT_COLOR_ALPHA);
+  surf->SetDeviceOffset(-bounds.TopLeft());
+  nsRefPtr<gfxContext> context = new gfxContext(surf);
+  nsRefPtr<nsRenderingContext> ctx = new nsRenderingContext();
+  ctx->Init(aDest->DeviceContext(), context);
+
+  aItem->Paint(aBuilder, ctx);
+  DumpPaintedImage(aItem, surf);
+  aItem->SetPainted();
+    
+  surf->SetDeviceOffset(gfxPoint(0, 0));
+  aDest->ThebesContext()->SetSource(surf, bounds.TopLeft());
+  aDest->ThebesContext()->Rectangle(bounds);
+  aDest->ThebesContext()->Fill();
+}
+#endif
+
 /*
  * A note on residual transforms:
  *
  * In a transformed subtree we sometimes apply the ThebesLayer's
  * "residual transform" when drawing content into the ThebesLayer.
  * This is a translation by components in the range [-0.5,0.5) provided
  * by the layer system; applying the residual transform followed by the
  * transforms used by layer compositing ensures that the subpixel alignment
@@ -2171,17 +2234,26 @@ FrameLayerBuilder::DrawThebesLayer(Thebe
 
     if (cdi->mInactiveLayer) {
       PaintInactiveLayer(builder, cdi->mItem, aContext);
     } else {
       nsIFrame* frame = cdi->mItem->GetUnderlyingFrame();
       if (frame) {
         frame->AddStateBits(NS_FRAME_PAINTED_THEBES);
       }
-      cdi->mItem->Paint(builder, rc);
+#ifdef MOZ_DUMP_PAINTING
+
+      if (gfxUtils::sDumpPainting) {
+        DebugPaintItem(rc, cdi->mItem, builder);
+      } else {
+#else
+      {
+#endif
+        cdi->mItem->Paint(builder, rc);
+      }
     }
 
     if (builder->LayerBuilder()->CheckDOMModified())
       break;
   }
 
   if (setClipRect) {
     aContext->Restore();
@@ -2205,20 +2277,20 @@ FrameLayerBuilder::CheckDOMModified()
   // we can do here though. Invalidating the window to get another repaint
   // is likely to lead to an infinite repaint loop.
   NS_WARNING("Detected DOM modification during paint, bailing out!");
   return true;
 }
 
 #ifdef MOZ_DUMP_PAINTING
 void
-FrameLayerBuilder::DumpRetainedLayerTree()
+FrameLayerBuilder::DumpRetainedLayerTree(FILE* aFile)
 {
   if (mRetainingManager) {
-    mRetainingManager->Dump(stdout);
+    mRetainingManager->Dump(aFile);
   }
 }
 #endif
 
 FrameLayerBuilder::Clip::Clip(const Clip& aOther, nsDisplayItem* aClipItem)
   : mRoundedClipRects(aOther.mRoundedClipRects),
     mHaveClipRect(true)
 {
--- a/layout/base/FrameLayerBuilder.h
+++ b/layout/base/FrameLayerBuilder.h
@@ -244,17 +244,17 @@ public:
                               const nsIntRegion& aRegionToInvalidate,
                               void* aCallbackData);
 
 #ifdef MOZ_DUMP_PAINTING
   /**
    * Dumps this FrameLayerBuilder's retained layer manager's retained
    * layer tree to stderr.
    */
-  void DumpRetainedLayerTree();
+  void DumpRetainedLayerTree(FILE* aFile = stdout);
 #endif
 
   /******* PRIVATE METHODS to FrameLayerBuilder.cpp ********/
   /* These are only in the public section because they need
    * to be called by file-scope helper functions in FrameLayerBuilder.cpp.
    */
   
   /**
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -561,18 +561,22 @@ class nsDisplayItem : public nsDisplayIt
 public:
   typedef mozilla::layers::FrameMetrics::ViewID ViewID;
   typedef mozilla::layers::Layer Layer;
   typedef mozilla::layers::LayerManager LayerManager;
   typedef mozilla::LayerState LayerState;
 
   // This is never instantiated directly (it has pure virtual methods), so no
   // need to count constructors and destructors.
-  nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) :
-    mFrame(aFrame) {
+  nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+    : mFrame(aFrame)
+#ifdef MOZ_DUMP_PAINTING
+    , mPainted(false)
+#endif
+  {
     if (aFrame) {
       mToReferenceFrame = aBuilder->ToReferenceFrame(aFrame);
     }
   }
   virtual ~nsDisplayItem() {}
   
   void* operator new(size_t aSize,
                      nsDisplayListBuilder* aBuilder) CPP_THROW_NEW {
@@ -710,16 +714,29 @@ public:
                                    LayerManager* aManager)
   { return mozilla::LAYER_NONE; }
   /**
    * Actually paint this item to some rendering context.
    * Content outside mVisibleRect need not be painted.
    * aCtx must be set up as for nsDisplayList::Paint.
    */
   virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) {}
+
+#ifdef MOZ_DUMP_PAINTING
+  /**
+   * Mark this display item as being painted via FrameLayerBuilder::DrawThebesLayer.
+   */
+  bool Painted() { return mPainted; }
+
+  /**
+   * Check if this display item has been painted.
+   */
+  void SetPainted() { mPainted = true; }
+#endif
+
   /**
    * Get the layer drawn by this display item. Call this only if
    * GetLayerState() returns something other than LAYER_NONE.
    * If GetLayerState returned LAYER_NONE then Paint will be called
    * instead.
    * This is called while aManager is in the construction phase.
    * 
    * The caller (nsDisplayList) is responsible for setting the visible
@@ -845,16 +862,20 @@ protected:
   // Result of ToReferenceFrame(mFrame), if mFrame is non-null
   nsPoint   mToReferenceFrame;
   // This is the rectangle that needs to be painted.
   // nsDisplayList::ComputeVisibility sets this to the visible region
   // of the item by intersecting the current visible region with the bounds
   // of the item. Paint implementations can use this to limit their drawing.
   // Guaranteed to be contained in GetBounds().
   nsRect    mVisibleRect;
+#ifdef MOZ_DUMP_PAINTING
+  // True if this frame has been painted.
+  bool      mPainted;
+#endif
 };
 
 /**
  * Manages a singly-linked list of display list items.
  * 
  * mSentinel is the sentinel list value, the first value in the null-terminated
  * linked list of items. mTop is the last item in the list (whose 'above'
  * pointer is null). This class has no virtual methods. So list objects are just
--- a/layout/base/nsLayoutDebugger.cpp
+++ b/layout/base/nsLayoutDebugger.cpp
@@ -148,26 +148,26 @@ nsLayoutDebugger::GetStyleSize(nsIPresSh
   *aSizeInBytesResult = 0;
   return NS_ERROR_FAILURE;
 }
 #endif
 
 #ifdef MOZ_DUMP_PAINTING
 static void
 PrintDisplayListTo(nsDisplayListBuilder* aBuilder, const nsDisplayList& aList,
-                   PRInt32 aIndent, FILE* aOutput)
+                   FILE* aOutput)
 {
+  fprintf(aOutput, "<ul>");
+
   for (nsDisplayItem* i = aList.GetBottom(); i != nsnull; i = i->GetAbove()) {
 #ifdef DEBUG
     if (aList.DidComputeVisibility() && i->GetVisibleRect().IsEmpty())
       continue;
 #endif
-    for (PRInt32 j = 0; j < aIndent; ++j) {
-      fputc(' ', aOutput);
-    }
+    fprintf(aOutput, "<li>");
     nsIFrame* f = i->GetUnderlyingFrame();
     nsAutoString fName;
 #ifdef DEBUG
     if (f) {
       f->GetFrameName(fName);
     }
 #endif
     nsRect rect = i->GetBounds(aBuilder);
@@ -189,39 +189,52 @@ PrintDisplayListTo(nsDisplayListBuilder*
         nsDisplayTransform* t = static_cast<nsDisplayTransform*>(i);
         list = t->GetStoredList()->GetList();
     }
 #ifdef DEBUG
     if (!list || list->DidComputeVisibility()) {
       opaque = i->GetOpaqueRegion(aBuilder);
     }
 #endif
+    if (i->Painted()) {
+      nsCString string(i->Name());
+      string.Append("-");
+      string.AppendInt((PRUint64)i);
+      fprintf(aOutput, "<a href=\"javascript:ViewImage('%s')\">", string.BeginReading());
+    }
     fprintf(aOutput, "%s %p(%s) (%d,%d,%d,%d)(%d,%d,%d,%d)%s%s",
             i->Name(), (void*)f, NS_ConvertUTF16toUTF8(fName).get(),
             rect.x, rect.y, rect.width, rect.height,
             vis.x, vis.y, vis.width, vis.height,
             opaque.IsEmpty() ? "" : " opaque",
             i->IsUniform(aBuilder, &color) ? " uniform" : "");
+    if (i->Painted()) {
+      fprintf(aOutput, "</a>");
+    }
     if (f) {
       PRUint32 key = i->GetPerFrameKey();
       Layer* layer = aBuilder->LayerBuilder()->GetOldLayerFor(f, key);
       if (layer) {
-        fprintf(aOutput, " layer=%p", layer);
+        fprintf(aOutput, " <a href=\"#%p\">layer=%p</a>", layer, layer);
       }
     }
     if (i->GetType() == nsDisplayItem::TYPE_SVG_EFFECTS) {
       (static_cast<nsDisplaySVGEffects*>(i))->PrintEffects(aOutput);
     }
     fputc('\n', aOutput);
     if (list) {
-      PrintDisplayListTo(aBuilder, *list, aIndent + 4, aOutput);
+      PrintDisplayListTo(aBuilder, *list, aOutput);
     }
+    fprintf(aOutput, "</li>");
   }
+  
+  fprintf(aOutput, "</ul>");
 }
 
 void
 nsFrame::PrintDisplayList(nsDisplayListBuilder* aBuilder,
-                          const nsDisplayList& aList)
+                          const nsDisplayList& aList,
+                          FILE* aFile)
 {
-  PrintDisplayListTo(aBuilder, aList, 0, stdout);
+  PrintDisplayListTo(aBuilder, aList, aFile);
 }
 
 #endif
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1376,18 +1376,18 @@ nsLayoutUtils::CombineBreakType(PRUint8 
     }
   }
   return breakType;
 }
 
 #ifdef MOZ_DUMP_PAINTING
 #include <stdio.h>
 
-static bool gDumpPaintList = getenv("MOZ_DUMP_PAINT_LIST") != 0;
 static bool gDumpEventList = false;
+int gPaintCount = 0;
 #endif
 
 nsresult
 nsLayoutUtils::GetRemoteContentIds(nsIFrame* aFrame,
                                    const nsRect& aTarget,
                                    nsTArray<ViewID> &aOutIDs,
                                    bool aIgnoreRootScrollFrame)
 {
@@ -1769,20 +1769,32 @@ nsLayoutUtils::PaintFrame(nsRenderingCon
   builder.LeavePresShell(aFrame, dirtyRect);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (builder.GetHadToIgnorePaintSuppression()) {
     willFlushRetainedLayers = true;
   }
 
 #ifdef MOZ_DUMP_PAINTING
-  if (gDumpPaintList) {
-    fprintf(stdout, "Painting --- before optimization (dirty %d,%d,%d,%d):\n",
+  if (gfxUtils::sDumpPainting) {
+    if (gfxUtils::sDumpPaintingToFile) {
+      nsCString string("dump-");
+      string.AppendInt(gPaintCount);
+      string.Append(".html");
+      gfxUtils::sDumpPaintFile = fopen(string.BeginReading(), "w");
+    } else {
+      gfxUtils::sDumpPaintFile = stdout;
+    }
+    fprintf(gfxUtils::sDumpPaintFile, "<html><head><script>var array = {}; function ViewImage(index) { window.location = array[index]; }</script></head><body>");
+    fprintf(gfxUtils::sDumpPaintFile, "Painting --- before optimization (dirty %d,%d,%d,%d):\n",
             dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
-    nsFrame::PrintDisplayList(&builder, list);
+    nsFrame::PrintDisplayList(&builder, list, gfxUtils::sDumpPaintFile);
+    if (gfxUtils::sDumpPaintingToFile) {
+      fprintf(gfxUtils::sDumpPaintFile, "<script>");
+    }
   }
 #endif
 
   list.ComputeVisibilityForRoot(&builder, &visibleRegion);
 
   PRUint32 flags = nsDisplayList::PAINT_DEFAULT;
   if (aFlags & PAINT_WIDGET_LAYERS) {
     flags |= nsDisplayList::PAINT_USE_WIDGET_LAYERS;
@@ -1821,22 +1833,29 @@ nsLayoutUtils::PaintFrame(nsRenderingCon
       nsRegion excludedRegion = builder.GetExcludedGlassRegion();
       excludedRegion.Sub(excludedRegion, visibleRegion);
       nsIntRegion windowRegion(excludedRegion.ToNearestPixels(presContext->AppUnitsPerDevPixel()));
       widget->UpdateOpaqueRegion(windowRegion);
     }
   }
 
 #ifdef MOZ_DUMP_PAINTING
-  if (gDumpPaintList) {
-    fprintf(stdout, "Painting --- after optimization:\n");
-    nsFrame::PrintDisplayList(&builder, list);
-
-    fprintf(stdout, "Painting --- retained layer tree:\n");
-    builder.LayerBuilder()->DumpRetainedLayerTree();
+  if (gfxUtils::sDumpPainting) {
+    fprintf(gfxUtils::sDumpPaintFile, "</script>Painting --- after optimization:\n");
+    nsFrame::PrintDisplayList(&builder, list, gfxUtils::sDumpPaintFile);
+
+    fprintf(gfxUtils::sDumpPaintFile, "Painting --- retained layer tree:\n");
+    builder.LayerBuilder()->DumpRetainedLayerTree(gfxUtils::sDumpPaintFile);
+    fprintf(gfxUtils::sDumpPaintFile, "</body></html>");
+    
+    if (gfxUtils::sDumpPaintingToFile) {
+      fclose(gfxUtils::sDumpPaintFile);
+    }
+    gfxUtils::sDumpPaintFile = NULL;
+    gPaintCount++;
   }
 #endif
 
   // Flush the list so we don't trigger the IsEmpty-on-destruction assertion
   list.DeleteAll();
   return NS_OK;
 }
 
--- a/layout/generic/nsFrame.h
+++ b/layout/generic/nsFrame.h
@@ -723,17 +723,18 @@ public:
   static void ShowEventTargetFrameBorder(bool aEnable);
   static bool GetShowEventTargetFrameBorder();
 
 #endif
 #ifdef MOZ_DUMP_PAINTING
 public:
 
   static void PrintDisplayList(nsDisplayListBuilder* aBuilder,
-                               const nsDisplayList& aList);
+                               const nsDisplayList& aList,
+                               FILE* aFile = stdout);
 
 #endif
 };
 
 // Start Display Reflow Debugging
 #ifdef DEBUG
 
   struct DR_cookie {