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 88049 ec9be13d5fad1e78c066ad6e51248672cb84b8da
parent 88048 4392f9d56d2eb020fb3d181bf413cbfc546a1eda
child 88050 2bf1f0b762b2f8bb31cbc922cb954bf75d7689a4
push id6655
push usermwoodrow@mozilla.com
push dateThu, 01 Mar 2012 08:26:41 +0000
treeherdermozilla-inbound@ec9be13d5fad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc, bjacob
bugs700240
milestone13.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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 {