Skip to content
Snippets Groups Projects
Commit e55badb6 authored by brettw@chromium.org's avatar brettw@chromium.org
Browse files

Partially implement the new pepper API in Chrome. This is not actually hooked

up, which will require some changes in render_view as well as the plugin list.

TEST=none
BUG=none
Review URL: http://codereview.chromium.org/1697008

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@46760 0039d316-1c4b-4281-b951-d872f2087c98
parent 3d7a2199
No related merge requests found
Showing
with 1562 additions and 0 deletions
......@@ -141,6 +141,9 @@ deps = {
"src/third_party/ffmpeg/source/patched-ffmpeg-mt":
"/trunk/deps/third_party/ffmpeg/patched-ffmpeg-mt@" +
Var("ffmpeg_revision"),
"src/third_party/ppapi":
"http://ppapi.googlecode.com/svn/trunk@5",
}
......
......@@ -36,6 +36,7 @@
'../third_party/lzma_sdk/lzma_sdk.gyp:*',
'../third_party/modp_b64/modp_b64.gyp:*',
'../third_party/npapi/npapi.gyp:*',
'../third_party/ppapi/ppapi.gyp:*',
'../third_party/ots/ots.gyp:*',
'../third_party/sqlite/sqlite.gyp:*',
'../third_party/WebKit/WebKit/chromium/WebKit.gyp:*',
......
// Copyright (c) 2010 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.
#include "chrome/renderer/pepper_plugin_delegate_impl.h"
#include "app/surface/transport_dib.h"
#include "base/scoped_ptr.h"
namespace {
// Implements the Image2D using a TransportDIB.
class PlatformImage2DImpl : public pepper::PluginDelegate::PlatformImage2D {
public:
// This constructor will take ownership of the dib pointer.
PlatformImage2DImpl(int width, int height, TransportDIB* dib)
: width_(width),
height_(height),
dib_(dib) {
}
virtual skia::PlatformCanvas* Map() {
return dib_->GetPlatformCanvas(width_, height_);
}
virtual intptr_t GetSharedMemoryHandle() const {
return dib_->handle();
}
private:
int width_;
int height_;
scoped_ptr<TransportDIB> dib_;
DISALLOW_COPY_AND_ASSIGN(PlatformImage2DImpl);
};
} // namespace
PepperPluginDelegateImpl::PepperPluginDelegateImpl(RenderView* render_view)
: render_view_(render_view) {
}
pepper::PluginDelegate::PlatformImage2D*
PepperPluginDelegateImpl::CreateImage2D(int width, int height) {
uint32 buffer_size = width * height * 4;
// Allocate the transport DIB and the PlatformCanvas pointing to it.
#if defined(OS_MACOSX)
// On the Mac, shared memory has to be created in the browser in order to
// work in the sandbox. Do this by sending a message to the browser
// requesting a TransportDIB (see also
// chrome/renderer/webplugin_delegate_proxy.cc, method
// WebPluginDelegateProxy::CreateBitmap() for similar code). Note that the
// TransportDIB is _not_ cached in the browser; this is because this memory
// gets flushed by the renderer into another TransportDIB that represents the
// page, which is then in turn flushed to the screen by the browser process.
// When |transport_dib_| goes out of scope in the dtor, all of its shared
// memory gets reclaimed.
TransportDIB::Handle dib_handle;
IPC::Message* msg = new ViewHostMsg_AllocTransportDIB(buffer_size,
false,
&dib_handle);
if (!RenderThread::current()->Send(msg))
return NULL;
if (!TransportDIB::is_valid(dib_handle))
return NULL;
TransportDIB* dib = TransportDIB::Map(dib_handle);
#else
static int next_dib_id = 0;
TransportDIB* dib = TransportDIB::Create(buffer_size, next_dib_id++);
if (!dib)
return NULL;
#endif
return new PlatformImage2DImpl(width, height, dib);
}
// Copyright (c) 2010 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.
#ifndef CHROME_RENDERER_PEPPER_PLUGIN_DELEGATE_IMPL_H_
#define CHROME_RENDERER_PEPPER_PLUGIN_DELEGATE_IMPL_H_
#include "base/basictypes.h"
#include "webkit/glue/plugins/pepper_plugin_delegate.h"
class RenderView;
class PepperPluginDelegateImpl : public pepper::PluginDelegate {
public:
explicit PepperPluginDelegateImpl(RenderView* render_view);
// pepper::PluginDelegate implementation.
virtual PlatformImage2D* CreateImage2D(int width, int height);
private:
// Pointer to the RenderView that owns us.
RenderView* render_view_;
DISALLOW_COPY_AND_ASSIGN(PepperPluginDelegateImpl);
};
#endif // CHROME_RENDERER_PEPPER_PLUGIN_DELEGATE_IMPL_H_
include_rules = [
"+third_party/ppapi/c",
]
// Copyright (c) 2010 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.
#include "webkit/glue/plugins/pepper_device_context_2d.h"
#include "base/logging.h"
#include "gfx/point.h"
#include "gfx/rect.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/ppapi/c/pp_module.h"
#include "third_party/ppapi/c/pp_rect.h"
#include "third_party/ppapi/c/pp_resource.h"
#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_module.h"
#include "webkit/glue/plugins/pepper_resource_tracker.h"
#if defined(OS_MACOSX)
#include "base/mac_util.h"
#include "base/scoped_cftyperef.h"
#endif
namespace pepper {
namespace {
PP_Resource Create(PP_Module module_id, int32_t width, int32_t height) {
PluginModule* module = PluginModule::FromPPModule(module_id);
if (!module)
return NullPPResource();
scoped_refptr<DeviceContext2D> context(new DeviceContext2D(module));
if (!context->Init(width, height))
return NullPPResource();
context->AddRef(); // AddRef for the caller.
return context->GetResource();
}
void PaintImageData(PP_Resource device_context,
PP_Resource image,
int32_t x, int32_t y,
const PP_Rect* dirty,
uint32_t dirty_rect_count,
PPB_DeviceContext2D_PaintCallback callback,
void* callback_data) {
scoped_refptr<Resource> device_resource =
ResourceTracker::Get()->GetResource(device_context);
if (!device_resource.get())
return;
DeviceContext2D* context = device_resource->AsDeviceContext2D();
if (!context)
return;
context->PaintImageData(image, x, y, dirty, dirty_rect_count,
callback, callback_data);
}
const PPB_DeviceContext2D ppb_devicecontext2d = {
&Create,
&PaintImageData,
};
} // namespace
DeviceContext2D::DeviceContext2D(PluginModule* module) : Resource(module) {
}
DeviceContext2D::~DeviceContext2D() {
}
// static
const PPB_DeviceContext2D* DeviceContext2D::GetInterface() {
return &ppb_devicecontext2d;
}
bool DeviceContext2D::Init(int width, int height) {
image_data_.reset(new ImageData(module()));
if (!image_data_->Init(PP_IMAGEDATAFORMAT_BGRA_PREMUL, width, height) ||
!image_data_->Map()) {
image_data_.reset();
return false;
}
return true;
}
void DeviceContext2D::PaintImageData(PP_Resource image,
int32_t x, int32_t y,
const PP_Rect* dirty,
uint32_t dirty_rect_count,
PPB_DeviceContext2D_PaintCallback callback,
void* callback_data) {
scoped_refptr<Resource> image_resource =
ResourceTracker::Get()->GetResource(image);
if (!image_resource.get())
return;
ImageData* new_image_data = image_resource->AsImageData();
if (!new_image_data)
return;
const SkBitmap& new_image_bitmap = new_image_data->GetMappedBitmap();
// TODO(brettw) handle multiple dirty rects.
DCHECK(dirty_rect_count == 1);
// Draw the bitmap to the backing store.
SkIRect src_rect;
if (dirty->left == 0 && dirty->top == 0 &&
dirty->right == 0 && dirty->bottom == 0) {
// Default to the entire bitmap.
src_rect.fLeft = 0;
src_rect.fTop = 0;
src_rect.fRight = new_image_bitmap.width();
src_rect.fBottom = new_image_bitmap.height();
} else {
src_rect.fLeft = dirty->left;
src_rect.fTop = dirty->top;
src_rect.fRight = dirty->right;
src_rect.fBottom = dirty->bottom;
}
SkRect dest_rect = { SkIntToScalar(src_rect.fLeft),
SkIntToScalar(src_rect.fTop),
SkIntToScalar(src_rect.fRight),
SkIntToScalar(src_rect.fBottom) };
// We're guaranteed to have a mapped canvas since we mapped it in Init().
skia::PlatformCanvas* backing_canvas = image_data_->mapped_canvas();
// We want to replace the contents of the bitmap rather than blend.
SkPaint paint;
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
backing_canvas->drawBitmapRect(new_image_bitmap,
&src_rect, dest_rect, &paint);
// 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(dirty->left, dirty->top,
dirty->right - dirty->left,
dirty->bottom - dirty->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));
}
*/
}
void DeviceContext2D::Paint(WebKit::WebCanvas* canvas,
const gfx::Rect& plugin_rect,
const gfx::Rect& paint_rect) {
// We're guaranteed to have a mapped canvas since we mapped it in Init().
const SkBitmap& backing_bitmap = image_data_->GetMappedBitmap();
#if defined(OS_MACOSX)
SkAutoLockPixels lock(backing_bitmap);
scoped_cftyperef<CGDataProviderRef> data_provider(
CGDataProviderCreateWithData(
NULL, backing_bitmap.getAddr32(0, 0),
backing_bitmap.rowBytes() * backing_bitmap.height(), NULL));
scoped_cftyperef<CGImageRef> image(
CGImageCreate(
backing_bitmap.width(), backing_bitmap.height(),
8, 32, backing_bitmap.rowBytes(),
mac_util::GetSystemColorSpace(),
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
data_provider, NULL, false, kCGRenderingIntentDefault));
// Flip the transform
CGContextSaveGState(canvas);
float window_height = static_cast<float>(CGBitmapContextGetHeight(canvas));
CGContextTranslateCTM(canvas, 0, window_height);
CGContextScaleCTM(canvas, 1.0, -1.0);
CGRect bounds;
bounds.origin.x = plugin_rect.origin().x();
bounds.origin.y = window_height - plugin_rect.origin().y() -
backing_bitmap.height();
bounds.size.width = backing_bitmap.width();
bounds.size.height = backing_bitmap.height();
CGContextDrawImage(canvas, bounds, image);
CGContextRestoreGState(canvas);
#else
gfx::Point origin(plugin_rect.origin().x(), plugin_rect.origin().y());
canvas->drawBitmap(backing_bitmap,
SkIntToScalar(plugin_rect.origin().x()),
SkIntToScalar(plugin_rect.origin().y()));
#endif
}
} // namespace pepper
// Copyright (c) 2010 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.
#ifndef WEBKIT_GLUE_PLUGINS_PEPPER_DEVICE_CONTEXT_2D_H_
#define WEBKIT_GLUE_PLUGINS_PEPPER_DEVICE_CONTEXT_2D_H_
#include "base/basictypes.h"
#include "base/scoped_ptr.h"
#include "third_party/ppapi/c/ppb_device_context_2d.h"
#include "third_party/WebKit/WebKit/chromium/public/WebCanvas.h"
#include "webkit/glue/plugins/pepper_resource.h"
typedef struct _ppb_DeviceContext2D PPB_DeviceContext2D;
namespace gfx {
class Rect;
}
namespace pepper {
class ImageData;
class PluginModule;
class DeviceContext2D : public Resource {
public:
DeviceContext2D(PluginModule* module);
virtual ~DeviceContext2D();
// Returns a pointer to the interface implementing PPB_ImageData that is
// exposed to the plugin.
static const PPB_DeviceContext2D* GetInterface();
bool Init(int width, int height);
// Resource override.
virtual DeviceContext2D* AsDeviceContext2D() { return this; }
void PaintImageData(PP_Resource image,
int32_t x, int32_t y,
const PP_Rect* dirty,
uint32_t dirty_rect_count,
PPB_DeviceContext2D_PaintCallback callback,
void* callback_data);
void Paint(WebKit::WebCanvas* canvas,
const gfx::Rect& plugin_rect,
const gfx::Rect& paint_rect);
private:
scoped_ptr<ImageData> image_data_;
DISALLOW_COPY_AND_ASSIGN(DeviceContext2D);
};
} // namespace pepper
#endif // WEBKIT_GLUE_PLUGINS_PEPPER_DEVICE_CONTEXT_2D_H_
// Copyright (c) 2010 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.
#include "webkit/glue/plugins/pepper_image_data.h"
#include "base/scoped_ptr.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/ppapi/c/pp_instance.h"
#include "third_party/ppapi/c/pp_module.h"
#include "third_party/ppapi/c/pp_resource.h"
#include "third_party/ppapi/c/ppb_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"
namespace pepper {
namespace {
ImageData* ResourceAsImageData(PP_Resource resource) {
scoped_refptr<Resource> image_resource =
ResourceTracker::Get()->GetResource(resource);
if (!image_resource.get())
return NULL;
return image_resource->AsImageData();
}
PP_Resource Create(PP_Module module_id,
PP_ImageDataFormat format,
int32_t width,
int32_t height) {
PluginModule* module = PluginModule::FromPPModule(module_id);
if (!module)
return NullPPResource();
scoped_refptr<ImageData> data(new ImageData(module));
if (!data->Init(format, width, height))
return NullPPResource();
data->AddRef(); // AddRef for the caller.
return data->GetResource();
}
bool IsImageData(PP_Resource resource) {
scoped_refptr<Resource> image_resource =
ResourceTracker::Get()->GetResource(resource);
if (!image_resource.get())
return false;
return !!image_resource->AsImageData();
}
bool Describe(PP_Resource resource,
PP_ImageDataDesc* desc) {
// Give predictable values on failure.
memset(desc, 0, sizeof(PP_ImageDataDesc));
ImageData* image_data = ResourceAsImageData(resource);
if (!image_data)
return false;
image_data->Describe(desc);
return true;
}
void* Map(PP_Resource resource) {
ImageData* image_data = ResourceAsImageData(resource);
if (!image_data)
return NULL;
return image_data->Map();
}
void Unmap(PP_Resource resource) {
ImageData* image_data = ResourceAsImageData(resource);
if (!image_data)
return;
return image_data->Unmap();
}
const PPB_ImageData ppb_imagedata = {
&Create,
&IsImageData,
&Describe,
&Map,
&Unmap,
};
} // namespace
ImageData::ImageData(PluginModule* module)
: Resource(module),
width_(0),
height_(0) {
}
ImageData::~ImageData() {
}
// static
const PPB_ImageData* ImageData::GetInterface() {
return &ppb_imagedata;
}
bool ImageData::Init(PP_ImageDataFormat format,
int width,
int height) {
// TODO(brettw) this should be called only on the main thread!
platform_image_.reset(
module()->GetSomeInstance()->delegate()->CreateImage2D(width, height));
width_ = width;
height_ = height;
return !!platform_image_.get();
}
void ImageData::Describe(PP_ImageDataDesc* desc) const {
desc->format = PP_IMAGEDATAFORMAT_BGRA_PREMUL;
desc->width = width_;
desc->height = height_;
desc->stride = width_ * 4;
}
void* ImageData::Map() {
if (!mapped_canvas_.get()) {
mapped_canvas_.reset(platform_image_->Map());
if (!mapped_canvas_.get())
return NULL;
}
const SkBitmap& bitmap =
mapped_canvas_->getTopPlatformDevice().accessBitmap(true);
// Our platform bitmaps are set to opaque by default, which we don't want.
const_cast<SkBitmap&>(bitmap).setIsOpaque(false);
bitmap.lockPixels();
return bitmap.getAddr32(0, 0);
}
void ImageData::Unmap() {
// This is currently unimplemented, which is OK. The data will just always
// be around once it's mapped. Chrome's TransportDIB isn't currently
// unmappable without freeing it, but this may be something we want to support
// in the future to save some memory.
}
const SkBitmap& ImageData::GetMappedBitmap() const {
return mapped_canvas_->getTopPlatformDevice().accessBitmap(false);
}
} // namespace pepper
// Copyright (c) 2010 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.
#ifndef WEBKIT_GLUE_PLUGINS_PEPPER_IMAGE_DATA_H_
#define WEBKIT_GLUE_PLUGINS_PEPPER_IMAGE_DATA_H_
#include "base/scoped_ptr.h"
#include "third_party/ppapi/c/ppb_image_data.h"
#include "webkit/glue/plugins/pepper_plugin_delegate.h"
#include "webkit/glue/plugins/pepper_resource.h"
typedef struct _ppb_ImageData PPB_ImageData;
namespace skia {
class PlatformCanvas;
}
class SkBitmap;
namespace pepper {
class PluginInstance;
class ImageData : public Resource {
public:
explicit ImageData(PluginModule* module);
virtual ~ImageData();
// Returns a pointer to the interface implementing PPB_ImageData that is
// exposed to the plugin.
static const PPB_ImageData* GetInterface();
// Resource overrides.
ImageData* AsImageData() { return this; }
// PPB_ImageData implementation.
bool Init(PP_ImageDataFormat format,
int width,
int height);
void Describe(PP_ImageDataDesc* desc) const;
void* Map();
void Unmap();
skia::PlatformCanvas* mapped_canvas() const { return mapped_canvas_.get(); }
const SkBitmap& GetMappedBitmap() const;
private:
scoped_ptr<PluginDelegate::PlatformImage2D> platform_image_;
// When the device is mapped, this is the image. Null when umapped.
scoped_ptr<skia::PlatformCanvas> mapped_canvas_;
int width_;
int height_;
};
} // namespace pepper
#endif // WEBKIT_GLUE_PLUGINS_PEPPER_IMAGE_DATA_H_
// Copyright (c) 2010 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.
#ifndef WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_DELEGATE_H_
#define WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_DELEGATE_H_
#include "third_party/ppapi/c/pp_stdint.h"
namespace skia {
class PlatformCanvas;
}
namespace pepper {
// Virtual interface that the browser implements to implement features for
// Pepper plugins.
class PluginDelegate {
public:
// Represents an image. This is to allow the browser layer to supply a correct
// image representation. In Chrome, this will be a TransportDIB.
class PlatformImage2D {
public:
virtual ~PlatformImage2D() {}
// Caller will own the returned pointer, returns NULL on failure.
virtual skia::PlatformCanvas* Map() = 0;
// Returns the platform-specific shared memory handle of the data backing
// this image. This is used by NativeClient to send the image to the
// out-of-process plugin. Returns 0 on failure.
virtual intptr_t GetSharedMemoryHandle() const = 0;
};
// The caller will own the pointer returned from this.
virtual PlatformImage2D* CreateImage2D(int width, int height) = 0;
};
} // namespace pepper
#endif // WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_DELEGATE_H_
// Copyright (c) 2010 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.
#include "webkit/glue/plugins/pepper_plugin_instance.h"
#include "base/logging.h"
#include "base/scoped_ptr.h"
#include "gfx/rect.h"
#include "third_party/ppapi/c/pp_instance.h"
#include "third_party/ppapi/c/pp_event.h"
#include "third_party/ppapi/c/pp_rect.h"
#include "third_party/ppapi/c/pp_resource.h"
#include "third_party/ppapi/c/ppb_instance.h"
#include "third_party/ppapi/c/ppp_instance.h"
#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.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"
using WebKit::WebInputEvent;
namespace pepper {
namespace {
void RectToPPRect(const gfx::Rect& input, PP_Rect* output) {
output->left = input.x();
output->top = input.y();
output->right = input.right();
output->bottom = input.bottom();
}
PP_Event_Type ConvertEventTypes(WebInputEvent::Type wetype) {
switch (wetype) {
case WebInputEvent::MouseDown:
return PP_Event_Type_MouseDown;
case WebInputEvent::MouseUp:
return PP_Event_Type_MouseUp;
case WebInputEvent::MouseMove:
return PP_Event_Type_MouseMove;
case WebInputEvent::MouseEnter:
return PP_Event_Type_MouseEnter;
case WebInputEvent::MouseLeave:
return PP_Event_Type_MouseLeave;
case WebInputEvent::MouseWheel:
return PP_Event_Type_MouseWheel;
case WebInputEvent::RawKeyDown:
return PP_Event_Type_RawKeyDown;
case WebInputEvent::KeyDown:
return PP_Event_Type_KeyDown;
case WebInputEvent::KeyUp:
return PP_Event_Type_KeyUp;
case WebInputEvent::Char:
return PP_Event_Type_Char;
case WebInputEvent::Undefined:
default:
return PP_Event_Type_Undefined;
}
}
void BuildKeyEvent(const WebInputEvent* event, PP_Event* pp_event) {
const WebKit::WebKeyboardEvent* key_event =
reinterpret_cast<const WebKit::WebKeyboardEvent*>(event);
pp_event->u.key.modifier = key_event->modifiers;
pp_event->u.key.normalizedKeyCode = key_event->windowsKeyCode;
}
void BuildCharEvent(const WebInputEvent* event, PP_Event* pp_event) {
const WebKit::WebKeyboardEvent* key_event =
reinterpret_cast<const WebKit::WebKeyboardEvent*>(event);
pp_event->u.character.modifier = key_event->modifiers;
// For consistency, check that the sizes of the texts agree.
DCHECK(sizeof(pp_event->u.character.text) == sizeof(key_event->text));
DCHECK(sizeof(pp_event->u.character.unmodifiedText) ==
sizeof(key_event->unmodifiedText));
for (size_t i = 0; i < WebKit::WebKeyboardEvent::textLengthCap; ++i) {
pp_event->u.character.text[i] = key_event->text[i];
pp_event->u.character.unmodifiedText[i] = key_event->unmodifiedText[i];
}
}
void BuildMouseEvent(const WebInputEvent* event, PP_Event* pp_event) {
const WebKit::WebMouseEvent* mouse_event =
reinterpret_cast<const WebKit::WebMouseEvent*>(event);
pp_event->u.mouse.modifier = mouse_event->modifiers;
pp_event->u.mouse.button = mouse_event->button;
pp_event->u.mouse.x = mouse_event->x;
pp_event->u.mouse.y = mouse_event->y;
pp_event->u.mouse.clickCount = mouse_event->clickCount;
}
void BuildMouseWheelEvent(const WebInputEvent* event, PP_Event* pp_event) {
const WebKit::WebMouseWheelEvent* mouse_wheel_event =
reinterpret_cast<const WebKit::WebMouseWheelEvent*>(event);
pp_event->u.wheel.modifier = mouse_wheel_event->modifiers;
pp_event->u.wheel.deltaX = mouse_wheel_event->deltaX;
pp_event->u.wheel.deltaY = mouse_wheel_event->deltaY;
pp_event->u.wheel.wheelTicksX = mouse_wheel_event->wheelTicksX;
pp_event->u.wheel.wheelTicksY = mouse_wheel_event->wheelTicksY;
pp_event->u.wheel.scrollByPage = mouse_wheel_event->scrollByPage;
}
bool BindGraphicsDeviceContext(PP_Instance instance_id, PP_Resource device_id) {
PluginInstance* instance = PluginInstance::FromPPInstance(instance_id);
if (!instance)
return false;
return instance->BindGraphicsDeviceContext(device_id);
}
const PPB_Instance ppb_instance = {
&BindGraphicsDeviceContext,
};
} // namespace
PluginInstance::PluginInstance(PluginDelegate* delegate,
PluginModule* module,
const PPP_Instance* instance_interface)
: delegate_(delegate),
module_(module),
instance_interface_(instance_interface) {
DCHECK(delegate);
module_->InstanceCreated(this);
}
PluginInstance::~PluginInstance() {
module_->InstanceDeleted(this);
}
// static
const PPB_Instance* PluginInstance::GetInterface() {
return &ppb_instance;
}
// static
PluginInstance* PluginInstance::FromPPInstance(PP_Instance instance) {
return reinterpret_cast<PluginInstance*>(instance.id);
}
PP_Instance PluginInstance::GetPPInstance() {
PP_Instance ret;
ret.id = reinterpret_cast<intptr_t>(this);
return ret;
}
void PluginInstance::Paint(WebKit::WebCanvas* canvas,
const gfx::Rect& plugin_rect,
const gfx::Rect& paint_rect) {
if (device_context_2d_)
device_context_2d_->Paint(canvas, plugin_rect, paint_rect);
}
bool PluginInstance::BindGraphicsDeviceContext(PP_Resource device_id) {
scoped_refptr<Resource> device_resource =
ResourceTracker::Get()->GetResource(device_id);
if (!device_resource.get())
return false;
DeviceContext2D* device_2d = device_resource->AsDeviceContext2D();
if (device_2d) {
device_context_2d_ = device_2d;
// TODO(brettw) repaint the plugin.
}
return true;
}
void PluginInstance::Delete() {
instance_interface_->Delete(GetPPInstance());
}
bool PluginInstance::Initialize(const std::vector<std::string>& arg_names,
const std::vector<std::string>& arg_values) {
if (!instance_interface_->New(GetPPInstance()))
return false;
size_t argc = 0;
scoped_array<const char*> argn(new const char*[arg_names.size()]);
scoped_array<const char*> argv(new const char*[arg_names.size()]);
for (size_t i = 0; i < arg_names.size(); ++i) {
argn[argc] = arg_names[i].c_str();
argv[argc] = arg_values[i].c_str();
argc++;
}
return instance_interface_->Initialize(GetPPInstance(),
argc, argn.get(), argv.get());
}
bool PluginInstance::HandleInputEvent(const WebKit::WebInputEvent& event,
WebKit::WebCursorInfo* cursor_info) {
PP_Event pp_event;
pp_event.type = ConvertEventTypes(event.type);
pp_event.size = sizeof(pp_event);
pp_event.time_stamp_seconds = event.timeStampSeconds;
switch (pp_event.type) {
case PP_Event_Type_Undefined:
return false;
case PP_Event_Type_MouseDown:
case PP_Event_Type_MouseUp:
case PP_Event_Type_MouseMove:
case PP_Event_Type_MouseEnter:
case PP_Event_Type_MouseLeave:
BuildMouseEvent(&event, &pp_event);
break;
case PP_Event_Type_MouseWheel:
BuildMouseWheelEvent(&event, &pp_event);
break;
case PP_Event_Type_RawKeyDown:
case PP_Event_Type_KeyDown:
case PP_Event_Type_KeyUp:
BuildKeyEvent(&event, &pp_event);
break;
case PP_Event_Type_Char:
BuildCharEvent(&event, &pp_event);
break;
}
return instance_interface_->HandleEvent(GetPPInstance(), &pp_event);
}
void PluginInstance::ViewChanged(const gfx::Rect& position,
const gfx::Rect& clip) {
PP_Rect pp_position, pp_clip;
RectToPPRect(position, &pp_position);
RectToPPRect(clip, &pp_clip);
instance_interface_->ViewChanged(GetPPInstance(), &pp_position, &pp_clip);
}
} // namespace pepper
// Copyright (c) 2010 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.
#ifndef WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_INSTANCE_H_
#define WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_INSTANCE_H_
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "base/ref_counted.h"
#include "third_party/WebKit/WebKit/chromium/public/WebCanvas.h"
typedef struct _pp_Instance PP_Instance;
typedef struct _pp_Resource PP_Resource;
typedef struct _ppb_Instance PPB_Instance;
typedef struct _ppp_Instance PPP_Instance;
namespace gfx {
class Rect;
}
namespace WebKit {
struct WebCursorInfo;
class WebInputEvent;
}
namespace pepper {
class DeviceContext2D;
class PluginDelegate;
class PluginModule;
class PluginInstance : public base::RefCounted<PluginInstance> {
public:
PluginInstance(PluginDelegate* delegate,
PluginModule* module,
const PPP_Instance* instance_interface);
~PluginInstance();
static const PPB_Instance* GetInterface();
// Converts the given instance ID to an actual instance object.
static PluginInstance* FromPPInstance(PP_Instance instance);
PluginDelegate* delegate() const { return delegate_; }
PluginModule* module() const { return module_.get(); }
PP_Instance GetPPInstance();
void Paint(WebKit::WebCanvas* canvas,
const gfx::Rect& plugin_rect,
const gfx::Rect& paint_rect);
// PPB_Instance implementation.
bool BindGraphicsDeviceContext(PP_Resource device_id);
// PPP_Instance pass-through.
void Delete();
bool Initialize(const std::vector<std::string>& arg_names,
const std::vector<std::string>& arg_values);
bool HandleInputEvent(const WebKit::WebInputEvent& event,
WebKit::WebCursorInfo* cursor_info);
void ViewChanged(const gfx::Rect& position, const gfx::Rect& clip);
private:
PluginDelegate* delegate_;
scoped_refptr<PluginModule> module_;
const PPP_Instance* instance_interface_;
// The current device context for painting in 2D.
scoped_refptr<DeviceContext2D> device_context_2d_;
DISALLOW_COPY_AND_ASSIGN(PluginInstance);
};
} // namespace pepper
#endif // WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_INSTANCE_H_
// Copyright (c) 2010 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.
#include "webkit/glue/plugins/pepper_plugin_module.h"
#include <set>
#include "base/logging.h"
#include "base/scoped_ptr.h"
#include "third_party/ppapi/c/ppb_core.h"
#include "third_party/ppapi/c/ppb_device_context_2d.h"
#include "third_party/ppapi/c/ppb_image_data.h"
#include "third_party/ppapi/c/ppb_instance.h"
#include "third_party/ppapi/c/ppb_var.h"
#include "third_party/ppapi/c/ppp.h"
#include "third_party/ppapi/c/ppp_instance.h"
#include "third_party/ppapi/c/pp_module.h"
#include "third_party/ppapi/c/pp_resource.h"
#include "third_party/ppapi/c/pp_var.h"
#include "webkit/glue/plugins/pepper_device_context_2d.h"
#include "webkit/glue/plugins/pepper_image_data.h"
#include "webkit/glue/plugins/pepper_plugin_instance.h"
#include "webkit/glue/plugins/pepper_resource_tracker.h"
#include "webkit/glue/plugins/pepper_var.h"
typedef bool (*PPP_InitializeModuleFunc)(PP_Module, PPB_GetInterface);
typedef void (*PPP_ShutdownModuleFunc)();
namespace pepper {
namespace {
// Maintains all currently loaded plugin libs for validating PP_Module
// identifiers.
typedef std::set<PluginModule*> PluginModuleSet;
PluginModuleSet* GetLivePluginSet() {
static PluginModuleSet live_plugin_libs;
return &live_plugin_libs;
}
// PPB_Core --------------------------------------------------------------------
void AddRefResource(PP_Resource resource) {
Resource* res = ResourceTracker::Get()->GetResource(resource);
if (!res) {
DLOG(WARNING) << "AddRef()ing a nonexistent resource";
return;
}
res->AddRef();
}
void ReleaseResource(PP_Resource resource) {
Resource* res = ResourceTracker::Get()->GetResource(resource);
if (!res) {
DLOG(WARNING) << "Release()ing a nonexistent resource";
return;
}
res->Release();
}
const PPB_Core core_interface = {
&AddRefResource,
&ReleaseResource,
};
// GetInterface ----------------------------------------------------------------
const void* GetInterface(const char* name) {
if (strcmp(name, PPB_CORE_INTERFACE) == 0)
return &core_interface;
if (strcmp(name, PPB_VAR_INTERFACE) == 0)
return GetVarInterface();
if (strcmp(name, PPB_INSTANCE_INTERFACE) == 0)
return PluginInstance::GetInterface();
if (strcmp(name, PPB_IMAGEDATA_INTERFACE) == 0)
return ImageData::GetInterface();
if (strcmp(name, PPB_DEVICECONTEXT2D_INTERFACE) == 0)
return DeviceContext2D::GetInterface();
return NULL;
}
} // namespace
PluginModule::PluginModule(const FilePath& filename)
: filename_(filename),
initialized_(false),
library_(0),
ppp_get_interface_(NULL) {
GetLivePluginSet()->insert(this);
}
PluginModule::~PluginModule() {
// When the module is being deleted, there should be no more instances still
// holding a reference to us.
DCHECK(instances_.empty());
GetLivePluginSet()->erase(this);
if (library_) {
PPP_ShutdownModuleFunc shutdown_module =
reinterpret_cast<PPP_ShutdownModuleFunc>(
base::GetFunctionPointerFromNativeLibrary(library_,
"PPP_ShutdownModule"));
if (shutdown_module)
shutdown_module();
base::UnloadNativeLibrary(library_);
}
}
// static
scoped_refptr<PluginModule> PluginModule::CreateModule(
const FilePath& filename) {
// FIXME(brettw) do uniquifying of the plugin here like the NPAPI one.
scoped_refptr<PluginModule> lib(new PluginModule(filename));
if (!lib->Load())
lib = NULL;
return lib;
}
// static
PluginModule* PluginModule::FromPPModule(PP_Module module) {
PluginModule* lib = reinterpret_cast<PluginModule*>(module.id);
if (GetLivePluginSet()->find(lib) == GetLivePluginSet()->end())
return NULL; // Invalid plugin.
return lib;
}
bool PluginModule::Load() {
if (initialized_)
return true;
initialized_ = true;
library_ = base::LoadNativeLibrary(filename_);
if (!library_)
return false;
// Save the GetInterface function pointer for later.
ppp_get_interface_ =
reinterpret_cast<PPP_GetInterfaceFunc>(
base::GetFunctionPointerFromNativeLibrary(library_,
"PPP_GetInterface"));
if (!ppp_get_interface_) {
LOG(WARNING) << "No PPP_GetInterface in plugin library";
return false;
}
// Call the plugin initialize function.
PPP_InitializeModuleFunc initialize_module =
reinterpret_cast<PPP_InitializeModuleFunc>(
base::GetFunctionPointerFromNativeLibrary(library_,
"PPP_InitializeModule"));
if (!initialize_module) {
LOG(WARNING) << "No PPP_InitializeModule in plugin library";
return false;
}
int retval = initialize_module(GetPPModule(), &GetInterface);
if (retval != 0) {
LOG(WARNING) << "PPP_InitializeModule returned failure " << retval;
return false;
}
return true;
}
PP_Module PluginModule::GetPPModule() const {
PP_Module ret;
ret.id = reinterpret_cast<intptr_t>(this);
return ret;
}
PluginInstance* PluginModule::CreateInstance(PluginDelegate* delegate) {
const PPP_Instance* plugin_instance_interface =
reinterpret_cast<const PPP_Instance*>(GetPluginInterface(
PPP_INSTANCE_INTERFACE));
if (!plugin_instance_interface) {
LOG(WARNING) << "Plugin doesn't support instance interface, failing.";
return NULL;
}
return new PluginInstance(delegate, this, plugin_instance_interface);
}
PluginInstance* PluginModule::GetSomeInstance() const {
// This will generally crash later if there is not actually any instance to
// return, so we force a crash now to make bugs easier to track down.
CHECK(!instances_.empty());
return *instances_.begin();
}
const void* PluginModule::GetPluginInterface(const char* name) const {
if (!ppp_get_interface_)
return NULL;
return ppp_get_interface_(name);
}
void PluginModule::InstanceCreated(PluginInstance* instance) {
instances_.insert(instance);
}
void PluginModule::InstanceDeleted(PluginInstance* instance) {
instances_.erase(instance);
}
} // namespace pepper
// Copyright (c) 2010 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.
#ifndef WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_MODULE_H_
#define WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_MODULE_H_
#include <set>
#include "base/basictypes.h"
#include "base/file_path.h"
#include "base/native_library.h"
#include "base/ref_counted.h"
typedef struct _pp_Module PP_Module;
namespace pepper {
class PluginDelegate;
class PluginInstance;
class PluginModule : public base::RefCounted<PluginModule> {
public:
~PluginModule();
static scoped_refptr<PluginModule> CreateModule(const FilePath& filename);
// Converts the given module ID to an actual module object. Will return NULL
// if the module is invalid.
static PluginModule* FromPPModule(PP_Module module);
PP_Module GetPPModule() const;
PluginInstance* CreateInstance(PluginDelegate* delegate);
// Returns "some" plugin instance associated with this module. This is not
// guaranteed to be any one in particular. This is normally used to execute
// callbacks up to the browser layer that are not inherently per-instance,
// but the delegate lives only on the plugin instance so we need one of them.
PluginInstance* GetSomeInstance() const;
const void* GetPluginInterface(const char* name) const;
// This module is associated with a set of instances. The PluginInstance
// object declares its association with this module in its destructor and
// releases us in its destructor.
void InstanceCreated(PluginInstance* instance);
void InstanceDeleted(PluginInstance* instance);
private:
typedef const void* (*PPP_GetInterfaceFunc)(const char*);
explicit PluginModule(const FilePath& filename);
bool Load();
FilePath filename_;
bool initialized_;
base::NativeLibrary library_;
PPP_GetInterfaceFunc ppp_get_interface_;
// Non-owning pointers to all instances associated with this module. When
// there are no more instances, this object should be deleted.
typedef std::set<PluginInstance*> PluginInstanceSet;
PluginInstanceSet instances_;
DISALLOW_COPY_AND_ASSIGN(PluginModule);
};
} // namespace pepper
#endif // WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_MODULE_H_
// Copyright (c) 2010 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.
#include "webkit/glue/plugins/pepper_resource.h"
#include "third_party/ppapi/c/pp_resource.h"
#include "webkit/glue/plugins/pepper_resource_tracker.h"
namespace pepper {
PP_Resource NullPPResource() {
PP_Resource ret = { 0 };
return ret;
}
Resource::Resource(PluginModule* module) : module_(module) {
ResourceTracker::Get()->AddResource(this);
}
Resource::~Resource() {
ResourceTracker::Get()->DeleteResource(this);
}
PP_Resource Resource::GetResource() const {
PP_Resource ret;
ret.id = reinterpret_cast<intptr_t>(this);
return ret;
}
} // namespace pepper
// Copyright (c) 2010 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.
#ifndef WEBKIT_GLUE_PLUGINS_PEPPER_RESOURCE_H_
#define WEBKIT_GLUE_PLUGINS_PEPPER_RESOURCE_H_
#include "base/basictypes.h"
#include "base/ref_counted.h"
typedef struct _pp_Resource PP_Resource;
namespace pepper {
class DeviceContext2D;
class ImageData;
class PluginModule;
class Resource : public base::RefCountedThreadSafe<Resource> {
public:
explicit Resource(PluginModule* module);
virtual ~Resource();
PP_Resource GetResource() const;
PluginModule* module() const { return module_; }
// Type-specific getters for individual resource types. These will return
// NULL if the resource does not match the specified type.
virtual DeviceContext2D* AsDeviceContext2D() { return NULL; }
virtual ImageData* AsImageData() { return NULL; }
private:
PluginModule* module_; // Non-owning pointer to our module.
DISALLOW_COPY_AND_ASSIGN(Resource);
};
// Returns a "NULL" resource. This is just a helper function so callers
// can avoid creating a resource with a 0 ID.
PP_Resource NullPPResource();
} // namespace pepper
#endif // WEBKIT_GLUE_PLUGINS_PEPPER_RESOURCE_H_
// Copyright (c) 2010 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.
#include "webkit/glue/plugins/pepper_resource_tracker.h"
#include <set>
#include "base/logging.h"
#include "third_party/ppapi/c/pp_resource.h"
#include "webkit/glue/plugins/pepper_resource.h"
namespace pepper {
ResourceTracker::ResourceTracker() {
}
ResourceTracker::~ResourceTracker() {
}
// static
ResourceTracker* ResourceTracker::Get() {
return Singleton<ResourceTracker>::get();
}
Resource* ResourceTracker::GetResource(PP_Resource res) const {
AutoLock lock(lock_);
Resource* resource = reinterpret_cast<Resource*>(res.id);
if (live_resources_.find(resource) == live_resources_.end())
return NULL;
return resource;
}
void ResourceTracker::AddResource(Resource* resource) {
AutoLock lock(lock_);
DCHECK(live_resources_.find(resource) == live_resources_.end());
live_resources_.insert(resource);
}
void ResourceTracker::DeleteResource(Resource* resource) {
AutoLock lock(lock_);
ResourceSet::iterator found = live_resources_.find(resource);
if (found == live_resources_.end()) {
NOTREACHED();
return;
}
live_resources_.erase(found);
}
} // namespace pepper
// Copyright (c) 2010 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.
#ifndef WEBKIT_GLUE_PLUGINS_PEPPER_RESOURCE_TRACKER_H_
#define WEBKIT_GLUE_PLUGINS_PEPPER_RESOURCE_TRACKER_H_
#include <set>
#include "base/atomic_sequence_num.h"
#include "base/basictypes.h"
#include "base/lock.h"
#include "base/ref_counted.h"
#include "base/singleton.h"
typedef struct _pp_Resource PP_Resource;
namespace pepper {
class Resource;
// This class maintains a global list of all live pepper resources. It allows
// us to check resource ID validity and to map them to a specific module.
//
// This object is threadsafe.
class ResourceTracker {
public:
// Returns the pointer to the singleton object.
static ResourceTracker* Get();
// The returned pointer will be NULL if there is no resource.
Resource* GetResource(PP_Resource res) const;
// Adds the given resource to the tracker and assigns it a resource ID. The
// assigned resource ID will be returned.
void AddResource(Resource* resource);
void DeleteResource(Resource* resource);
private:
friend struct DefaultSingletonTraits<ResourceTracker>;
ResourceTracker();
~ResourceTracker();
// Hold this lock when accessing this object's members.
mutable Lock lock_;
typedef std::set<Resource*> ResourceSet;
ResourceSet live_resources_;
DISALLOW_COPY_AND_ASSIGN(ResourceTracker);
};
} // namespace pepper
#endif // WEBKIT_GLUE_PLUGINS_PEPPER_RESOURCE_TRACKER_H_
// Copyright (c) 2010 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.
#ifndef WEBKIT_GLUE_PLUGINS_PEPPER_STRING_H_
#define WEBKIT_GLUE_PLUGINS_PEPPER_STRING_H_
#include <string>
#include "base/basictypes.h"
#include "base/ref_counted.h"
namespace pepper {
class String : public base::RefCountedThreadSafe<String> {
public:
String(const char* str, uint32 len) : value_(str, len) {
}
const std::string& value() const { return value_; }
private:
std::string value_;
DISALLOW_COPY_AND_ASSIGN(String);
};
} // namespace pepper
#endif // WEBKIT_GLUE_PLUGINS_PEPPER_STRING_H_
// Copyright (c) 2010 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.
#include "webkit/glue/plugins/pepper_var.h"
#include "third_party/ppapi/c/pp_var.h"
#include "third_party/ppapi/c/ppb_var.h"
#include "webkit/glue/plugins/pepper_string.h"
namespace pepper {
namespace {
void AddRef(PP_Var var) {
if (var.type == PP_VarType_String) {
reinterpret_cast<String*>(var.value.as_id)->AddRef();
} else if (var.type == PP_VarType_Object) {
// TODO(implement objects).
}
}
void Release(PP_Var var) {
if (var.type == PP_VarType_String) {
reinterpret_cast<String*>(var.value.as_id)->Release();
} else if (var.type == PP_VarType_Object) {
// TODO(implement objects).
}
}
PP_Var VarFromUtf8(const char* data, uint32_t len) {
PP_Var ret;
ret.type = PP_VarType_String;
String* str = new String(data, len);
str->AddRef(); // This is for the caller, we return w/ a refcount of 1.
return ret;
}
const char* VarToUtf8(PP_Var var, uint32_t* len) {
if (var.type != PP_VarType_String) {
*len = 0;
return NULL;
}
const std::string& str =
reinterpret_cast<const String*>(var.value.as_id)->value();
*len = static_cast<uint32_t>(str.size());
if (str.empty())
return ""; // Don't return NULL on success.
return str.data();
}
bool HasProperty(PP_Var object,
PP_Var name,
PP_Var* exception) {
// TODO(brettw) implement this.
return false;
}
PP_Var GetProperty(PP_Var object,
PP_Var name,
PP_Var* exception) {
// TODO(brettw) implement this.
PP_Var ret;
ret.type = PP_VarType_Void;
return ret;
}
void GetAllPropertyNames(PP_Var object,
uint32_t* property_count,
PP_Var** properties,
PP_Var* exception) {
// TODO(brettw) implement this.
}
void SetProperty(PP_Var object,
PP_Var name,
PP_Var value,
PP_Var* exception) {
// TODO(brettw) implement this.
}
void RemoveProperty(PP_Var object,
PP_Var name,
PP_Var* exception) {
// TODO(brettw) implement this.
}
PP_Var Call(PP_Var object,
PP_Var method_name,
int32_t argc,
PP_Var* argv,
PP_Var* exception) {
// TODO(brettw) implement this.
PP_Var ret;
ret.type = PP_VarType_Void;
return ret;
}
PP_Var Construct(PP_Var object,
int32_t argc,
PP_Var* argv,
PP_Var* exception) {
// TODO(brettw) implement this.
PP_Var ret;
ret.type = PP_VarType_Void;
return ret;
}
PP_Var CreateObject(const PPP_Class* object_class,
void* object_data) {
// TODO(brettw) implement this.
PP_Var ret;
ret.type = PP_VarType_Void;
return ret;
}
const PPB_Var var_interface = {
&AddRef,
&Release,
&VarFromUtf8,
&VarToUtf8,
&HasProperty,
&GetProperty,
&GetAllPropertyNames,
&SetProperty,
&RemoveProperty,
&Call,
&Construct,
&CreateObject
};
} // namespace
const PPB_Var* GetVarInterface() {
return &var_interface;
}
} // namespace pepper
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment