Home | History | Annotate | Download | only in pepper
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/renderer/pepper/pepper_graphics_2d_host.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/debug/trace_event.h"
      9 #include "base/logging.h"
     10 #include "base/message_loop/message_loop.h"
     11 #include "content/public/renderer/renderer_ppapi_host.h"
     12 #include "content/renderer/pepper/common.h"
     13 #include "content/renderer/pepper/gfx_conversion.h"
     14 #include "content/renderer/pepper/pepper_plugin_instance_impl.h"
     15 #include "content/renderer/pepper/ppb_image_data_impl.h"
     16 #include "ppapi/c/pp_bool.h"
     17 #include "ppapi/c/pp_errors.h"
     18 #include "ppapi/c/pp_rect.h"
     19 #include "ppapi/c/pp_resource.h"
     20 #include "ppapi/host/dispatch_host_message.h"
     21 #include "ppapi/host/host_message_context.h"
     22 #include "ppapi/host/ppapi_host.h"
     23 #include "ppapi/proxy/ppapi_messages.h"
     24 #include "ppapi/thunk/enter.h"
     25 #include "skia/ext/platform_canvas.h"
     26 #include "third_party/skia/include/core/SkBitmap.h"
     27 #include "ui/gfx/blit.h"
     28 #include "ui/gfx/point.h"
     29 #include "ui/gfx/point_conversions.h"
     30 #include "ui/gfx/rect.h"
     31 #include "ui/gfx/rect_conversions.h"
     32 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
     33 #include "ui/gfx/size_conversions.h"
     34 #include "ui/gfx/skia_util.h"
     35 
     36 #if defined(OS_MACOSX)
     37 #include "base/mac/mac_util.h"
     38 #include "base/mac/scoped_cftyperef.h"
     39 #endif
     40 
     41 using ppapi::thunk::EnterResourceNoLock;
     42 using ppapi::thunk::PPB_ImageData_API;
     43 
     44 namespace content {
     45 
     46 namespace {
     47 
     48 const int64 kOffscreenCallbackDelayMs = 1000 / 30;  // 30 fps
     49 
     50 // Converts a rect inside an image of the given dimensions. The rect may be
     51 // NULL to indicate it should be the entire image. If the rect is outside of
     52 // the image, this will do nothing and return false.
     53 bool ValidateAndConvertRect(const PP_Rect* rect,
     54                             int image_width, int image_height,
     55                             gfx::Rect* dest) {
     56   if (!rect) {
     57     // Use the entire image area.
     58     *dest = gfx::Rect(0, 0, image_width, image_height);
     59   } else {
     60     // Validate the passed-in area.
     61     if (rect->point.x < 0 || rect->point.y < 0 ||
     62         rect->size.width <= 0 || rect->size.height <= 0)
     63       return false;
     64 
     65     // Check the max bounds, being careful of overflow.
     66     if (static_cast<int64>(rect->point.x) +
     67         static_cast<int64>(rect->size.width) >
     68         static_cast<int64>(image_width))
     69       return false;
     70     if (static_cast<int64>(rect->point.y) +
     71         static_cast<int64>(rect->size.height) >
     72         static_cast<int64>(image_height))
     73       return false;
     74 
     75     *dest = gfx::Rect(rect->point.x, rect->point.y,
     76                       rect->size.width, rect->size.height);
     77   }
     78   return true;
     79 }
     80 
     81 // Converts BGRA <-> RGBA.
     82 void ConvertBetweenBGRAandRGBA(const uint32_t* input,
     83                                int pixel_length,
     84                                uint32_t* output) {
     85   for (int i = 0; i < pixel_length; i++) {
     86     const unsigned char* pixel_in =
     87         reinterpret_cast<const unsigned char*>(&input[i]);
     88     unsigned char* pixel_out = reinterpret_cast<unsigned char*>(&output[i]);
     89     pixel_out[0] = pixel_in[2];
     90     pixel_out[1] = pixel_in[1];
     91     pixel_out[2] = pixel_in[0];
     92     pixel_out[3] = pixel_in[3];
     93   }
     94 }
     95 
     96 // Converts ImageData from PP_IMAGEDATAFORMAT_BGRA_PREMUL to
     97 // PP_IMAGEDATAFORMAT_RGBA_PREMUL, or reverse. It's assumed that the
     98 // destination image is always mapped (so will have non-NULL data).
     99 void ConvertImageData(PPB_ImageData_Impl* src_image, const SkIRect& src_rect,
    100                       PPB_ImageData_Impl* dest_image, const SkRect& dest_rect) {
    101   ImageDataAutoMapper auto_mapper(src_image);
    102 
    103   DCHECK(src_image->format() != dest_image->format());
    104   DCHECK(PPB_ImageData_Impl::IsImageDataFormatSupported(src_image->format()));
    105   DCHECK(PPB_ImageData_Impl::IsImageDataFormatSupported(dest_image->format()));
    106 
    107   const SkBitmap* src_bitmap = src_image->GetMappedBitmap();
    108   const SkBitmap* dest_bitmap = dest_image->GetMappedBitmap();
    109   if (src_rect.width() == src_image->width() &&
    110       dest_rect.width() == dest_image->width()) {
    111     // Fast path if the full line needs to be converted.
    112     ConvertBetweenBGRAandRGBA(
    113         src_bitmap->getAddr32(static_cast<int>(src_rect.fLeft),
    114                               static_cast<int>(src_rect.fTop)),
    115         src_rect.width() * src_rect.height(),
    116         dest_bitmap->getAddr32(static_cast<int>(dest_rect.fLeft),
    117                                static_cast<int>(dest_rect.fTop)));
    118   } else {
    119     // Slow path where we convert line by line.
    120     for (int y = 0; y < src_rect.height(); y++) {
    121       ConvertBetweenBGRAandRGBA(
    122           src_bitmap->getAddr32(static_cast<int>(src_rect.fLeft),
    123                                 static_cast<int>(src_rect.fTop + y)),
    124           src_rect.width(),
    125           dest_bitmap->getAddr32(static_cast<int>(dest_rect.fLeft),
    126                                  static_cast<int>(dest_rect.fTop + y)));
    127     }
    128   }
    129 }
    130 
    131 }  // namespace
    132 
    133 struct PepperGraphics2DHost::QueuedOperation {
    134   enum Type {
    135     PAINT,
    136     SCROLL,
    137     REPLACE
    138   };
    139 
    140   QueuedOperation(Type t)
    141       : type(t),
    142         paint_x(0),
    143         paint_y(0),
    144         scroll_dx(0),
    145         scroll_dy(0) {
    146   }
    147 
    148   Type type;
    149 
    150   // Valid when type == PAINT.
    151   scoped_refptr<PPB_ImageData_Impl> paint_image;
    152   int paint_x, paint_y;
    153   gfx::Rect paint_src_rect;
    154 
    155   // Valid when type == SCROLL.
    156   gfx::Rect scroll_clip_rect;
    157   int scroll_dx, scroll_dy;
    158 
    159   // Valid when type == REPLACE.
    160   scoped_refptr<PPB_ImageData_Impl> replace_image;
    161 };
    162 
    163 // static
    164 PepperGraphics2DHost* PepperGraphics2DHost::Create(RendererPpapiHost* host,
    165                                                    PP_Instance instance,
    166                                                    PP_Resource resource,
    167                                                    const PP_Size& size,
    168                                                    PP_Bool is_always_opaque) {
    169   PepperGraphics2DHost* resource_host =
    170       new PepperGraphics2DHost(host, instance, resource);
    171   if (!resource_host->Init(size.width, size.height,
    172                            PP_ToBool(is_always_opaque))) {
    173     delete resource_host;
    174     return NULL;
    175   }
    176   return resource_host;
    177 }
    178 
    179 PepperGraphics2DHost::PepperGraphics2DHost(RendererPpapiHost* host,
    180                                            PP_Instance instance,
    181                                            PP_Resource resource)
    182     : ResourceHost(host->GetPpapiHost(), instance, resource),
    183       renderer_ppapi_host_(host),
    184       bound_instance_(NULL),
    185       need_flush_ack_(false),
    186       offscreen_flush_pending_(false),
    187       is_always_opaque_(false),
    188       scale_(1.0f),
    189       weak_ptr_factory_(this),
    190       is_running_in_process_(host->IsRunningInProcess()) {
    191 }
    192 
    193 PepperGraphics2DHost::~PepperGraphics2DHost() {
    194   // Unbind from the instance when destroyed if we're still bound.
    195   if (bound_instance_)
    196     bound_instance_->BindGraphics(bound_instance_->pp_instance(), 0);
    197 }
    198 
    199 bool PepperGraphics2DHost::Init(int width, int height, bool is_always_opaque) {
    200   // The underlying PPB_ImageData_Impl will validate the dimensions.
    201   image_data_ = new PPB_ImageData_Impl(pp_instance(),
    202                                        PPB_ImageData_Impl::PLATFORM);
    203   if (!image_data_->Init(PPB_ImageData_Impl::GetNativeImageDataFormat(),
    204                          width, height, true) ||
    205       !image_data_->Map()) {
    206     image_data_ = NULL;
    207     return false;
    208   }
    209   is_always_opaque_ = is_always_opaque;
    210   scale_ = 1.0f;
    211   return true;
    212 }
    213 
    214 int32_t PepperGraphics2DHost::OnResourceMessageReceived(
    215     const IPC::Message& msg,
    216     ppapi::host::HostMessageContext* context) {
    217   IPC_BEGIN_MESSAGE_MAP(PepperGraphics2DHost, msg)
    218     PPAPI_DISPATCH_HOST_RESOURCE_CALL(
    219         PpapiHostMsg_Graphics2D_PaintImageData,
    220         OnHostMsgPaintImageData)
    221     PPAPI_DISPATCH_HOST_RESOURCE_CALL(
    222         PpapiHostMsg_Graphics2D_Scroll,
    223         OnHostMsgScroll)
    224     PPAPI_DISPATCH_HOST_RESOURCE_CALL(
    225         PpapiHostMsg_Graphics2D_ReplaceContents,
    226         OnHostMsgReplaceContents)
    227     PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
    228         PpapiHostMsg_Graphics2D_Flush,
    229         OnHostMsgFlush)
    230     PPAPI_DISPATCH_HOST_RESOURCE_CALL(
    231         PpapiHostMsg_Graphics2D_Dev_SetScale,
    232         OnHostMsgSetScale)
    233     PPAPI_DISPATCH_HOST_RESOURCE_CALL(
    234         PpapiHostMsg_Graphics2D_ReadImageData,
    235         OnHostMsgReadImageData)
    236   IPC_END_MESSAGE_MAP()
    237   return PP_ERROR_FAILED;
    238 }
    239 
    240 bool PepperGraphics2DHost::IsGraphics2DHost() {
    241   return true;
    242 }
    243 
    244 bool PepperGraphics2DHost::ReadImageData(PP_Resource image,
    245                                          const PP_Point* top_left) {
    246   // Get and validate the image object to paint into.
    247   EnterResourceNoLock<PPB_ImageData_API> enter(image, true);
    248   if (enter.failed())
    249     return false;
    250   PPB_ImageData_Impl* image_resource =
    251       static_cast<PPB_ImageData_Impl*>(enter.object());
    252   if (!PPB_ImageData_Impl::IsImageDataFormatSupported(
    253           image_resource->format()))
    254     return false;  // Must be in the right format.
    255 
    256   // Validate the bitmap position.
    257   int x = top_left->x;
    258   if (x < 0 ||
    259       static_cast<int64>(x) + static_cast<int64>(image_resource->width()) >
    260       image_data_->width())
    261     return false;
    262   int y = top_left->y;
    263   if (y < 0 ||
    264       static_cast<int64>(y) + static_cast<int64>(image_resource->height()) >
    265       image_data_->height())
    266     return false;
    267 
    268   ImageDataAutoMapper auto_mapper(image_resource);
    269   if (!auto_mapper.is_valid())
    270     return false;
    271 
    272   SkIRect src_irect = { x, y,
    273                         x + image_resource->width(),
    274                         y + image_resource->height() };
    275   SkRect dest_rect = { SkIntToScalar(0),
    276                        SkIntToScalar(0),
    277                        SkIntToScalar(image_resource->width()),
    278                        SkIntToScalar(image_resource->height()) };
    279 
    280   if (image_resource->format() != image_data_->format()) {
    281     // Convert the image data if the format does not match.
    282     ConvertImageData(image_data_.get(), src_irect, image_resource, dest_rect);
    283   } else {
    284     SkCanvas* dest_canvas = image_resource->GetCanvas();
    285 
    286     // We want to replace the contents of the bitmap rather than blend.
    287     SkPaint paint;
    288     paint.setXfermodeMode(SkXfermode::kSrc_Mode);
    289     dest_canvas->drawBitmapRect(*image_data_->GetMappedBitmap(),
    290                                 &src_irect, dest_rect, &paint);
    291   }
    292   return true;
    293 }
    294 
    295 bool PepperGraphics2DHost::BindToInstance(
    296     PepperPluginInstanceImpl* new_instance) {
    297   if (new_instance && new_instance->pp_instance() != pp_instance())
    298     return false;  // Can't bind other instance's contexts.
    299   if (bound_instance_ == new_instance)
    300     return true;  // Rebinding the same device, nothing to do.
    301   if (bound_instance_ && new_instance)
    302     return false;  // Can't change a bound device.
    303 
    304   if (!new_instance) {
    305     // When the device is detached, we'll not get any more paint callbacks so
    306     // we need to clear the list, but we still want to issue any pending
    307     // callbacks to the plugin.
    308     if (need_flush_ack_)
    309       ScheduleOffscreenFlushAck();
    310   } else {
    311     // Devices being replaced, redraw the plugin.
    312     new_instance->InvalidateRect(gfx::Rect());
    313   }
    314 
    315   bound_instance_ = new_instance;
    316   return true;
    317 }
    318 
    319 // The |backing_bitmap| must be clipped to the |plugin_rect| to avoid painting
    320 // outside the plugin area. This can happen if the plugin has been resized since
    321 // PaintImageData verified the image is within the plugin size.
    322 void PepperGraphics2DHost::Paint(WebKit::WebCanvas* canvas,
    323                                  const gfx::Rect& plugin_rect,
    324                                  const gfx::Rect& paint_rect) {
    325   TRACE_EVENT0("pepper", "PepperGraphics2DHost::Paint");
    326   ImageDataAutoMapper auto_mapper(image_data_.get());
    327   const SkBitmap& backing_bitmap = *image_data_->GetMappedBitmap();
    328 
    329   gfx::Rect invalidate_rect = plugin_rect;
    330   invalidate_rect.Intersect(paint_rect);
    331   SkRect sk_invalidate_rect = gfx::RectToSkRect(invalidate_rect);
    332   SkAutoCanvasRestore auto_restore(canvas, true);
    333   canvas->clipRect(sk_invalidate_rect);
    334   gfx::Size pixel_image_size(image_data_->width(), image_data_->height());
    335   gfx::Size image_size = gfx::ToFlooredSize(
    336       gfx::ScaleSize(pixel_image_size, scale_));
    337 
    338   PepperPluginInstance* plugin_instance =
    339       renderer_ppapi_host_->GetPluginInstance(pp_instance());
    340   if (!plugin_instance)
    341     return;
    342   if (plugin_instance->IsFullPagePlugin()) {
    343     // When we're resizing a window with a full-frame plugin, the plugin may
    344     // not yet have bound a new device, which will leave parts of the
    345     // background exposed if the window is getting larger. We want this to
    346     // show white (typically less jarring) rather than black or uninitialized.
    347     // We don't do this for non-full-frame plugins since we specifically want
    348     // the page background to show through.
    349     SkAutoCanvasRestore auto_restore(canvas, true);
    350     SkRect image_data_rect =
    351         gfx::RectToSkRect(gfx::Rect(plugin_rect.origin(), image_size));
    352     canvas->clipRect(image_data_rect, SkRegion::kDifference_Op);
    353 
    354     SkPaint paint;
    355     paint.setXfermodeMode(SkXfermode::kSrc_Mode);
    356     paint.setColor(SK_ColorWHITE);
    357     canvas->drawRect(sk_invalidate_rect, paint);
    358   }
    359 
    360   SkBitmap image;
    361   // Copy to device independent bitmap when target canvas doesn't support
    362   // platform paint.
    363   if (!skia::SupportsPlatformPaint(canvas))
    364     backing_bitmap.copyTo(&image, SkBitmap::kARGB_8888_Config);
    365   else
    366     image = backing_bitmap;
    367 
    368   SkPaint paint;
    369   if (is_always_opaque_) {
    370     // When we know the device is opaque, we can disable blending for slightly
    371     // more optimized painting.
    372     paint.setXfermodeMode(SkXfermode::kSrc_Mode);
    373   }
    374 
    375   SkPoint origin;
    376   origin.set(SkIntToScalar(plugin_rect.x()), SkIntToScalar(plugin_rect.y()));
    377 
    378   SkPoint pixel_origin = origin;
    379   if (scale_ != 1.0f && scale_ > 0.0f) {
    380     float inverse_scale = 1.0f / scale_;
    381     pixel_origin.scale(inverse_scale);
    382     canvas->scale(scale_, scale_);
    383   }
    384   canvas->drawBitmap(image, pixel_origin.x(), pixel_origin.y(), &paint);
    385 }
    386 
    387 void PepperGraphics2DHost::ViewWillInitiatePaint() {
    388 }
    389 
    390 void PepperGraphics2DHost::ViewInitiatedPaint() {
    391 }
    392 
    393 void PepperGraphics2DHost::ViewFlushedPaint() {
    394   TRACE_EVENT0("pepper", "PepperGraphics2DHost::ViewFlushedPaint");
    395   if (need_flush_ack_) {
    396     SendFlushAck();
    397     need_flush_ack_ = false;
    398   }
    399 }
    400 
    401 void PepperGraphics2DHost::SetScale(float scale) {
    402   scale_ = scale;
    403 }
    404 
    405 float PepperGraphics2DHost::GetScale() const {
    406   return scale_;
    407 }
    408 
    409 bool PepperGraphics2DHost::IsAlwaysOpaque() const {
    410   return is_always_opaque_;
    411 }
    412 
    413 PPB_ImageData_Impl* PepperGraphics2DHost::ImageData() {
    414   return image_data_.get();
    415 }
    416 
    417 int32_t PepperGraphics2DHost::OnHostMsgPaintImageData(
    418     ppapi::host::HostMessageContext* context,
    419     const ppapi::HostResource& image_data,
    420     const PP_Point& top_left,
    421     bool src_rect_specified,
    422     const PP_Rect& src_rect) {
    423   EnterResourceNoLock<PPB_ImageData_API> enter(image_data.host_resource(),
    424                                                true);
    425   if (enter.failed())
    426     return PP_ERROR_BADRESOURCE;
    427   PPB_ImageData_Impl* image_resource =
    428       static_cast<PPB_ImageData_Impl*>(enter.object());
    429 
    430   QueuedOperation operation(QueuedOperation::PAINT);
    431   operation.paint_image = image_resource;
    432   if (!ValidateAndConvertRect(src_rect_specified ? &src_rect : NULL,
    433                               image_resource->width(),
    434                               image_resource->height(),
    435                               &operation.paint_src_rect))
    436     return PP_ERROR_BADARGUMENT;
    437 
    438   // Validate the bitmap position using the previously-validated rect, there
    439   // should be no painted area outside of the image.
    440   int64 x64 = static_cast<int64>(top_left.x);
    441   int64 y64 = static_cast<int64>(top_left.y);
    442   if (x64 + static_cast<int64>(operation.paint_src_rect.x()) < 0 ||
    443       x64 + static_cast<int64>(operation.paint_src_rect.right()) >
    444       image_data_->width())
    445     return PP_ERROR_BADARGUMENT;
    446   if (y64 + static_cast<int64>(operation.paint_src_rect.y()) < 0 ||
    447       y64 + static_cast<int64>(operation.paint_src_rect.bottom()) >
    448       image_data_->height())
    449     return PP_ERROR_BADARGUMENT;
    450   operation.paint_x = top_left.x;
    451   operation.paint_y = top_left.y;
    452 
    453   queued_operations_.push_back(operation);
    454   return PP_OK;
    455 }
    456 
    457 int32_t PepperGraphics2DHost::OnHostMsgScroll(
    458     ppapi::host::HostMessageContext* context,
    459     bool clip_specified,
    460     const PP_Rect& clip,
    461     const PP_Point& amount) {
    462   QueuedOperation operation(QueuedOperation::SCROLL);
    463   if (!ValidateAndConvertRect(clip_specified ? &clip : NULL,
    464                               image_data_->width(),
    465                               image_data_->height(),
    466                               &operation.scroll_clip_rect))
    467     return PP_ERROR_BADARGUMENT;
    468 
    469   // If we're being asked to scroll by more than the clip rect size, just
    470   // ignore this scroll command and say it worked.
    471   int32 dx = amount.x;
    472   int32 dy = amount.y;
    473   if (dx <= -image_data_->width() || dx >= image_data_->width() ||
    474       dy <= -image_data_->height() || dy >= image_data_->height())
    475     return PP_ERROR_BADARGUMENT;
    476 
    477   operation.scroll_dx = dx;
    478   operation.scroll_dy = dy;
    479 
    480   queued_operations_.push_back(operation);
    481   return PP_OK;
    482 }
    483 
    484 int32_t PepperGraphics2DHost::OnHostMsgReplaceContents(
    485     ppapi::host::HostMessageContext* context,
    486     const ppapi::HostResource& image_data) {
    487   EnterResourceNoLock<PPB_ImageData_API> enter(image_data.host_resource(),
    488                                                true);
    489   if (enter.failed())
    490     return PP_ERROR_BADRESOURCE;
    491   PPB_ImageData_Impl* image_resource =
    492       static_cast<PPB_ImageData_Impl*>(enter.object());
    493 
    494   if (!PPB_ImageData_Impl::IsImageDataFormatSupported(
    495           image_resource->format()))
    496     return PP_ERROR_BADARGUMENT;
    497 
    498   if (image_resource->width() != image_data_->width() ||
    499       image_resource->height() != image_data_->height())
    500     return PP_ERROR_BADARGUMENT;
    501 
    502   QueuedOperation operation(QueuedOperation::REPLACE);
    503   operation.replace_image = image_resource;
    504   queued_operations_.push_back(operation);
    505   return PP_OK;
    506 }
    507 
    508 int32_t PepperGraphics2DHost::OnHostMsgFlush(
    509     ppapi::host::HostMessageContext* context) {
    510   // Don't allow more than one pending flush at a time.
    511   if (HasPendingFlush())
    512     return PP_ERROR_INPROGRESS;
    513 
    514   PP_Resource old_image_data = 0;
    515   flush_reply_context_ = context->MakeReplyMessageContext();
    516   if (is_running_in_process_)
    517     return Flush(NULL);
    518 
    519   // Reuse image data when running out of process.
    520   int32_t result = Flush(&old_image_data);
    521 
    522   if (old_image_data) {
    523     // If the Graphics2D has an old image data it's not using any more, send
    524     // it back to the plugin for possible re-use. See ppb_image_data_proxy.cc
    525     // for a description how this process works.
    526     ppapi::HostResource old_image_data_host_resource;
    527     old_image_data_host_resource.SetHostResource(pp_instance(),
    528                                                  old_image_data);
    529     host()->Send(new PpapiMsg_PPBImageData_NotifyUnusedImageData(
    530         ppapi::API_ID_PPB_IMAGE_DATA, old_image_data_host_resource));
    531   }
    532 
    533   return result;
    534 }
    535 
    536 int32_t PepperGraphics2DHost::OnHostMsgSetScale(
    537     ppapi::host::HostMessageContext* context,
    538     float scale) {
    539   if (scale > 0.0f) {
    540     scale_ = scale;
    541     return PP_OK;
    542   }
    543   return PP_ERROR_BADARGUMENT;
    544 }
    545 
    546 int32_t PepperGraphics2DHost::OnHostMsgReadImageData(
    547     ppapi::host::HostMessageContext* context,
    548     PP_Resource image,
    549     const PP_Point& top_left) {
    550   context->reply_msg = PpapiPluginMsg_Graphics2D_ReadImageDataAck();
    551   return ReadImageData(image, &top_left) ? PP_OK : PP_ERROR_FAILED;
    552 }
    553 
    554 int32_t PepperGraphics2DHost::Flush(PP_Resource* old_image_data) {
    555   bool done_replace_contents = false;
    556   bool no_update_visible = true;
    557   bool is_plugin_visible = true;
    558   for (size_t i = 0; i < queued_operations_.size(); i++) {
    559     QueuedOperation& operation = queued_operations_[i];
    560     gfx::Rect op_rect;
    561     switch (operation.type) {
    562       case QueuedOperation::PAINT:
    563         ExecutePaintImageData(operation.paint_image.get(),
    564                               operation.paint_x,
    565                               operation.paint_y,
    566                               operation.paint_src_rect,
    567                               &op_rect);
    568         break;
    569       case QueuedOperation::SCROLL:
    570         ExecuteScroll(operation.scroll_clip_rect,
    571                       operation.scroll_dx, operation.scroll_dy,
    572                       &op_rect);
    573         break;
    574       case QueuedOperation::REPLACE:
    575         // Since the out parameter |old_image_data| takes ownership of the
    576         // reference, if there are more than one ReplaceContents calls queued
    577         // the first |old_image_data| will get overwritten and leaked. So we
    578         // only supply this for the first call.
    579         ExecuteReplaceContents(operation.replace_image.get(),
    580                                &op_rect,
    581                                done_replace_contents ? NULL : old_image_data);
    582         done_replace_contents = true;
    583         break;
    584     }
    585 
    586     // For correctness with accelerated compositing, we must issue an invalidate
    587     // on the full op_rect even if it is partially or completely off-screen.
    588     // However, if we issue an invalidate for a clipped-out region, WebKit will
    589     // do nothing and we won't get any ViewWillInitiatePaint/ViewFlushedPaint
    590     // calls, leaving our callback stranded. So we still need to check whether
    591     // the repainted area is visible to determine how to deal with the callback.
    592     if (bound_instance_ && !op_rect.IsEmpty()) {
    593       gfx::Point scroll_delta(operation.scroll_dx, operation.scroll_dy);
    594       if (!ConvertToLogicalPixels(scale_,
    595                                   &op_rect,
    596                                   operation.type == QueuedOperation::SCROLL ?
    597                                       &scroll_delta : NULL)) {
    598         // Conversion requires falling back to InvalidateRect.
    599         operation.type = QueuedOperation::PAINT;
    600       }
    601 
    602       gfx::Rect clip = PP_ToGfxRect(bound_instance_->view_data().clip_rect);
    603       is_plugin_visible = !clip.IsEmpty();
    604 
    605       // Set |no_update_visible| to false if the change overlaps the visible
    606       // area.
    607       gfx::Rect visible_changed_rect = gfx::IntersectRects(clip, op_rect);
    608       if (!visible_changed_rect.IsEmpty())
    609         no_update_visible = false;
    610 
    611       // Notify the plugin of the entire change (op_rect), even if it is
    612       // partially or completely off-screen.
    613       if (operation.type == QueuedOperation::SCROLL) {
    614         bound_instance_->ScrollRect(scroll_delta.x(), scroll_delta.y(),
    615                                     op_rect);
    616       } else {
    617         bound_instance_->InvalidateRect(op_rect);
    618       }
    619     }
    620   }
    621   queued_operations_.clear();
    622 
    623   if (!bound_instance_) {
    624     // As promised in the API, we always schedule callback when unbound.
    625     ScheduleOffscreenFlushAck();
    626   } else if (no_update_visible && is_plugin_visible &&
    627              bound_instance_->view_data().is_page_visible) {
    628     // There's nothing visible to invalidate so just schedule the callback to
    629     // execute in the next round of the message loop.
    630     ScheduleOffscreenFlushAck();
    631   } else {
    632     need_flush_ack_ = true;
    633   }
    634 
    635   return PP_OK_COMPLETIONPENDING;
    636 }
    637 
    638 void PepperGraphics2DHost::ExecutePaintImageData(PPB_ImageData_Impl* image,
    639                                                 int x, int y,
    640                                                 const gfx::Rect& src_rect,
    641                                                 gfx::Rect* invalidated_rect) {
    642   // Ensure the source image is mapped to read from it.
    643   ImageDataAutoMapper auto_mapper(image);
    644   if (!auto_mapper.is_valid())
    645     return;
    646 
    647   // Portion within the source image to cut out.
    648   SkIRect src_irect = { src_rect.x(), src_rect.y(),
    649                         src_rect.right(), src_rect.bottom() };
    650 
    651   // Location within the backing store to copy to.
    652   *invalidated_rect = src_rect;
    653   invalidated_rect->Offset(x, y);
    654   SkRect dest_rect = { SkIntToScalar(invalidated_rect->x()),
    655                        SkIntToScalar(invalidated_rect->y()),
    656                        SkIntToScalar(invalidated_rect->right()),
    657                        SkIntToScalar(invalidated_rect->bottom()) };
    658 
    659   if (image->format() != image_data_->format()) {
    660     // Convert the image data if the format does not match.
    661     ConvertImageData(image, src_irect, image_data_.get(), dest_rect);
    662   } else {
    663     // We're guaranteed to have a mapped canvas since we mapped it in Init().
    664     SkCanvas* backing_canvas = image_data_->GetCanvas();
    665 
    666     // We want to replace the contents of the bitmap rather than blend.
    667     SkPaint paint;
    668     paint.setXfermodeMode(SkXfermode::kSrc_Mode);
    669     backing_canvas->drawBitmapRect(*image->GetMappedBitmap(),
    670                                    &src_irect, dest_rect, &paint);
    671   }
    672 }
    673 
    674 void PepperGraphics2DHost::ExecuteScroll(const gfx::Rect& clip,
    675                                         int dx, int dy,
    676                                         gfx::Rect* invalidated_rect) {
    677   gfx::ScrollCanvas(image_data_->GetCanvas(),
    678                     clip, gfx::Vector2d(dx, dy));
    679   *invalidated_rect = clip;
    680 }
    681 
    682 void PepperGraphics2DHost::ExecuteReplaceContents(PPB_ImageData_Impl* image,
    683                                                  gfx::Rect* invalidated_rect,
    684                                                  PP_Resource* old_image_data) {
    685   if (image->format() != image_data_->format()) {
    686     DCHECK(image->width() == image_data_->width() &&
    687            image->height() == image_data_->height());
    688     // Convert the image data if the format does not match.
    689     SkIRect src_irect = { 0, 0, image->width(), image->height() };
    690     SkRect dest_rect = { SkIntToScalar(0),
    691                          SkIntToScalar(0),
    692                          SkIntToScalar(image_data_->width()),
    693                          SkIntToScalar(image_data_->height()) };
    694     ConvertImageData(image, src_irect, image_data_.get(), dest_rect);
    695   } else {
    696     // The passed-in image may not be mapped in our process, and we need to
    697     // guarantee that the current backing store is always mapped.
    698     if (!image->Map())
    699       return;
    700 
    701     if (old_image_data)
    702       *old_image_data = image_data_->GetReference();
    703     image_data_ = image;
    704   }
    705   *invalidated_rect = gfx::Rect(0, 0,
    706                                 image_data_->width(), image_data_->height());
    707 }
    708 
    709 void PepperGraphics2DHost::SendFlushAck() {
    710   host()->SendReply(flush_reply_context_,
    711                     PpapiPluginMsg_Graphics2D_FlushAck());
    712 }
    713 
    714 void PepperGraphics2DHost::SendOffscreenFlushAck() {
    715   DCHECK(offscreen_flush_pending_);
    716 
    717   // We must clear this flag before issuing the callback. It will be
    718   // common for the plugin to issue another invalidate in response to a flush
    719   // callback, and we don't want to think that a callback is already pending.
    720   offscreen_flush_pending_ = false;
    721   SendFlushAck();
    722 }
    723 
    724 void PepperGraphics2DHost::ScheduleOffscreenFlushAck() {
    725   offscreen_flush_pending_ = true;
    726   base::MessageLoop::current()->PostDelayedTask(
    727       FROM_HERE,
    728       base::Bind(&PepperGraphics2DHost::SendOffscreenFlushAck,
    729                  weak_ptr_factory_.GetWeakPtr()),
    730       base::TimeDelta::FromMilliseconds(kOffscreenCallbackDelayMs));
    731 }
    732 
    733 bool PepperGraphics2DHost::HasPendingFlush() const {
    734   return need_flush_ack_ || offscreen_flush_pending_;
    735 }
    736 
    737 // static
    738 bool PepperGraphics2DHost::ConvertToLogicalPixels(float scale,
    739                                                  gfx::Rect* op_rect,
    740                                                  gfx::Point* delta) {
    741   if (scale == 1.0f || scale <= 0.0f)
    742     return true;
    743 
    744   gfx::Rect original_rect = *op_rect;
    745   // Take the enclosing rectangle after scaling so a rectangle scaled down then
    746   // scaled back up by the inverse scale would fully contain the entire area
    747   // affected by the original rectangle.
    748   *op_rect = gfx::ToEnclosingRect(gfx::ScaleRect(*op_rect, scale));
    749   if (delta) {
    750     gfx::Point original_delta = *delta;
    751     float inverse_scale = 1.0f / scale;
    752     *delta = gfx::ToFlooredPoint(gfx::ScalePoint(*delta, scale));
    753 
    754     gfx::Rect inverse_scaled_rect =
    755         gfx::ToEnclosingRect(gfx::ScaleRect(*op_rect, inverse_scale));
    756     if (original_rect != inverse_scaled_rect)
    757       return false;
    758     gfx::Point inverse_scaled_point =
    759         gfx::ToFlooredPoint(gfx::ScalePoint(*delta, inverse_scale));
    760     if (original_delta != inverse_scaled_point)
    761       return false;
    762   }
    763 
    764   return true;
    765 }
    766 
    767 }  // namespace content
    768