Bug 458928. Render windowed Windows plugins using PrintWindow when necessary, and use gfxWindowsNativeDrawing for windowless plugins so we can draw them correctly under all circumstances. r=vlad,sr=jst
authorRobert O'Callahan <robert@ocallahan.org>
Wed, 29 Oct 2008 22:28:25 -0700
changeset 21080 45f2c96d4a9b04d774443a1b838ae761a42bfb2f
parent 21079 e8cd2199cf0c8eeadce6217223c191006583b6f3
child 21081 b7e0323caf3a62152aea2fe87fec56c93410078a
push id3292
push userrocallahan@mozilla.com
push dateThu, 30 Oct 2008 05:28:47 +0000
treeherdermozilla-central@45f2c96d4a9b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvlad, jst
bugs458928
milestone1.9.1b2pre
Bug 458928. Render windowed Windows plugins using PrintWindow when necessary, and use gfxWindowsNativeDrawing for windowless plugins so we can draw them correctly under all circumstances. r=vlad,sr=jst
gfx/thebes/public/gfxContext.h
layout/generic/nsObjectFrame.cpp
widget/src/windows/nsWindow.cpp
--- a/gfx/thebes/public/gfxContext.h
+++ b/gfx/thebes/public/gfxContext.h
@@ -610,17 +610,25 @@ public:
          * However, when printing complex renderings such as SVG,
          * care should be taken to clear this flag.
          */
         FLAG_SIMPLIFY_OPERATORS = (1 << 0),
         /**
          * When this flag is set, snapping to device pixels is disabled.
          * It simply never does anything.
          */
-        FLAG_DISABLE_SNAPPING = (1 << 1)
+        FLAG_DISABLE_SNAPPING = (1 << 1),
+        /**
+         * When this flag is set, rendering through this context
+         * is destined to be (eventually) drawn on the screen. It can be
+         * useful to know this, for example so that windowed plugins are
+         * not unnecessarily rendered (since they will already appear
+         * on the screen, thanks to their windows).
+         */
+        FLAG_DESTINED_FOR_SCREEN = (1 << 2)
     };
 
     void SetFlag(PRInt32 aFlag) { mFlags |= aFlag; }
     void ClearFlag(PRInt32 aFlag) { mFlags &= ~aFlag; }
     PRInt32 GetFlags() const { return mFlags; }
 
 private:
     cairo_t *mCairo;
--- a/layout/generic/nsObjectFrame.cpp
+++ b/layout/generic/nsObjectFrame.cpp
@@ -131,16 +131,17 @@
 #include "nsIPluginDocument.h"
 
 #include "nsThreadUtils.h"
 
 #include "gfxContext.h"
 
 #ifdef XP_WIN
 #include "gfxWindowsNativeDrawing.h"
+#include "gfxWindowsSurface.h"
 #endif
 
 // accessibility support
 #ifdef ACCESSIBILITY
 #include "nsIAccessibilityService.h"
 #endif
 
 #ifdef MOZ_LOGGING
@@ -351,17 +352,17 @@ public:
   nsresult Destroy();  
 
   void PrepareToStop(PRBool aDelayedStop);
 
   // nsIEventListener interface
   nsEventStatus ProcessEvent(const nsGUIEvent & anEvent);
   
 #ifdef XP_WIN
-  void Paint(const nsRect& aDirtyRect, HDC ndc);
+  void Paint(const RECT& aDirty, HDC aDC);
 #elif defined(XP_MACOSX)
   void Paint(const nsRect& aDirtyRect);  
 #elif defined(MOZ_X11) || defined(MOZ_DFB)
   void Paint(gfxContext* aContext,
              const gfxRect& aFrameRect,
              const gfxRect& aDirtyRect);
 #elif defined(XP_OS2)
   void Paint(const nsRect& aDirtyRect, HPS aHPS);
