Retry snapshots if they are too empty
authorjonlee@apple.com <jonlee@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 7 Dec 2012 00:10:23 +0000 (00:10 +0000)
committerjonlee@apple.com <jonlee@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 7 Dec 2012 00:10:23 +0000 (00:10 +0000)
https://bugs.webkit.org/show_bug.cgi?id=104174
<rdar://problem/12820146>

Reviewed by Simon Fraser.

Source/WebCore:

* html/HTMLPlugInImageElement.cpp:
(WebCore::HTMLPlugInImageElement::updateSnapshot): Change the state machine check so that even
when the plug-in is displaying a snapshot, the snapshot can still be updated. This allows for the
retries to be drawn.

Source/WebKit2:

* WebProcess/Plugins/PluginView.h: Add a new variable that keeps track of the number of times we've
retried to come up with a snapshot.
* WebProcess/Plugins/PluginView.cpp:
(WebKit::PluginView::PluginView): Initialize the count to 0.
(WebKit::isAlmostSolidColor): Figure out if the image is almost a solid color by overlaying
a grid of dots, and calculate the differences among them. If the average color difference is greater than
a threshold, we consider it to have meaningful content. For now we expect a minimum size and a specific
bitmap image format, otherwise we return early.
(WebKit::PluginView::pluginSnapshotTimerFired): If we have a snapshot image to look at, and if it is
evaluated to be too empty, then try again.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@136905 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/html/HTMLPlugInImageElement.cpp
Source/WebKit2/ChangeLog
Source/WebKit2/WebProcess/Plugins/PluginView.cpp
Source/WebKit2/WebProcess/Plugins/PluginView.h

index f39cca2..8029c5f 100644 (file)
@@ -1,3 +1,16 @@
+2012-12-06  Jon Lee  <jonlee@apple.com>
+
+        Retry snapshots if they are too empty
+        https://bugs.webkit.org/show_bug.cgi?id=104174
+        <rdar://problem/12820146>
+
+        Reviewed by Simon Fraser.
+
+        * html/HTMLPlugInImageElement.cpp:
+        (WebCore::HTMLPlugInImageElement::updateSnapshot): Change the state machine check so that even
+        when the plug-in is displaying a snapshot, the snapshot can still be updated. This allows for the
+        retries to be drawn.
+
 2012-12-06  Adam Klein  <adamk@chromium.org>
 
         [HTMLTemplateElement] make content readonly and cloneNode(deep) clone content
