diff options
author | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-10 17:56:53 +0000 |
---|---|---|
committer | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-10 17:56:53 +0000 |
commit | df4bdd0741c7d8bd09f9f1ef7f318935cbb25356 (patch) | |
tree | 7d109eb90238d0134a0ba01a4443c853f61b858b /webkit/glue | |
parent | 20737699af37ed66f628e5222962c118d6a06175 (diff) | |
download | chromium_src-df4bdd0741c7d8bd09f9f1ef7f318935cbb25356.zip chromium_src-df4bdd0741c7d8bd09f9f1ef7f318935cbb25356.tar.gz chromium_src-df4bdd0741c7d8bd09f9f1ef7f318935cbb25356.tar.bz2 |
First pass at implementing real Flush callbacks. This does not currently
include a test since that needs some work on threading and the testing
interface. I plan on doing that in a separate pass.
TEST=none
BUG=none
Review URL: http://codereview.chromium.org/2712002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@49415 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/glue')
-rw-r--r-- | webkit/glue/plugins/pepper_device_context_2d.cc | 176 | ||||
-rw-r--r-- | webkit/glue/plugins/pepper_device_context_2d.h | 74 | ||||
-rw-r--r-- | webkit/glue/plugins/pepper_plugin_instance.cc | 49 | ||||
-rw-r--r-- | webkit/glue/plugins/pepper_plugin_instance.h | 27 |
4 files changed, 286 insertions, 40 deletions
diff --git a/webkit/glue/plugins/pepper_device_context_2d.cc b/webkit/glue/plugins/pepper_device_context_2d.cc index 4a4b374..e549f0e 100644 --- a/webkit/glue/plugins/pepper_device_context_2d.cc +++ b/webkit/glue/plugins/pepper_device_context_2d.cc @@ -4,7 +4,11 @@ #include "webkit/glue/plugins/pepper_device_context_2d.h" +#include <iterator> + #include "base/logging.h" +#include "base/message_loop.h" +#include "base/task.h" #include "gfx/point.h" #include "gfx/rect.h" #include "skia/ext/platform_canvas.h" @@ -14,6 +18,7 @@ #include "third_party/ppapi/c/ppb_device_context_2d.h" #include "third_party/skia/include/core/SkBitmap.h" #include "webkit/glue/plugins/pepper_image_data.h" +#include "webkit/glue/plugins/pepper_plugin_instance.h" #include "webkit/glue/plugins/pepper_plugin_module.h" #include "webkit/glue/plugins/pepper_resource_tracker.h" @@ -70,6 +75,12 @@ PP_Resource Create(PP_Module module_id, int32_t width, int32_t height, return context->GetResource(); } +bool IsDeviceContext2D(PP_Resource resource) { + scoped_refptr<DeviceContext2D> context( + ResourceTracker::Get()->GetAsDeviceContext2D(resource)); + return !!context.get(); +} + bool Describe(PP_Resource device_context, int32_t* width, int32_t* height, bool* is_always_opaque) { scoped_refptr<DeviceContext2D> context( @@ -120,6 +131,7 @@ bool Flush(PP_Resource device_context, const PPB_DeviceContext2D ppb_devicecontext2d = { &Create, + &IsDeviceContext2D, &Describe, &PaintImageData, &Scroll, @@ -159,7 +171,21 @@ struct DeviceContext2D::QueuedOperation { scoped_refptr<ImageData> replace_image; }; -DeviceContext2D::DeviceContext2D(PluginModule* module) : Resource(module) { +DeviceContext2D::FlushCallbackData::FlushCallbackData( + PPB_DeviceContext2D_FlushCallback c, + void* d) + : callback_(c), + callback_data_(d) { +} + +void DeviceContext2D::FlushCallbackData::Execute(PP_Resource device_context) { + callback_(device_context, callback_data_); +} + +DeviceContext2D::DeviceContext2D(PluginModule* module) + : Resource(module), + bound_instance_(NULL), + flushed_any_data_(false) { } DeviceContext2D::~DeviceContext2D() { @@ -266,42 +292,57 @@ bool DeviceContext2D::ReplaceContents(PP_Resource image) { bool DeviceContext2D::Flush(PPB_DeviceContext2D_FlushCallback callback, void* callback_data) { + // TODO(brettw) check that the current thread is not the main one and + // implement blocking flushes in this case. + if (!callback) + return false; + + if (queued_operations_.empty()) + return true; // Nothing to do. + + gfx::Rect changed_rect; for (size_t i = 0; i < queued_operations_.size(); i++) { QueuedOperation& operation = queued_operations_[i]; + gfx::Rect op_rect; switch (operation.type) { case QueuedOperation::PAINT: ExecutePaintImageData(operation.paint_image.get(), operation.paint_x, operation.paint_y, - operation.paint_src_rect); + operation.paint_src_rect, + &op_rect); break; case QueuedOperation::SCROLL: ExecuteScroll(operation.scroll_clip_rect, - operation.scroll_dx, operation.scroll_dy); + operation.scroll_dx, operation.scroll_dy, + &op_rect); break; case QueuedOperation::REPLACE: - ExecuteReplaceContents(operation.replace_image.get()); + ExecuteReplaceContents(operation.replace_image.get(), &op_rect); break; } + changed_rect = changed_rect.Union(op_rect); } queued_operations_.clear(); - // TODO(brettw) implement invalidate and callbacks! - - // Cause the updated part of the screen to be repainted. This will happen - // asynchronously. - /* - gfx::Rect dest_gfx_rect(src_rect->left, src_rect->top, - src_rect->right - src_rect->left, - src_rect->bottom - src_rect->top); - - plugin_delegate_->instance()->webplugin()->InvalidateRect(dest_gfx_rect); - - // Save the callback to execute later. See |unpainted_flush_callbacks_| in - // the header file. - if (callback) { - unpainted_flush_callbacks_.push_back( - FlushCallbackData(callback, id, context, user_data)); + flushed_any_data_ = true; + + // We need the rect to be in terms of the current clip rect of the plugin + // since that's what will actually be painted. If we issue an invalidate + // for a clipped-out region, WebKit will do nothing and we won't get any + // ViewInitiatedPaint/ViewFlushedPaint calls, leaving our callback stranded. + gfx::Rect visible_changed_rect; + if (bound_instance_ && !changed_rect.IsEmpty()) + visible_changed_rect = bound_instance_->clip().Intersect(changed_rect); + + if (bound_instance_ && !visible_changed_rect.IsEmpty()) { + unpainted_flush_callbacks_.push_back(FlushCallbackData(callback, + callback_data)); + bound_instance_->InvalidateRect(visible_changed_rect); + } else { + // There's nothing visible to invalidate so just schedule the callback to + // execute in the next round of the message loop. + ScheduleOffscreenCallback( + FlushCallbackData(callback, callback_data)); } -*/ return true; } @@ -345,6 +386,38 @@ bool DeviceContext2D::ReadImageData(PP_Resource image, int32_t x, int32_t y) { return true; } +bool DeviceContext2D::BindToInstance(PluginInstance* new_instance) { + if (bound_instance_ == new_instance) + return true; // Rebinding the same device, nothing to do. + if (bound_instance_ && new_instance) + return false; // Can't change a bound device. + + if (!new_instance) { + // When the device is detached, we'll not get any more paint callbacks so + // we need to clear the list, but we still want to issue any pending + // callbacks to the plugin. + for (size_t i = 0; i < unpainted_flush_callbacks_.size(); i++) + ScheduleOffscreenCallback(unpainted_flush_callbacks_[i]); + for (size_t i = 0; i < painted_flush_callbacks_.size(); i++) + ScheduleOffscreenCallback(painted_flush_callbacks_[i]); + unpainted_flush_callbacks_.clear(); + painted_flush_callbacks_.clear(); + } else if (flushed_any_data_) { + // Only schedule a paint if this backing store has had any data flushed to + // it. This is an optimization. A "normal" plugin will first allocated a + // backing store, bind it, and then execute their normal painting and + // update loop. If binding a device always invalidated, it would mean we + // would get one paint for the bind, and one for the first time the plugin + // actually painted something. By not bothering to schedule an invalidate + // when an empty device is initially bound, we can save an extra paint for + // many plugins during the critical page initialization phase. + new_instance->InvalidateRect(gfx::Rect()); + } + + bound_instance_ = new_instance; + return true; +} + void DeviceContext2D::Paint(WebKit::WebCanvas* canvas, const gfx::Rect& plugin_rect, const gfx::Rect& paint_rect) { @@ -389,27 +462,47 @@ void DeviceContext2D::Paint(WebKit::WebCanvas* canvas, #endif } +void DeviceContext2D::ViewInitiatedPaint() { + // Move all "unpainted" callbacks to the painted state. See + // |unpainted_flush_callbacks_| in the header for more. + std::copy(unpainted_flush_callbacks_.begin(), + unpainted_flush_callbacks_.end(), + std::back_inserter(painted_flush_callbacks_)); + unpainted_flush_callbacks_.clear(); +} + +void DeviceContext2D::ViewFlushedPaint() { + // Notify all "painted" callbacks. See |unpainted_flush_callbacks_| in the + // header for more. + for (size_t i = 0; i < painted_flush_callbacks_.size(); i++) + painted_flush_callbacks_[i].Execute(GetResource()); + painted_flush_callbacks_.clear(); +} + void DeviceContext2D::ExecutePaintImageData(ImageData* image, int x, int y, - const gfx::Rect& src_rect) { + const gfx::Rect& src_rect, + gfx::Rect* invalidated_rect) { + // Ensure the source image is mapped to read from it. + ImageDataAutoMapper auto_mapper(image); + if (!auto_mapper.is_valid()) + return; + // Portion within the source image to cut out. SkIRect src_irect = { src_rect.x(), src_rect.y(), src_rect.right(), src_rect.bottom() }; // Location within the backing store to copy to. - SkRect dest_rect = { SkIntToScalar(x + src_rect.x()), - SkIntToScalar(y + src_rect.y()), - SkIntToScalar(x + src_rect.right()), - SkIntToScalar(y + src_rect.bottom()) }; + *invalidated_rect = src_rect; + invalidated_rect->Offset(x, y); + SkRect dest_rect = { SkIntToScalar(invalidated_rect->x()), + SkIntToScalar(invalidated_rect->y()), + SkIntToScalar(invalidated_rect->right()), + SkIntToScalar(invalidated_rect->bottom()) }; // We're guaranteed to have a mapped canvas since we mapped it in Init(). skia::PlatformCanvas* backing_canvas = image_data_->mapped_canvas(); - // Ensure the source image is mapped to read from it. - ImageDataAutoMapper auto_mapper(image); - if (!auto_mapper.is_valid()) - return; - // We want to replace the contents of the bitmap rather than blend. SkPaint paint; paint.setXfermodeMode(SkXfermode::kSrc_Mode); @@ -417,12 +510,29 @@ void DeviceContext2D::ExecutePaintImageData(ImageData* image, &src_irect, dest_rect, &paint); } -void DeviceContext2D::ExecuteScroll(const gfx::Rect& clip, int dx, int dy) { +void DeviceContext2D::ExecuteScroll(const gfx::Rect& clip, int dx, int dy, + gfx::Rect* invalidated_rect) { // FIXME(brettw) } -void DeviceContext2D::ExecuteReplaceContents(ImageData* image) { +void DeviceContext2D::ExecuteReplaceContents(ImageData* image, + gfx::Rect* invalidated_rect) { image_data_->Swap(image); + *invalidated_rect = gfx::Rect(0, 0, + image_data_->width(), image_data_->height()); +} + +void DeviceContext2D::ScheduleOffscreenCallback( + const FlushCallbackData& callback) { + MessageLoop::current()->PostTask( + FROM_HERE, + NewRunnableMethod(this, + &DeviceContext2D::ExecuteOffscreenCallback, + callback)); +} + +void DeviceContext2D::ExecuteOffscreenCallback(FlushCallbackData data) { + data.Execute(GetResource()); } } // namespace pepper diff --git a/webkit/glue/plugins/pepper_device_context_2d.h b/webkit/glue/plugins/pepper_device_context_2d.h index 998f7c0..bb488ab 100644 --- a/webkit/glue/plugins/pepper_device_context_2d.h +++ b/webkit/glue/plugins/pepper_device_context_2d.h @@ -21,6 +21,7 @@ class Rect; namespace pepper { class ImageData; +class PluginInstance; class PluginModule; class DeviceContext2D : public Resource { @@ -48,26 +49,91 @@ class DeviceContext2D : public Resource { void* callback_data); bool ReadImageData(PP_Resource image, int32_t x, int32_t y); + // Assciates this device with the given plugin instance. You can pass NULL to + // clear the existing device. Returns true on success. In this case, a + // repaint of the page will also be scheduled. Failure means that the device + // is already bound to a different instance, and nothing will happen. + bool BindToInstance(PluginInstance* new_instance); + + // Paints the current backing store to the web page. void Paint(WebKit::WebCanvas* canvas, const gfx::Rect& plugin_rect, const gfx::Rect& paint_rect); + // Notifications that the view has rendered the page and that it has been + // flushed to the screen. These messages are used to send Flush callbacks to + // the plugin. See + void ViewInitiatedPaint(); + void ViewFlushedPaint(); + private: + // Tracks a call to flush that requires a callback. + // See unpainted_flush_callbacks_ below. + class FlushCallbackData { + public: + FlushCallbackData(PPB_DeviceContext2D_FlushCallback c, void* d); + + void Execute(PP_Resource device_context); + + private: + PPB_DeviceContext2D_FlushCallback callback_; + void* callback_data_; + }; + typedef std::vector<FlushCallbackData> FlushCallbackVector; + // Called internally to execute the different queued commands. The - // parameters to these functions will have already been validated. + // parameters to these functions will have already been validated. The last + // rect argument will be filled by each function with the area affected by + // the update that requires invalidation. If there were no pixels changed, + // this rect can be untouched. void ExecutePaintImageData(ImageData* image, int x, int y, - const gfx::Rect& src_rect); - void ExecuteScroll(const gfx::Rect& clip, int dx, int dy); - void ExecuteReplaceContents(ImageData* image); + const gfx::Rect& src_rect, + gfx::Rect* invalidated_rect); + void ExecuteScroll(const gfx::Rect& clip, int dx, int dy, + gfx::Rect* invalidated_rect); + void ExecuteReplaceContents(ImageData* image, + gfx::Rect* invalidated_rect); + + // Schedules the offscreen callback to be fired at a future time. This + // will add the given item to the offscreen_flush_callbacks_ vector. + void ScheduleOffscreenCallback(const FlushCallbackData& callback); + + // Function scheduled to execute by ScheduleOffscreenCallback that actually + // issues the offscreen callbacks. + void ExecuteOffscreenCallback(FlushCallbackData data); scoped_refptr<ImageData> image_data_; + // Non-owning pointer to the plugin instance this device context is currently + // bound to, if any. If the device context is currently unbound, this will + // be NULL. + PluginInstance* bound_instance_; + // Keeps track of all drawing commands queued before a Flush call. struct QueuedOperation; typedef std::vector<QueuedOperation> OperationQueue; OperationQueue queued_operations_; + // Indicates whether any changes have been flushed to the backing store. + // This is initially false and is set to true at the first Flush() call. + bool flushed_any_data_; + + // The plugin may be constantly giving us paint messages. "Unpainted" ones + // are paint requests which have never been painted. These could have been + // done while the RenderView was already waiting for an ACK from a previous + // paint, so won't generate a new one yet. + // + // "Painted" ones are those paints that have been painted by RenderView, but + // for which the ACK from the browser has not yet been received. + // + // When we get updates from a plugin with a callback, it is first added to + // the unpainted callbacks. When the renderer has initiated a paint, we'll + // move it to the painted callbacks list. When the renderer receives a flush, + // we'll execute the callback and remove it from the list. + FlushCallbackVector unpainted_flush_callbacks_; + FlushCallbackVector painted_flush_callbacks_; + DISALLOW_COPY_AND_ASSIGN(DeviceContext2D); }; diff --git a/webkit/glue/plugins/pepper_plugin_instance.cc b/webkit/glue/plugins/pepper_plugin_instance.cc index 15bff8c..a2f83b8 100644 --- a/webkit/glue/plugins/pepper_plugin_instance.cc +++ b/webkit/glue/plugins/pepper_plugin_instance.cc @@ -19,6 +19,7 @@ #include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" #include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" #include "third_party/WebKit/WebKit/chromium/public/WebPluginContainer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebRect.h" #include "webkit/glue/plugins/pepper_device_context_2d.h" #include "webkit/glue/plugins/pepper_plugin_module.h" #include "webkit/glue/plugins/pepper_resource_tracker.h" @@ -172,6 +173,15 @@ void PluginInstance::Paint(WebKit::WebCanvas* canvas, device_context_2d_->Paint(canvas, plugin_rect, paint_rect); } +void PluginInstance::InvalidateRect(const gfx::Rect& rect) { + if (!container_ || position_.IsEmpty()) + return; // Nothing to do. + if (rect.IsEmpty()) + container_->invalidate(); + else + container_->invalidateRect(rect); +} + PP_Var PluginInstance::GetWindowObject() { if (!container_) return PP_MakeVoid(); @@ -191,6 +201,16 @@ PP_Var PluginInstance::GetOwnerElementObject() { } bool PluginInstance::BindGraphicsDeviceContext(PP_Resource device_id) { + if (!device_id) { + // Special-case clearing the current device. + if (device_context_2d_) { + device_context_2d_->BindToInstance(NULL); + device_context_2d_ = NULL; + InvalidateRect(gfx::Rect()); + } + return true; + } + scoped_refptr<Resource> device_resource = ResourceTracker::Get()->GetResource(device_id); if (!device_resource.get()) @@ -198,8 +218,10 @@ bool PluginInstance::BindGraphicsDeviceContext(PP_Resource device_id) { DeviceContext2D* device_2d = device_resource->AsDeviceContext2D(); if (device_2d) { + if (!device_2d->BindToInstance(this)) + return false; // Can't bind to more than one instance. device_context_2d_ = device_2d; - // TODO(brettw) repaint the plugin. + // BindToInstance will have invalidated the plugin if necessary. } return true; @@ -270,10 +292,31 @@ PP_Var PluginInstance::GetInstanceObject() { void PluginInstance::ViewChanged(const gfx::Rect& position, const gfx::Rect& clip) { + position_ = position; + if (clip.IsEmpty()) { + // WebKit can give weird (x,y) positions for empty clip rects (since the + // position technically doesn't matter). But we want to make these + // consistent since this is given to the plugin, so force everything to 0 + // in the "everything is clipped" case. + clip_ = gfx::Rect(); + } else { + clip_ = clip; + } + PP_Rect pp_position, pp_clip; - RectToPPRect(position, &pp_position); - RectToPPRect(clip, &pp_clip); + RectToPPRect(position_, &pp_position); + RectToPPRect(clip_, &pp_clip); instance_interface_->ViewChanged(GetPPInstance(), &pp_position, &pp_clip); } +void PluginInstance::ViewInitiatedPaint() { + if (device_context_2d_) + device_context_2d_->ViewInitiatedPaint(); +} + +void PluginInstance::ViewFlushedPaint() { + if (device_context_2d_) + device_context_2d_->ViewFlushedPaint(); +} + } // namespace pepper diff --git a/webkit/glue/plugins/pepper_plugin_instance.h b/webkit/glue/plugins/pepper_plugin_instance.h index 3b045d4..aaf9bc0 100644 --- a/webkit/glue/plugins/pepper_plugin_instance.h +++ b/webkit/glue/plugins/pepper_plugin_instance.h @@ -10,6 +10,7 @@ #include "base/basictypes.h" #include "base/ref_counted.h" +#include "gfx/rect.h" #include "third_party/ppapi/c/pp_instance.h" #include "third_party/ppapi/c/pp_resource.h" #include "third_party/WebKit/WebKit/chromium/public/WebCanvas.h" @@ -49,12 +50,22 @@ class PluginInstance : public base::RefCounted<PluginInstance> { PluginDelegate* delegate() const { return delegate_; } PluginModule* module() const { return module_.get(); } + const gfx::Rect& position() const { return position_; } + const gfx::Rect& clip() const { return clip_; } + PP_Instance GetPPInstance(); + // Paints the current backing store to the web page. void Paint(WebKit::WebCanvas* canvas, const gfx::Rect& plugin_rect, const gfx::Rect& paint_rect); + // Schedules a paint of the page for the given region. The coordinates are + // relative to the top-left of the plugin. This does nothing if the plugin + // has not yet been positioned. You can supply an empty gfx::Rect() to + // invalidate the entire plugin. + void InvalidateRect(const gfx::Rect& rect); + // PPB_Instance implementation. PP_Var GetWindowObject(); PP_Var GetOwnerElementObject(); @@ -70,6 +81,12 @@ class PluginInstance : public base::RefCounted<PluginInstance> { PP_Var GetInstanceObject(); void ViewChanged(const gfx::Rect& position, const gfx::Rect& clip); + // Notifications that the view has rendered the page and that it has been + // flushed to the screen. These messages are used to send Flush callbacks to + // the plugin for DeviceContext2D. + void ViewInitiatedPaint(); + void ViewFlushedPaint(); + private: PluginDelegate* delegate_; scoped_refptr<PluginModule> module_; @@ -78,6 +95,16 @@ class PluginInstance : public base::RefCounted<PluginInstance> { // NULL until we have been initialized. WebKit::WebPluginContainer* container_; + // Position in the viewport (which moves as the page is scrolled) of this + // plugin. This will be a 0-sized rectangle if the plugin has not yet been + // laid out. + gfx::Rect position_; + + // Current clip rect. This will be empty if the plugin is not currently + // visible. This is in the plugin's coordinate system, so fully visible will + // be (0, 0, w, h) regardless of scroll position. + gfx::Rect clip_; + // The current device context for painting in 2D. scoped_refptr<DeviceContext2D> device_context_2d_; |