@@ -1264,49 +1265,36 @@ nsObjectFrame::PrintPlugin(nsIRenderingC
   window.height = presContext->AppUnitsToDevPixels(mRect.height);
 
   gfxContext *ctx = aRenderingContext.ThebesContext();
 
   ctx->Save();
 
   /* Make sure plugins don't do any damage outside of where they're supposed to */
   ctx->NewPath();
-  ctx->Rectangle(gfxRect(window.x, window.y,
-                         window.width, window.height));
+  gfxRect r(window.x, window.y, window.width, window.height);
+  ctx->Rectangle(r);
   ctx->Clip();
 
-  /* If we're windowless, we need to do COLOR_ALPHA, and do alpha recovery.
-   * XXX - we could have some sort of flag here that would indicate whether
-   * the plugin knows how to render to an ARGB DIB
-   */
-  if (windowless)
-    ctx->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA);
-  else
-    ctx->PushGroup(gfxASurface::CONTENT_COLOR);
-
-  gfxWindowsNativeDrawing nativeDraw(ctx,
-                                     gfxRect(window.x, window.y,
-                                             window.width, window.height));
+  gfxWindowsNativeDrawing nativeDraw(ctx, r);
   do {
     HDC dc = nativeDraw.BeginNativeDrawing();
     if (!dc)
       return;
 
+    // XXX don't we need to call nativeDraw.TransformToNativeRect here?
     npprint.print.embedPrint.platformPrint = dc;
     npprint.print.embedPrint.window = window;
     // send off print info to plugin
     pi->Print(&npprint);
 
     nativeDraw.EndNativeDrawing();
   } while (nativeDraw.ShouldRenderAgain());
   nativeDraw.PaintToContext();
 
-  ctx->PopGroupToSource();
-  ctx->Paint();
-
   ctx->Restore();
 
 #else
 
   // Get the offset of the DC
   nsTransform2D* rcTransform;
   aRenderingContext.GetCurrentTransform(rcTransform);
   nsPoint origin;
@@ -1391,32 +1379,164 @@ nsObjectFrame::PaintPlugin(nsIRenderingC
       mInstanceOwner->Paint(aDirtyRect);
 
       nativeDrawing.EndNativeDrawing();
     } else {
       mInstanceOwner->Paint(aDirtyRect);
     }
   }
 #elif defined(MOZ_X11) || defined(MOZ_DFB)
