summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/extensions/api/tabs/tabs_api.cc38
-rw-r--r--chrome/browser/extensions/api/tabs/tabs_api.h16
-rw-r--r--chrome/common/extensions/api/webview_tag.json22
-rw-r--r--content/browser/compositor/delegated_frame_host.cc158
-rw-r--r--content/browser/compositor/delegated_frame_host.h15
-rw-r--r--content/browser/compositor/surface_utils.cc174
-rw-r--r--content/browser/compositor/surface_utils.h10
-rw-r--r--content/browser/frame_host/render_widget_host_view_child_frame.cc56
-rw-r--r--content/browser/frame_host/render_widget_host_view_child_frame.h23
-rw-r--r--content/browser/frame_host/render_widget_host_view_guest.cc2
-rw-r--r--content/browser/renderer_host/render_widget_host_view_android.cc5
-rw-r--r--extensions/browser/api/guest_view/extension_view/extension_view_internal_api.h1
-rw-r--r--extensions/browser/api/guest_view/web_view/web_view_internal_api.cc56
-rw-r--r--extensions/browser/api/guest_view/web_view/web_view_internal_api.h25
-rw-r--r--extensions/browser/api/web_contents_capture_client.cc (renamed from extensions/browser/api/capture_web_contents_function.cc)84
-rw-r--r--extensions/browser/api/web_contents_capture_client.h (renamed from extensions/browser/api/capture_web_contents_function.h)37
-rw-r--r--extensions/browser/guest_view/web_view/web_view_apitest.cc3
-rw-r--r--extensions/common/api/_api_features.json6
-rw-r--r--extensions/common/api/web_view_internal.json27
-rw-r--r--extensions/extensions.gypi4
-rw-r--r--extensions/renderer/dispatcher.cc6
-rw-r--r--extensions/renderer/resources/extensions_renderer_resources.grd1
-rw-r--r--extensions/renderer/resources/guest_view/web_view/web_view.js9
-rw-r--r--extensions/renderer/resources/guest_view/web_view/web_view_experimental.js24
-rw-r--r--extensions/test/data/web_view/apitest/main.js33
25 files changed, 584 insertions, 251 deletions
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc
index 011aa4b..c4c02a5c 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api.cc
@@ -1647,6 +1647,10 @@ TabsCaptureVisibleTabFunction::TabsCaptureVisibleTabFunction()
: chrome_details_(this) {
}
+bool TabsCaptureVisibleTabFunction::HasPermission() {
+ return true;
+}
+
bool TabsCaptureVisibleTabFunction::IsScreenshotEnabled() {
PrefService* service = chrome_details_.GetProfile()->GetPrefs();
if (service->GetBoolean(prefs::kDisableScreenshots)) {
@@ -1674,6 +1678,40 @@ WebContents* TabsCaptureVisibleTabFunction::GetWebContentsForID(int window_id) {
return contents;
}
+bool TabsCaptureVisibleTabFunction::RunAsync() {
+ using api::extension_types::ImageDetails;
+
+ EXTENSION_FUNCTION_VALIDATE(args_);
+
+ int context_id = extension_misc::kCurrentWindowId;
+ args_->GetInteger(0, &context_id);
+
+ scoped_ptr<ImageDetails> image_details;
+ if (args_->GetSize() > 1) {
+ base::Value* spec = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_->Get(1, &spec) && spec);
+ image_details = ImageDetails::FromValue(*spec);
+ }
+
+ WebContents* contents = GetWebContentsForID(context_id);
+
+ return CaptureAsync(
+ contents, image_details.get(),
+ base::Bind(&TabsCaptureVisibleTabFunction::CopyFromBackingStoreComplete,
+ this));
+}
+
+void TabsCaptureVisibleTabFunction::OnCaptureSuccess(const SkBitmap& bitmap) {
+ std::string base64_result;
+ if (!EncodeBitmap(bitmap, &base64_result)) {
+ OnCaptureFailure(FAILURE_REASON_ENCODING_FAILED);
+ return;
+ }
+
+ SetResult(new base::StringValue(base64_result));
+ SendResponse(true);
+}
+
void TabsCaptureVisibleTabFunction::OnCaptureFailure(FailureReason reason) {
const char* reason_description = "internal error";
switch (reason) {
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.h b/chrome/browser/extensions/api/tabs/tabs_api.h
index b398e70..e0a5f24 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.h
+++ b/chrome/browser/extensions/api/tabs/tabs_api.h
@@ -15,8 +15,8 @@
#include "components/ui/zoom/zoom_controller.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
-#include "extensions/browser/api/capture_web_contents_function.h"
#include "extensions/browser/api/execute_code_function.h"
+#include "extensions/browser/api/web_contents_capture_client.h"
#include "extensions/common/extension_resource.h"
#include "extensions/common/user_script.h"
#include "url/gurl.h"
@@ -195,21 +195,29 @@ class TabsDetectLanguageFunction : public ChromeAsyncExtensionFunction,
content::NotificationRegistrar registrar_;
DECLARE_EXTENSION_FUNCTION("tabs.detectLanguage", TABS_DETECTLANGUAGE)
};
+
class TabsCaptureVisibleTabFunction
- : public extensions::CaptureWebContentsFunction {
+ : public extensions::WebContentsCaptureClient,
+ public AsyncExtensionFunction {
public:
TabsCaptureVisibleTabFunction();
static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+ // ExtensionFunction implementation.
+ bool HasPermission() override;
+ bool RunAsync() override;
+
protected:
~TabsCaptureVisibleTabFunction() override {}
private:
ChromeExtensionFunctionDetails chrome_details_;
- // extensions::CaptureWebContentsFunction:
+ content::WebContents* GetWebContentsForID(int window_id);
+
+ // extensions::WebContentsCaptureClient:
bool IsScreenshotEnabled() override;
- content::WebContents* GetWebContentsForID(int id) override;
+ void OnCaptureSuccess(const SkBitmap& bitmap) override;
void OnCaptureFailure(FailureReason reason) override;
DECLARE_EXTENSION_FUNCTION("tabs.captureVisibleTab", TABS_CAPTUREVISIBLETAB)
diff --git a/chrome/common/extensions/api/webview_tag.json b/chrome/common/extensions/api/webview_tag.json
index 8828eb3..0263954 100644
--- a/chrome/common/extensions/api/webview_tag.json
+++ b/chrome/common/extensions/api/webview_tag.json
@@ -676,6 +676,28 @@
],
"functions": [
{
+ "name": "captureVisibleRegion",
+ "type": "function",
+ "description": "Captures the visible region of the webview.",
+ "parameters": [
+ {
+ "$ref": "extensionTypes.ImageDetails",
+ "name": "options",
+ "optional": true
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {"type": "string",
+ "name": "dataUrl",
+ "description": "A data URL which encodes an image of the visible area of the captured tab. May be assigned to the 'src' property of an HTML Image element for display."
+ }
+ ]
+ }
+ ]
+ },
+ {
"name": "addContentScripts",
"type": "function",
"description": "<p>Adds content script injection rules to the <code>webview</code>. When the <code>webview</code> navigates to a page matching one or more rules, the associated scripts will be injected. You can programmatically add rules or update existing rules.</p><p>The following example adds two rules to the <code>webview</code>: 'myRule' and 'anotherRule'.</p><pre>webview.addContentScripts([\r {\r name: 'myRule',\r matches: ['http://www.foo.com/*'],\r css: { files: ['mystyles.css'] },\r js: { files: ['jquery.js', 'myscript.js'] },\r run_at: 'document_start'\r },\r {\r name: 'anotherRule',\r matches: ['http://www.bar.com/*'],\r js: { code: \"document.body.style.backgroundColor = 'red';\" },\r run_at: 'document_end'\r }]);\r ...\r\r// Navigates webview.\rwebview.src = 'http://www.foo.com';</pre><p>You can defer addContentScripts call until you needs to inject scripts.</p><p>The following example shows how to overwrite an existing rule.</p><pre>webview.addContentScripts([{\r name: 'rule',\r matches: ['http://www.foo.com/*'],\r js: { files: ['scriptA.js'] },\r run_at: 'document_start'}]);\r\r// Do something.\rwebview.src = 'http://www.foo.com/*';\r ...\r// Overwrite 'rule' defined before.\rwebview.addContentScripts([{\r name: 'rule',\r matches: ['http://www.bar.com/*'],\r js: { files: ['scriptB.js'] },\r run_at: 'document_end'}]);</pre><p>If <code>webview</code> has been naviagted to the origin (e.g., foo.com) and calls <code>webview.addContentScripts</code> to add 'myRule', you need to wait for next navigation to make the scripts injected. If you want immediate injection, <code>executeScript</code> will do the right thing.</p><p>Rules are preserved even if the guest process crashes or is killed or even if the <code>webview</code> is reparented.</p><p>Refer to the <a href='/extensions/content_scripts'>content scripts</a> documentation for more details.</p>",
diff --git a/content/browser/compositor/delegated_frame_host.cc b/content/browser/compositor/delegated_frame_host.cc
index ac1b92b..5ffef42 100644
--- a/content/browser/compositor/delegated_frame_host.cc
+++ b/content/browser/compositor/delegated_frame_host.cc
@@ -150,8 +150,8 @@ void DelegatedFrameHost::CopyFromCompositingSurface(
scoped_ptr<cc::CopyOutputRequest> request =
cc::CopyOutputRequest::CreateRequest(
- base::Bind(&DelegatedFrameHost::CopyFromCompositingSurfaceHasResult,
- output_size, preferred_color_type, callback));
+ base::Bind(&CopyFromCompositingSurfaceHasResult, output_size,
+ preferred_color_type, callback));
if (!src_subrect.IsEmpty())
request->set_area(src_subrect);
RequestCopyOfOutput(std::move(request));
@@ -527,155 +527,6 @@ void DelegatedFrameHost::EvictDelegatedFrame() {
}
// static
-void DelegatedFrameHost::CopyFromCompositingSurfaceHasResult(
- const gfx::Size& dst_size_in_pixel,
- const SkColorType color_type,
- const ReadbackRequestCallback& callback,
- scoped_ptr<cc::CopyOutputResult> result) {
- if (result->IsEmpty() || result->size().IsEmpty()) {
- callback.Run(SkBitmap(), content::READBACK_FAILED);
- return;
- }
-
- gfx::Size output_size_in_pixel;
- if (dst_size_in_pixel.IsEmpty())
- output_size_in_pixel = result->size();
- else
- output_size_in_pixel = dst_size_in_pixel;
-
- if (result->HasTexture()) {
- // GPU-accelerated path
- PrepareTextureCopyOutputResult(output_size_in_pixel, color_type, callback,
- std::move(result));
- return;
- }
-
- DCHECK(result->HasBitmap());
- // Software path
- PrepareBitmapCopyOutputResult(output_size_in_pixel, color_type, callback,
- std::move(result));
-}
-
-static void CopyFromCompositingSurfaceFinished(
- const ReadbackRequestCallback& callback,
- scoped_ptr<cc::SingleReleaseCallback> release_callback,
- scoped_ptr<SkBitmap> bitmap,
- scoped_ptr<SkAutoLockPixels> bitmap_pixels_lock,
- bool result) {
- bitmap_pixels_lock.reset();
-
- gpu::SyncToken sync_token;
- if (result) {
- GLHelper* gl_helper = ImageTransportFactory::GetInstance()->GetGLHelper();
- if (gl_helper)
- gl_helper->GenerateSyncToken(&sync_token);
- }
- const bool lost_resource = !sync_token.HasData();
- release_callback->Run(sync_token, lost_resource);
-
- callback.Run(*bitmap,
- result ? content::READBACK_SUCCESS : content::READBACK_FAILED);
-}
-
-// static
-void DelegatedFrameHost::PrepareTextureCopyOutputResult(
- const gfx::Size& dst_size_in_pixel,
- const SkColorType color_type,
- const ReadbackRequestCallback& callback,
- scoped_ptr<cc::CopyOutputResult> result) {
- DCHECK(result->HasTexture());
- base::ScopedClosureRunner scoped_callback_runner(
- base::Bind(callback, SkBitmap(), content::READBACK_FAILED));
-
- // TODO(siva.gunturi): We should be able to validate the format here using
- // GLHelper::IsReadbackConfigSupported before we processs the result.
- // See crbug.com/415682 and crbug.com/415131.
- scoped_ptr<SkBitmap> bitmap(new SkBitmap);
- if (!bitmap->tryAllocPixels(SkImageInfo::Make(
- dst_size_in_pixel.width(), dst_size_in_pixel.height(), color_type,
- kOpaque_SkAlphaType))) {
- scoped_callback_runner.Reset(base::Bind(
- callback, SkBitmap(), content::READBACK_BITMAP_ALLOCATION_FAILURE));
- return;
- }
-
- ImageTransportFactory* factory = ImageTransportFactory::GetInstance();
- GLHelper* gl_helper = factory->GetGLHelper();
- if (!gl_helper)
- return;
-
- scoped_ptr<SkAutoLockPixels> bitmap_pixels_lock(
- new SkAutoLockPixels(*bitmap));
- uint8_t* pixels = static_cast<uint8_t*>(bitmap->getPixels());
-
- cc::TextureMailbox texture_mailbox;
- scoped_ptr<cc::SingleReleaseCallback> release_callback;
- result->TakeTexture(&texture_mailbox, &release_callback);
- DCHECK(texture_mailbox.IsTexture());
-
- ignore_result(scoped_callback_runner.Release());
-
- gl_helper->CropScaleReadbackAndCleanMailbox(
- texture_mailbox.mailbox(), texture_mailbox.sync_token(), result->size(),
- gfx::Rect(result->size()), dst_size_in_pixel, pixels, color_type,
- base::Bind(&CopyFromCompositingSurfaceFinished, callback,
- base::Passed(&release_callback), base::Passed(&bitmap),
- base::Passed(&bitmap_pixels_lock)),
- GLHelper::SCALER_QUALITY_GOOD);
-}
-
-// static
-void DelegatedFrameHost::PrepareBitmapCopyOutputResult(
- const gfx::Size& dst_size_in_pixel,
- const SkColorType preferred_color_type,
- const ReadbackRequestCallback& callback,
- scoped_ptr<cc::CopyOutputResult> result) {
- SkColorType color_type = preferred_color_type;
- if (color_type != kN32_SkColorType && color_type != kAlpha_8_SkColorType) {
- // Switch back to default colortype if format not supported.
- color_type = kN32_SkColorType;
- }
- DCHECK(result->HasBitmap());
- scoped_ptr<SkBitmap> source = result->TakeBitmap();
- DCHECK(source);
- SkBitmap scaled_bitmap;
- if (source->width() != dst_size_in_pixel.width() ||
- source->height() != dst_size_in_pixel.height()) {
- scaled_bitmap =
- skia::ImageOperations::Resize(*source,
- skia::ImageOperations::RESIZE_BEST,
- dst_size_in_pixel.width(),
- dst_size_in_pixel.height());
- } else {
- scaled_bitmap = *source;
- }
- if (color_type == kN32_SkColorType) {
- DCHECK_EQ(scaled_bitmap.colorType(), kN32_SkColorType);
- callback.Run(scaled_bitmap, READBACK_SUCCESS);
- return;
- }
- DCHECK_EQ(color_type, kAlpha_8_SkColorType);
- // The software path currently always returns N32 bitmap regardless of the
- // |color_type| we ask for.
- DCHECK_EQ(scaled_bitmap.colorType(), kN32_SkColorType);
- // Paint |scaledBitmap| to alpha-only |grayscale_bitmap|.
- SkBitmap grayscale_bitmap;
- bool success = grayscale_bitmap.tryAllocPixels(
- SkImageInfo::MakeA8(scaled_bitmap.width(), scaled_bitmap.height()));
- if (!success) {
- callback.Run(SkBitmap(), content::READBACK_BITMAP_ALLOCATION_FAILURE);
- return;
- }
- SkCanvas canvas(grayscale_bitmap);
- SkPaint paint;
- skia::RefPtr<SkColorFilter> filter =
- skia::AdoptRef(SkLumaColorFilter::Create());
- paint.setColorFilter(filter.get());
- canvas.drawBitmap(scaled_bitmap, SkIntToScalar(0), SkIntToScalar(0), &paint);
- callback.Run(grayscale_bitmap, READBACK_SUCCESS);
-}
-
-// static
void DelegatedFrameHost::ReturnSubscriberTexture(
base::WeakPtr<DelegatedFrameHost> dfh,
scoped_refptr<OwnedMailbox> subscriber_texture,
@@ -979,11 +830,12 @@ void DelegatedFrameHost::LockResources() {
void DelegatedFrameHost::RequestCopyOfOutput(
scoped_ptr<cc::CopyOutputRequest> request) {
- if (!request_copy_of_output_callback_for_testing_.is_null())
+ if (!request_copy_of_output_callback_for_testing_.is_null()) {
request_copy_of_output_callback_for_testing_.Run(std::move(request));
- else
+ } else {
client_->DelegatedFrameHostGetLayer()->RequestCopyOfOutput(
std::move(request));
+ }
}
void DelegatedFrameHost::UnlockResources() {
diff --git a/content/browser/compositor/delegated_frame_host.h b/content/browser/compositor/delegated_frame_host.h
index 16a11b7..558d178 100644
--- a/content/browser/compositor/delegated_frame_host.h
+++ b/content/browser/compositor/delegated_frame_host.h
@@ -211,21 +211,6 @@ class CONTENT_EXPORT DelegatedFrameHost
// Called after async thumbnailer task completes. Scales and crops the result
// of the copy.
- static void CopyFromCompositingSurfaceHasResult(
- const gfx::Size& dst_size_in_pixel,
- const SkColorType color_type,
- const ReadbackRequestCallback& callback,
- scoped_ptr<cc::CopyOutputResult> result);
- static void PrepareTextureCopyOutputResult(
- const gfx::Size& dst_size_in_pixel,
- const SkColorType color_type,
- const ReadbackRequestCallback& callback,
- scoped_ptr<cc::CopyOutputResult> result);
- static void PrepareBitmapCopyOutputResult(
- const gfx::Size& dst_size_in_pixel,
- const SkColorType color_type,
- const ReadbackRequestCallback& callback,
- scoped_ptr<cc::CopyOutputResult> result);
static void CopyFromCompositingSurfaceHasResultForVideo(
base::WeakPtr<DelegatedFrameHost> rwhva,
scoped_refptr<OwnedMailbox> subscriber_texture,
diff --git a/content/browser/compositor/surface_utils.cc b/content/browser/compositor/surface_utils.cc
index 1049ba2..ad4ce98 100644
--- a/content/browser/compositor/surface_utils.cc
+++ b/content/browser/compositor/surface_utils.cc
@@ -4,8 +4,20 @@
#include "content/browser/compositor/surface_utils.h"
+#include "base/callback_helpers.h"
+#include "base/memory/ref_counted.h"
#include "build/build_config.h"
+#include "cc/output/copy_output_result.h"
+#include "cc/resources/single_release_callback.h"
#include "cc/surfaces/surface_id_allocator.h"
+#include "content/common/gpu/client/gl_helper.h"
+#include "skia/ext/image_operations.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColorFilter.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/effects/SkLumaColorFilter.h"
+#include "ui/gfx/geometry/rect.h"
#if defined(OS_ANDROID)
#include "content/browser/renderer_host/compositor_impl_android.h"
@@ -14,6 +26,139 @@
#include "ui/compositor/compositor.h"
#endif
+namespace {
+
+#if !defined(OS_ANDROID) || defined(USE_AURA)
+void CopyFromCompositingSurfaceFinished(
+ const content::ReadbackRequestCallback& callback,
+ scoped_ptr<cc::SingleReleaseCallback> release_callback,
+ scoped_ptr<SkBitmap> bitmap,
+ scoped_ptr<SkAutoLockPixels> bitmap_pixels_lock,
+ bool result) {
+ bitmap_pixels_lock.reset();
+
+ gpu::SyncToken sync_token;
+ if (result) {
+ content::GLHelper* gl_helper =
+ content::ImageTransportFactory::GetInstance()->GetGLHelper();
+ if (gl_helper)
+ gl_helper->GenerateSyncToken(&sync_token);
+ }
+ const bool lost_resource = !sync_token.HasData();
+ release_callback->Run(sync_token, lost_resource);
+
+ callback.Run(*bitmap,
+ result ? content::READBACK_SUCCESS : content::READBACK_FAILED);
+}
+#endif
+
+// TODO(wjmaclean): There is significant overlap between
+// PrepareTextureCopyOutputResult and CopyFromCompositingSurfaceFinished in
+// this file, and the versions in RenderWidgetHostViewAndroid. They should
+// be merged. See https://crbug.com/582955
+void PrepareTextureCopyOutputResult(
+ const gfx::Size& dst_size_in_pixel,
+ const SkColorType color_type,
+ const content::ReadbackRequestCallback& callback,
+ scoped_ptr<cc::CopyOutputResult> result) {
+#if defined(OS_ANDROID) && !defined(USE_AURA)
+ // TODO(wjmaclean): See if there's an equivalent pathway for Android and
+ // implement it here.
+ callback.Run(SkBitmap(), content::READBACK_FAILED);
+#else
+ DCHECK(result->HasTexture());
+ base::ScopedClosureRunner scoped_callback_runner(
+ base::Bind(callback, SkBitmap(), content::READBACK_FAILED));
+
+ // TODO(siva.gunturi): We should be able to validate the format here using
+ // GLHelper::IsReadbackConfigSupported before we processs the result.
+ // See crbug.com/415682 and crbug.com/415131.
+ scoped_ptr<SkBitmap> bitmap(new SkBitmap);
+ if (!bitmap->tryAllocPixels(SkImageInfo::Make(
+ dst_size_in_pixel.width(), dst_size_in_pixel.height(), color_type,
+ kOpaque_SkAlphaType))) {
+ scoped_callback_runner.Reset(base::Bind(
+ callback, SkBitmap(), content::READBACK_BITMAP_ALLOCATION_FAILURE));
+ return;
+ }
+
+ content::ImageTransportFactory* factory =
+ content::ImageTransportFactory::GetInstance();
+ content::GLHelper* gl_helper = factory->GetGLHelper();
+ if (!gl_helper)
+ return;
+
+ scoped_ptr<SkAutoLockPixels> bitmap_pixels_lock(
+ new SkAutoLockPixels(*bitmap));
+ uint8_t* pixels = static_cast<uint8_t*>(bitmap->getPixels());
+
+ cc::TextureMailbox texture_mailbox;
+ scoped_ptr<cc::SingleReleaseCallback> release_callback;
+ result->TakeTexture(&texture_mailbox, &release_callback);
+ DCHECK(texture_mailbox.IsTexture());
+
+ ignore_result(scoped_callback_runner.Release());
+
+ gl_helper->CropScaleReadbackAndCleanMailbox(
+ texture_mailbox.mailbox(), texture_mailbox.sync_token(), result->size(),
+ gfx::Rect(result->size()), dst_size_in_pixel, pixels, color_type,
+ base::Bind(&CopyFromCompositingSurfaceFinished, callback,
+ base::Passed(&release_callback), base::Passed(&bitmap),
+ base::Passed(&bitmap_pixels_lock)),
+ content::GLHelper::SCALER_QUALITY_GOOD);
+#endif
+}
+
+void PrepareBitmapCopyOutputResult(
+ const gfx::Size& dst_size_in_pixel,
+ const SkColorType preferred_color_type,
+ const content::ReadbackRequestCallback& callback,
+ scoped_ptr<cc::CopyOutputResult> result) {
+ SkColorType color_type = preferred_color_type;
+ if (color_type != kN32_SkColorType && color_type != kAlpha_8_SkColorType) {
+ // Switch back to default colortype if format not supported.
+ color_type = kN32_SkColorType;
+ }
+ DCHECK(result->HasBitmap());
+ scoped_ptr<SkBitmap> source = result->TakeBitmap();
+ DCHECK(source);
+ SkBitmap scaled_bitmap;
+ if (source->width() != dst_size_in_pixel.width() ||
+ source->height() != dst_size_in_pixel.height()) {
+ scaled_bitmap = skia::ImageOperations::Resize(
+ *source, skia::ImageOperations::RESIZE_BEST, dst_size_in_pixel.width(),
+ dst_size_in_pixel.height());
+ } else {
+ scaled_bitmap = *source;
+ }
+ if (color_type == kN32_SkColorType) {
+ DCHECK_EQ(scaled_bitmap.colorType(), kN32_SkColorType);
+ callback.Run(scaled_bitmap, content::READBACK_SUCCESS);
+ return;
+ }
+ DCHECK_EQ(color_type, kAlpha_8_SkColorType);
+ // The software path currently always returns N32 bitmap regardless of the
+ // |color_type| we ask for.
+ DCHECK_EQ(scaled_bitmap.colorType(), kN32_SkColorType);
+ // Paint |scaledBitmap| to alpha-only |grayscale_bitmap|.
+ SkBitmap grayscale_bitmap;
+ bool success = grayscale_bitmap.tryAllocPixels(
+ SkImageInfo::MakeA8(scaled_bitmap.width(), scaled_bitmap.height()));
+ if (!success) {
+ callback.Run(SkBitmap(), content::READBACK_BITMAP_ALLOCATION_FAILURE);
+ return;
+ }
+ SkCanvas canvas(grayscale_bitmap);
+ SkPaint paint;
+ skia::RefPtr<SkColorFilter> filter =
+ skia::AdoptRef(SkLumaColorFilter::Create());
+ paint.setColorFilter(filter.get());
+ canvas.drawBitmap(scaled_bitmap, SkIntToScalar(0), SkIntToScalar(0), &paint);
+ callback.Run(grayscale_bitmap, content::READBACK_SUCCESS);
+}
+
+} // namespace
+
namespace content {
scoped_ptr<cc::SurfaceIdAllocator> CreateSurfaceIdAllocator() {
@@ -34,4 +179,33 @@ cc::SurfaceManager* GetSurfaceManager() {
#endif
}
+void CopyFromCompositingSurfaceHasResult(
+ const gfx::Size& dst_size_in_pixel,
+ const SkColorType color_type,
+ const ReadbackRequestCallback& callback,
+ scoped_ptr<cc::CopyOutputResult> result) {
+ if (result->IsEmpty() || result->size().IsEmpty()) {
+ callback.Run(SkBitmap(), READBACK_FAILED);
+ return;
+ }
+
+ gfx::Size output_size_in_pixel;
+ if (dst_size_in_pixel.IsEmpty())
+ output_size_in_pixel = result->size();
+ else
+ output_size_in_pixel = dst_size_in_pixel;
+
+ if (result->HasTexture()) {
+ // GPU-accelerated path
+ PrepareTextureCopyOutputResult(output_size_in_pixel, color_type, callback,
+ std::move(result));
+ return;
+ }
+
+ DCHECK(result->HasBitmap());
+ // Software path
+ PrepareBitmapCopyOutputResult(output_size_in_pixel, color_type, callback,
+ std::move(result));
+}
+
} // namespace content
diff --git a/content/browser/compositor/surface_utils.h b/content/browser/compositor/surface_utils.h
index b56fef2..00d0fd7 100644
--- a/content/browser/compositor/surface_utils.h
+++ b/content/browser/compositor/surface_utils.h
@@ -6,8 +6,12 @@
#define CONTENT_BROWSER_COMPOSITOR_SURFACE_UTILS_H_
#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/readback_types.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
+#include "ui/gfx/geometry/size.h"
namespace cc {
+class CopyOutputResult;
class SurfaceIdAllocator;
class SurfaceManager;
} // namespace cc
@@ -18,6 +22,12 @@ scoped_ptr<cc::SurfaceIdAllocator> CreateSurfaceIdAllocator();
cc::SurfaceManager* GetSurfaceManager();
+void CopyFromCompositingSurfaceHasResult(
+ const gfx::Size& dst_size_in_pixel,
+ const SkColorType color_type,
+ const ReadbackRequestCallback& callback,
+ scoped_ptr<cc::CopyOutputResult> result);
+
} // namespace content
#endif // CONTENT_BROWSER_COMPOSITOR_SURFACE_UTILS_H_
diff --git a/content/browser/frame_host/render_widget_host_view_child_frame.cc b/content/browser/frame_host/render_widget_host_view_child_frame.cc
index b5de3a5..ee65594 100644
--- a/content/browser/frame_host/render_widget_host_view_child_frame.cc
+++ b/content/browser/frame_host/render_widget_host_view_child_frame.cc
@@ -9,6 +9,8 @@
#include <vector>
#include "build/build_config.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/output/copy_output_result.h"
#include "cc/surfaces/surface.h"
#include "cc/surfaces/surface_factory.h"
#include "cc/surfaces/surface_manager.h"
@@ -84,8 +86,7 @@ bool RenderWidgetHostViewChildFrame::HasFocus() const {
}
bool RenderWidgetHostViewChildFrame::IsSurfaceAvailableForCopy() const {
- NOTIMPLEMENTED();
- return false;
+ return surface_factory_ && !surface_id_.is_null();
}
void RenderWidgetHostViewChildFrame::Show() {
@@ -321,6 +322,17 @@ void RenderWidgetHostViewChildFrame::OnSwapCompositorFrame(
DCHECK_LT(ack_pending_count_, 1000U);
surface_factory_->SubmitCompositorFrame(surface_id_, std::move(frame),
ack_callback);
+
+ ProcessFrameSwappedCallbacks();
+}
+
+void RenderWidgetHostViewChildFrame::ProcessFrameSwappedCallbacks() {
+ // We only use callbacks once, therefore we make a new list for registration
+ // before we start, and discard the old list entries when we are done.
+ FrameSwappedCallbackList process_callbacks;
+ process_callbacks.swap(frame_swapped_callbacks_);
+ for (scoped_ptr<base::Closure>& callback : process_callbacks)
+ callback->Run();
}
void RenderWidgetHostViewChildFrame::GetScreenInfo(
@@ -431,12 +443,44 @@ bool RenderWidgetHostViewChildFrame::PostProcessEventForPluginIme(
}
#endif // defined(OS_MACOSX)
+void RenderWidgetHostViewChildFrame::RegisterFrameSwappedCallback(
+ scoped_ptr<base::Closure> callback) {
+ frame_swapped_callbacks_.push_back(std::move(callback));
+}
+
void RenderWidgetHostViewChildFrame::CopyFromCompositingSurface(
- const gfx::Rect& /* src_subrect */,
- const gfx::Size& /* dst_size */,
+ const gfx::Rect& src_subrect,
+ const gfx::Size& output_size,
const ReadbackRequestCallback& callback,
- const SkColorType /* preferred_color_type */) {
- callback.Run(SkBitmap(), READBACK_FAILED);
+ const SkColorType preferred_color_type) {
+ if (!IsSurfaceAvailableForCopy()) {
+ // Defer submitting the copy request until after a frame is drawn, at which
+ // point we should be guaranteed that the surface is available.
+ RegisterFrameSwappedCallback(make_scoped_ptr(new base::Closure(base::Bind(
+ &RenderWidgetHostViewChildFrame::SubmitSurfaceCopyRequest, AsWeakPtr(),
+ src_subrect, output_size, callback, preferred_color_type))));
+ return;
+ }
+
+ SubmitSurfaceCopyRequest(src_subrect, output_size, callback,
+ preferred_color_type);
+}
+
+void RenderWidgetHostViewChildFrame::SubmitSurfaceCopyRequest(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& output_size,
+ const ReadbackRequestCallback& callback,
+ const SkColorType preferred_color_type) {
+ DCHECK(IsSurfaceAvailableForCopy());
+
+ scoped_ptr<cc::CopyOutputRequest> request =
+ cc::CopyOutputRequest::CreateRequest(
+ base::Bind(&CopyFromCompositingSurfaceHasResult, output_size,
+ preferred_color_type, callback));
+ if (!src_subrect.IsEmpty())
+ request->set_area(src_subrect);
+
+ surface_factory_->RequestCopyOfSurface(surface_id_, std::move(request));
}
void RenderWidgetHostViewChildFrame::CopyFromCompositingSurfaceToVideoFrame(
diff --git a/content/browser/frame_host/render_widget_host_view_child_frame.h b/content/browser/frame_host/render_widget_host_view_child_frame.h
index c77b216..817bc2e 100644
--- a/content/browser/frame_host/render_widget_host_view_child_frame.h
+++ b/content/browser/frame_host/render_widget_host_view_child_frame.h
@@ -8,8 +8,10 @@
#include <stddef.h>
#include <stdint.h>
+#include <deque>
#include <vector>
+#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "build/build_config.h"
@@ -58,6 +60,15 @@ class CONTENT_EXPORT RenderWidgetHostViewChildFrame
frame_connector_ = frame_connector;
}
+ // This functions registers single-use callbacks that want to be notified when
+ // the next frame is swapped. The callback is triggered by
+ // OnSwapCompositorFrame, which is the appropriate time to request pixel
+ // readback for the frame that is about to be drawn. Once called, the callback
+ // pointer is released.
+ // TODO(wjmaclean): We should consider making this available in other view
+ // types, such as RenderWidgetHostViewAura.
+ void RegisterFrameSwappedCallback(scoped_ptr<base::Closure> callback);
+
// RenderWidgetHostView implementation.
void InitAsChild(gfx::NativeView parent_view) override;
RenderWidgetHost* GetRenderWidgetHost() const override;
@@ -175,6 +186,8 @@ class CONTENT_EXPORT RenderWidgetHostViewChildFrame
// Clears current compositor surface, if one is in use.
void ClearCompositorSurfaceIfNecessary();
+ void ProcessFrameSwappedCallbacks();
+
// The last scroll offset of the view.
gfx::Vector2dF last_scroll_offset_;
@@ -205,6 +218,16 @@ class CONTENT_EXPORT RenderWidgetHostViewChildFrame
}
private:
+ void SubmitSurfaceCopyRequest(const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ const ReadbackRequestCallback& callback,
+ const SkColorType preferred_color_type);
+
+ using FrameSwappedCallbackList = std::deque<scoped_ptr<base::Closure>>;
+ // Since frame-drawn callbacks are "fire once", we use std::deque to make
+ // it convenient to swap() when processing the list.
+ FrameSwappedCallbackList frame_swapped_callbacks_;
+
base::WeakPtrFactory<RenderWidgetHostViewChildFrame> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewChildFrame);
};
diff --git a/content/browser/frame_host/render_widget_host_view_guest.cc b/content/browser/frame_host/render_widget_host_view_guest.cc
index b3b8d68..a080567 100644
--- a/content/browser/frame_host/render_widget_host_view_guest.cc
+++ b/content/browser/frame_host/render_widget_host_view_guest.cc
@@ -300,6 +300,8 @@ void RenderWidgetHostViewGuest::OnSwapCompositorFrame(
DCHECK(ack_pending_count_ < 1000);
surface_factory_->SubmitCompositorFrame(surface_id_, std::move(frame),
ack_callback);
+
+ ProcessFrameSwappedCallbacks();
}
bool RenderWidgetHostViewGuest::OnMessageReceived(const IPC::Message& msg) {
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index 60e92a8..461708d 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -1917,6 +1917,11 @@ void RenderWidgetHostViewAndroid::
callback, std::move(result));
}
+// TODO(wjmaclean): There is significant overlap between
+// PrepareTextureCopyOutputResult and CopyFromCompositingSurfaceFinished in
+// this file, and the versions in surface_utils.cc. They should
+// be merged. See https://crbug.com/582955
+
// static
void RenderWidgetHostViewAndroid::PrepareTextureCopyOutputResult(
const gfx::Size& dst_size_in_pixel,
diff --git a/extensions/browser/api/guest_view/extension_view/extension_view_internal_api.h b/extensions/browser/api/guest_view/extension_view/extension_view_internal_api.h
index 9e77cbd..1899d42 100644
--- a/extensions/browser/api/guest_view/extension_view/extension_view_internal_api.h
+++ b/extensions/browser/api/guest_view/extension_view/extension_view_internal_api.h
@@ -6,7 +6,6 @@
#define EXTENSIONS_BROWSER_API_EXTENSION_VIEW_EXTENSION_VIEW_INTERNAL_API_H_
#include "base/macros.h"
-#include "extensions/browser/api/capture_web_contents_function.h"
#include "extensions/browser/api/execute_code_function.h"
#include "extensions/browser/extension_function.h"
#include "extensions/browser/guest_view/extension_view/extension_view_guest.h"
diff --git a/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc b/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc
index bba531d..3e3a8ed 100644
--- a/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc
+++ b/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc
@@ -259,6 +259,62 @@ bool WebViewInternalExtensionFunction::RunAsync() {
return RunAsyncSafe(guest);
}
+bool WebViewInternalCaptureVisibleRegionFunction::RunAsyncSafe(
+ WebViewGuest* guest) {
+ using api::extension_types::ImageDetails;
+
+ scoped_ptr<web_view_internal::CaptureVisibleRegion::Params> params(
+ web_view_internal::CaptureVisibleRegion::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ scoped_ptr<ImageDetails> image_details;
+ if (args_->GetSize() > 1) {
+ base::Value* spec = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_->Get(1, &spec) && spec);
+ image_details = ImageDetails::FromValue(*spec);
+ }
+
+ return CaptureAsync(guest->web_contents(), image_details.get(),
+ base::Bind(&WebViewInternalCaptureVisibleRegionFunction::
+ CopyFromBackingStoreComplete,
+ this));
+}
+bool WebViewInternalCaptureVisibleRegionFunction::IsScreenshotEnabled() {
+ // TODO(wjmaclean): Is it ok to always return true here?
+ return true;
+}
+
+void WebViewInternalCaptureVisibleRegionFunction::OnCaptureSuccess(
+ const SkBitmap& bitmap) {
+ std::string base64_result;
+ if (!EncodeBitmap(bitmap, &base64_result)) {
+ OnCaptureFailure(FAILURE_REASON_ENCODING_FAILED);
+ return;
+ }
+
+ SetResult(new base::StringValue(base64_result));
+ SendResponse(true);
+}
+
+void WebViewInternalCaptureVisibleRegionFunction::OnCaptureFailure(
+ FailureReason reason) {
+ const char* reason_description = "internal error";
+ switch (reason) {
+ case FAILURE_REASON_UNKNOWN:
+ reason_description = "unknown error";
+ break;
+ case FAILURE_REASON_ENCODING_FAILED:
+ reason_description = "encoding failed";
+ break;
+ case FAILURE_REASON_VIEW_INVISIBLE:
+ reason_description = "view is invisible";
+ break;
+ }
+ error_ = ErrorUtils::FormatErrorMessage("Failed to capture webview: *",
+ reason_description);
+ SendResponse(false);
+}
+
bool WebViewInternalNavigateFunction::RunAsyncSafe(WebViewGuest* guest) {
scoped_ptr<web_view_internal::Navigate::Params> params(
web_view_internal::Navigate::Params::Create(*args_));
diff --git a/extensions/browser/api/guest_view/web_view/web_view_internal_api.h b/extensions/browser/api/guest_view/web_view/web_view_internal_api.h
index dcdb828..388491e 100644
--- a/extensions/browser/api/guest_view/web_view/web_view_internal_api.h
+++ b/extensions/browser/api/guest_view/web_view/web_view_internal_api.h
@@ -8,8 +8,8 @@
#include <stdint.h>
#include "base/macros.h"
-#include "extensions/browser/api/capture_web_contents_function.h"
#include "extensions/browser/api/execute_code_function.h"
+#include "extensions/browser/api/web_contents_capture_client.h"
#include "extensions/browser/extension_function.h"
#include "extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.h"
#include "extensions/browser/guest_view/web_view/web_view_guest.h"
@@ -37,6 +37,29 @@ class WebViewInternalExtensionFunction : public AsyncExtensionFunction {
virtual bool RunAsyncSafe(WebViewGuest* guest) = 0;
};
+class WebViewInternalCaptureVisibleRegionFunction
+ : public WebViewInternalExtensionFunction,
+ public WebContentsCaptureClient {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.captureVisibleRegion",
+ WEBVIEWINTERNAL_CAPTUREVISIBLEREGION);
+ WebViewInternalCaptureVisibleRegionFunction() {}
+
+ protected:
+ ~WebViewInternalCaptureVisibleRegionFunction() override {}
+
+ private:
+ // WebViewInternalExtensionFunction implementation.
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ // extensions::WebContentsCaptureClient:
+ bool IsScreenshotEnabled() override;
+ void OnCaptureSuccess(const SkBitmap& bitmap) override;
+ void OnCaptureFailure(FailureReason reason) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalCaptureVisibleRegionFunction);
+};
+
class WebViewInternalNavigateFunction
: public WebViewInternalExtensionFunction {
public:
diff --git a/extensions/browser/api/capture_web_contents_function.cc b/extensions/browser/api/web_contents_capture_client.cc
index 77c8a3e..78c3760 100644
--- a/extensions/browser/api/capture_web_contents_function.cc
+++ b/extensions/browser/api/web_contents_capture_client.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "extensions/browser/api/capture_web_contents_function.h"
+#include "extensions/browser/api/web_contents_capture_client.h"
#include "base/base64.h"
#include "base/strings/stringprintf.h"
@@ -25,28 +25,14 @@ namespace extensions {
using api::extension_types::ImageDetails;
-bool CaptureWebContentsFunction::HasPermission() {
- return true;
-}
-
-bool CaptureWebContentsFunction::RunAsync() {
- EXTENSION_FUNCTION_VALIDATE(args_);
-
- context_id_ = extension_misc::kCurrentWindowId;
- args_->GetInteger(0, &context_id_);
-
- scoped_ptr<ImageDetails> image_details;
- if (args_->GetSize() > 1) {
- base::Value* spec = NULL;
- EXTENSION_FUNCTION_VALIDATE(args_->Get(1, &spec) && spec);
- image_details = ImageDetails::FromValue(*spec);
- }
-
- if (!IsScreenshotEnabled())
+bool WebContentsCaptureClient::CaptureAsync(
+ WebContents* web_contents,
+ const ImageDetails* image_details,
+ const content::ReadbackRequestCallback callback) {
+ if (!web_contents)
return false;
- WebContents* contents = GetWebContentsForID(context_id_);
- if (!contents)
+ if (!IsScreenshotEnabled())
return false;
// The default format and quality setting used when encoding jpegs.
@@ -65,7 +51,7 @@ bool CaptureWebContentsFunction::RunAsync() {
}
// TODO(miu): Account for fullscreen render widget? http://crbug.com/419878
- RenderWidgetHostView* const view = contents->GetRenderWidgetHostView();
+ RenderWidgetHostView* const view = web_contents->GetRenderWidgetHostView();
RenderWidgetHost* const host = view ? view->GetRenderWidgetHost() : nullptr;
if (!view || !host) {
OnCaptureFailure(FAILURE_REASON_VIEW_INVISIBLE);
@@ -84,26 +70,41 @@ bool CaptureWebContentsFunction::RunAsync() {
if (scale > 1.0f)
bitmap_size = gfx::ScaleToCeiledSize(view_size, scale);
- host->CopyFromBackingStore(
- gfx::Rect(view_size),
- bitmap_size,
- base::Bind(&CaptureWebContentsFunction::CopyFromBackingStoreComplete,
- this),
- kN32_SkColorType);
+ host->CopyFromBackingStore(gfx::Rect(view_size), bitmap_size, callback,
+ kN32_SkColorType);
return true;
}
-void CaptureWebContentsFunction::CopyFromBackingStoreComplete(
+void WebContentsCaptureClient::CopyFromBackingStoreComplete(
const SkBitmap& bitmap,
content::ReadbackResponse response) {
if (response == content::READBACK_SUCCESS) {
OnCaptureSuccess(bitmap);
return;
}
+ // TODO(wjmaclean): Improve error reporting. Why aren't we passing more
+ // information here?
+ std::string reason;
+ switch (response) {
+ case content::READBACK_FAILED:
+ reason = "READBACK_FAILED";
+ break;
+ case content::READBACK_SURFACE_UNAVAILABLE:
+ reason = "READBACK_SURFACE_UNAVAILABLE";
+ break;
+ case content::READBACK_BITMAP_ALLOCATION_FAILURE:
+ reason = "READBACK_BITMAP_ALLOCATION_FAILURE";
+ break;
+ default:
+ reason = "<unknown>";
+ }
OnCaptureFailure(FAILURE_REASON_UNKNOWN);
}
-void CaptureWebContentsFunction::OnCaptureSuccess(const SkBitmap& bitmap) {
+// TODO(wjmaclean) can this be static?
+bool WebContentsCaptureClient::EncodeBitmap(const SkBitmap& bitmap,
+ std::string* base64_result) {
+ DCHECK(base64_result);
std::vector<unsigned char> data;
SkAutoLockPixels screen_capture_lock(bitmap);
bool encoded = false;
@@ -112,12 +113,8 @@ void CaptureWebContentsFunction::OnCaptureSuccess(const SkBitmap& bitmap) {
case api::extension_types::IMAGE_FORMAT_JPEG:
encoded = gfx::JPEGCodec::Encode(
reinterpret_cast<unsigned char*>(bitmap.getAddr32(0, 0)),
- gfx::JPEGCodec::FORMAT_SkBitmap,
- bitmap.width(),
- bitmap.height(),
- static_cast<int>(bitmap.rowBytes()),
- image_quality_,
- &data);
+ gfx::JPEGCodec::FORMAT_SkBitmap, bitmap.width(), bitmap.height(),
+ static_cast<int>(bitmap.rowBytes()), image_quality_, &data);
mime_type = kMimeTypeJpeg;
break;
case api::extension_types::IMAGE_FORMAT_PNG:
@@ -131,20 +128,17 @@ void CaptureWebContentsFunction::OnCaptureSuccess(const SkBitmap& bitmap) {
NOTREACHED() << "Invalid image format.";
}
- if (!encoded) {
- OnCaptureFailure(FAILURE_REASON_ENCODING_FAILED);
- return;
- }
+ if (!encoded)
+ return false;
- std::string base64_result;
base::StringPiece stream_as_string(reinterpret_cast<const char*>(data.data()),
data.size());
- base::Base64Encode(stream_as_string, &base64_result);
- base64_result.insert(
+ base::Base64Encode(stream_as_string, base64_result);
+ base64_result->insert(
0, base::StringPrintf("data:%s;base64,", mime_type.c_str()));
- SetResult(new base::StringValue(base64_result));
- SendResponse(true);
+
+ return true;
}
} // namespace extensions
diff --git a/extensions/browser/api/capture_web_contents_function.h b/extensions/browser/api/web_contents_capture_client.h
index 53f78f7..4107ee4 100644
--- a/extensions/browser/api/capture_web_contents_function.h
+++ b/extensions/browser/api/web_contents_capture_client.h
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef EXTENSIONS_BROWSER_API_CAPTURE_WEB_CONTENTS_FUNCTION_H_
-#define EXTENSIONS_BROWSER_API_CAPTURE_WEB_CONTENTS_FUNCTION_H_
+#ifndef EXTENSIONS_BROWSER_API_WEB_CONTENTS_CAPTURE_CLIENT_H_
+#define EXTENSIONS_BROWSER_API_WEB_CONTENTS_CAPTURE_CLIENT_H_
#include "base/macros.h"
#include "content/public/browser/readback_types.h"
@@ -18,50 +18,41 @@ class WebContents;
namespace extensions {
-// Base class for capturing visibile area of a WebContents.
+// Base class for capturing visible area of a WebContents.
// This is used by both webview.captureVisibleRegion and tabs.captureVisibleTab.
-class CaptureWebContentsFunction : public AsyncExtensionFunction {
+class WebContentsCaptureClient {
public:
- CaptureWebContentsFunction() {}
+ WebContentsCaptureClient() {}
protected:
- ~CaptureWebContentsFunction() override {}
-
- // ExtensionFunction implementation.
- bool HasPermission() override;
- bool RunAsync() override;
+ virtual ~WebContentsCaptureClient() {}
virtual bool IsScreenshotEnabled() = 0;
- virtual content::WebContents* GetWebContentsForID(int context_id) = 0;
enum FailureReason {
FAILURE_REASON_UNKNOWN,
FAILURE_REASON_ENCODING_FAILED,
FAILURE_REASON_VIEW_INVISIBLE
};
+ bool CaptureAsync(content::WebContents* web_contents,
+ const api::extension_types::ImageDetails* image_detail,
+ const content::ReadbackRequestCallback callback);
+ bool EncodeBitmap(const SkBitmap& bitmap, std::string* base64_result);
virtual void OnCaptureFailure(FailureReason reason) = 0;
-
- private:
-
+ virtual void OnCaptureSuccess(const SkBitmap& bitmap) = 0;
void CopyFromBackingStoreComplete(const SkBitmap& bitmap,
content::ReadbackResponse response);
- void OnCaptureSuccess(const SkBitmap& bitmap);
-
- // |context_id_| is the ID used to find the relevant WebContents. In the
- // |tabs.captureVisibleTab()| api, this represents the window-id, and in the
- // |webview.captureVisibleRegion()| api, this represents the instance-id of
- // the guest.
- int context_id_;
+ private:
// The format (JPEG vs PNG) of the resulting image. Set in RunAsync().
api::extension_types::ImageFormat image_format_;
// Quality setting to use when encoding jpegs. Set in RunAsync().
int image_quality_;
- DISALLOW_COPY_AND_ASSIGN(CaptureWebContentsFunction);
+ DISALLOW_COPY_AND_ASSIGN(WebContentsCaptureClient);
};
} // namespace extensions
-#endif // EXTENSIONS_BROWSER_API_CAPTURE_WEB_CONTENTS_FUNCTION_H_
+#endif // EXTENSIONS_BROWSER_API_WEB_CONTENTS_CAPTURE_CLIENT_H_
diff --git a/extensions/browser/guest_view/web_view/web_view_apitest.cc b/extensions/browser/guest_view/web_view/web_view_apitest.cc
index 9896e13..89bbf05 100644
--- a/extensions/browser/guest_view/web_view/web_view_apitest.cc
+++ b/extensions/browser/guest_view/web_view/web_view_apitest.cc
@@ -736,5 +736,8 @@ IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestWebViewInsideFrame) {
LaunchApp("web_view/inside_iframe");
}
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestCaptureVisibleRegion) {
+ RunTest("testCaptureVisibleRegion", "web_view/apitest");
+}
} // namespace extensions
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index b30775f..5e07269 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -460,6 +460,12 @@
"chrome://oobe/*"
]
}],
+ "webViewExperimentalInternal": [{
+ "internal": true,
+ "channel": "dev",
+ "dependencies": ["permission:webview"],
+ "contexts": ["blessed_extension"]
+ }],
"webViewRequest": [{
"dependencies": ["permission:webview"],
"contexts": ["blessed_extension"]
diff --git a/extensions/common/api/web_view_internal.json b/extensions/common/api/web_view_internal.json
index a33f500..94a6eb9 100644
--- a/extensions/common/api/web_view_internal.json
+++ b/extensions/common/api/web_view_internal.json
@@ -670,6 +670,33 @@
]
},
{
+ "name": "captureVisibleRegion",
+ "type": "function",
+ "description": "foo",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest <webview> process."
+ },
+ {
+ "$ref": "extensionTypes.ImageDetails",
+ "name": "options",
+ "optional": true
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {"type": "string",
+ "name": "dataUrl",
+ "description": "A data URL which encodes an image of the visible area of the captured tab. May be assigned to the 'src' property of an HTML Image element for display."
+ }
+ ]
+ }
+ ]
+ },
+ {
"name": "clearData",
"type": "function",
"description": "Clears various types of browsing data stored in a storage partition of a <webview>.",
diff --git a/extensions/extensions.gypi b/extensions/extensions.gypi
index 9128d49..4d285b8 100644
--- a/extensions/extensions.gypi
+++ b/extensions/extensions.gypi
@@ -272,8 +272,6 @@
'browser/api/bluetooth_socket/bluetooth_socket_api.h',
'browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc',
'browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h',
- 'browser/api/capture_web_contents_function.cc',
- 'browser/api/capture_web_contents_function.h',
'browser/api/cast_channel/cast_auth_ica.cc',
'browser/api/cast_channel/cast_auth_ica.h',
'browser/api/cast_channel/cast_auth_util.cc',
@@ -490,6 +488,8 @@
'browser/api/virtual_keyboard_private/virtual_keyboard_delegate.h',
'browser/api/virtual_keyboard_private/virtual_keyboard_private_api.cc',
'browser/api/virtual_keyboard_private/virtual_keyboard_private_api.h',
+ 'browser/api/web_contents_capture_client.cc',
+ 'browser/api/web_contents_capture_client.h',
'browser/api/web_request/form_data_parser.cc',
'browser/api/web_request/form_data_parser.h',
'browser/api/web_request/upload_data_presenter.cc',
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index 7ec76ad..a8a2b3e 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -644,6 +644,8 @@ std::vector<std::pair<std::string, int> > Dispatcher::GetJsResources() {
resources.push_back(std::make_pair("webViewEvents", IDR_WEB_VIEW_EVENTS_JS));
resources.push_back(std::make_pair("webViewInternal",
IDR_WEB_VIEW_INTERNAL_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("webViewExperimental", IDR_WEB_VIEW_EXPERIMENTAL_JS));
if (content::BrowserPluginGuestMode::UseCrossProcessFramesForGuests()) {
resources.push_back(std::make_pair("webViewIframe",
IDR_WEB_VIEW_IFRAME_JS));
@@ -1597,6 +1599,10 @@ void Dispatcher::RequireGuestViewModules(ScriptContext* context) {
module_system->Require("webView");
module_system->Require("webViewApiMethods");
module_system->Require("webViewAttributes");
+ if (context->GetAvailability("webViewExperimentalInternal")
+ .is_available()) {
+ module_system->Require("webViewExperimental");
+ }
if (content::BrowserPluginGuestMode::UseCrossProcessFramesForGuests()) {
module_system->Require("webViewIframe");
diff --git a/extensions/renderer/resources/extensions_renderer_resources.grd b/extensions/renderer/resources/extensions_renderer_resources.grd
index bad8e29..b4f5dad 100644
--- a/extensions/renderer/resources/extensions_renderer_resources.grd
+++ b/extensions/renderer/resources/extensions_renderer_resources.grd
@@ -61,6 +61,7 @@
<include name="IDR_WEB_VIEW_ATTRIBUTES_JS" file="guest_view/web_view/web_view_attributes.js" type="BINDATA" />
<include name="IDR_WEB_VIEW_CONSTANTS_JS" file="guest_view/web_view/web_view_constants.js" type="BINDATA" />
<include name="IDR_WEB_VIEW_EVENTS_JS" file="guest_view/web_view/web_view_events.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_EXPERIMENTAL_JS" file="guest_view/web_view/web_view_experimental.js" type="BINDATA" />
<include name="IDR_WEB_VIEW_IFRAME_JS" file="guest_view/web_view/web_view_iframe.js" type="BINDATA" />
<include name="IDR_WEB_VIEW_INTERNAL_CUSTOM_BINDINGS_JS" file="guest_view/web_view/web_view_internal.js" type="BINDATA" />
<include name="IDR_WEB_VIEW_JS" file="guest_view/web_view/web_view.js" type="BINDATA" />
diff --git a/extensions/renderer/resources/guest_view/web_view/web_view.js b/extensions/renderer/resources/guest_view/web_view/web_view.js
index 6dd4fae..b5d08c1 100644
--- a/extensions/renderer/resources/guest_view/web_view/web_view.js
+++ b/extensions/renderer/resources/guest_view/web_view/web_view.js
@@ -31,6 +31,10 @@ WebViewImpl.setupElement = function(proto) {
// Public-facing API methods.
var apiMethods = WebViewImpl.getApiMethods();
+ // Add the experimental API methods, if available.
+ var experimentalApiMethods = WebViewImpl.maybeGetExperimentalApiMethods();
+ apiMethods = $Array.concat(apiMethods, experimentalApiMethods);
+
// Create default implementations for undefined API methods.
var createDefaultApiMethod = function(m) {
return function(var_args) {
@@ -220,6 +224,11 @@ WebViewImpl.prototype.makeElementFullscreen = function() {
// Implemented when the ChromeWebView API is available.
WebViewImpl.prototype.maybeSetupContextMenus = function() {};
+// Implemented when the experimental WebView API is available.
+WebViewImpl.maybeGetExperimentalApiMethods = function() {
+ return [];
+};
+
GuestViewContainer.registerElement(WebViewImpl);
// Exports.
diff --git a/extensions/renderer/resources/guest_view/web_view/web_view_experimental.js b/extensions/renderer/resources/guest_view/web_view/web_view_experimental.js
new file mode 100644
index 0000000..96331df
--- /dev/null
+++ b/extensions/renderer/resources/guest_view/web_view/web_view_experimental.js
@@ -0,0 +1,24 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module implements experimental API for <webview>.
+// See web_view.js and web_view_api_methods.js for details.
+//
+// <webview> Experimental API is only available on canary and channels of
+// Chrome.
+
+var WebViewImpl = require('webView').WebViewImpl;
+var WebViewInternal = require('webViewInternal').WebViewInternal;
+
+// An array of <webview>'s experimental API methods. See |WEB_VIEW_API_METHODS|
+// in web_view_api_methods.js for more details.
+var WEB_VIEW_EXPERIMENTAL_API_METHODS = [
+ // Captures the visible region of the WebView contents into a bitmap.
+ 'captureVisibleRegion'
+];
+
+// Registers the experimantal WebVIew API when available.
+WebViewImpl.maybeGetExperimentalApiMethods = function() {
+ return WEB_VIEW_EXPERIMENTAL_API_METHODS;
+};
diff --git a/extensions/test/data/web_view/apitest/main.js b/extensions/test/data/web_view/apitest/main.js
index 983212c..3d6e50f 100644
--- a/extensions/test/data/web_view/apitest/main.js
+++ b/extensions/test/data/web_view/apitest/main.js
@@ -1685,6 +1685,36 @@ function testWebRequestAPIGoogleProperty() {
document.body.appendChild(webview);
}
+// This is a basic test to verify that image data is returned by
+// captureVisibleRegion().
+function testCaptureVisibleRegion() {
+ var webview = document.createElement('webview');
+ webview.setAttribute('src', 'data:text/html,webview test');
+
+ webview.addEventListener('loadstop', function(e) {
+ webview.captureVisibleRegion(
+ {},
+ function(imgdata) {
+ if (chrome.runtime.lastError) {
+ console.log(
+ 'webview.apitest.testCaptureVisibleRegion: ' +
+ chrome.runtime.lastError.message);
+ embedder.test.fail();
+ } else {
+ if (imgdata.indexOf('data:image/jpeg;base64') != 0) {
+ console_log('imgdata = ' + imgdata);
+ }
+ embedder.test.assertTrue(
+ imgdata.indexOf('data:image/jpeg;base64') == 0);
+ embedder.test.succeed();
+ }
+ });
+ });
+ document.body.appendChild(webview);
+}
+
+function captureVisibleRegionDoCapture() {}
+
// Tests end.
embedder.test.testList = {
@@ -1752,7 +1782,8 @@ embedder.test.testList = {
'testWebRequestAPI': testWebRequestAPI,
'testWebRequestAPIWithHeaders': testWebRequestAPIWithHeaders,
'testWebRequestAPIExistence': testWebRequestAPIExistence,
- 'testWebRequestAPIGoogleProperty': testWebRequestAPIGoogleProperty
+ 'testWebRequestAPIGoogleProperty': testWebRequestAPIGoogleProperty,
+ 'testCaptureVisibleRegion': testCaptureVisibleRegion
};
onload = function() {