diff options
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() { |