index 7a40add..5904cc8 100644 (file)
@@ -253,7 +253,7 @@ void HTMLPlugInImageElement::updateWidgetCallback(Node* n, unsigned)
 
 void HTMLPlugInImageElement::updateSnapshot(PassRefPtr<Image> image)
 {
-    if (displayState() > WaitingForSnapshot || !renderer()->isSnapshottedPlugIn())
+    if (displayState() > DisplayingSnapshot || !renderer()->isSnapshottedPlugIn())
         return;
 
     toRenderSnapshottedPlugIn(renderer())->updateSnapshot(image);
index a3bbc7a..e014fb0 100644 (file)
@@ -1,3 +1,22 @@
+2012-12-06  Jon Lee  <jonlee@apple.com>
+
+        Retry snapshots if they are too empty
+        https://bugs.webkit.org/show_bug.cgi?id=104174
+        <rdar://problem/12820146>
+
+        Reviewed by Simon Fraser.
+
+        * WebProcess/Plugins/PluginView.h: Add a new variable that keeps track of the number of times we've
+        retried to come up with a snapshot.
+        * WebProcess/Plugins/PluginView.cpp:
+        (WebKit::PluginView::PluginView): Initialize the count to 0.
+        (WebKit::isAlmostSolidColor): Figure out if the image is almost a solid color by overlaying
+        a grid of dots, and calculate the differences among them. If the average color difference is greater than
+        a threshold, we consider it to have meaningful content. For now we expect a minimum size and a specific
+        bitmap image format, otherwise we return early.
+        (WebKit::PluginView::pluginSnapshotTimerFired): If we have a snapshot image to look at, and if it is
+        evaluated to be too empty, then try again.
+
 2012-12-06  Tony Chang  <tony@chromium.org>
 
         Unreviewed, Apple Win Debug build fix.
index 6c74f46..f32104b 100644 (file)
@@ -34,6 +34,7 @@
 #include "WebPage.h"
 #include "WebPageProxyMessages.h"
 #include "WebProcess.h"
+#include <WebCore/BitmapImage.h>
 #include <WebCore/Chrome.h>
 #include <WebCore/CookieJar.h>
 #include <WebCore/Credential.h>
@@ -70,6 +71,7 @@ namespace WebKit {
 
 // This simulated mouse click delay in HTMLPlugInImageElement.cpp should generally be the same or shorter than this delay.
 static const double pluginSnapshotTimerDelay = 1.1;
+static const unsigned maximumSnapshotRetries = 5;
 
 class PluginView::URLRequest : public RefCounted<URLRequest> {
 public:
@@ -272,6 +274,7 @@ PluginView::PluginView(PassRefPtr<HTMLPlugInElement> pluginElement, PassRefPtr<P
 #endif
     , m_manualStreamState(StreamStateInitial)
     , m_pluginSnapshotTimer(this, &PluginView::pluginSnapshotTimerFired, pluginSnapshotTimerDelay)
+    , m_countSnapshotRetries(0)
     , m_pageScaleFactor(1)
 {
     m_webPage->addPluginView(this);
@@ -1494,9 +1497,71 @@ void PluginView::windowedPluginGeometryDidChange(const WebCore::IntRect& frameRe
 }
 #endif
 
-void PluginView::pluginSnapshotTimerFired(DeferrableOneShotTimer<PluginView>* timer)
+#if PLATFORM(MAC)
+static bool isAlmostSolidColor(BitmapImage* bitmap)
+{
+    CGImageRef image = bitmap->getCGImageRef();
+    ASSERT(CGImageGetBitsPerComponent(image) == 8);
+
+    CGBitmapInfo imageInfo = CGImageGetBitmapInfo(image);
+    if (!(imageInfo & kCGBitmapByteOrder32Little) || (imageInfo & kCGBitmapAlphaInfoMask) != kCGImageAlphaPremultipliedFirst) {
+        // FIXME: Consider being able to handle other pixel formats.
+        ASSERT_NOT_REACHED();
+        return false;
+    }
+
+    size_t width = CGImageGetWidth(image);
+    size_t height = CGImageGetHeight(image);
+    size_t bytesPerRow = CGImageGetBytesPerRow(image);
+
+    RetainPtr<CFDataRef> provider = adoptCF(CGDataProviderCopyData(CGImageGetDataProvider(image)));
+    const UInt8* data = CFDataGetBytePtr(provider.get());
+
+    // Overlay a grid of sampling dots on top of a grayscale version of the image.
+    // For the interior points, calculate the difference in luminance among the sample point
+    // and its surrounds points, scaled by transparency.
+    const unsigned sampleRows = 7;
+    const unsigned sampleCols = 7;
+    // FIXME: Refine the proper number of samples, and accommodate different aspect ratios.
+    if (width < sampleCols || height < sampleRows)
+        return false;
+
+    // Ensure that the last row/column land on the image perimeter.
+    const float strideWidth = static_cast<float>(width - 1) / (sampleCols - 1);
+    const float strideHeight = static_cast<float>(height - 1) / (sampleRows - 1);
+    float samples[sampleRows][sampleCols];
+
+    // Find the luminance of the sample points.
+    float y = 0;
+    const UInt8* row = data;
+    for (unsigned i = 0; i < sampleRows; ++i) {
+        float x = 0;
+        for (unsigned j = 0; j < sampleCols; ++j) {
+            const UInt8* p0 = row + (static_cast<int>(x + .5)) * 4;
+            // R G B A
+            samples[i][j] = (0.2125 * *p0 + 0.7154 * *(p0+1) + 0.0721 * *(p0+2)) * *(p0+3) / 255;
+            x += strideWidth;
+        }
+        y += strideHeight;
+        row = data + (static_cast<int>(y + .5)) * bytesPerRow;
+    }
+
+    // Determine the image score.
+    float accumScore = 0;
+    for (unsigned i = 1; i < sampleRows - 1; ++i) {
+        for (unsigned j = 1; j < sampleCols - 1; ++j) {
+            float diff = samples[i - 1][j] + samples[i + 1][j] + samples[i][j - 1] + samples[i][j + 1] - 4 * samples[i][j];
+            accumScore += diff * diff;
+        }
+    }
+
+    // The score for a given sample can be within the range of 0 and 255^2.
+    return accumScore < 2500 * (sampleRows - 2) * (sampleCols - 2);
+}
+#endif
+
+void PluginView::pluginSnapshotTimerFired(DeferrableOneShotTimer<PluginView>*)
 {
-    ASSERT_UNUSED(timer, timer == &m_pluginSnapshotTimer);
     ASSERT(m_plugin);
 
     // Snapshot might be 0 if plugin size is 0x0.
@@ -1504,7 +1569,16 @@ void PluginView::pluginSnapshotTimerFired(DeferrableOneShotTimer<PluginView>* ti
     RefPtr<Image> snapshotImage;
     if (snapshot)
         snapshotImage = snapshot->createImage();
-    m_pluginElement->updateSnapshot(snapshotImage.release());
+    m_pluginElement->updateSnapshot(snapshotImage.get());
+
+#if PLATFORM(MAC)
+    if (snapshotImage && isAlmostSolidColor(static_cast<BitmapImage*>(snapshotImage.get())) && m_countSnapshotRetries < maximumSnapshotRetries) {
+        ++m_countSnapshotRetries;
+        m_pluginSnapshotTimer.restart();
+        return;
+    }
+#endif
+
     destroyPluginAndReset();
     m_plugin = 0;
 }
index cd9e35b..6426dcc 100644 (file)
@@ -250,6 +250,7 @@ private:
     RefPtr<ShareableBitmap> m_transientPaintingSnapshot;
     // This timer is used when plugin snapshotting is enabled, to capture a plugin placeholder.
     WebCore::DeferrableOneShotTimer<PluginView> m_pluginSnapshotTimer;
+    unsigned m_countSnapshotRetries;
 
     double m_pageScaleFactor;
 };