-  if (mInstanceOwner)
-    {
-      nsPluginWindow * window;
-      mInstanceOwner->GetWindow(window);
-
-      if (window->type == nsPluginWindowType_Drawable) {
-        gfxRect frameGfxRect =
-          PresContext()->AppUnitsToGfxUnits(nsRect(aFramePt, GetSize()));
-        gfxRect dirtyGfxRect =
-          PresContext()->AppUnitsToGfxUnits(aDirtyRect);
-        gfxContext* ctx = aRenderingContext.ThebesContext();
-
-        mInstanceOwner->Paint(ctx, frameGfxRect, dirtyGfxRect);
+  if (mInstanceOwner) {
+    nsPluginWindow * window;
+    mInstanceOwner->GetWindow(window);
+
+    if (window->type == nsPluginWindowType_Drawable) {
+      gfxRect frameGfxRect =
+        PresContext()->AppUnitsToGfxUnits(nsRect(aFramePt, GetSize()));
+      gfxRect dirtyGfxRect =
+        PresContext()->AppUnitsToGfxUnits(aDirtyRect);
+      gfxContext* ctx = aRenderingContext.ThebesContext();
+
+      mInstanceOwner->Paint(ctx, frameGfxRect, dirtyGfxRect);
+    }
+  }
+#elif defined(XP_WIN)
+  nsCOMPtr<nsIPluginInstance> inst;
+  GetPluginInstance(*getter_AddRefs(inst));
+  if (inst) {
+    gfxRect frameGfxRect =
+      PresContext()->AppUnitsToGfxUnits(nsRect(aFramePt, GetSize()));
+    gfxRect dirtyGfxRect =
+      PresContext()->AppUnitsToGfxUnits(aDirtyRect);
+    gfxContext *ctx = aRenderingContext.ThebesContext();
+    gfxMatrix currentMatrix = ctx->CurrentMatrix();
+
+    if (ctx->UserToDevicePixelSnapped(frameGfxRect, PR_FALSE)) {
+      dirtyGfxRect = ctx->UserToDevice(dirtyGfxRect);
+      ctx->IdentityMatrix();
+    }
+    dirtyGfxRect.RoundOut();
+
+    // Look if it's windowless
+    nsPluginWindow * window;
+    mInstanceOwner->GetWindow(window);
+
+    if (window->type == nsPluginWindowType_Drawable) {
+      // check if we need to call SetWindow with updated parameters
+      PRBool doupdatewindow = PR_FALSE;
+      // the offset of the DC
+      nsPoint origin;
+      
+      gfxWindowsNativeDrawing nativeDraw(ctx, frameGfxRect);
+      do {
+        HDC hdc = nativeDraw.BeginNativeDrawing();
+        if (!hdc)
+          return;
+
+        RECT dest;
+        nativeDraw.TransformToNativeRect(frameGfxRect, dest);
+        RECT dirty;
+        nativeDraw.TransformToNativeRect(dirtyGfxRect, dirty);
+
+        // XXX how can we be sure that window->window doesn't point to
+        // a dead DC and hdc has been reallocated at the same address?
+        if (reinterpret_cast<HDC>(window->window) != hdc ||
+            window->x != dest.left || window->y != dest.top) {
+          window->window = reinterpret_cast<nsPluginPort*>(hdc);
+          window->x = dest.left;
+          window->y = dest.top;
+
+          // Windowless plugins on windows need a special event to update their location, see bug 135737
+          // bug 271442: note, the rectangle we send is now purely the bounds of the plugin
+          // relative to the window it is contained in, which is useful for the plugin to correctly translate mouse coordinates
+          //
+          // this does not mesh with the comments for bug 135737 which imply that the rectangle
+          // must be clipped in some way to prevent the plugin attempting to paint over areas it shouldn't;
+          //
+          // since the two uses of the rectangle are mutually exclusive in some cases,
+          // and since I don't see any incorrect painting (at least with Flash and ViewPoint - the originator of 135737),
+          // it seems that windowless plugins are not relying on information here for clipping their drawing,
+          // and we can safely use this message to tell the plugin exactly where it is in all cases.
+
+          nsIntPoint origin = GetWindowOriginInPixels(PR_TRUE);
+          nsRect winlessRect = nsRect(origin, nsSize(window->width, window->height));
+          // XXX I don't think we can be certain that the location wrt to
+          // the window only changes when the location wrt to the drawable
+          // changes, but the hdc probably changes on every paint so
+          // doupdatewindow is rarely false, and there is not likely to be
+          // a problem.
+          if (mWindowlessRect != winlessRect) {
+            mWindowlessRect = winlessRect;
+
+            WINDOWPOS winpos;
+            memset(&winpos, 0, sizeof(winpos));
+            winpos.x = mWindowlessRect.x;
+            winpos.y = mWindowlessRect.y;
+            winpos.cx = mWindowlessRect.width;
+            winpos.cy = mWindowlessRect.height;
+
+            // finally, update the plugin by sending it a WM_WINDOWPOSCHANGED event
+            nsPluginEvent pluginEvent;
+            pluginEvent.event = WM_WINDOWPOSCHANGED;
+            pluginEvent.wParam = 0;
+            pluginEvent.lParam = (uint32)&winpos;
+            PRBool eventHandled = PR_FALSE;
+
+            inst->HandleEvent(&pluginEvent, &eventHandled);
+          }
+
+          inst->SetWindow(window);        
+        }
+
+        mInstanceOwner->Paint(dirty, hdc);
+        nativeDraw.EndNativeDrawing();
+      } while (nativeDraw.ShouldRenderAgain());
+
+      nativeDraw.PaintToContext();
+    } else if (!(ctx->GetFlags() & gfxContext::FLAG_DESTINED_FOR_SCREEN)) {
+      // Get PrintWindow dynamically since it's not present on Win2K,
+      // which we still support
+      typedef BOOL (WINAPI * PrintWindowPtr)
+          (HWND hwnd, HDC hdcBlt, UINT nFlags);
+      PrintWindowPtr printProc = nsnull;
+      HMODULE module = ::GetModuleHandleW(L"user32.dll");
+      if (module) {
+        printProc = reinterpret_cast<PrintWindowPtr>
+          (::GetProcAddress(module, "PrintWindow"));
+      }
+      if (printProc) {
+        HWND hwnd = reinterpret_cast<HWND>(window->window);
+        RECT rc;
+        GetWindowRect(hwnd, &rc);
+        nsRefPtr<gfxWindowsSurface> surface =
+          new gfxWindowsSurface(gfxIntSize(rc.right - rc.left, rc.bottom - rc.top));
+
+        if (surface && printProc) {
+          // CAUTION: EVIL AHEAD
+          // We have to temporarily make hwnd a top-level window so that
+          // PrintWindow won't clip it
+          HWND parent = ::GetParent(hwnd);
+          ::SetParent(hwnd, NULL);
+          printProc(hwnd, surface->GetDC(), 0);
+          ::SetParent(hwnd, parent);
+          // END EVIL
+        
+          ctx->Translate(frameGfxRect.pos);
+          ctx->SetSource(surface);
+          gfxRect r = frameGfxRect.Intersect(dirtyGfxRect) - frameGfxRect.pos;
+          ctx->NewPath();
+          ctx->Rectangle(r);
+          ctx->Fill();
+        }
       }
     }
-#elif defined (XP_WIN) || defined(XP_OS2)
+
+    ctx->SetMatrix(currentMatrix);
+  }
+#elif defined(XP_OS2)
   nsCOMPtr<nsIPluginInstance> inst;
   GetPluginInstance(*getter_AddRefs(inst));
   if (inst) {
     // Look if it's windowless
     nsPluginWindow * window;
     mInstanceOwner->GetWindow(window);
 
     if (window->type == nsPluginWindowType_Drawable) {
@@ -1433,16 +1553,17 @@ nsObjectFrame::PaintPlugin(nsIRenderingC
        * Layout now has an optimized way of painting. Now we always get
        * a new drawing surface, sized to be just what's needed. Windowless
        * plugins need a transform applied to their origin so they paint
        * in the right place. Since |SetWindow| is no longer being used
        * to tell the plugin where it is, we dispatch a NPWindow through
        * |HandleEvent| to tell the plugin when its window moved
        */
       gfxContext *ctx = aRenderingContext.ThebesContext();
+
       gfxMatrix ctxMatrix = ctx->CurrentMatrix();
       if (ctxMatrix.HasNonTranslation()) {
         // soo; in the future, we should be able to render
         // the object content to an offscreen DC, and then
         // composite it in with the right transforms.
 
         // But, we don't bother doing that, because we don't
         // have the event handling story figured out yet.
@@ -1461,31 +1582,16 @@ nsObjectFrame::PaintPlugin(nsIRenderingC
       gfxFloat xoff, yoff;
       nsRefPtr<gfxASurface> surf = ctx->CurrentSurface(&xoff, &yoff);
 
       if (surf->CairoStatus() != 0) {
         NS_WARNING("Plugin is being asked to render to a surface that's in error!");
         return;
       }
 
-#ifdef XP_WIN
-      // check if we need to update hdc
-      HDC hdc = (HDC)aRenderingContext.GetNativeGraphicData(nsIRenderingContext::NATIVE_WINDOWS_DC);
-
-      if (reinterpret_cast<HDC>(window->window) != hdc) {
-        window->window = reinterpret_cast<nsPluginPort*>(hdc);
-        doupdatewindow = PR_TRUE;
-      }
-
-      SaveDC(hdc);
-
-      POINT origViewportOrigin;
-      GetViewportOrgEx(hdc, &origViewportOrigin);
-      SetViewportOrgEx(hdc, origViewportOrigin.x + (int) xoff, origViewportOrigin.y + (int) yoff, NULL);
-#else // do something similar on OS/2
       // check if we need to update the PS
       HPS hps = (HPS)aRenderingContext.GetNativeGraphicData(nsIRenderingContext::NATIVE_OS2_PS);
       if (reinterpret_cast<HPS>(window->window) != hps) {
         window->window = reinterpret_cast<nsPluginPort*>(hps);
         doupdatewindow = PR_TRUE;
       }
       LONG lPSid = GpiSavePS(hps);
       RECTL rclViewport;
@@ -1493,83 +1599,32 @@ nsObjectFrame::PaintPlugin(nsIRenderingC
         if (GpiQueryPageViewport(hps, &rclViewport)) {
           rclViewport.xLeft += (LONG)xoff;
           rclViewport.xRight += (LONG)xoff;
           rclViewport.yBottom += (LONG)yoff;
           rclViewport.yTop += (LONG)yoff;
           GpiSetPageViewport(hps, &rclViewport);
         }
       }
-#endif
 
       if ((window->x != origin.x) || (window->y != origin.y)) {
         window->x = origin.x;
         window->y = origin.y;
         doupdatewindow = PR_TRUE;
       }
 
       // if our location or visible area has changed, we need to tell the plugin
       if (doupdatewindow) {
-#ifdef XP_WIN    // Windowless plugins on windows need a special event to update their location, see bug 135737
-           // bug 271442: note, the rectangle we send is now purely the bounds of the plugin
-           // relative to the window it is contained in, which is useful for the plugin to correctly translate mouse coordinates
-           //
-           // this does not mesh with the comments for bug 135737 which imply that the rectangle
-           // must be clipped in some way to prevent the plugin attempting to paint over areas it shouldn't;
-           //
-           // since the two uses of the rectangle are mutually exclusive in some cases,
-           // and since I don't see any incorrect painting (at least with Flash and ViewPoint - the originator of 135737),
-           // it seems that windowless plugins are not relying on information here for clipping their drawing,
-           // and we can safely use this message to tell the plugin exactly where it is in all cases.
-
-              origin = GetWindowOriginInPixels(PR_TRUE);
-              nsRect winlessRect = nsRect(origin, nsSize(window->width, window->height));
-              // XXX I don't think we can be certain that the location wrt to
-              // the window only changes when the location wrt to the drawable
-              // changes, but the hdc probably changes on every paint so
-              // doupdatewindow is rarely false, and there is not likely to be
-              // a problem.
-              if (mWindowlessRect != winlessRect) {
-                mWindowlessRect = winlessRect;
-
-                WINDOWPOS winpos;
-                memset(&winpos, 0, sizeof(winpos));
-                winpos.x = mWindowlessRect.x;
-                winpos.y = mWindowlessRect.y;
-                winpos.cx = mWindowlessRect.width;
-                winpos.cy = mWindowlessRect.height;
-
-                // finally, update the plugin by sending it a WM_WINDOWPOSCHANGED event
-                nsPluginEvent pluginEvent;
-                pluginEvent.event = WM_WINDOWPOSCHANGED;
-                pluginEvent.wParam = 0;
-                pluginEvent.lParam = (uint32)&winpos;
-                PRBool eventHandled = PR_FALSE;
-
-                inst->HandleEvent(&pluginEvent, &eventHandled);
-              }
-#endif
-
         inst->SetWindow(window);        
       }
 
-#ifdef XP_WIN
-      // FIXME - Bug 385435:
-      // This expects a dirty rect relative to the plugin's rect
-      // XXX I wonder if this breaks if we give the frame a border so the
-      // frame origin and plugin origin are not the same
-      mInstanceOwner->Paint(aDirtyRect, hdc);
-
-      RestoreDC(hdc, -1);
-#else // do something similar on OS/2
       mInstanceOwner->Paint(aDirtyRect, hps);
       if (lPSid >= 1) {
         GpiRestorePS(hps, lPSid);
       }
-#endif
       surf->MarkDirty();
     }
   }
 #endif
 }
 
 NS_IMETHODIMP
 nsObjectFrame::HandleEvent(nsPresContext* aPresContext,
@@ -3938,40 +3993,25 @@ void nsPluginInstanceOwner::Paint(const 
       mInstance->HandleEvent(&pluginEvent, &eventHandled);
     }
     pluginWidget->EndDrawPlugin();
   }
 }
 #endif
 
 #ifdef XP_WIN
-void nsPluginInstanceOwner::Paint(const nsRect& aDirtyRect, HDC ndc)
+void nsPluginInstanceOwner::Paint(const RECT& aDirty, HDC aDC)
 {
   if (!mInstance || !mOwner)
     return;
 
-  nsPluginWindow * window;
-  GetWindow(window);
-  nsRect relDirtyRect = nsRect(aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
-  nsIntRect relDirtyRectInPixels;
-  ConvertAppUnitsToPixels(*mOwner->PresContext(), relDirtyRect,
-                          relDirtyRectInPixels);
-
-  // we got dirty rectangle in relative window coordinates, but we
-  // need it in absolute units and in the (left, top, right, bottom) form
-  RECT drc;
-  drc.left   = relDirtyRectInPixels.x + window->x;
-  drc.top    = relDirtyRectInPixels.y + window->y;
-  drc.right  = drc.left + relDirtyRectInPixels.width;
-  drc.bottom = drc.top + relDirtyRectInPixels.height;
-
   nsPluginEvent pluginEvent;
   pluginEvent.event = WM_PAINT;
-  pluginEvent.wParam = (uint32)ndc;
-  pluginEvent.lParam = (uint32)&drc;
+  pluginEvent.wParam = WPARAM(aDC);
+  pluginEvent.lParam = LPARAM(&aDirty);
   PRBool eventHandled = PR_FALSE;
   mInstance->HandleEvent(&pluginEvent, &eventHandled);
 }
 #endif
 
 #ifdef XP_OS2
 void nsPluginInstanceOwner::Paint(const nsRect& aDirtyRect, HPS aHPS)
 {
--- a/widget/src/windows/nsWindow.cpp
+++ b/widget/src/windows/nsWindow.cpp
@@ -5873,16 +5873,17 @@ PRBool nsWindow::OnPaint(HDC aDC)
       } else {
         targetSurface = new gfxWindowsSurface(hDC);
       }
 #else
       nsRefPtr<gfxASurface> targetSurface = new gfxWindowsSurface(hDC);
 #endif
 
       nsRefPtr<gfxContext> thebesContext = new gfxContext(targetSurface);
+      thebesContext->SetFlag(FLAG_DESTINED_FOR_SCREEN);
 
 #ifdef MOZ_XUL
       if (eTransparencyGlass == mTransparencyMode && nsUXThemeData::sHaveCompositor) {
         thebesContext->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA);
       } else if (eTransparencyTransparent == mTransparencyMode) {
         // If we're rendering with translucency, we're going to be
         // rendering the whole window; make sure we clear it first
         thebesContext->SetOperator(gfxContext::OPERATOR_CLEAR);