Revert 233414 "x11: Move XInput2 availability information out of..."
[chromium-blink-merge.git] / ppapi / proxy / ppb_image_data_proxy.cc
blobc9d61f9feae039505b9121dab1244f5f567e2925
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.
5 #include "ppapi/proxy/ppb_image_data_proxy.h"
7 #include <string.h> // For memcpy
9 #include <map>
10 #include <vector>
12 #include "base/logging.h"
13 #include "base/memory/singleton.h"
14 #include "base/memory/weak_ptr.h"
15 #include "build/build_config.h"
16 #include "ppapi/c/pp_completion_callback.h"
17 #include "ppapi/c/pp_errors.h"
18 #include "ppapi/c/pp_resource.h"
19 #include "ppapi/proxy/enter_proxy.h"
20 #include "ppapi/proxy/host_dispatcher.h"
21 #include "ppapi/proxy/plugin_dispatcher.h"
22 #include "ppapi/proxy/plugin_globals.h"
23 #include "ppapi/proxy/plugin_resource_tracker.h"
24 #include "ppapi/proxy/ppapi_messages.h"
25 #include "ppapi/shared_impl/host_resource.h"
26 #include "ppapi/shared_impl/proxy_lock.h"
27 #include "ppapi/shared_impl/resource.h"
28 #include "ppapi/shared_impl/scoped_pp_resource.h"
29 #include "ppapi/thunk/enter.h"
30 #include "ppapi/thunk/thunk.h"
32 #if !defined(OS_NACL)
33 #include "skia/ext/platform_canvas.h"
34 #include "ui/surface/transport_dib.h"
35 #endif
37 using ppapi::thunk::PPB_ImageData_API;
39 namespace ppapi {
40 namespace proxy {
42 namespace {
44 // How ImageData re-use works
45 // --------------------------
47 // When animating plugins (like video), re-creating image datas for each frame
48 // and mapping the memory has a high overhead. So we try to re-use these when
49 // possible.
51 // 1. Plugin makes an asynchronous call that transfers an ImageData to the
52 // implementation of some API.
53 // 2. Plugin frees its ImageData reference. If it doesn't do this we can't
54 // re-use it.
55 // 3. When the last plugin ref of an ImageData is released, we don't actually
56 // delete it. Instead we put it on a queue where we hold onto it in the
57 // plugin process for a short period of time.
58 // 4. The API implementation that received the ImageData finishes using it.
59 // Without our caching system it would get deleted at this point.
60 // 5. The proxy in the renderer will send NotifyUnusedImageData back to the
61 // plugin process. We check if the given resource is in the queue and mark
62 // it as usable.
63 // 6. When the plugin requests a new image data, we check our queue and if there
64 // is a usable ImageData of the right size and format, we'll return it
65 // instead of making a new one. It's important that caching is only requested
66 // when the size is unlikely to change, so cache hits are high.
68 // Some notes:
70 // - We only re-use image data when the plugin and host are rapidly exchanging
71 // them and the size is likely to remain constant. It should be clear that
72 // the plugin is promising that it's done with the image.
74 // - Theoretically we could re-use them in other cases but the lifetime
75 // becomes more difficult to manage. The plugin could have used an ImageData
76 // in an arbitrary number of queued up PaintImageData calls which we would
77 // have to check.
79 // - If a flush takes a long time or there are many released image datas
80 // accumulating in our queue such that some are deleted, we will have
81 // released our reference by the time the renderer notifies us of an unused
82 // image data. In this case we just give up.
84 // - We maintain a per-instance cache. Some pages have many instances of
85 // Flash, for example, each of a different size. If they're all animating we
86 // want each to get its own image data re-use.
88 // - We generate new resource IDs when re-use happens to try to avoid weird
89 // problems if the plugin messes up its refcounting.
91 // Keep a cache entry for this many seconds before expiring it. We get an entry
92 // back from the renderer after an ImageData is swapped out, so it means the
93 // plugin has to be painting at least two frames for this time interval to
94 // get caching.
95 static const int kMaxAgeSeconds = 2;
97 // ImageDataCacheEntry ---------------------------------------------------------
99 struct ImageDataCacheEntry {
100 ImageDataCacheEntry() : added_time(), usable(false), image() {}
101 ImageDataCacheEntry(ImageData* i)
102 : added_time(base::TimeTicks::Now()),
103 usable(false),
104 image(i) {
107 base::TimeTicks added_time;
109 // Set to true when the renderer tells us that it's OK to re-use this iamge.
110 bool usable;
112 scoped_refptr<ImageData> image;
115 // ImageDataInstanceCache ------------------------------------------------------
117 // Per-instance cache of image datas.
118 class ImageDataInstanceCache {
119 public:
120 ImageDataInstanceCache() : next_insertion_point_(0) {}
122 // These functions have the same spec as the ones in ImageDataCache.
123 scoped_refptr<ImageData> Get(PPB_ImageData_Shared::ImageDataType type,
124 int width, int height,
125 PP_ImageDataFormat format);
126 void Add(ImageData* image_data);
127 void ImageDataUsable(ImageData* image_data);
129 // Expires old entries. Returns true if there are still entries in the list,
130 // false if this instance cache is now empty.
131 bool ExpireEntries();
133 private:
134 void IncrementInsertionPoint();
136 // We'll store this many ImageDatas per instance.
137 const static int kCacheSize = 2;
139 ImageDataCacheEntry images_[kCacheSize];
141 // Index into cache where the next item will go.
142 int next_insertion_point_;
145 scoped_refptr<ImageData> ImageDataInstanceCache::Get(
146 PPB_ImageData_Shared::ImageDataType type,
147 int width, int height,
148 PP_ImageDataFormat format) {
149 // Just do a brute-force search since the cache is so small.
150 for (int i = 0; i < kCacheSize; i++) {
151 if (!images_[i].usable)
152 continue;
153 if (images_[i].image->type() != type)
154 continue;
155 const PP_ImageDataDesc& desc = images_[i].image->desc();
156 if (desc.format == format &&
157 desc.size.width == width && desc.size.height == height) {
158 scoped_refptr<ImageData> ret(images_[i].image);
159 images_[i] = ImageDataCacheEntry();
161 // Since we just removed an item, this entry is the best place to insert
162 // a subsequent one.
163 next_insertion_point_ = i;
164 return ret;
167 return scoped_refptr<ImageData>();
170 void ImageDataInstanceCache::Add(ImageData* image_data) {
171 images_[next_insertion_point_] = ImageDataCacheEntry(image_data);
172 IncrementInsertionPoint();
175 void ImageDataInstanceCache::ImageDataUsable(ImageData* image_data) {
176 for (int i = 0; i < kCacheSize; i++) {
177 if (images_[i].image.get() == image_data) {
178 images_[i].usable = true;
180 // This test is important. The renderer doesn't guarantee how many image
181 // datas it has or when it notifies us when one is usable. Its possible
182 // to get into situations where it's always telling us the old one is
183 // usable, and then the older one immediately gets expired. Therefore,
184 // if the next insertion would overwrite this now-usable entry, make the
185 // next insertion overwrite some other entry to avoid the replacement.
186 if (next_insertion_point_ == i)
187 IncrementInsertionPoint();
188 return;
193 bool ImageDataInstanceCache::ExpireEntries() {
194 base::TimeTicks threshold_time =
195 base::TimeTicks::Now() - base::TimeDelta::FromSeconds(kMaxAgeSeconds);
197 bool has_entry = false;
198 for (int i = 0; i < kCacheSize; i++) {
199 if (images_[i].image.get()) {
200 // Entry present.
201 if (images_[i].added_time <= threshold_time) {
202 // Found an entry to expire.
203 images_[i] = ImageDataCacheEntry();
204 next_insertion_point_ = i;
205 } else {
206 // Found an entry that we're keeping.
207 has_entry = true;
211 return has_entry;
214 void ImageDataInstanceCache::IncrementInsertionPoint() {
215 // Go to the next location, wrapping around to get LRU.
216 next_insertion_point_++;
217 if (next_insertion_point_ >= kCacheSize)
218 next_insertion_point_ = 0;
221 // ImageDataCache --------------------------------------------------------------
223 class ImageDataCache {
224 public:
225 ImageDataCache() : weak_factory_(this) {}
226 ~ImageDataCache() {}
228 static ImageDataCache* GetInstance();
230 // Retrieves an image data from the cache of the specified type, size and
231 // format if one exists. If one doesn't exist, this will return a null refptr.
232 scoped_refptr<ImageData> Get(PP_Instance instance,
233 PPB_ImageData_Shared::ImageDataType type,
234 int width, int height,
235 PP_ImageDataFormat format);
237 // Adds the given image data to the cache. There should be no plugin
238 // references to it. This may delete an older item from the cache.
239 void Add(ImageData* image_data);
241 // Notification from the renderer that the given image data is usable.
242 void ImageDataUsable(ImageData* image_data);
244 void DidDeleteInstance(PP_Instance instance);
246 private:
247 friend struct LeakySingletonTraits<ImageDataCache>;
249 // Timer callback to expire entries for the given instance.
250 void OnTimer(PP_Instance instance);
252 typedef std::map<PP_Instance, ImageDataInstanceCache> CacheMap;
253 CacheMap cache_;
255 // This class does timer calls and we don't want to run these outside of the
256 // scope of the object. Technically, since this class is a leaked static,
257 // this will never happen and this factory is unnecessary. However, it's
258 // probably better not to make assumptions about the lifetime of this class.
259 base::WeakPtrFactory<ImageDataCache> weak_factory_;
261 DISALLOW_COPY_AND_ASSIGN(ImageDataCache);
264 // static
265 ImageDataCache* ImageDataCache::GetInstance() {
266 return Singleton<ImageDataCache,
267 LeakySingletonTraits<ImageDataCache> >::get();
270 scoped_refptr<ImageData> ImageDataCache::Get(
271 PP_Instance instance,
272 PPB_ImageData_Shared::ImageDataType type,
273 int width, int height,
274 PP_ImageDataFormat format) {
275 CacheMap::iterator found = cache_.find(instance);
276 if (found == cache_.end())
277 return scoped_refptr<ImageData>();
278 return found->second.Get(type, width, height, format);
281 void ImageDataCache::Add(ImageData* image_data) {
282 cache_[image_data->pp_instance()].Add(image_data);
284 // Schedule a timer to invalidate this entry.
285 base::MessageLoop::current()->PostDelayedTask(
286 FROM_HERE,
287 RunWhileLocked(base::Bind(&ImageDataCache::OnTimer,
288 weak_factory_.GetWeakPtr(),
289 image_data->pp_instance())),
290 base::TimeDelta::FromSeconds(kMaxAgeSeconds));
293 void ImageDataCache::ImageDataUsable(ImageData* image_data) {
294 CacheMap::iterator found = cache_.find(image_data->pp_instance());
295 if (found != cache_.end())
296 found->second.ImageDataUsable(image_data);
299 void ImageDataCache::DidDeleteInstance(PP_Instance instance) {
300 cache_.erase(instance);
303 void ImageDataCache::OnTimer(PP_Instance instance) {
304 CacheMap::iterator found = cache_.find(instance);
305 if (found == cache_.end())
306 return;
307 if (!found->second.ExpireEntries()) {
308 // There are no more entries for this instance, remove it from the cache.
309 cache_.erase(found);
313 } // namespace
315 // ImageData -------------------------------------------------------------------
317 ImageData::ImageData(const HostResource& resource,
318 PPB_ImageData_Shared::ImageDataType type,
319 const PP_ImageDataDesc& desc)
320 : Resource(OBJECT_IS_PROXY, resource),
321 type_(type),
322 desc_(desc),
323 is_candidate_for_reuse_(false) {
326 ImageData::~ImageData() {
329 PPB_ImageData_API* ImageData::AsPPB_ImageData_API() {
330 return this;
333 void ImageData::LastPluginRefWasDeleted() {
334 // The plugin no longer needs this ImageData, add it to our cache if it's
335 // been used in a ReplaceContents. These are the ImageDatas that the renderer
336 // will send back ImageDataUsable messages for.
337 if (is_candidate_for_reuse_)
338 ImageDataCache::GetInstance()->Add(this);
341 void ImageData::InstanceWasDeleted() {
342 ImageDataCache::GetInstance()->DidDeleteInstance(pp_instance());
345 PP_Bool ImageData::Describe(PP_ImageDataDesc* desc) {
346 memcpy(desc, &desc_, sizeof(PP_ImageDataDesc));
347 return PP_TRUE;
350 int32_t ImageData::GetSharedMemory(int* /* handle */,
351 uint32_t* /* byte_count */) {
352 // Not supported in the proxy (this method is for actually implementing the
353 // proxy in the host).
354 return PP_ERROR_NOACCESS;
357 void ImageData::SetIsCandidateForReuse() {
358 is_candidate_for_reuse_ = true;
361 void ImageData::RecycleToPlugin(bool zero_contents) {
362 is_candidate_for_reuse_ = false;
363 if (zero_contents) {
364 void* data = Map();
365 memset(data, 0, desc_.stride * desc_.size.height);
366 Unmap();
370 // PlatformImageData -----------------------------------------------------------
372 #if !defined(OS_NACL)
373 PlatformImageData::PlatformImageData(const HostResource& resource,
374 const PP_ImageDataDesc& desc,
375 ImageHandle handle)
376 : ImageData(resource, PPB_ImageData_Shared::PLATFORM, desc) {
377 #if defined(OS_WIN)
378 transport_dib_.reset(TransportDIB::CreateWithHandle(handle));
379 #else
380 transport_dib_.reset(TransportDIB::Map(handle));
381 #endif // defined(OS_WIN)
384 PlatformImageData::~PlatformImageData() {
387 void* PlatformImageData::Map() {
388 if (!mapped_canvas_.get()) {
389 mapped_canvas_.reset(transport_dib_->GetPlatformCanvas(desc_.size.width,
390 desc_.size.height));
391 if (!mapped_canvas_.get())
392 return NULL;
394 const SkBitmap& bitmap =
395 skia::GetTopDevice(*mapped_canvas_)->accessBitmap(true);
397 bitmap.lockPixels();
398 return bitmap.getAddr(0, 0);
401 void PlatformImageData::Unmap() {
402 // TODO(brettw) have a way to unmap a TransportDIB. Currently this isn't
403 // possible since deleting the TransportDIB also frees all the handles.
404 // We need to add a method to TransportDIB to release the handles.
407 SkCanvas* PlatformImageData::GetPlatformCanvas() {
408 return mapped_canvas_.get();
411 SkCanvas* PlatformImageData::GetCanvas() {
412 return mapped_canvas_.get();
415 // static
416 ImageHandle PlatformImageData::NullHandle() {
417 #if defined(OS_WIN)
418 return NULL;
419 #elif defined(TOOLKIT_GTK)
420 return 0;
421 #else
422 return ImageHandle();
423 #endif
426 ImageHandle PlatformImageData::HandleFromInt(int32_t i) {
427 #if defined(OS_WIN)
428 return reinterpret_cast<ImageHandle>(i);
429 #elif defined(TOOLKIT_GTK)
430 return static_cast<ImageHandle>(i);
431 #else
432 return ImageHandle(i, false);
433 #endif
435 #endif // !defined(OS_NACL)
437 // SimpleImageData -------------------------------------------------------------
439 SimpleImageData::SimpleImageData(const HostResource& resource,
440 const PP_ImageDataDesc& desc,
441 const base::SharedMemoryHandle& handle)
442 : ImageData(resource, PPB_ImageData_Shared::SIMPLE, desc),
443 shm_(handle, false /* read_only */),
444 size_(desc.size.width * desc.size.height * 4),
445 map_count_(0) {
448 SimpleImageData::~SimpleImageData() {
451 void* SimpleImageData::Map() {
452 if (map_count_++ == 0)
453 shm_.Map(size_);
454 return shm_.memory();
457 void SimpleImageData::Unmap() {
458 if (--map_count_ == 0)
459 shm_.Unmap();
462 SkCanvas* SimpleImageData::GetPlatformCanvas() {
463 return NULL; // No canvas available.
466 SkCanvas* SimpleImageData::GetCanvas() {
467 return NULL; // No canvas available.
470 // PPB_ImageData_Proxy ---------------------------------------------------------
472 PPB_ImageData_Proxy::PPB_ImageData_Proxy(Dispatcher* dispatcher)
473 : InterfaceProxy(dispatcher) {
476 PPB_ImageData_Proxy::~PPB_ImageData_Proxy() {
479 // static
480 PP_Resource PPB_ImageData_Proxy::CreateProxyResource(
481 PP_Instance instance,
482 PPB_ImageData_Shared::ImageDataType type,
483 PP_ImageDataFormat format,
484 const PP_Size& size,
485 PP_Bool init_to_zero) {
486 PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance);
487 if (!dispatcher)
488 return 0;
490 // Check the cache.
491 scoped_refptr<ImageData> cached_image_data =
492 ImageDataCache::GetInstance()->Get(instance, type,
493 size.width, size.height, format);
494 if (cached_image_data.get()) {
495 // We have one we can re-use rather than allocating a new one.
496 cached_image_data->RecycleToPlugin(PP_ToBool(init_to_zero));
497 return cached_image_data->GetReference();
500 HostResource result;
501 PP_ImageDataDesc desc;
502 switch (type) {
503 case PPB_ImageData_Shared::SIMPLE: {
504 ppapi::proxy::SerializedHandle image_handle_wrapper;
505 dispatcher->Send(new PpapiHostMsg_PPBImageData_CreateSimple(
506 kApiID, instance, format, size, init_to_zero,
507 &result, &desc, &image_handle_wrapper));
508 if (image_handle_wrapper.is_shmem()) {
509 base::SharedMemoryHandle image_handle = image_handle_wrapper.shmem();
510 if (!result.is_null())
511 return
512 (new SimpleImageData(result, desc, image_handle))->GetReference();
514 break;
516 case PPB_ImageData_Shared::PLATFORM: {
517 #if !defined(OS_NACL)
518 ImageHandle image_handle = PlatformImageData::NullHandle();
519 dispatcher->Send(new PpapiHostMsg_PPBImageData_CreatePlatform(
520 kApiID, instance, format, size, init_to_zero,
521 &result, &desc, &image_handle));
522 if (!result.is_null())
523 return
524 (new PlatformImageData(result, desc, image_handle))->GetReference();
525 #else
526 // PlatformImageData shouldn't be created in untrusted code.
527 NOTREACHED();
528 #endif
529 break;
533 return 0;
536 bool PPB_ImageData_Proxy::OnMessageReceived(const IPC::Message& msg) {
537 bool handled = true;
538 IPC_BEGIN_MESSAGE_MAP(PPB_ImageData_Proxy, msg)
539 #if !defined(OS_NACL)
540 IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBImageData_CreatePlatform,
541 OnHostMsgCreatePlatform)
542 IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBImageData_CreateSimple,
543 OnHostMsgCreateSimple)
544 #endif
545 IPC_MESSAGE_HANDLER(PpapiMsg_PPBImageData_NotifyUnusedImageData,
546 OnPluginMsgNotifyUnusedImageData)
548 IPC_MESSAGE_UNHANDLED(handled = false)
549 IPC_END_MESSAGE_MAP()
550 return handled;
553 #if !defined(OS_NACL)
554 // static
555 PP_Resource PPB_ImageData_Proxy::CreateImageData(
556 PP_Instance instance,
557 PPB_ImageData_Shared::ImageDataType type,
558 PP_ImageDataFormat format,
559 const PP_Size& size,
560 bool init_to_zero,
561 PP_ImageDataDesc* desc,
562 IPC::PlatformFileForTransit* image_handle,
563 uint32_t* byte_count) {
564 HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance);
565 if (!dispatcher)
566 return 0;
568 thunk::EnterResourceCreation enter(instance);
569 if (enter.failed())
570 return 0;
572 PP_Bool pp_init_to_zero = init_to_zero ? PP_TRUE : PP_FALSE;
573 PP_Resource pp_resource = 0;
574 switch (type) {
575 case PPB_ImageData_Shared::SIMPLE: {
576 pp_resource = enter.functions()->CreateImageDataSimple(
577 instance, format, &size, pp_init_to_zero);
578 break;
580 case PPB_ImageData_Shared::PLATFORM: {
581 pp_resource = enter.functions()->CreateImageData(
582 instance, format, &size, pp_init_to_zero);
583 break;
587 if (!pp_resource)
588 return 0;
590 ppapi::ScopedPPResource resource(ppapi::ScopedPPResource::PassRef(),
591 pp_resource);
593 thunk::EnterResourceNoLock<PPB_ImageData_API> enter_resource(resource.get(),
594 false);
595 if (enter_resource.object()->Describe(desc) != PP_TRUE) {
596 DVLOG(1) << "CreateImageData failed: could not Describe";
597 return 0;
600 int local_fd = 0;
601 if (enter_resource.object()->GetSharedMemory(&local_fd,
602 byte_count) != PP_OK) {
603 DVLOG(1) << "CreateImageData failed: could not GetSharedMemory";
604 return 0;
607 #if defined(OS_WIN)
608 *image_handle = dispatcher->ShareHandleWithRemote(
609 reinterpret_cast<HANDLE>(static_cast<intptr_t>(local_fd)), false);
610 #elif defined(TOOLKIT_GTK)
611 // On X Windows, a PlatformImageData is backed by a SysV shared memory key,
612 // so embed that in a fake PlatformFileForTransit and don't share it across
613 // processes.
614 if (type == PPB_ImageData_Shared::PLATFORM)
615 *image_handle = IPC::PlatformFileForTransit(local_fd, false);
616 else
617 *image_handle = dispatcher->ShareHandleWithRemote(local_fd, false);
618 #elif defined(OS_POSIX)
619 *image_handle = dispatcher->ShareHandleWithRemote(local_fd, false);
620 #else
621 #error Not implemented.
622 #endif
624 return resource.Release();
627 void PPB_ImageData_Proxy::OnHostMsgCreatePlatform(
628 PP_Instance instance,
629 int32_t format,
630 const PP_Size& size,
631 PP_Bool init_to_zero,
632 HostResource* result,
633 PP_ImageDataDesc* desc,
634 ImageHandle* result_image_handle) {
635 IPC::PlatformFileForTransit image_handle;
636 uint32_t byte_count;
637 PP_Resource resource =
638 CreateImageData(instance,
639 PPB_ImageData_Shared::PLATFORM,
640 static_cast<PP_ImageDataFormat>(format),
641 size,
642 true /* init_to_zero */,
643 desc, &image_handle, &byte_count);
644 result->SetHostResource(instance, resource);
645 if (resource) {
646 #if defined(TOOLKIT_GTK)
647 // On X Windows ImageHandle is a SysV shared memory key.
648 *result_image_handle = image_handle.fd;
649 #else
650 *result_image_handle = image_handle;
651 #endif
652 } else {
653 *result_image_handle = PlatformImageData::NullHandle();
657 void PPB_ImageData_Proxy::OnHostMsgCreateSimple(
658 PP_Instance instance,
659 int32_t format,
660 const PP_Size& size,
661 PP_Bool init_to_zero,
662 HostResource* result,
663 PP_ImageDataDesc* desc,
664 ppapi::proxy::SerializedHandle* result_image_handle) {
665 IPC::PlatformFileForTransit image_handle;
666 uint32_t byte_count;
667 PP_Resource resource =
668 CreateImageData(instance,
669 PPB_ImageData_Shared::SIMPLE,
670 static_cast<PP_ImageDataFormat>(format),
671 size,
672 true /* init_to_zero */,
673 desc, &image_handle, &byte_count);
675 result->SetHostResource(instance, resource);
676 if (resource) {
677 result_image_handle->set_shmem(image_handle, byte_count);
678 } else {
679 result_image_handle->set_null_shmem();
682 #endif // !defined(OS_NACL)
684 void PPB_ImageData_Proxy::OnPluginMsgNotifyUnusedImageData(
685 const HostResource& old_image_data) {
686 PluginGlobals* plugin_globals = PluginGlobals::Get();
687 if (!plugin_globals)
688 return; // This may happen if the plugin is maliciously sending this
689 // message to the renderer.
691 EnterPluginFromHostResource<PPB_ImageData_API> enter(old_image_data);
692 if (enter.succeeded()) {
693 ImageData* image_data = static_cast<ImageData*>(enter.object());
694 ImageDataCache::GetInstance()->ImageDataUsable(image_data);
697 // The renderer sent us a reference with the message. If the image data was
698 // still cached in our process, the proxy still holds a reference so we can
699 // remove the one the renderer just sent is. If the proxy no longer holds a
700 // reference, we released everything and we should also release the one the
701 // renderer just sent us.
702 dispatcher()->Send(new PpapiHostMsg_PPBCore_ReleaseResource(
703 API_ID_PPB_CORE, old_image_data));
706 } // namespace proxy
707 } // namespace ppapi