#include "ppapi/proxy/ppb_image_data_proxy.h"
#include <string.h>
#include <map>
#include <vector>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/singleton.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/nacl/common/buildflags.h"
#include "ppapi/c/pp_completion_callback.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/pp_resource.h"
#include "ppapi/proxy/enter_proxy.h"
#include "ppapi/proxy/host_dispatcher.h"
#include "ppapi/proxy/plugin_dispatcher.h"
#include "ppapi/proxy/plugin_globals.h"
#include "ppapi/proxy/plugin_resource_tracker.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/shared_impl/host_resource.h"
#include "ppapi/shared_impl/proxy_lock.h"
#include "ppapi/shared_impl/resource.h"
#include "ppapi/shared_impl/scoped_pp_resource.h"
#include "ppapi/thunk/enter.h"
#include "ppapi/thunk/thunk.h"
#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_MINIMAL_TOOLCHAIN)
#include "skia/ext/platform_canvas.h"
#include "ui/surface/transport_dib.h"
#endif
using ppapi::thunk::PPB_ImageData_API;
namespace ppapi {
namespace proxy {
namespace {
static const int kMaxAgeSeconds = 2;
struct ImageDataCacheEntry {
ImageDataCacheEntry() : usable(false) {}
explicit ImageDataCacheEntry(ImageData* i)
: added_time(base::TimeTicks::Now()), usable(false), image(i) {}
base::TimeTicks added_time;
bool usable;
scoped_refptr<ImageData> image;
};
class ImageDataInstanceCache {
public:
ImageDataInstanceCache() : next_insertion_point_(0) {}
scoped_refptr<ImageData> Get(PPB_ImageData_Shared::ImageDataType type,
int width, int height,
PP_ImageDataFormat format);
void Add(ImageData* image_data);
void ImageDataUsable(ImageData* image_data);
bool ExpireEntries();
private:
void IncrementInsertionPoint();
static const size_t kCacheSize = 2;
ImageDataCacheEntry images_[kCacheSize];
size_t next_insertion_point_;
};
scoped_refptr<ImageData> ImageDataInstanceCache::Get(
PPB_ImageData_Shared::ImageDataType type,
int width, int height,
PP_ImageDataFormat format) {
for (size_t i = 0; i < kCacheSize; i++) {
if (!images_[i].usable)
continue;
if (images_[i].image->type() != type)
continue;
const PP_ImageDataDesc& desc = images_[i].image->desc();
if (desc.format == format &&
desc.size.width == width && desc.size.height == height) {
scoped_refptr<ImageData> ret(images_[i].image);
images_[i] = ImageDataCacheEntry();
next_insertion_point_ = i;
return ret;
}
}
return scoped_refptr<ImageData>();
}
void ImageDataInstanceCache::Add(ImageData* image_data) {
images_[next_insertion_point_] = ImageDataCacheEntry(image_data);
IncrementInsertionPoint();
}
void ImageDataInstanceCache::ImageDataUsable(ImageData* image_data) {
for (size_t i = 0; i < kCacheSize; i++) {
if (images_[i].image.get() == image_data) {
images_[i].usable = true;
if (next_insertion_point_ == i)
IncrementInsertionPoint();
return;
}
}
}
bool ImageDataInstanceCache::ExpireEntries() {
base::TimeTicks threshold_time =
base::TimeTicks::Now() - base::Seconds(kMaxAgeSeconds);
bool has_entry = false;
for (size_t i = 0; i < kCacheSize; i++) {
if (images_[i].image.get()) {
if (images_[i].added_time <= threshold_time) {
images_[i] = ImageDataCacheEntry();
next_insertion_point_ = i;
} else {
has_entry = true;
}
}
}
return has_entry;
}
void ImageDataInstanceCache::IncrementInsertionPoint() {
next_insertion_point_++;
if (next_insertion_point_ >= kCacheSize)
next_insertion_point_ = 0;
}
class ImageDataCache {
public:
ImageDataCache() {}
ImageDataCache(const ImageDataCache&) = delete;
ImageDataCache& operator=(const ImageDataCache&) = delete;
~ImageDataCache() {}
static ImageDataCache* GetInstance();
scoped_refptr<ImageData> Get(PP_Instance instance,
PPB_ImageData_Shared::ImageDataType type,
int width, int height,
PP_ImageDataFormat format);
void Add(ImageData* image_data);
void ImageDataUsable(ImageData* image_data);
void DidDeleteInstance(PP_Instance instance);
private:
friend struct base::LeakySingletonTraits<ImageDataCache>;
void OnTimer(PP_Instance instance);
typedef std::map<PP_Instance, ImageDataInstanceCache> CacheMap;
CacheMap cache_;
base::WeakPtrFactory<ImageDataCache> weak_factory_{this};
};
ImageDataCache* ImageDataCache::GetInstance() {
return base::Singleton<ImageDataCache,
base::LeakySingletonTraits<ImageDataCache>>::get();
}
scoped_refptr<ImageData> ImageDataCache::Get(
PP_Instance instance,
PPB_ImageData_Shared::ImageDataType type,
int width, int height,
PP_ImageDataFormat format) {
CacheMap::iterator found = cache_.find(instance);
if (found == cache_.end())
return scoped_refptr<ImageData>();
return found->second.Get(type, width, height, format);
}
void ImageDataCache::Add(ImageData* image_data) {
cache_[image_data->pp_instance()].Add(image_data);
PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostDelayedTask(
FROM_HERE,
RunWhileLocked(base::BindOnce(&ImageDataCache::OnTimer,
weak_factory_.GetWeakPtr(),
image_data->pp_instance())),
base::Seconds(kMaxAgeSeconds));
}
void ImageDataCache::ImageDataUsable(ImageData* image_data) {
CacheMap::iterator found = cache_.find(image_data->pp_instance());
if (found != cache_.end())
found->second.ImageDataUsable(image_data);
}
void ImageDataCache::DidDeleteInstance(PP_Instance instance) {
cache_.erase(instance);
}
void ImageDataCache::OnTimer(PP_Instance instance) {
CacheMap::iterator found = cache_.find(instance);
if (found == cache_.end())
return;
if (!found->second.ExpireEntries()) {
cache_.erase(found);
}
}
}
ImageData::ImageData(const HostResource& resource,
PPB_ImageData_Shared::ImageDataType type,
const PP_ImageDataDesc& desc)
: Resource(OBJECT_IS_PROXY, resource),
type_(type),
desc_(desc),
is_candidate_for_reuse_(false) {
}
ImageData::~ImageData() {
}
PPB_ImageData_API* ImageData::AsPPB_ImageData_API() {
return this;
}
void ImageData::LastPluginRefWasDeleted() {
if (is_candidate_for_reuse_)
ImageDataCache::GetInstance()->Add(this);
}
void ImageData::InstanceWasDeleted() {
ImageDataCache::GetInstance()->DidDeleteInstance(pp_instance());
}
PP_Bool ImageData::Describe(PP_ImageDataDesc* desc) {
memcpy(desc, &desc_, sizeof(PP_ImageDataDesc));
return PP_TRUE;
}
int32_t ImageData::GetSharedMemoryRegion(
base::UnsafeSharedMemoryRegion** ) {
return PP_ERROR_NOACCESS;
}
void ImageData::SetIsCandidateForReuse() {
is_candidate_for_reuse_ = true;
}
void ImageData::RecycleToPlugin(bool zero_contents) {
is_candidate_for_reuse_ = false;
if (zero_contents) {
void* data = Map();
memset(data, 0, desc_.stride * desc_.size.height);
Unmap();
}
}
#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_MINIMAL_TOOLCHAIN)
PlatformImageData::PlatformImageData(
const HostResource& resource,
const PP_ImageDataDesc& desc,
base::UnsafeSharedMemoryRegion image_region)
: ImageData(resource, PPB_ImageData_Shared::PLATFORM, desc) {
#if BUILDFLAG(IS_WIN)
transport_dib_ = TransportDIB::CreateWithHandle(std::move(image_region));
#else
transport_dib_ = TransportDIB::Map(std::move(image_region));
#endif
}
PlatformImageData::~PlatformImageData() = default;
void* PlatformImageData::Map() {
if (!mapped_canvas_.get()) {
if (!transport_dib_.get())
return nullptr;
const bool is_opaque = false;
mapped_canvas_ = transport_dib_->GetPlatformCanvas(
desc_.size.width, desc_.size.height, is_opaque);
if (!mapped_canvas_.get())
return nullptr;
}
SkPixmap pixmap;
skia::GetWritablePixels(mapped_canvas_.get(), &pixmap);
return pixmap.writable_addr();
}
void PlatformImageData::Unmap() {
}
SkCanvas* PlatformImageData::GetCanvas() {
return mapped_canvas_.get();
}
#endif
SimpleImageData::SimpleImageData(const HostResource& resource,
const PP_ImageDataDesc& desc,
base::UnsafeSharedMemoryRegion region)
: ImageData(resource, PPB_ImageData_Shared::SIMPLE, desc),
shm_region_(std::move(region)),
size_(desc.size.width * desc.size.height * 4),
map_count_(0) {}
SimpleImageData::~SimpleImageData() = default;
void* SimpleImageData::Map() {
if (map_count_++ == 0)
shm_mapping_ = shm_region_.MapAt(0, size_);
return shm_mapping_.IsValid() ? shm_mapping_.memory() : nullptr;
}
void SimpleImageData::Unmap() {
if (--map_count_ == 0)
shm_mapping_ = base::WritableSharedMemoryMapping();
}
SkCanvas* SimpleImageData::GetCanvas() {
return nullptr;
}
PPB_ImageData_Proxy::PPB_ImageData_Proxy(Dispatcher* dispatcher)
: InterfaceProxy(dispatcher) {
}
PPB_ImageData_Proxy::~PPB_ImageData_Proxy() {
}
PP_Resource PPB_ImageData_Proxy::CreateProxyResource(
PP_Instance instance,
PPB_ImageData_Shared::ImageDataType type,
PP_ImageDataFormat format,
const PP_Size& size,
PP_Bool init_to_zero) {
PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance);
if (!dispatcher)
return 0;
scoped_refptr<ImageData> cached_image_data =
ImageDataCache::GetInstance()->Get(instance, type,
size.width, size.height, format);
if (cached_image_data.get()) {
cached_image_data->RecycleToPlugin(PP_ToBool(init_to_zero));
return cached_image_data->GetReference();
}
HostResource result;
PP_ImageDataDesc desc;
switch (type) {
case PPB_ImageData_Shared::SIMPLE: {
ppapi::proxy::SerializedHandle image_handle;
dispatcher->Send(new PpapiHostMsg_PPBImageData_CreateSimple(
kApiID, instance, format, size, init_to_zero, &result, &desc,
&image_handle));
if (image_handle.is_shmem_region()) {
base::UnsafeSharedMemoryRegion image_region =
base::UnsafeSharedMemoryRegion::Deserialize(
image_handle.TakeSharedMemoryRegion());
if (!result.is_null()) {
return (new SimpleImageData(result, desc, std::move(image_region)))
->GetReference();
}
}
break;
}
case PPB_ImageData_Shared::PLATFORM: {
#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_MINIMAL_TOOLCHAIN)
ppapi::proxy::SerializedHandle image_handle;
dispatcher->Send(new PpapiHostMsg_PPBImageData_CreatePlatform(
kApiID, instance, format, size, init_to_zero, &result, &desc,
&image_handle));
if (image_handle.is_shmem_region()) {
base::UnsafeSharedMemoryRegion image_region =
base::UnsafeSharedMemoryRegion::Deserialize(
image_handle.TakeSharedMemoryRegion());
if (!result.is_null()) {
return (new PlatformImageData(result, desc, std::move(image_region)))
->GetReference();
}
}
#else
NOTREACHED();
#endif
break;
}
}
return 0;
}
bool PPB_ImageData_Proxy::OnMessageReceived(const IPC::Message& msg) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(PPB_ImageData_Proxy, msg)
#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_MINIMAL_TOOLCHAIN)
IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBImageData_CreatePlatform,
OnHostMsgCreatePlatform)
IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBImageData_CreateSimple,
OnHostMsgCreateSimple)
#endif
IPC_MESSAGE_HANDLER(PpapiMsg_PPBImageData_NotifyUnusedImageData,
OnPluginMsgNotifyUnusedImageData)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_MINIMAL_TOOLCHAIN)
PP_Resource PPB_ImageData_Proxy::CreateImageData(
PP_Instance instance,
PPB_ImageData_Shared::ImageDataType type,
PP_ImageDataFormat format,
const PP_Size& size,
bool init_to_zero,
PP_ImageDataDesc* desc,
base::UnsafeSharedMemoryRegion* image_region) {
HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance);
if (!dispatcher)
return 0;
thunk::EnterResourceCreation enter(instance);
if (enter.failed())
return 0;
PP_Bool pp_init_to_zero = init_to_zero ? PP_TRUE : PP_FALSE;
PP_Resource pp_resource = 0;
switch (type) {
case PPB_ImageData_Shared::SIMPLE: {
pp_resource = enter.functions()->CreateImageDataSimple(
instance, format, &size, pp_init_to_zero);
break;
}
case PPB_ImageData_Shared::PLATFORM: {
pp_resource = enter.functions()->CreateImageData(
instance, format, &size, pp_init_to_zero);
break;
}
}
if (!pp_resource)
return 0;
ppapi::ScopedPPResource resource(ppapi::ScopedPPResource::PassRef(),
pp_resource);
thunk::EnterResourceNoLock<PPB_ImageData_API> enter_resource(resource.get(),
false);
if (enter_resource.object()->Describe(desc) != PP_TRUE) {
DVLOG(1) << "CreateImageData failed: could not Describe";
return 0;
}
base::UnsafeSharedMemoryRegion* local_shm;
if (enter_resource.object()->GetSharedMemoryRegion(&local_shm) != PP_OK) {
DVLOG(1) << "CreateImageData failed: could not GetSharedMemory";
return 0;
}
*image_region =
dispatcher->ShareUnsafeSharedMemoryRegionWithRemote(*local_shm);
return resource.Release();
}
void PPB_ImageData_Proxy::OnHostMsgCreatePlatform(
PP_Instance instance,
int32_t format,
const PP_Size& size,
PP_Bool init_to_zero,
HostResource* result,
PP_ImageDataDesc* desc,
ppapi::proxy::SerializedHandle* result_image_handle) {
*desc = PP_ImageDataDesc();
base::UnsafeSharedMemoryRegion image_region;
PP_Resource resource =
CreateImageData(instance, PPB_ImageData_Shared::PLATFORM,
static_cast<PP_ImageDataFormat>(format), size,
true , desc, &image_region);
result->SetHostResource(instance, resource);
if (resource) {
result_image_handle->set_shmem_region(
base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
std::move(image_region)));
} else {
result_image_handle->set_null_shmem_region();
}
}
void PPB_ImageData_Proxy::OnHostMsgCreateSimple(
PP_Instance instance,
int32_t format,
const PP_Size& size,
PP_Bool init_to_zero,
HostResource* result,
PP_ImageDataDesc* desc,
ppapi::proxy::SerializedHandle* result_image_handle) {
*desc = PP_ImageDataDesc();
base::UnsafeSharedMemoryRegion image_region;
PP_Resource resource =
CreateImageData(instance, PPB_ImageData_Shared::SIMPLE,
static_cast<PP_ImageDataFormat>(format), size,
true , desc, &image_region);
result->SetHostResource(instance, resource);
if (resource) {
result_image_handle->set_shmem_region(
base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
std::move(image_region)));
} else {
result_image_handle->set_null_shmem_region();
}
}
#endif
void PPB_ImageData_Proxy::OnPluginMsgNotifyUnusedImageData(
const HostResource& old_image_data) {
PluginGlobals* plugin_globals = PluginGlobals::Get();
if (!plugin_globals) {
return;
}
EnterPluginFromHostResource<PPB_ImageData_API> enter(old_image_data);
if (enter.succeeded()) {
ImageData* image_data = static_cast<ImageData*>(enter.object());
ImageDataCache::GetInstance()->ImageDataUsable(image_data);
}
dispatcher()->Send(new PpapiHostMsg_PPBCore_ReleaseResource(
API_ID_PPB_CORE, old_image_data));
}
}
}