Bug 1232181 - Add support for capturing plugin windows on Windows. r?aklotz draft
authorJim Mathies <jmathies@mozilla.com>
Fri, 26 Feb 2016 08:58:07 -0600
changeset 334925 226eba46982223f3d81421fd748e502bab0b216d
parent 334924 fb9c550d86c20531c12c35614e50750520d51e5e
child 515028 33b15cc225e3e4edc40c820060f93b10adcbb53d
push id11672
push userjmathies@mozilla.com
push dateFri, 26 Feb 2016 14:58:31 +0000
reviewersaklotz
bugs1232181
milestone47.0a1
Bug 1232181 - Add support for capturing plugin windows on Windows. r?aklotz MozReview-Commit-ID: DFTkIphShiw
dom/plugins/ipc/PluginInstanceParent.cpp
dom/plugins/ipc/PluginInstanceParent.h
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -55,32 +55,40 @@
 #include <windowsx.h>
 #include "gfxWindowsPlatform.h"
 #include "mozilla/plugins/PluginSurfaceParent.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 #include "nsIWidget.h"
 #include "nsPluginNativeWindow.h"
 #include "PluginQuirks.h"
+#include "nsWindowsHelpers.h"
 extern const wchar_t* kFlashFullscreenClass;
 #elif defined(MOZ_WIDGET_GTK)
 #include "mozilla/dom/ContentChild.h"
 #include <gdk/gdk.h>
 #elif defined(XP_MACOSX)
 #include <ApplicationServices/ApplicationServices.h>
 #endif // defined(XP_MACOSX)
 
 // This is the pref used to determine whether to use Shumway on a Flash object
 // (when Shumway is enabled).
 static const char kShumwayWhitelistPref[] = "shumway.swf.whitelist";
 
 using namespace mozilla::plugins;
 using namespace mozilla::layers;
 using namespace mozilla::gl;
 
+#if defined(XP_WIN)
+// Delays associated with attempting an e10s window capture for scrolling.
+const int kScrollCaptureDelayMs = 100;
+const int kInitScrollCaptureDelayMs = 1000;
+const uint32_t kScrollCaptureFillColor = 0xFFa0a0a0; // gray
+#endif
+
 void
 StreamNotifyParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   // Implement me! Bug 1005162
 }
 
 bool
 StreamNotifyParent::RecvRedirectNotifyResponse(const bool& allow)
@@ -108,16 +116,23 @@ PluginInstanceParent::LookupPluginInstan
         return sPluginInstanceList->Get((void*)aId);
     }
     return nullptr;
 }
 }
 }
 #endif
 
+template<>
+struct RunnableMethodTraits<PluginInstanceParent>
+{
+    static void RetainCallee(PluginInstanceParent* obj) { }
+    static void ReleaseCallee(PluginInstanceParent* obj) { }
+};
+
 PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent,
                                            NPP npp,
                                            const nsCString& aMimeType,
                                            const NPNetscapeFuncs* npniface)
     : mParent(parent)
     , mSurrogate(PluginAsyncSurrogate::Cast(npp))
     , mUseSurrogate(true)
     , mNPP(npp)
@@ -134,16 +149,21 @@ PluginInstanceParent::PluginInstancePare
     , mPluginWndProc(nullptr)
     , mNestedEventState(false)
 #endif // defined(XP_WIN)
 #if defined(XP_MACOSX)
     , mShWidth(0)
     , mShHeight(0)
     , mShColorSpace(nullptr)
 #endif
+#if defined(XP_WIN)
+    , mCaptureRefreshTask(nullptr)
+    , mValidFirstCapture(false)
+    , mIsScrolling(false)
+#endif
 {
 #if defined(OS_WIN)
     if (!sPluginInstanceList) {
         sPluginInstanceList = new nsClassHashtable<nsVoidPtrHashKey, PluginInstanceParent>();
     }
 #endif
 }
 
@@ -158,16 +178,19 @@ PluginInstanceParent::~PluginInstancePar
 #endif
 #if defined(MOZ_WIDGET_COCOA)
     if (mShWidth != 0 && mShHeight != 0) {
         DeallocShmem(mShSurface);
     }
     if (mShColorSpace)
         ::CGColorSpaceRelease(mShColorSpace);
 #endif
+#if defined(XP_WIN)
+    CancelScheduledScrollCapture();
+#endif
 }
 
 bool
 PluginInstanceParent::InitMetadata(const nsACString& aMimeType,
                                    const nsACString& aSrcAttribute)
 {
     if (aSrcAttribute.IsEmpty()) {
         return false;
@@ -435,16 +458,22 @@ bool
 PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginWindow(
     const bool& windowed, NPError* result)
 {
     // Yes, we are passing a boolean as a void*.  We have to cast to intptr_t
     // first to avoid gcc warnings about casting to a pointer from a
     // non-pointer-sized integer.
     *result = mNPNIface->setvalue(mNPP, NPPVpluginWindowBool,
                                   (void*)(intptr_t)windowed);
+
+#if defined(XP_WIN)
+        if (windowed) {
+            ScheduleScrollCapture(kScrollCaptureDelayMs);
+        }
+#endif
     return true;
 }
 
 bool
 PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginTransparent(
     const bool& transparent, NPError* result)
 {
     *result = mNPNIface->setvalue(mNPP, NPPVpluginTransparentBool,
@@ -1182,16 +1211,218 @@ PluginInstanceParent::EndUpdateBackgroun
     XSync(DefaultXDisplay(), False);
 #endif
 
     Unused << SendUpdateBackground(BackgroundDescriptor(), aRect);
 
     return NS_OK;
 }
 
+#if defined(XP_WIN)
+//#define CAPTURE_LOG(...) printf_stderr("CAPTURE [%X]: ", this);printf_stderr(__VA_ARGS__);printf_stderr("\n");
+#define CAPTURE_LOG(...)
+
+void
+PluginInstanceParent::ScheduleScrollCapture(int aTimeout)
+{
+    if (mCaptureRefreshTask) {
+        return;
+    }
+    CAPTURE_LOG("delayed scroll capture requested.");
+    mCaptureRefreshTask =
+        NewRunnableMethod(this, &PluginInstanceParent::ScheduledUpdateScrollCaptureCallback);
+    MessageLoop::current()->PostDelayedTask(FROM_HERE, mCaptureRefreshTask,
+                                            kScrollCaptureDelayMs);
+}
+
+void
+PluginInstanceParent::ScheduledUpdateScrollCaptureCallback()
+{
+    CAPTURE_LOG("taking delayed scrollcapture.");
+    mCaptureRefreshTask = nullptr;
+    bool retrigger = false;
+    UpdateScrollCapture(retrigger);
+    if (retrigger) {
+        // reset the async request
+        ScheduleScrollCapture(kScrollCaptureDelayMs);
+    }
+}
+
+void
+PluginInstanceParent::CancelScheduledScrollCapture()
+{
+    CAPTURE_LOG("delayed scroll capture cancelled.");
+    if (mCaptureRefreshTask) {
+        mCaptureRefreshTask->Cancel();
+        mCaptureRefreshTask = nullptr;
+    }
+}
+
+bool
+PluginInstanceParent::UpdateScrollCapture(bool& aRequestNewCapture)
+{
+    aRequestNewCapture = false;
+    if (!::IsWindow(mChildPluginHWND)) {
+        CAPTURE_LOG("invalid window");
+        aRequestNewCapture = true;
+        return false;
+    }
+
+    nsAutoHDC windowDC(::GetDC(mChildPluginHWND));
+    if (!windowDC) {
+        CAPTURE_LOG("no windowdc");
+        aRequestNewCapture = true;
+        return false;
+    }
+
+    RECT bounds = {0};
+    ::GetWindowRect(mChildPluginHWND, &bounds);
+    if ((bounds.left == bounds.right && bounds.top == bounds.bottom) ||
+        (!mWindowSize.width && !mWindowSize.height)) {
+        CAPTURE_LOG("empty bounds");
+        // Lots of null window plugins in content, don't capture.
+        return false;
+    }
+
+    // If we need to init mScrollCapture do so, also reset it if the size of the
+    // plugin window changes.
+    if (!mScrollCapture || mScrollCapture->GetSize() != mWindowSize) {
+        mValidFirstCapture = false;
+        mScrollCapture =
+            gfxPlatform::GetPlatform()->CreateOffscreenSurface(mWindowSize,
+                                                               SurfaceFormat::X8R8G8B8_UINT32);
+    }
+
+    // Check clipping, we don't want to capture windows that are clipped by
+    // the viewport.
+    RECT clip = {0};
+    int rgnType = ::GetWindowRgnBox(mPluginHWND, &clip);
+    bool clipCorrect = !clip.left && !clip.top &&
+                       clip.right == mWindowSize.width &&
+                       clip.bottom == mWindowSize.height;
+
+    bool isVisible = ::IsWindowVisible(mChildPluginHWND);
+
+    CAPTURE_LOG("validcap=%d visible=%d region=%d clip=%d:%dx%dx%dx%d",
+                mValidFirstCapture, isVisible, rgnType, clipCorrect
+                clip.left, clip.top, clip.right, clip.bottom);
+
+    // We have a good capture and can't update so keep using the existing
+    // capture image. Otherwise fall through so we paint the fill color to
+    // the layer.
+    if (mValidFirstCapture && (!isVisible || !clipCorrect)) {
+        return true;
+    }
+
+    // On Windows we'll need a native bitmap for BitBlt.
+    RefPtr<gfxWindowsSurface> nativeScrollCapture;
+
+    // Copy the plugin window if it's visible and there's no clipping, otherwise
+    // use a default fill color.
+    if (isVisible && clipCorrect) {
+        CAPTURE_LOG("capturing window");
+        nativeScrollCapture =
+            new gfxWindowsSurface(mWindowSize, SurfaceFormat::X8R8G8B8_UINT32);
+        if (!::BitBlt(nativeScrollCapture->GetDC(), 0, 0, mWindowSize.width,
+                      mWindowSize.height, windowDC, 0, 0, SRCCOPY)) {
+            CAPTURE_LOG("blt failure??");
+            return false;
+        }
+        ::GdiFlush();
+        mValidFirstCapture = true;
+    }
+
+    IntSize targetSize = mScrollCapture->GetSize();
+    RefPtr<gfx::DrawTarget> dt =
+        gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(mScrollCapture,
+                                                               targetSize);
+
+    if (nativeScrollCapture) {
+        // Copy the native capture image over to a remotable gfx surface.
+        RefPtr<gfx::SourceSurface> sourceSurface =
+            gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr,
+                                                                   nativeScrollCapture);
+        dt->CopySurface(sourceSurface,
+                        IntRect(0, 0, targetSize.width, targetSize.height),
+                        IntPoint());
+    } else {
+        CAPTURE_LOG("using fill color");
+        dt->FillRect(gfx::Rect(0, 0, targetSize.width, targetSize.height),
+                     gfx::ColorPattern(gfx::Color::FromABGR(kScrollCaptureFillColor)),
+                     gfx::DrawOptions(1.f, CompositionOp::OP_SOURCE));
+        dt->Flush();
+        aRequestNewCapture = true;
+    }
+
+    // Get a source for mScrollCapture and load it into the image container.
+    RefPtr<gfx::SourceSurface> cachedSource =
+        gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(dt,
+                                                               mScrollCapture);
+    RefPtr<SourceSurfaceImage> image =
+        new SourceSurfaceImage(cachedSource->GetSize(), cachedSource);
+
+    ImageContainer::NonOwningImage holder(image);
+    holder.mFrameID = ++mFrameID;
+
+    AutoTArray<ImageContainer::NonOwningImage,1> imageList;
+    imageList.AppendElement(holder);
+
+    // inits mImageContainer
+    ImageContainer *container = GetImageContainer();
+    container->SetCurrentImages(imageList);
+
+    // Invalidate our area in the page so the image gets flushed.
+    NPRect nprect = {0, 0, targetSize.width, targetSize.height};
+    RecvNPN_InvalidateRect(nprect);
+
+    return true;
+}
+
+nsresult
+PluginInstanceParent::GetScrollCaptureContainer(ImageContainer** aContainer)
+{
+    if (!::IsWindow(mPluginHWND)) {
+        return NS_ERROR_FAILURE;
+    }
+
+    if (!mImageContainer) {
+        ScheduleScrollCapture(kInitScrollCaptureDelayMs);
+        return NS_ERROR_FAILURE;
+    }
+
+    ImageContainer *container = GetImageContainer();
+    NS_IF_ADDREF(container);
+    *aContainer = container;
+
+    return NS_OK;
+}
+
+nsresult
+PluginInstanceParent::UpdateScrollState(bool aIsScrolling)
+{
+#if defined(XP_WIN)
+  bool scrollStateChanged = (mIsScrolling != aIsScrolling);
+  mIsScrolling = aIsScrolling;
+  if (scrollStateChanged && !aIsScrolling) {
+      // At the end of a dom scroll operation capturing now will attempt to
+      // capture a window that is still hidden due to the current scroll
+      // operation. (The browser process will update visibility after layer
+      // updates get pushed over.) So we delay our attempt for a bit. This
+      // shouldn't hurt our chances of capturing with APZ scroll since the
+      // delay is short.
+      ScheduleScrollCapture(kScrollCaptureDelayMs);
+  }
+  return NS_OK;
+#else
+    NS_NOTREACHED("Not implemented");
+    return NS_ERROR_FAILURE;
+#endif
+}
+#endif // XP_WIN
+
 PluginAsyncSurrogate*
 PluginInstanceParent::GetAsyncSurrogate()
 {
     return mSurrogate;
 }
 
 bool
 PluginInstanceParent::CreateBackground(const nsIntSize& aSize)
@@ -1334,16 +1565,19 @@ PluginInstanceParent::NPP_SetWindow(cons
     window.x = aWindow->x;
     window.y = aWindow->y;
     window.width = aWindow->width;
     window.height = aWindow->height;
     window.clipRect = aWindow->clipRect; // MacOS specific
     window.type = aWindow->type;
 #endif
 
+    mWindowSize.width = window.width;
+    mWindowSize.height = window.height;
+
 #if defined(XP_MACOSX)
     double floatScaleFactor = 1.0;
     mNPNIface->getvalue(mNPP, NPNVcontentsScaleFactor, &floatScaleFactor);
     int scaleFactor = ceil(floatScaleFactor);
     window.contentsScaleFactor = floatScaleFactor;
 
     if (mShWidth != window.width * scaleFactor || mShHeight != window.height * scaleFactor) {
         if (mDrawingModel == NPDrawingModelCoreAnimation ||
@@ -1378,16 +1612,22 @@ PluginInstanceParent::NPP_SetWindow(cons
     window.colormap = ws_info->colormap;
 #endif
 
     if (!CallNPP_SetWindow(window)) {
         return NPERR_GENERIC_ERROR;
     }
 
     RecordDrawingModel();
+
+#if defined(XP_WIN)
+    if (!mCaptureRefreshTask) {
+        ScheduleScrollCapture(kScrollCaptureDelayMs);
+    }
+#endif
     return NPERR_NO_ERROR;
 }
 
 NPError
 PluginInstanceParent::NPP_GetValue(NPPVariable aVariable,
                                    void* _retval)
 {
     switch (aVariable) {
--- a/dom/plugins/ipc/PluginInstanceParent.h
+++ b/dom/plugins/ipc/PluginInstanceParent.h
@@ -337,16 +337,20 @@ public:
 #ifdef XP_MACOSX
     nsresult IsRemoteDrawingCoreAnimation(bool *aDrawing);
     nsresult ContentsScaleFactorChanged(double aContentsScaleFactor);
 #endif
     nsresult SetBackgroundUnknown();
     nsresult BeginUpdateBackground(const nsIntRect& aRect,
                                    DrawTarget** aDrawTarget);
     nsresult EndUpdateBackground(const nsIntRect& aRect);
+#if defined(XP_WIN)
+    nsresult GetScrollCaptureContainer(mozilla::layers::ImageContainer** aContainer);
+    nsresult UpdateScrollState(bool aIsScrolling);
+#endif
     void DidComposite();
 
     bool IsUsingDirectDrawing();
 
     virtual PluginAsyncSurrogate* GetAsyncSurrogate() override;
 
     virtual PluginInstanceParent* GetInstance() override { return this; }
 
@@ -396,16 +400,17 @@ private:
     RefPtr<PluginAsyncSurrogate> mSurrogate;
     bool mUseSurrogate;
     NPP mNPP;
     const NPNetscapeFuncs* mNPNIface;
     nsCString mSrcAttribute;
     bool mIsWhitelistedForShumway;
     NPWindowType mWindowType;
     int16_t mDrawingModel;
+    IntSize mWindowSize;
 
     // Since plugins may request different drawing models to find a compatible
     // one, we only record the drawing model after a SetWindow call and if the
     // drawing model has changed.
     int mLastRecordedDrawingModel;
 
     nsDataHashtable<nsPtrHashKey<NPObject>, PluginScriptableObjectParent*> mScriptableObjects;
 
@@ -460,15 +465,27 @@ private:
     // the browser, but a "read-only" reference is sent to the plugin.
     //
     // We have explicitly chosen not to provide any guarantees about
     // the consistency of the pixels in |mBackground|.  A plugin may
     // be able to observe partial updates to the background.
     RefPtr<gfxASurface>    mBackground;
 
     RefPtr<ImageContainer> mImageContainer;
+
+#if defined(XP_WIN)
+    void ScheduleScrollCapture(int aTimeout);
+    void ScheduledUpdateScrollCaptureCallback();
+    bool UpdateScrollCapture(bool& aRequestNewCapture);
+    void CancelScheduledScrollCapture();
+
+    RefPtr<gfxASurface> mScrollCapture;
+    CancelableTask* mCaptureRefreshTask;
+    bool mValidFirstCapture;
+    bool mIsScrolling;
+#endif
 };
 
 
 } // namespace plugins
 } // namespace mozilla
 
 #endif // ifndef dom_plugins_PluginInstanceParent_h