diff options
author | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-17 19:15:35 +0000 |
---|---|---|
committer | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-17 19:15:35 +0000 |
commit | 6f516083864319feeb02ae4dc32609216455366b (patch) | |
tree | 285a57fee02c581e22d272951633d4a56a7846e6 /content/renderer | |
parent | 6421fe84fee932545c65ab77b7dcdc60be28accc (diff) | |
download | chromium_src-6f516083864319feeb02ae4dc32609216455366b.zip chromium_src-6f516083864319feeb02ae4dc32609216455366b.tar.gz chromium_src-6f516083864319feeb02ae4dc32609216455366b.tar.bz2 |
Move a bunch of gpu/worker/plugin renderer code to content. I temporarily disabled the sad plugin code while I add a renderer chrome interface in a follow up.
TBR=avi
Review URL: http://codereview.chromium.org/6713005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@78579 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/renderer')
25 files changed, 5816 insertions, 3 deletions
diff --git a/content/renderer/DEPS b/content/renderer/DEPS new file mode 100644 index 0000000..03cc095 --- /dev/null +++ b/content/renderer/DEPS @@ -0,0 +1,3 @@ +include_rules = [
+ "+content/plugin", # For shared npruntime proxying code.
+]
diff --git a/content/renderer/command_buffer_proxy.cc b/content/renderer/command_buffer_proxy.cc new file mode 100644 index 0000000..7a5cc74 --- /dev/null +++ b/content/renderer/command_buffer_proxy.cc @@ -0,0 +1,409 @@ +// Copyright (c) 2009 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 "content/renderer/command_buffer_proxy.h" + +#include "base/logging.h" +#include "base/process_util.h" +#include "base/shared_memory.h" +#include "base/task.h" +#include "chrome/common/render_messages.h" +#include "chrome/renderer/render_thread.h" +#include "content/common/gpu_messages.h" +#include "content/common/plugin_messages.h" +#include "content/renderer/plugin_channel_host.h" +#include "gpu/command_buffer/common/cmd_buffer_common.h" +#include "ui/gfx/size.h" + +using gpu::Buffer; + +CommandBufferProxy::CommandBufferProxy( + IPC::Channel::Sender* channel, + int route_id) + : num_entries_(0), + channel_(channel), + route_id_(route_id) { +} + +CommandBufferProxy::~CommandBufferProxy() { + // Delete all the locally cached shared memory objects, closing the handle + // in this process. + for (TransferBufferMap::iterator it = transfer_buffers_.begin(); + it != transfer_buffers_.end(); + ++it) { + delete it->second.shared_memory; + it->second.shared_memory = NULL; + } +} + +bool CommandBufferProxy::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(CommandBufferProxy, message) + IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_UpdateState, OnUpdateState); + IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_SwapBuffers, OnSwapBuffers); + IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_NotifyRepaint, + OnNotifyRepaint); + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + DCHECK(handled); + return handled; +} + +void CommandBufferProxy::OnChannelError() { + // Prevent any further messages from being sent. + channel_ = NULL; + + // When the client sees that the context is lost, they should delete this + // CommandBufferProxy and create a new one. + last_state_.error = gpu::error::kLostContext; + + if (channel_error_callback_.get()) + channel_error_callback_->Run(); +} + +void CommandBufferProxy::SetChannelErrorCallback(Callback0::Type* callback) { + channel_error_callback_.reset(callback); +} + +bool CommandBufferProxy::Initialize(int32 size) { + DCHECK(!ring_buffer_.get()); + + RenderThread* render_thread = RenderThread::current(); + if (!render_thread) + return false; + + base::SharedMemoryHandle handle; + if (!render_thread->Send(new ViewHostMsg_AllocateSharedMemoryBuffer( + size, + &handle))) { + return false; + } + + if (!base::SharedMemory::IsHandleValid(handle)) + return false; + +#if defined(OS_POSIX) + handle.auto_close = false; +#endif + + // Take ownership of shared memory. This will close the handle if Send below + // fails. Otherwise, callee takes ownership before this variable + // goes out of scope. + base::SharedMemory shared_memory(handle, false); + + return Initialize(&shared_memory, size); +} + +bool CommandBufferProxy::Initialize(base::SharedMemory* buffer, int32 size) { + bool result; + if (!Send(new GpuCommandBufferMsg_Initialize(route_id_, + buffer->handle(), + size, + &result))) { + LOG(ERROR) << "Could not send GpuCommandBufferMsg_Initialize."; + return false; + } + + if (!result) { + LOG(ERROR) << "Failed to initialize command buffer service."; + return false; + } + + base::SharedMemoryHandle handle; + if (!buffer->GiveToProcess(base::GetCurrentProcessHandle(), &handle)) { + LOG(ERROR) << "Failed to duplicate command buffer handle."; + return false; + } + + ring_buffer_.reset(new base::SharedMemory(handle, false)); + if (!ring_buffer_->Map(size)) { + LOG(ERROR) << "Failed to map shared memory for command buffer."; + ring_buffer_.reset(); + return false; + } + + num_entries_ = size / sizeof(gpu::CommandBufferEntry); + return true; +} + +Buffer CommandBufferProxy::GetRingBuffer() { + DCHECK(ring_buffer_.get()); + // Return locally cached ring buffer. + Buffer buffer; + buffer.ptr = ring_buffer_->memory(); + buffer.size = num_entries_ * sizeof(gpu::CommandBufferEntry); + buffer.shared_memory = ring_buffer_.get(); + return buffer; +} + +gpu::CommandBuffer::State CommandBufferProxy::GetState() { + // Send will flag state with lost context if IPC fails. + if (last_state_.error == gpu::error::kNoError) + Send(new GpuCommandBufferMsg_GetState(route_id_, &last_state_)); + + return last_state_; +} + +void CommandBufferProxy::Flush(int32 put_offset) { + AsyncFlush(put_offset, NULL); +} + +gpu::CommandBuffer::State CommandBufferProxy::FlushSync(int32 put_offset) { + // Send will flag state with lost context if IPC fails. + if (last_state_.error == gpu::error::kNoError) { + Send(new GpuCommandBufferMsg_Flush(route_id_, + put_offset, + &last_state_)); + } + + return last_state_; +} + +void CommandBufferProxy::SetGetOffset(int32 get_offset) { + // Not implemented in proxy. + NOTREACHED(); +} + +int32 CommandBufferProxy::CreateTransferBuffer(size_t size) { + if (last_state_.error != gpu::error::kNoError) + return -1; + + RenderThread* render_thread = RenderThread::current(); + if (!render_thread) + return -1; + + base::SharedMemoryHandle handle; + if (!render_thread->Send(new ViewHostMsg_AllocateSharedMemoryBuffer( + size, + &handle))) { + return -1; + } + + if (!base::SharedMemory::IsHandleValid(handle)) + return -1; + + // Handle is closed by the SharedMemory object below. This stops + // base::FileDescriptor from closing it as well. +#if defined(OS_POSIX) + handle.auto_close = false; +#endif + + // Take ownership of shared memory. This will close the handle if Send below + // fails. Otherwise, callee takes ownership before this variable + // goes out of scope by duping the handle. + base::SharedMemory shared_memory(handle, false); + + int32 id; + if (!Send(new GpuCommandBufferMsg_RegisterTransferBuffer(route_id_, + handle, + size, + &id))) { + return -1; + } + + return id; +} + +int32 CommandBufferProxy::RegisterTransferBuffer( + base::SharedMemory* shared_memory, + size_t size) { + if (last_state_.error != gpu::error::kNoError) + return -1; + + int32 id; + if (!Send(new GpuCommandBufferMsg_RegisterTransferBuffer( + route_id_, + shared_memory->handle(), // Returns FileDescriptor with auto_close off. + size, + &id))) { + return -1; + } + + return id; +} + +void CommandBufferProxy::DestroyTransferBuffer(int32 id) { + if (last_state_.error != gpu::error::kNoError) + return; + + // Remove the transfer buffer from the client side cache. + TransferBufferMap::iterator it = transfer_buffers_.find(id); + if (it != transfer_buffers_.end()) { + delete it->second.shared_memory; + transfer_buffers_.erase(it); + } + + Send(new GpuCommandBufferMsg_DestroyTransferBuffer(route_id_, id)); +} + +Buffer CommandBufferProxy::GetTransferBuffer(int32 id) { + if (last_state_.error != gpu::error::kNoError) + return Buffer(); + + // Check local cache to see if there is already a client side shared memory + // object for this id. + TransferBufferMap::iterator it = transfer_buffers_.find(id); + if (it != transfer_buffers_.end()) { + return it->second; + } + + // Assuming we are in the renderer process, the service is responsible for + // duplicating the handle. This might not be true for NaCl. + base::SharedMemoryHandle handle; + uint32 size; + if (!Send(new GpuCommandBufferMsg_GetTransferBuffer(route_id_, + id, + &handle, + &size))) { + return Buffer(); + } + + // Cache the transfer buffer shared memory object client side. + base::SharedMemory* shared_memory = new base::SharedMemory(handle, false); + + // Map the shared memory on demand. + if (!shared_memory->memory()) { + if (!shared_memory->Map(size)) { + delete shared_memory; + return Buffer(); + } + } + + Buffer buffer; + buffer.ptr = shared_memory->memory(); + buffer.size = size; + buffer.shared_memory = shared_memory; + transfer_buffers_[id] = buffer; + + return buffer; +} + +void CommandBufferProxy::SetToken(int32 token) { + // Not implemented in proxy. + NOTREACHED(); +} + +void CommandBufferProxy::OnNotifyRepaint() { + if (notify_repaint_task_.get()) + MessageLoop::current()->PostNonNestableTask( + FROM_HERE, notify_repaint_task_.release()); +} + +void CommandBufferProxy::SetParseError( + gpu::error::Error error) { + // Not implemented in proxy. + NOTREACHED(); +} + +void CommandBufferProxy::OnSwapBuffers() { + if (swap_buffers_callback_.get()) + swap_buffers_callback_->Run(); +} + +void CommandBufferProxy::SetSwapBuffersCallback(Callback0::Type* callback) { + swap_buffers_callback_.reset(callback); +} + +void CommandBufferProxy::ResizeOffscreenFrameBuffer(const gfx::Size& size) { + if (last_state_.error != gpu::error::kNoError) + return; + + IPC::Message* message = + new GpuCommandBufferMsg_ResizeOffscreenFrameBuffer(route_id_, size); + + // We need to set the unblock flag on this message to guarantee the + // order in which it is processed in the GPU process. Ordinarily in + // certain situations, namely if a synchronous message is being + // processed, other synchronous messages may be processed before + // asynchronous messages. During some page reloads WebGL seems to + // send three messages (sync, async, sync) in rapid succession in + // that order, and the sync message (GpuCommandBufferMsg_Flush, on + // behalf of SwapBuffers) is sometimes processed before the async + // message (GpuCommandBufferMsg_ResizeOffscreenFrameBuffer). This + // causes the WebGL content to disappear because the back buffer is + // not correctly resized. + message->set_unblock(true); + Send(message); +} + +void CommandBufferProxy::SetNotifyRepaintTask(Task* task) { + notify_repaint_task_.reset(task); +} + +#if defined(OS_MACOSX) +void CommandBufferProxy::SetWindowSize(const gfx::Size& size) { + if (last_state_.error != gpu::error::kNoError) + return; + + Send(new GpuCommandBufferMsg_SetWindowSize(route_id_, size)); +} +#endif + +void CommandBufferProxy::AsyncGetState(Task* completion_task) { + if (last_state_.error != gpu::error::kNoError) + return; + + IPC::Message* message = new GpuCommandBufferMsg_AsyncGetState(route_id_); + + // Do not let a synchronous flush hold up this message. If this handler is + // deferred until after the synchronous flush completes, it will overwrite the + // cached last_state_ with out-of-date data. + message->set_unblock(true); + + if (Send(message)) + pending_async_flush_tasks_.push(linked_ptr<Task>(completion_task)); +} + +void CommandBufferProxy::AsyncFlush(int32 put_offset, Task* completion_task) { + if (last_state_.error != gpu::error::kNoError) + return; + + IPC::Message* message = new GpuCommandBufferMsg_AsyncFlush(route_id_, + put_offset); + + // Do not let a synchronous flush hold up this message. If this handler is + // deferred until after the synchronous flush completes, it will overwrite the + // cached last_state_ with out-of-date data. + message->set_unblock(true); + + if (Send(message)) + pending_async_flush_tasks_.push(linked_ptr<Task>(completion_task)); +} + +bool CommandBufferProxy::Send(IPC::Message* msg) { + // Caller should not intentionally send a message if the context is lost. + DCHECK(last_state_.error == gpu::error::kNoError); + + if (channel_) { + if (channel_->Send(msg)) { + return true; + } else { + // Flag the command buffer as lost. Defer deleting the channel until + // OnChannelError is called after returning to the message loop in case + // it is referenced elsewhere. + last_state_.error = gpu::error::kLostContext; + return false; + } + } + + // Callee takes ownership of message, regardless of whether Send is + // successful. See IPC::Message::Sender. + delete msg; + return false; +} + +void CommandBufferProxy::OnUpdateState(const gpu::CommandBuffer::State& state) { + last_state_ = state; + + linked_ptr<Task> task = pending_async_flush_tasks_.front(); + pending_async_flush_tasks_.pop(); + + if (task.get()) { + // Although we need need to update last_state_ while potentially waiting + // for a synchronous flush to complete, we do not need to invoke the + // callback synchonously. Also, post it as a non nestable task so it is + // always invoked by the outermost message loop. + MessageLoop::current()->PostNonNestableTask(FROM_HERE, task.release()); + } +} diff --git a/content/renderer/command_buffer_proxy.h b/content/renderer/command_buffer_proxy.h new file mode 100644 index 0000000..a626cd9 --- /dev/null +++ b/content/renderer/command_buffer_proxy.h @@ -0,0 +1,131 @@ +// Copyright (c) 2009 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 CONTENT_RENDERER_COMMAND_BUFFER_PROXY_H_ +#define CONTENT_RENDERER_COMMAND_BUFFER_PROXY_H_ +#pragma once + +#if defined(ENABLE_GPU) + +#include <map> +#include <queue> + +#include "base/callback.h" +#include "base/linked_ptr.h" +#include "base/scoped_ptr.h" +#include "gpu/command_buffer/common/command_buffer.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_message.h" + +namespace base { +class SharedMemory; +} + +namespace gfx { +class Size; +} + +class PluginChannelHost; +class Task; + +// Client side proxy that forwards messages synchronously to a +// CommandBufferStub. +class CommandBufferProxy : public gpu::CommandBuffer, + public IPC::Channel::Listener { + public: + CommandBufferProxy(IPC::Channel::Sender* channel, int route_id); + virtual ~CommandBufferProxy(); + + // IPC::Channel::Listener implementation: + virtual bool OnMessageReceived(const IPC::Message& message); + virtual void OnChannelError(); + + int route_id() const { return route_id_; } + + // CommandBuffer implementation: + virtual bool Initialize(int32 size); + virtual bool Initialize(base::SharedMemory* buffer, int32 size); + virtual gpu::Buffer GetRingBuffer(); + virtual State GetState(); + virtual void Flush(int32 put_offset); + virtual State FlushSync(int32 put_offset); + virtual void SetGetOffset(int32 get_offset); + virtual int32 CreateTransferBuffer(size_t size); + virtual int32 RegisterTransferBuffer(base::SharedMemory* shared_memory, + size_t size); + virtual void DestroyTransferBuffer(int32 id); + virtual gpu::Buffer GetTransferBuffer(int32 handle); + virtual void SetToken(int32 token); + virtual void SetParseError(gpu::error::Error error); + virtual void OnSwapBuffers(); + + // Set a callback that will be invoked when the SwapBuffers call has been + // issued. + void SetSwapBuffersCallback(Callback0::Type* callback); + void SetChannelErrorCallback(Callback0::Type* callback); + + // Asynchronously resizes an offscreen frame buffer. + void ResizeOffscreenFrameBuffer(const gfx::Size& size); + + // Set a task that will be invoked the next time the window becomes invalid + // and needs to be repainted. Takes ownership of task. + void SetNotifyRepaintTask(Task* task); + +#if defined(OS_MACOSX) + virtual void SetWindowSize(const gfx::Size& size); +#endif + + // Get the last state received from the service without synchronizing. + State GetLastState() { + return last_state_; + } + + // Get the state asynchronously. The task is posted when the state is + // updated. Takes ownership of the task object. + void AsyncGetState(Task* completion_task); + + // Flush the command buffer asynchronously. The task is posted when the flush + // completes. Takes ownership of the task object. + void AsyncFlush(int32 put_offset, Task* completion_task); + + private: + + // Send an IPC message over the GPU channel. This is private to fully + // encapsulate the channel; all callers of this function must explicitly + // verify that the context has not been lost. + bool Send(IPC::Message* msg); + + // Message handlers: + void OnUpdateState(const gpu::CommandBuffer::State& state); + void OnNotifyRepaint(); + + // As with the service, the client takes ownership of the ring buffer. + int32 num_entries_; + scoped_ptr<base::SharedMemory> ring_buffer_; + + // Local cache of id to transfer buffer mapping. + typedef std::map<int32, gpu::Buffer> TransferBufferMap; + TransferBufferMap transfer_buffers_; + + // The last cached state received from the service. + State last_state_; + + IPC::Channel::Sender* channel_; + int route_id_; + + // Pending asynchronous flush callbacks. + typedef std::queue<linked_ptr<Task> > AsyncFlushTaskQueue; + AsyncFlushTaskQueue pending_async_flush_tasks_; + + scoped_ptr<Task> notify_repaint_task_; + + scoped_ptr<Callback0::Type> swap_buffers_callback_; + scoped_ptr<Callback0::Type> channel_error_callback_; + + DISALLOW_COPY_AND_ASSIGN(CommandBufferProxy); +}; + +#endif // ENABLE_GPU + +#endif // CONTENT_RENDERER_COMMAND_BUFFER_PROXY_H_ diff --git a/content/renderer/ggl.cc b/content/renderer/ggl.cc index e73f50d..9e8cf1f 100644 --- a/content/renderer/ggl.cc +++ b/content/renderer/ggl.cc @@ -7,10 +7,10 @@ #include "base/lazy_instance.h" #include "base/ref_counted.h" #include "base/weak_ptr.h" -#include "chrome/renderer/command_buffer_proxy.h" -#include "chrome/renderer/gpu_channel_host.h" -#include "chrome/renderer/gpu_video_service_host.h" #include "chrome/renderer/render_widget.h" +#include "content/renderer/command_buffer_proxy.h" +#include "content/renderer/gpu_channel_host.h" +#include "content/renderer/gpu_video_service_host.h" #include "content/renderer/media/gles2_video_decode_context.h" #include "ipc/ipc_channel_handle.h" diff --git a/content/renderer/gpu_channel_host.cc b/content/renderer/gpu_channel_host.cc new file mode 100644 index 0000000..38e345d --- /dev/null +++ b/content/renderer/gpu_channel_host.cc @@ -0,0 +1,178 @@ +// 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 "content/renderer/gpu_channel_host.h" + +#include "chrome/renderer/render_thread.h" +#include "content/common/child_process.h" +#include "content/common/gpu_messages.h" +#include "content/renderer/command_buffer_proxy.h" +#include "content/renderer/gpu_video_service_host.h" + +GpuChannelHost::GpuChannelHost() : state_(kUnconnected) { +} + +GpuChannelHost::~GpuChannelHost() { +} + +void GpuChannelHost::Connect( + const IPC::ChannelHandle& channel_handle, + base::ProcessHandle renderer_process_for_gpu) { + // Open a channel to the GPU process. + channel_.reset(new IPC::SyncChannel( + channel_handle, IPC::Channel::MODE_CLIENT, this, + ChildProcess::current()->io_message_loop(), true, + ChildProcess::current()->GetShutDownEvent())); + + // It is safe to send IPC messages before the channel completes the connection + // and receives the hello message from the GPU process. The messages get + // cached. + state_ = kConnected; + + // Notify the GPU process of our process handle. This gives it the ability + // to map renderer handles into the GPU process. + Send(new GpuChannelMsg_Initialize(renderer_process_for_gpu)); +} + +void GpuChannelHost::set_gpu_info(const GPUInfo& gpu_info) { + gpu_info_ = gpu_info; +} + +const GPUInfo& GpuChannelHost::gpu_info() const { + return gpu_info_; +} + +void GpuChannelHost::SetStateLost() { + state_ = kLost; +} + +bool GpuChannelHost::OnMessageReceived(const IPC::Message& message) { + DCHECK(message.routing_id() != MSG_ROUTING_CONTROL); + + // The object to which the message is addressed might have been destroyed. + // This is expected, for example an asynchronous SwapBuffers notification + // to a command buffer proxy that has since been destroyed. This function + // fails silently in that case. + return router_.RouteMessage(message); +} + +void GpuChannelHost::OnChannelConnected(int32 peer_pid) { + // When the channel is connected we create a GpuVideoServiceHost and add it + // as a message filter. + gpu_video_service_host_ = new GpuVideoServiceHost(); + channel_->AddFilter(gpu_video_service_host_.get()); +} + +void GpuChannelHost::OnChannelError() { + state_ = kLost; + + // Channel is invalid and will be reinitialized if this host is requested + // again. + channel_.reset(); + + // Inform all the proxies that an error has occured. This will be reported via + // OpenGL as a lost context. + for (ProxyMap::iterator iter = proxies_.begin(); + iter != proxies_.end(); iter++) { + router_.RemoveRoute(iter->first); + iter->second->OnChannelError(); + } + + // The proxies are reference counted so this will not result in their + // destruction if the client still holds a reference. The proxy will report + // a lost context, indicating to the client that it needs to be recreated. + proxies_.clear(); +} + +bool GpuChannelHost::Send(IPC::Message* message) { + if (channel_.get()) + return channel_->Send(message); + + // Callee takes ownership of message, regardless of whether Send is + // successful. See IPC::Message::Sender. + delete message; + return false; +} + +CommandBufferProxy* GpuChannelHost::CreateViewCommandBuffer( + int render_view_id, + const std::string& allowed_extensions, + const std::vector<int32>& attribs) { +#if defined(ENABLE_GPU) + // An error occurred. Need to get the host again to reinitialize it. + if (!channel_.get()) + return NULL; + + GPUCreateCommandBufferConfig init_params; + init_params.allowed_extensions = allowed_extensions; + init_params.attribs = attribs; + int32 route_id; + if (!RenderThread::current()->Send( + new GpuHostMsg_CreateViewCommandBuffer( + render_view_id, init_params, &route_id))) { + return NULL; + } + + if (route_id == MSG_ROUTING_NONE) + return NULL; + + CommandBufferProxy* command_buffer = new CommandBufferProxy(this, route_id); + router_.AddRoute(route_id, command_buffer); + proxies_[route_id] = command_buffer; + return command_buffer; +#else + return NULL; +#endif +} + +CommandBufferProxy* GpuChannelHost::CreateOffscreenCommandBuffer( + CommandBufferProxy* parent, + const gfx::Size& size, + const std::string& allowed_extensions, + const std::vector<int32>& attribs, + uint32 parent_texture_id) { +#if defined(ENABLE_GPU) + // An error occurred. Need to get the host again to reinitialize it. + if (!channel_.get()) + return NULL; + + GPUCreateCommandBufferConfig init_params; + init_params.allowed_extensions = allowed_extensions; + init_params.attribs = attribs; + int32 parent_route_id = parent ? parent->route_id() : 0; + int32 route_id; + if (!Send(new GpuChannelMsg_CreateOffscreenCommandBuffer(parent_route_id, + size, + init_params, + parent_texture_id, + &route_id))) { + return NULL; + } + + if (route_id == MSG_ROUTING_NONE) + return NULL; + + CommandBufferProxy* command_buffer = new CommandBufferProxy(this, route_id); + router_.AddRoute(route_id, command_buffer); + proxies_[route_id] = command_buffer; + return command_buffer; +#else + return NULL; +#endif +} + +void GpuChannelHost::DestroyCommandBuffer(CommandBufferProxy* command_buffer) { +#if defined(ENABLE_GPU) + Send(new GpuChannelMsg_DestroyCommandBuffer(command_buffer->route_id())); + + // Check the proxy has not already been removed after a channel error. + int route_id = command_buffer->route_id(); + if (proxies_.find(command_buffer->route_id()) != proxies_.end()) { + proxies_.erase(route_id); + router_.RemoveRoute(route_id); + } + + delete command_buffer; +#endif +} diff --git a/content/renderer/gpu_channel_host.h b/content/renderer/gpu_channel_host.h new file mode 100644 index 0000000..0b7666e --- /dev/null +++ b/content/renderer/gpu_channel_host.h @@ -0,0 +1,110 @@ +// 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 CONTENT_RENDERER_GPU_CHANNEL_HOST_H_ +#define CONTENT_RENDERER_GPU_CHANNEL_HOST_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/hash_tables.h" +#include "base/process_util.h" +#include "base/scoped_ptr.h" +#include "content/common/gpu_info.h" +#include "content/common/message_router.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_sync_channel.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/size.h" + +class CommandBufferProxy; +class GpuVideoServiceHost; + +// Encapsulates an IPC channel between the renderer and one plugin process. +// On the plugin side there's a corresponding GpuChannel. +class GpuChannelHost : public IPC::Channel::Listener, + public IPC::Message::Sender, + public base::RefCountedThreadSafe<GpuChannelHost> { + public: + enum State { + // Not yet connected. + kUnconnected, + // Ready to use. + kConnected, + // An error caused the host to become disconnected. Recreate channel to + // reestablish connection. + kLost + }; + + // Called on the render thread + GpuChannelHost(); + ~GpuChannelHost(); + + // Connect to GPU process channel. + void Connect(const IPC::ChannelHandle& channel_handle, + base::ProcessHandle renderer_process_for_gpu); + + State state() const { return state_; } + + // Change state to kLost. + void SetStateLost(); + + // The GPU stats reported by the GPU process. + void set_gpu_info(const GPUInfo& gpu_info); + const GPUInfo& gpu_info() const; + + // IPC::Channel::Listener implementation: + virtual bool OnMessageReceived(const IPC::Message& msg); + virtual void OnChannelConnected(int32 peer_pid); + virtual void OnChannelError(); + + // IPC::Message::Sender implementation: + virtual bool Send(IPC::Message* msg); + + // Create and connect to a command buffer in the GPU process. + CommandBufferProxy* CreateViewCommandBuffer( + int render_view_id, + const std::string& allowed_extensions, + const std::vector<int32>& attribs); + + // Create and connect to a command buffer in the GPU process. + CommandBufferProxy* CreateOffscreenCommandBuffer( + CommandBufferProxy* parent, + const gfx::Size& size, + const std::string& allowed_extensions, + const std::vector<int32>& attribs, + uint32 parent_texture_id); + + // Destroy a command buffer created by this channel. + void DestroyCommandBuffer(CommandBufferProxy* command_buffer); + + GpuVideoServiceHost* gpu_video_service_host() { + return gpu_video_service_host_.get(); + } + + private: + State state_; + + GPUInfo gpu_info_; + + scoped_ptr<IPC::SyncChannel> channel_; + + // Used to implement message routing functionality to CommandBufferProxy + // objects + MessageRouter router_; + + // Keep track of all the registered CommandBufferProxies to + // inform about OnChannelError + typedef base::hash_map<int, IPC::Channel::Listener*> ProxyMap; + ProxyMap proxies_; + + // This is a MessageFilter to intercept IPC messages and distribute them + // to the corresponding GpuVideoDecoderHost. + scoped_refptr<GpuVideoServiceHost> gpu_video_service_host_; + + DISALLOW_COPY_AND_ASSIGN(GpuChannelHost); +}; + +#endif // CONTENT_RENDERER_GPU_CHANNEL_HOST_H_ diff --git a/content/renderer/gpu_video_decoder_host.cc b/content/renderer/gpu_video_decoder_host.cc new file mode 100644 index 0000000..315cd32 --- /dev/null +++ b/content/renderer/gpu_video_decoder_host.cc @@ -0,0 +1,399 @@ +// Copyright (c) 2011 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 "content/renderer/gpu_video_decoder_host.h" + +#include "content/common/gpu_messages.h" +#include "content/common/message_router.h" +#include "media/base/pipeline.h" +#include "media/video/video_decode_context.h" + +GpuVideoDecoderHost::GpuVideoDecoderHost(MessageRouter* router, + IPC::Message::Sender* ipc_sender, + int context_route_id, + int32 decoder_host_id) + : router_(router), + ipc_sender_(ipc_sender), + context_route_id_(context_route_id), + message_loop_(NULL), + event_handler_(NULL), + context_(NULL), + width_(0), + height_(0), + state_(kStateUninitialized), + decoder_host_id_(decoder_host_id), + decoder_id_(0), + input_buffer_busy_(false), + current_frame_id_(0) { +} + +GpuVideoDecoderHost::~GpuVideoDecoderHost() {} + +void GpuVideoDecoderHost::OnChannelError() { + ipc_sender_ = NULL; +} + +bool GpuVideoDecoderHost::OnMessageReceived(const IPC::Message& msg) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(GpuVideoDecoderHost, msg) + IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_CreateVideoDecoderDone, + OnCreateVideoDecoderDone) + IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_InitializeACK, + OnInitializeDone) + IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_DestroyACK, + OnUninitializeDone) + IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_FlushACK, + OnFlushDone) + IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_PrerollDone, + OnPrerollDone) + IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_EmptyThisBufferACK, + OnEmptyThisBufferACK) + IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_EmptyThisBufferDone, + OnProduceVideoSample) + IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_ConsumeVideoFrame, + OnConsumeVideoFrame) + IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_AllocateVideoFrames, + OnAllocateVideoFrames) + IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_ReleaseAllVideoFrames, + OnReleaseAllVideoFrames) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + DCHECK(handled); + return handled; +} + +void GpuVideoDecoderHost::Initialize( + MessageLoop* message_loop, VideoDecodeEngine::EventHandler* event_handler, + media::VideoDecodeContext* context, const media::VideoCodecConfig& config) { + DCHECK_EQ(kStateUninitialized, state_); + DCHECK(!message_loop_); + message_loop_ = message_loop; + event_handler_ = event_handler; + context_ = context; + width_ = config.width(); + height_ = config.height(); + + if (MessageLoop::current() != message_loop) { + message_loop->PostTask( + FROM_HERE, + NewRunnableMethod(this, &GpuVideoDecoderHost::CreateVideoDecoder)); + return; + } + CreateVideoDecoder(); +} + +void GpuVideoDecoderHost::ConsumeVideoSample(scoped_refptr<Buffer> buffer) { + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask( + FROM_HERE, + NewRunnableMethod( + this, &GpuVideoDecoderHost::ConsumeVideoSample, buffer)); + return; + } + + DCHECK_NE(state_, kStateUninitialized); + DCHECK_NE(state_, kStateFlushing); + + // We never own input buffers, therefore when client in flush state, it + // never call us with EmptyThisBuffer. + if (state_ != kStateNormal) + return; + + input_buffer_queue_.push_back(buffer); + SendConsumeVideoSample(); +} + +void GpuVideoDecoderHost::ProduceVideoFrame(scoped_refptr<VideoFrame> frame) { + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask( + FROM_HERE, + NewRunnableMethod( + this, &GpuVideoDecoderHost::ProduceVideoFrame, frame)); + return; + } + + DCHECK_NE(state_, kStateUninitialized); + + // During flush client of this object will call this method to return all + // video frames. We should only ignore such method calls if we are in error + // state. + if (state_ == kStateError) + return; + + // Check that video frame is valid. + if (!frame || frame->format() == media::VideoFrame::EMPTY || + frame->IsEndOfStream()) { + return; + } + + SendProduceVideoFrame(frame); +} + +void GpuVideoDecoderHost::Uninitialize() { + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &GpuVideoDecoderHost::Uninitialize)); + return; + } + + if (!ipc_sender_->Send(new GpuVideoDecoderMsg_Destroy(decoder_id_))) { + LOG(ERROR) << "GpuVideoDecoderMsg_Destroy failed"; + event_handler_->OnError(); + } +} + +void GpuVideoDecoderHost::Flush() { + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask( + FROM_HERE, NewRunnableMethod(this, &GpuVideoDecoderHost::Flush)); + return; + } + + state_ = kStateFlushing; + if (!ipc_sender_->Send(new GpuVideoDecoderMsg_Flush(decoder_id_))) { + LOG(ERROR) << "GpuVideoDecoderMsg_Flush failed"; + event_handler_->OnError(); + return; + } + + input_buffer_queue_.clear(); + // TODO(jiesun): because GpuVideoDeocder/GpuVideoDecoder are asynchronously. + // We need a way to make flush logic more clear. but I think ring buffer + // should make the busy flag obsolete, therefore I will leave it for now. + input_buffer_busy_ = false; +} + +void GpuVideoDecoderHost::Seek() { + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask( + FROM_HERE, NewRunnableMethod(this, &GpuVideoDecoderHost::Seek)); + return; + } + + if (!ipc_sender_->Send(new GpuVideoDecoderMsg_Preroll(decoder_id_))) { + LOG(ERROR) << "GpuVideoDecoderMsg_Preroll failed"; + event_handler_->OnError(); + return; + } +} + +void GpuVideoDecoderHost::CreateVideoDecoder() { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + // Add the route so we'll receive messages. + router_->AddRoute(decoder_host_id_, this); + + if (!ipc_sender_->Send( + new GpuChannelMsg_CreateVideoDecoder(context_route_id_, + decoder_host_id_))) { + LOG(ERROR) << "GpuChannelMsg_CreateVideoDecoder failed"; + event_handler_->OnError(); + return; + } +} + +void GpuVideoDecoderHost::OnCreateVideoDecoderDone(int32 decoder_id) { + DCHECK_EQ(message_loop_, MessageLoop::current()); + decoder_id_ = decoder_id; + + // TODO(hclam): Initialize |param| with the right values. + GpuVideoDecoderInitParam param; + param.width = width_; + param.height = height_; + + if (!ipc_sender_->Send( + new GpuVideoDecoderMsg_Initialize(decoder_id, param))) { + LOG(ERROR) << "GpuVideoDecoderMsg_Initialize failed"; + event_handler_->OnError(); + } +} + +void GpuVideoDecoderHost::OnInitializeDone( + const GpuVideoDecoderInitDoneParam& param) { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + bool success = param.success && + base::SharedMemory::IsHandleValid(param.input_buffer_handle); + + if (success) { + input_transfer_buffer_.reset( + new base::SharedMemory(param.input_buffer_handle, false)); + success = input_transfer_buffer_->Map(param.input_buffer_size); + } + state_ = success ? kStateNormal : kStateError; + + // TODO(hclam): There's too many unnecessary copies for width and height! + // Need to clean it up. + // TODO(hclam): Need to fill in more information. + media::VideoCodecInfo info; + info.success = success; + info.stream_info.surface_width = width_; + info.stream_info.surface_height = height_; + event_handler_->OnInitializeComplete(info); +} + +void GpuVideoDecoderHost::OnUninitializeDone() { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + input_transfer_buffer_.reset(); + router_->RemoveRoute(decoder_host_id_); + context_->ReleaseAllVideoFrames(); + event_handler_->OnUninitializeComplete(); +} + +void GpuVideoDecoderHost::OnFlushDone() { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + state_ = kStateNormal; + event_handler_->OnFlushComplete(); +} + +void GpuVideoDecoderHost::OnPrerollDone() { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + state_ = kStateNormal; + event_handler_->OnSeekComplete(); +} + +void GpuVideoDecoderHost::OnEmptyThisBufferACK() { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + input_buffer_busy_ = false; + SendConsumeVideoSample(); +} + +void GpuVideoDecoderHost::OnProduceVideoSample() { + DCHECK_EQ(message_loop_, MessageLoop::current()); + DCHECK_EQ(kStateNormal, state_); + + event_handler_->ProduceVideoSample(NULL); +} + +void GpuVideoDecoderHost::OnConsumeVideoFrame(int32 frame_id, int64 timestamp, + int64 duration, int32 flags) { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + scoped_refptr<VideoFrame> frame; + if (flags & kGpuVideoEndOfStream) { + VideoFrame::CreateEmptyFrame(&frame); + } else { + frame = video_frame_map_[frame_id]; + DCHECK(frame) << "Invalid frame ID received"; + + frame->SetDuration(base::TimeDelta::FromMicroseconds(duration)); + frame->SetTimestamp(base::TimeDelta::FromMicroseconds(timestamp)); + } + + media::PipelineStatistics statistics; + // TODO(sjl): Fill in statistics. + + event_handler_->ConsumeVideoFrame(frame, statistics); +} + +void GpuVideoDecoderHost::OnAllocateVideoFrames( + int32 n, uint32 width, uint32 height, int32 format) { + DCHECK_EQ(message_loop_, MessageLoop::current()); + DCHECK_EQ(0u, video_frames_.size()); + + context_->AllocateVideoFrames( + n, width, height, static_cast<media::VideoFrame::Format>(format), + &video_frames_, + NewRunnableMethod(this, + &GpuVideoDecoderHost::OnAllocateVideoFramesDone)); +} + +void GpuVideoDecoderHost::OnReleaseAllVideoFrames() { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + context_->ReleaseAllVideoFrames(); + video_frame_map_.clear(); + video_frames_.clear(); +} + +void GpuVideoDecoderHost::OnAllocateVideoFramesDone() { + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask( + FROM_HERE, + NewRunnableMethod( + this, &GpuVideoDecoderHost::OnAllocateVideoFramesDone)); + return; + } + + // After video frame allocation is done we add these frames to a map and + // send them to the GPU process. + DCHECK(video_frames_.size()) << "No video frames allocated"; + for (size_t i = 0; i < video_frames_.size(); ++i) { + DCHECK(video_frames_[i]); + video_frame_map_.insert( + std::make_pair(current_frame_id_, video_frames_[i])); + SendVideoFrameAllocated(current_frame_id_, video_frames_[i]); + ++current_frame_id_; + } +} + +void GpuVideoDecoderHost::SendVideoFrameAllocated( + int32 frame_id, scoped_refptr<media::VideoFrame> frame) { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + std::vector<uint32> textures; + for (size_t i = 0; i < frame->planes(); ++i) { + textures.push_back(frame->gl_texture(i)); + } + + if (!ipc_sender_->Send(new GpuVideoDecoderMsg_VideoFrameAllocated( + decoder_id_, frame_id, textures))) { + LOG(ERROR) << "GpuVideoDecoderMsg_EmptyThisBuffer failed"; + } +} + +void GpuVideoDecoderHost::SendConsumeVideoSample() { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + if (input_buffer_busy_ || input_buffer_queue_.empty()) + return; + input_buffer_busy_ = true; + + scoped_refptr<Buffer> buffer = input_buffer_queue_.front(); + input_buffer_queue_.pop_front(); + + // Send input data to GPU process. + GpuVideoDecoderInputBufferParam param; + param.offset = 0; + param.size = buffer->GetDataSize(); + param.timestamp = buffer->GetTimestamp().InMicroseconds(); + memcpy(input_transfer_buffer_->memory(), buffer->GetData(), param.size); + + if (!ipc_sender_->Send( + new GpuVideoDecoderMsg_EmptyThisBuffer(decoder_id_, param))) { + LOG(ERROR) << "GpuVideoDecoderMsg_EmptyThisBuffer failed"; + } +} + +void GpuVideoDecoderHost::SendProduceVideoFrame( + scoped_refptr<media::VideoFrame> frame) { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + // TODO(hclam): I should mark a frame being used to DCHECK and make sure + // user doesn't use it the second time. + // TODO(hclam): Derive a faster way to lookup the frame ID. + bool found = false; + int32 frame_id = 0; + for (VideoFrameMap::iterator i = video_frame_map_.begin(); + i != video_frame_map_.end(); ++i) { + if (frame == i->second) { + frame_id = i->first; + found = true; + break; + } + } + + DCHECK(found) << "Invalid video frame received"; + if (found && !ipc_sender_->Send( + new GpuVideoDecoderMsg_ProduceVideoFrame(decoder_id_, frame_id))) { + LOG(ERROR) << "GpuVideoDecoderMsg_ProduceVideoFrame failed"; + } +} + +DISABLE_RUNNABLE_METHOD_REFCOUNT(GpuVideoDecoderHost); diff --git a/content/renderer/gpu_video_decoder_host.h b/content/renderer/gpu_video_decoder_host.h new file mode 100644 index 0000000..faebb66 --- /dev/null +++ b/content/renderer/gpu_video_decoder_host.h @@ -0,0 +1,165 @@ +// Copyright (c) 2011 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 CONTENT_RENDERER_GPU_VIDEO_DECODER_HOST_H_ +#define CONTENT_RENDERER_GPU_VIDEO_DECODER_HOST_H_ + +#include <deque> +#include <map> + +#include "base/shared_memory.h" +#include "base/singleton.h" +#include "content/renderer/gpu_channel_host.h" +#include "media/base/buffers.h" +#include "media/base/video_frame.h" +#include "media/video/video_decode_engine.h" + +using media::VideoFrame; +using media::Buffer; + +class MessageRouter; +struct GpuVideoDecoderInitDoneParam; + +// This class is used to talk to GpuVideoDecoder in the GPU process through +// IPC messages. It implements the interface of VideoDecodeEngine so users +// view it as a regular video decode engine, the implementation is a portal +// to the GPU process. +// +// THREAD SEMANTICS +// +// All methods of this class can be accessed on any thread. A message loop +// needs to be provided to class through Initialize() for accessing the +// IPC channel. Event handlers are called on that message loop. +// +// Since this class is not refcounted, it is important to delete this +// object only after OnUninitializeCompelte() is called. +class GpuVideoDecoderHost : public media::VideoDecodeEngine, + public IPC::Channel::Listener { + public: + // |router| is used to dispatch IPC messages to this object. + // |ipc_sender| is used to send IPC messages to GPU process. + // It is important that the above two objects are accessed on the + // |message_loop_|. + GpuVideoDecoderHost(MessageRouter* router, + IPC::Message::Sender* ipc_sender, + int context_route_id, + int32 decoder_host_id); + virtual ~GpuVideoDecoderHost(); + + // IPC::Channel::Listener. + virtual void OnChannelConnected(int32 peer_pid) {} + virtual void OnChannelError(); + virtual bool OnMessageReceived(const IPC::Message& message); + + // media::VideoDecodeEngine implementation. + virtual void Initialize(MessageLoop* message_loop, + VideoDecodeEngine::EventHandler* event_handler, + media::VideoDecodeContext* context, + const media::VideoCodecConfig& config); + virtual void ConsumeVideoSample(scoped_refptr<Buffer> buffer); + virtual void ProduceVideoFrame(scoped_refptr<VideoFrame> frame); + virtual void Uninitialize(); + virtual void Flush(); + virtual void Seek(); + + private: + typedef std::map<int32, scoped_refptr<media::VideoFrame> > VideoFrameMap; + + // Internal states. + enum GpuVideoDecoderHostState { + kStateUninitialized, + kStateNormal, + kStateError, + kStateFlushing, + }; + + // Takes care of sending IPC message to create a video decoder. + void CreateVideoDecoder(); + + // Handlers for messages received from the GPU process. + void OnCreateVideoDecoderDone(int32 decoder_id); + void OnInitializeDone(const GpuVideoDecoderInitDoneParam& param); + void OnUninitializeDone(); + void OnFlushDone(); + void OnPrerollDone(); + void OnEmptyThisBufferACK(); + void OnProduceVideoSample(); + void OnConsumeVideoFrame(int32 frame_id, int64 timestamp, + int64 duration, int32 flags); + void OnAllocateVideoFrames(int32 n, uint32 width, + uint32 height, int32 format); + void OnReleaseAllVideoFrames(); + + // Handler for VideoDecodeContext. This method is called when video frames + // allocation is done. + void OnAllocateVideoFramesDone(); + + // Send a message to the GPU process to inform that a video frame is + // allocated. + void SendVideoFrameAllocated(int32 frame_id, + scoped_refptr<media::VideoFrame> frame); + + // Send a video sample to the GPU process and tell it to use the buffer for + // video decoding. + void SendConsumeVideoSample(); + + // Look up the frame_id for |frame| and send a message to the GPU process + // to use that video frame to produce an output. + void SendProduceVideoFrame(scoped_refptr<media::VideoFrame> frame); + + // A router used to send us IPC messages. + MessageRouter* router_; + + // Sends IPC messages to the GPU process. + IPC::Message::Sender* ipc_sender_; + + // Route ID of the GLES2 context in the GPU process. + int context_route_id_; + + // Message loop that this object runs on. + MessageLoop* message_loop_; + + // We expect that the client of us will always available during our life span. + EventHandler* event_handler_; + + // A Context for allocating video frame textures. + media::VideoDecodeContext* context_; + + // Dimensions of the video. + int width_; + int height_; + + // Current state of video decoder. + GpuVideoDecoderHostState state_; + + // ID of this GpuVideoDecoderHost. + int32 decoder_host_id_; + + // ID of GpuVideoDecoder in the GPU process. + int32 decoder_id_; + + // We are not able to push all received buffer to gpu process at once. + std::deque<scoped_refptr<Buffer> > input_buffer_queue_; + + // Currently we do not use ring buffer in input buffer, therefore before + // GPU process had finished access it, we should not touch it. + bool input_buffer_busy_; + + // Transfer buffers for both input and output. + // TODO(jiesun): remove output buffer when hardware composition is ready. + scoped_ptr<base::SharedMemory> input_transfer_buffer_; + + // Frame ID for the newly generated video frame. + int32 current_frame_id_; + + // The list of video frames allocated by VideoDecodeContext. + std::vector<scoped_refptr<media::VideoFrame> > video_frames_; + + // The mapping between video frame ID and a video frame. + VideoFrameMap video_frame_map_; + + DISALLOW_COPY_AND_ASSIGN(GpuVideoDecoderHost); +}; + +#endif // CONTENT_RENDERER_GPU_VIDEO_DECODER_HOST_H_ diff --git a/content/renderer/gpu_video_decoder_host_unittest.cc b/content/renderer/gpu_video_decoder_host_unittest.cc new file mode 100644 index 0000000..e9f69a6 --- /dev/null +++ b/content/renderer/gpu_video_decoder_host_unittest.cc @@ -0,0 +1,265 @@ +// Copyright (c) 2011 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 "base/message_loop.h" +#include "content/common/message_router.h" +#include "content/common/gpu_messages.h" +#include "content/renderer/gpu_video_decoder_host.h" +#include "media/base/pipeline.h" +#include "media/video/video_mock_objects.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::DoAll; +using testing::NotNull; +using testing::Return; +using testing::SetArgumentPointee; + +static const int kContextRouteId = 50; +static const int kDecoderHostId = 51; +static const int kDecoderId = 51; +static const int kVideoFrames = 3; +static const int kWidth = 320; +static const int kHeight = 240; +static const int kFrameRateNumerator = 25; +static const int kFrameRateDenominator = 1; +static const int kTransportBufferSize = 1024; + +ACTION_P(SimulateAllocateVideoFrames, frames) { + // Fake some texture IDs here. + media::VideoFrame::GlTexture textures[] = {4, 5, 6}; + for (int i = 0; i < kVideoFrames; ++i) { + scoped_refptr<media::VideoFrame> frame; + media::VideoFrame::CreateFrameGlTexture(media::VideoFrame::YV12, + kWidth, kHeight, textures, + &frame); + frames->push_back(frame); + arg4->push_back(frame); + } + + // Execute the callback to complete the task. + arg5->Run(); + delete arg5; +} + +ACTION_P2(SendMessage, handler, msg) { + handler->OnMessageReceived(msg); +} + +class GpuVideoDecoderHostTest : public testing::Test, + public IPC::Message::Sender, + public media::VideoDecodeEngine::EventHandler { + public: + // This method is used to dispatch IPC messages to mock methods. + virtual bool Send(IPC::Message* msg) { + EXPECT_TRUE(msg); + if (!msg) + return false; + + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(GpuVideoDecoderHostTest, *msg) + IPC_MESSAGE_HANDLER(GpuChannelMsg_CreateVideoDecoder, + OnCreateVideoDecoder) + IPC_MESSAGE_HANDLER(GpuVideoDecoderMsg_Initialize, + OnInitialize) + IPC_MESSAGE_HANDLER(GpuVideoDecoderMsg_Destroy, + OnDestroy) + IPC_MESSAGE_HANDLER(GpuVideoDecoderMsg_Flush, + OnFlush) + IPC_MESSAGE_HANDLER(GpuVideoDecoderMsg_EmptyThisBuffer, + OnEmptyThisBuffer) + IPC_MESSAGE_HANDLER(GpuVideoDecoderMsg_VideoFrameAllocated, + OnVideoFrameAllocated) + IPC_MESSAGE_HANDLER(GpuVideoDecoderMsg_ProduceVideoFrame, + OnProduceVideoFrame) + IPC_MESSAGE_UNHANDLED_ERROR() + IPC_END_MESSAGE_MAP() + EXPECT_TRUE(handled); + delete msg; + return true; + } + + // Mock methods for outgoing messages. + MOCK_METHOD1(OnInitialize, void(GpuVideoDecoderInitParam param)); + MOCK_METHOD0(OnDestroy, void()); + MOCK_METHOD0(OnFlush, void()); + MOCK_METHOD1(OnEmptyThisBuffer, + void(GpuVideoDecoderInputBufferParam param)); + MOCK_METHOD1(OnProduceVideoFrame, void(int32 frame_id)); + MOCK_METHOD2(OnVideoFrameAllocated, + void(int32 frame_id, std::vector<uint32> textures)); + MOCK_METHOD2(OnCreateVideoDecoder, + void(int32 context_route_id, int32 decoder_host_id)); + + // Mock methods for VideoDecodeEngine::EventHandler. + MOCK_METHOD1(ProduceVideoSample, + void(scoped_refptr<media::Buffer> buffer)); + MOCK_METHOD2(ConsumeVideoFrame, + void(scoped_refptr<media::VideoFrame> frame, + const media::PipelineStatistics& statistics)); + MOCK_METHOD1(OnInitializeComplete, + void(const media::VideoCodecInfo& info)); + MOCK_METHOD0(OnUninitializeComplete, void()); + MOCK_METHOD0(OnFlushComplete, void()); + MOCK_METHOD0(OnSeekComplete, void()); + MOCK_METHOD0(OnError, void()); + MOCK_METHOD1(OnFormatChange, + void(media::VideoStreamInfo stream_info)); + + void Initialize() { + decoder_host_.reset( + new GpuVideoDecoderHost(&router_, this, kContextRouteId, + kDecoderHostId)); + shared_memory_.reset(new base::SharedMemory()); + shared_memory_->CreateAnonymous(kTransportBufferSize); + + GpuVideoDecoderHostMsg_CreateVideoDecoderDone msg1(kDecoderHostId, + kDecoderId); + EXPECT_CALL(*this, OnCreateVideoDecoder(kContextRouteId, kDecoderHostId)) + .WillOnce(SendMessage(decoder_host_.get(), msg1)); + + GpuVideoDecoderInitDoneParam param; + param.success = true; + param.input_buffer_size = kTransportBufferSize; + param.input_buffer_handle = shared_memory_->handle(); + + GpuVideoDecoderHostMsg_InitializeACK msg2(kDecoderHostId, param); + EXPECT_CALL(*this, OnInitialize(_)) + .WillOnce(SendMessage(decoder_host_.get(), msg2)); + EXPECT_CALL(*this, OnInitializeComplete(_)); + + media::VideoCodecConfig config( + media::kCodecH264, + kWidth, + kHeight, + kFrameRateNumerator, + kFrameRateDenominator, + NULL, + 0); + decoder_host_->Initialize(&message_loop_, this, &context_, config); + message_loop_.RunAllPending(); + } + + void Uninitialize() { + // A message is sent to GPU process to destroy the decoder. + GpuVideoDecoderHostMsg_DestroyACK msg(kDecoderHostId); + EXPECT_CALL(*this, OnDestroy()) + .WillOnce(SendMessage(decoder_host_.get(), msg)); + EXPECT_CALL(context_, ReleaseAllVideoFrames()); + EXPECT_CALL(*this, OnUninitializeComplete()); + decoder_host_->Uninitialize(); + } + + void AllocateVideoFrames() { + // Expect context is called to allocate video frames. + EXPECT_CALL(context_, + AllocateVideoFrames(kVideoFrames, kWidth, kHeight, + media::VideoFrame::YV12, + NotNull(), NotNull())) + .WillOnce(SimulateAllocateVideoFrames(&frames_)) + .RetiresOnSaturation(); + + // Expect that we send the video frames to the GPU process. + EXPECT_CALL(*this, OnVideoFrameAllocated(_, _)) + .Times(kVideoFrames) + .RetiresOnSaturation(); + + // Pretend that a message is sent to GpuVideoDecoderHost to allocate + // video frames. + GpuVideoDecoderHostMsg_AllocateVideoFrames msg( + kDecoderHostId, kVideoFrames, kWidth, kHeight, + static_cast<int32>(media::VideoFrame::YV12)); + decoder_host_->OnMessageReceived(msg); + } + + void ReleaseVideoFrames() { + // Expect that context is called to release all video frames. + EXPECT_CALL(context_, ReleaseAllVideoFrames()) + .RetiresOnSaturation(); + + // Pretend a message is sent to release all video frames. + GpuVideoDecoderHostMsg_ReleaseAllVideoFrames msg(kDecoderHostId); + decoder_host_->OnMessageReceived(msg); + + // Clear the list of video frames allocated. + frames_.clear(); + } + + void ProduceVideoFrame(int first_frame_id) { + for (int i = 0; i < kVideoFrames; ++i) { + // Expect that a request is received to produce a video frame. + GpuVideoDecoderHostMsg_ConsumeVideoFrame msg( + kDecoderHostId, first_frame_id + i, 0, 0, 0); + EXPECT_CALL(*this, OnProduceVideoFrame(first_frame_id + i)) + .WillOnce(SendMessage(decoder_host_.get(), msg)) + .RetiresOnSaturation(); + + // Expect that a reply is made when a video frame is ready. + EXPECT_CALL(*this, ConsumeVideoFrame(frames_[i], _)) + .RetiresOnSaturation(); + + // Use the allocated video frames to make a request. + decoder_host_->ProduceVideoFrame(frames_[i]); + } + } + + private: + MessageLoop message_loop_; + MessageRouter router_; + media::MockVideoDecodeContext context_; + + scoped_ptr<GpuVideoDecoderHost> decoder_host_; + scoped_ptr<base::SharedMemory> shared_memory_; + + // Keeps the video frames allocated. + std::vector<scoped_refptr<media::VideoFrame> > frames_; +}; + +// Test that when we initialize GpuVideoDecoderHost the corresponding +// IPC messages are sent and at the end OnInitializeComplete() is +// called with the right parameters. +TEST_F(GpuVideoDecoderHostTest, Initialize) { + Initialize(); +} + +// Test that the sequence of method calls and IPC messages is correct. +// And at the end OnUninitializeComplete() is called. +TEST_F(GpuVideoDecoderHostTest, Uninitialize) { + Initialize(); + Uninitialize(); +} + +// Test that IPC messages are sent to GpuVideoDecoderHost and it +// calls VideoDecodeContext to allocate textures and send these +// textures back to the GPU process by IPC messages. +TEST_F(GpuVideoDecoderHostTest, AllocateVideoFrames) { + Initialize(); + AllocateVideoFrames(); + Uninitialize(); +} + +// Test that IPC messages are sent to GpuVideoDecoderHost to +// release textures and VideoDecodeContext is called correctly. +TEST_F(GpuVideoDecoderHostTest, ReleaseVideoFrames) { + Initialize(); + AllocateVideoFrames(); + ReleaseVideoFrames(); + AllocateVideoFrames(); + ReleaseVideoFrames(); + Uninitialize(); +} + +// Test the sequence of IPC messages and methods calls for a decode +// routine. This tests the output port only. +TEST_F(GpuVideoDecoderHostTest, ProduceVideoFrame) { + Initialize(); + AllocateVideoFrames(); + ProduceVideoFrame(0); + ReleaseVideoFrames(); + AllocateVideoFrames(); + ProduceVideoFrame(kVideoFrames); + ReleaseVideoFrames(); + Uninitialize(); +} diff --git a/content/renderer/gpu_video_service_host.cc b/content/renderer/gpu_video_service_host.cc new file mode 100644 index 0000000..412748d --- /dev/null +++ b/content/renderer/gpu_video_service_host.cc @@ -0,0 +1,57 @@ +// 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 "content/renderer/gpu_video_service_host.h" + +#include "chrome/renderer/render_thread.h" +#include "content/renderer/gpu_video_decoder_host.h" +#include "content/common/gpu_messages.h" + +GpuVideoServiceHost::GpuVideoServiceHost() + : channel_(NULL), + next_decoder_host_id_(0) { +} + +void GpuVideoServiceHost::OnFilterAdded(IPC::Channel* channel) { + channel_ = channel; +} + +void GpuVideoServiceHost::OnFilterRemoved() { + // TODO(hclam): Implement. +} + +void GpuVideoServiceHost::OnChannelClosing() { + // TODO(hclam): Implement. +} + +bool GpuVideoServiceHost::OnMessageReceived(const IPC::Message& msg) { + switch (msg.type()) { + case GpuVideoDecoderHostMsg_CreateVideoDecoderDone::ID: + case GpuVideoDecoderHostMsg_InitializeACK::ID: + case GpuVideoDecoderHostMsg_DestroyACK::ID: + case GpuVideoDecoderHostMsg_FlushACK::ID: + case GpuVideoDecoderHostMsg_PrerollDone::ID: + case GpuVideoDecoderHostMsg_EmptyThisBufferACK::ID: + case GpuVideoDecoderHostMsg_EmptyThisBufferDone::ID: + case GpuVideoDecoderHostMsg_ConsumeVideoFrame::ID: + case GpuVideoDecoderHostMsg_AllocateVideoFrames::ID: + case GpuVideoDecoderHostMsg_ReleaseAllVideoFrames::ID: + if (!router_.RouteMessage(msg)) { + LOG(ERROR) << "GpuVideoDecoderHostMsg cannot be dispatched."; + } + return true; + default: + return false; + } +} + +GpuVideoDecoderHost* GpuVideoServiceHost::CreateVideoDecoder( + int context_route_id) { + GpuVideoDecoderHost* host = new GpuVideoDecoderHost(&router_, channel_, + context_route_id, + next_decoder_host_id_); + // TODO(hclam): Handle thread safety of incrementing the ID. + ++next_decoder_host_id_; + return host; +} diff --git a/content/renderer/gpu_video_service_host.h b/content/renderer/gpu_video_service_host.h new file mode 100644 index 0000000..543448d --- /dev/null +++ b/content/renderer/gpu_video_service_host.h @@ -0,0 +1,53 @@ +// 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 CONTENT_RENDERER_GPU_VIDEO_SERVICE_HOST_H_ +#define CONTENT_RENDERER_GPU_VIDEO_SERVICE_HOST_H_ + +#include "base/singleton.h" +#include "content/renderer/gpu_channel_host.h" +#include "content/renderer/gpu_video_decoder_host.h" +#include "ipc/ipc_channel.h" +#include "media/base/buffers.h" +#include "media/base/video_frame.h" + +// GpuVideoServiceHost lives on IO thread and is used to dispatch IPC messages +// to GpuVideoDecoderHost objects. +class GpuVideoServiceHost : public IPC::ChannelProxy::MessageFilter { + public: + GpuVideoServiceHost(); + + // IPC::ChannelProxy::MessageFilter implementations. + virtual bool OnMessageReceived(const IPC::Message& message); + virtual void OnFilterAdded(IPC::Channel* channel); + virtual void OnFilterRemoved(); + virtual void OnChannelClosing(); + + // Called on RenderThread to create a hardware accelerated video decoder + // in the GPU process. + // + // A routing ID for the GLES2 context needs to be provided when creating a + // hardware video decoder. This is important because the resources used by + // the video decoder needs to be shared with the GLES2 context corresponding + // to the RenderView. + // + // This means that a GPU video decoder is tied to a specific RenderView and + // its GLES2 context in the GPU process. + // + // Returns a GpuVideoDecoderHost as a handle to control the video decoder. + GpuVideoDecoderHost* CreateVideoDecoder(int context_route_id); + + private: + IPC::Channel* channel_; + + // Router to send messages to a GpuVideoDecoderHost. + MessageRouter router_; + + // ID for the next GpuVideoDecoderHost. + int32 next_decoder_host_id_; + + DISALLOW_COPY_AND_ASSIGN(GpuVideoServiceHost); +}; + +#endif // CONTENT_RENDERER_GPU_VIDEO_SERVICE_HOST_H_ diff --git a/content/renderer/plugin_channel_host.cc b/content/renderer/plugin_channel_host.cc new file mode 100644 index 0000000..3d4993c --- /dev/null +++ b/content/renderer/plugin_channel_host.cc @@ -0,0 +1,145 @@ +// 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 "content/renderer/plugin_channel_host.h" + +#include "content/common/plugin_messages.h" +#include "content/plugin/npobject_base.h" + +#if defined(OS_POSIX) +#include "ipc/ipc_channel_posix.h" +#endif + +#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h" + +// A simple MessageFilter that will ignore all messages and respond to sync +// messages with an error when is_listening_ is false. +class IsListeningFilter : public IPC::ChannelProxy::MessageFilter { + public: + IsListeningFilter() : channel_(NULL) {} + + // MessageFilter overrides + virtual void OnFilterRemoved() {} + virtual void OnFilterAdded(IPC::Channel* channel) { channel_ = channel; } + virtual bool OnMessageReceived(const IPC::Message& message); + + static bool is_listening_; + + private: + IPC::Channel* channel_; + + DISALLOW_COPY_AND_ASSIGN(IsListeningFilter); +}; + +bool IsListeningFilter::OnMessageReceived(const IPC::Message& message) { + if (IsListeningFilter::is_listening_) { + // Proceed with normal operation. + return false; + } + + // Always process message reply to prevent renderer from hanging on sync + // messages. + if (message.is_reply() || message.is_reply_error()) { + return false; + } + + // Reply to synchronous messages with an error (so they don't block while + // we're not listening). + if (message.is_sync()) { + IPC::Message* reply = IPC::SyncMessage::GenerateReply(&message); + reply->set_reply_error(); + channel_->Send(reply); + } + return true; +} + +// static +bool IsListeningFilter::is_listening_ = true; + +// static +bool PluginChannelHost::IsListening() { + return IsListeningFilter::is_listening_; +} + +// static +void PluginChannelHost::SetListening(bool flag) { + IsListeningFilter::is_listening_ = flag; +} + +PluginChannelHost* PluginChannelHost::GetPluginChannelHost( + const IPC::ChannelHandle& channel_handle, MessageLoop* ipc_message_loop) { + PluginChannelHost* result = + static_cast<PluginChannelHost*>(PluginChannelBase::GetChannel( + channel_handle, + IPC::Channel::MODE_CLIENT, + ClassFactory, + ipc_message_loop, + true)); + return result; +} + +PluginChannelHost::PluginChannelHost() : expecting_shutdown_(false) { +} + +PluginChannelHost::~PluginChannelHost() { +} + +bool PluginChannelHost::Init(MessageLoop* ipc_message_loop, + bool create_pipe_now) { + bool ret = PluginChannelBase::Init(ipc_message_loop, create_pipe_now); + is_listening_filter_ = new IsListeningFilter; + channel_->AddFilter(is_listening_filter_); + return ret; +} + +int PluginChannelHost::GenerateRouteID() { + int route_id = MSG_ROUTING_NONE; + Send(new PluginMsg_GenerateRouteID(&route_id)); + + return route_id; +} + +void PluginChannelHost::AddRoute(int route_id, + IPC::Channel::Listener* listener, + NPObjectBase* npobject) { + PluginChannelBase::AddRoute(route_id, listener, npobject); + + if (!npobject) + proxies_[route_id] = listener; +} + +void PluginChannelHost::RemoveRoute(int route_id) { + proxies_.erase(route_id); + PluginChannelBase::RemoveRoute(route_id); +} + +bool PluginChannelHost::OnControlMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PluginChannelHost, message) + IPC_MESSAGE_HANDLER(PluginHostMsg_SetException, OnSetException) + IPC_MESSAGE_HANDLER(PluginHostMsg_PluginShuttingDown, OnPluginShuttingDown) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + DCHECK(handled); + return handled; +} + +void PluginChannelHost::OnSetException(const std::string& message) { + WebKit::WebBindings::setException(NULL, message.c_str()); +} + +void PluginChannelHost::OnPluginShuttingDown(const IPC::Message& message) { + expecting_shutdown_ = true; +} + +void PluginChannelHost::OnChannelError() { + PluginChannelBase::OnChannelError(); + + for (ProxyMap::iterator iter = proxies_.begin(); + iter != proxies_.end(); iter++) { + iter->second->OnChannelError(); + } + + proxies_.clear(); +} diff --git a/content/renderer/plugin_channel_host.h b/content/renderer/plugin_channel_host.h new file mode 100644 index 0000000..3e84412 --- /dev/null +++ b/content/renderer/plugin_channel_host.h @@ -0,0 +1,71 @@ +// 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 CONTENT_RENDERER_PLUGIN_CHANNEL_HOST_H_ +#define CONTENT_RENDERER_PLUGIN_CHANNEL_HOST_H_ +#pragma once + +#include "base/hash_tables.h" +#include "content/plugin/plugin_channel_base.h" +#include "ipc/ipc_channel_handle.h" + +class IsListeningFilter; +class NPObjectBase; + +// Encapsulates an IPC channel between the renderer and one plugin process. +// On the plugin side there's a corresponding PluginChannel. +class PluginChannelHost : public PluginChannelBase { + public: + static PluginChannelHost* GetPluginChannelHost( + const IPC::ChannelHandle& channel_handle, MessageLoop* ipc_message_loop); + + virtual bool Init(MessageLoop* ipc_message_loop, bool create_pipe_now); + + virtual int GenerateRouteID(); + + void AddRoute(int route_id, IPC::Channel::Listener* listener, + NPObjectBase* npobject); + void RemoveRoute(int route_id); + + // IPC::Channel::Listener override + virtual void OnChannelError(); + + static void SetListening(bool flag); + + static bool IsListening(); + + static void Broadcast(IPC::Message* message) { + PluginChannelBase::Broadcast(message); + } + + bool expecting_shutdown() { return expecting_shutdown_; } + + private: + // Called on the render thread + PluginChannelHost(); + ~PluginChannelHost(); + + static PluginChannelBase* ClassFactory() { return new PluginChannelHost(); } + + virtual bool OnControlMessageReceived(const IPC::Message& message); + void OnSetException(const std::string& message); + void OnPluginShuttingDown(const IPC::Message& message); + + // Keep track of all the registered WebPluginDelegeProxies to + // inform about OnChannelError + typedef base::hash_map<int, IPC::Channel::Listener*> ProxyMap; + ProxyMap proxies_; + + // An IPC MessageFilter that can be told to filter out all messages. This is + // used when the JS debugger is attached in order to avoid browser hangs. + scoped_refptr<IsListeningFilter> is_listening_filter_; + + // True if we are expecting the plugin process to go away - in which case, + // don't treat it as a crash. + bool expecting_shutdown_; + + DISALLOW_COPY_AND_ASSIGN(PluginChannelHost); +}; + +#endif // CONTENT_RENDERER_PLUGIN_CHANNEL_HOST_H_ diff --git a/content/renderer/webgraphicscontext3d_command_buffer_impl.cc b/content/renderer/webgraphicscontext3d_command_buffer_impl.cc new file mode 100644 index 0000000..8619e96 --- /dev/null +++ b/content/renderer/webgraphicscontext3d_command_buffer_impl.cc @@ -0,0 +1,1023 @@ +// 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. + +#if defined(ENABLE_GPU) + +#include "content/renderer/webgraphicscontext3d_command_buffer_impl.h" + +#include <GLES2/gl2.h> +#ifndef GL_GLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES 1 +#endif +#include <GLES2/gl2ext.h> + +#include <algorithm> + +#include "base/string_tokenizer.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/renderer/render_thread.h" +#include "chrome/renderer/render_view.h" +#include "content/renderer/gpu_channel_host.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" + +WebGraphicsContext3DCommandBufferImpl::WebGraphicsContext3DCommandBufferImpl() + : context_(NULL), + web_view_(NULL), +#if defined(OS_MACOSX) + plugin_handle_(NULL), +#endif // defined(OS_MACOSX) + context_lost_callback_(0), + cached_width_(0), + cached_height_(0), + bound_fbo_(0) { +} + +WebGraphicsContext3DCommandBufferImpl:: + ~WebGraphicsContext3DCommandBufferImpl() { + if (context_) { + ggl::DestroyContext(context_); + } +} + +static const char* kWebGraphicsContext3DPerferredGLExtensions = + "GL_OES_packed_depth_stencil " + "GL_OES_depth24 " + "GL_CHROMIUM_webglsl"; + +bool WebGraphicsContext3DCommandBufferImpl::initialize( + WebGraphicsContext3D::Attributes attributes, + WebKit::WebView* web_view, + bool render_directly_to_web_view) { + RenderThread* render_thread = RenderThread::current(); + if (!render_thread) + return false; + GpuChannelHost* host = render_thread->EstablishGpuChannelSync(); + if (!host) + return false; + DCHECK(host->state() == GpuChannelHost::kConnected); + + // Convert WebGL context creation attributes into GGL/EGL size requests. + const int alpha_size = attributes.alpha ? 8 : 0; + const int depth_size = attributes.depth ? 24 : 0; + const int stencil_size = attributes.stencil ? 8 : 0; + const int samples = attributes.antialias ? 4 : 0; + const int sample_buffers = attributes.antialias ? 1 : 0; + const int32 attribs[] = { + ggl::GGL_ALPHA_SIZE, alpha_size, + ggl::GGL_DEPTH_SIZE, depth_size, + ggl::GGL_STENCIL_SIZE, stencil_size, + ggl::GGL_SAMPLES, samples, + ggl::GGL_SAMPLE_BUFFERS, sample_buffers, + ggl::GGL_NONE, + }; + + GPUInfo gpu_info = host->gpu_info(); + UMA_HISTOGRAM_ENUMERATION( + "GPU.WebGraphicsContext3D_Init_CanLoseContext", + attributes.canRecoverFromContextLoss * 2 + gpu_info.can_lose_context, + 4); + if (attributes.canRecoverFromContextLoss == false) { + if (gpu_info.can_lose_context) + return false; + } + + if (render_directly_to_web_view) { + RenderView* renderview = RenderView::FromWebView(web_view); + if (!renderview) + return false; + + web_view_ = web_view; + context_ = ggl::CreateViewContext( + host, + renderview->routing_id(), + kWebGraphicsContext3DPerferredGLExtensions, + attribs); + if (context_) { + ggl::SetSwapBuffersCallback( + context_, + NewCallback(this, + &WebGraphicsContext3DCommandBufferImpl::OnSwapBuffers)); + } + } else { + bool compositing_enabled = !CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableAcceleratedCompositing); + ggl::Context* parent_context = NULL; + // If GPU compositing is enabled we need to create a GL context that shares + // resources with the compositor's context. + if (compositing_enabled) { + // Asking for the WebGraphicsContext3D on the WebView will force one to + // be created if it doesn't already exist. When the compositor is created + // for the view it will use the same context. + WebKit::WebGraphicsContext3D* view_context = + web_view->graphicsContext3D(); + if (view_context) { + WebGraphicsContext3DCommandBufferImpl* context_impl = + static_cast<WebGraphicsContext3DCommandBufferImpl*>(view_context); + parent_context = context_impl->context_; + } + } + context_ = ggl::CreateOffscreenContext( + host, + parent_context, + gfx::Size(1, 1), + kWebGraphicsContext3DPerferredGLExtensions, + attribs); + web_view_ = NULL; + } + if (!context_) + return false; + + ggl::SetContextLostCallback( + context_, + NewCallback(this, + &WebGraphicsContext3DCommandBufferImpl::OnContextLost)); + + // TODO(gman): Remove this. + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + if (command_line.HasSwitch(switches::kDisableGLSLTranslator)) { + DisableShaderTranslation(context_); + } + + // Set attributes_ from created offscreen context. + { + attributes_ = attributes; + GLint alpha_bits = 0; + getIntegerv(GL_ALPHA_BITS, &alpha_bits); + attributes_.alpha = alpha_bits > 0; + GLint depth_bits = 0; + getIntegerv(GL_DEPTH_BITS, &depth_bits); + attributes_.depth = depth_bits > 0; + GLint stencil_bits = 0; + getIntegerv(GL_STENCIL_BITS, &stencil_bits); + attributes_.stencil = stencil_bits > 0; + GLint samples = 0; + getIntegerv(GL_SAMPLES, &samples); + attributes_.antialias = samples > 0; + } + + return true; +} + +bool WebGraphicsContext3DCommandBufferImpl::makeContextCurrent() { + return ggl::MakeCurrent(context_); +} + +int WebGraphicsContext3DCommandBufferImpl::width() { + return cached_width_; +} + +int WebGraphicsContext3DCommandBufferImpl::height() { + return cached_height_; +} + +bool WebGraphicsContext3DCommandBufferImpl::isGLES2Compliant() { + return true; +} + +WebGLId WebGraphicsContext3DCommandBufferImpl::getPlatformTextureId() { + DCHECK(context_); + return ggl::GetParentTextureId(context_); +} + +void WebGraphicsContext3DCommandBufferImpl::prepareTexture() { + // Copies the contents of the off-screen render target into the texture + // used by the compositor. + ggl::SwapBuffers(context_); +} + +void WebGraphicsContext3DCommandBufferImpl::reshape(int width, int height) { + cached_width_ = width; + cached_height_ = height; + makeContextCurrent(); + + if (web_view_) { +#if defined(OS_MACOSX) + ggl::ResizeOnscreenContext(context_, gfx::Size(width, height)); +#else + glResizeCHROMIUM(width, height); +#endif + } else { + ggl::ResizeOffscreenContext(context_, gfx::Size(width, height)); + // Force a SwapBuffers to get the framebuffer to resize. + ggl::SwapBuffers(context_); + } + +#ifdef FLIP_FRAMEBUFFER_VERTICALLY + scanline_.reset(new uint8[width * 4]); +#endif // FLIP_FRAMEBUFFER_VERTICALLY +} + +WebGLId WebGraphicsContext3DCommandBufferImpl::createCompositorTexture( + WGC3Dsizei width, WGC3Dsizei height) { + makeContextCurrent(); + return ggl::CreateParentTexture(context_, gfx::Size(width, height)); +} + +void WebGraphicsContext3DCommandBufferImpl::deleteCompositorTexture( + WebGLId parent_texture) { + makeContextCurrent(); + ggl::DeleteParentTexture(context_, parent_texture); +} + +#ifdef FLIP_FRAMEBUFFER_VERTICALLY +void WebGraphicsContext3DCommandBufferImpl::FlipVertically( + uint8* framebuffer, + unsigned int width, + unsigned int height) { + uint8* scanline = scanline_.get(); + if (!scanline) + return; + unsigned int row_bytes = width * 4; + unsigned int count = height / 2; + for (unsigned int i = 0; i < count; i++) { + uint8* row_a = framebuffer + i * row_bytes; + uint8* row_b = framebuffer + (height - i - 1) * row_bytes; + // TODO(kbr): this is where the multiplication of the alpha + // channel into the color buffer will need to occur if the + // user specifies the "premultiplyAlpha" flag in the context + // creation attributes. + memcpy(scanline, row_b, row_bytes); + memcpy(row_b, row_a, row_bytes); + memcpy(row_a, scanline, row_bytes); + } +} +#endif + +bool WebGraphicsContext3DCommandBufferImpl::readBackFramebuffer( + unsigned char* pixels, + size_t buffer_size) { + if (buffer_size != static_cast<size_t>(4 * width() * height())) { + return false; + } + + makeContextCurrent(); + + // Earlier versions of this code used the GPU to flip the + // framebuffer vertically before reading it back for compositing + // via software. This code was quite complicated, used a lot of + // GPU memory, and didn't provide an obvious speedup. Since this + // vertical flip is only a temporary solution anyway until Chrome + // is fully GPU composited, it wasn't worth the complexity. + + bool mustRestoreFBO = (bound_fbo_ != 0); + if (mustRestoreFBO) { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + glReadPixels(0, 0, cached_width_, cached_height_, + GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Swizzle red and blue channels + // TODO(kbr): expose GL_BGRA as extension + for (size_t i = 0; i < buffer_size; i += 4) { + std::swap(pixels[i], pixels[i + 2]); + } + + if (mustRestoreFBO) { + glBindFramebuffer(GL_FRAMEBUFFER, bound_fbo_); + } + +#ifdef FLIP_FRAMEBUFFER_VERTICALLY + if (pixels) { + FlipVertically(pixels, cached_width_, cached_height_); + } +#endif + + return true; +} + +void WebGraphicsContext3DCommandBufferImpl::synthesizeGLError( + WGC3Denum error) { + if (find(synthetic_errors_.begin(), synthetic_errors_.end(), error) == + synthetic_errors_.end()) { + synthetic_errors_.push_back(error); + } +} + +void* WebGraphicsContext3DCommandBufferImpl::mapBufferSubDataCHROMIUM( + WGC3Denum target, + WGC3Dintptr offset, + WGC3Dsizeiptr size, + WGC3Denum access) { + return glMapBufferSubDataCHROMIUM(target, offset, size, access); +} + +void WebGraphicsContext3DCommandBufferImpl::unmapBufferSubDataCHROMIUM( + const void* mem) { + return glUnmapBufferSubDataCHROMIUM(mem); +} + +void* WebGraphicsContext3DCommandBufferImpl::mapTexSubImage2DCHROMIUM( + WGC3Denum target, + WGC3Dint level, + WGC3Dint xoffset, + WGC3Dint yoffset, + WGC3Dsizei width, + WGC3Dsizei height, + WGC3Denum format, + WGC3Denum type, + WGC3Denum access) { + return glMapTexSubImage2DCHROMIUM( + target, level, xoffset, yoffset, width, height, format, type, access); +} + +void WebGraphicsContext3DCommandBufferImpl::unmapTexSubImage2DCHROMIUM( + const void* mem) { + glUnmapTexSubImage2DCHROMIUM(mem); +} + +void WebGraphicsContext3DCommandBufferImpl::copyTextureToParentTextureCHROMIUM( + WebGLId texture, WebGLId parentTexture) { + copyTextureToCompositor(texture, parentTexture); +} + +WebKit::WebString WebGraphicsContext3DCommandBufferImpl:: + getRequestableExtensionsCHROMIUM() { + return WebKit::WebString::fromUTF8(glGetRequestableExtensionsCHROMIUM()); +} + +void WebGraphicsContext3DCommandBufferImpl::requestExtensionCHROMIUM( + const char* extension) { + glRequestExtensionCHROMIUM(extension); +} + +void WebGraphicsContext3DCommandBufferImpl::blitFramebufferCHROMIUM( + WGC3Dint srcX0, WGC3Dint srcY0, WGC3Dint srcX1, WGC3Dint srcY1, + WGC3Dint dstX0, WGC3Dint dstY0, WGC3Dint dstX1, WGC3Dint dstY1, + WGC3Dbitfield mask, WGC3Denum filter) { + glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, + dstX0, dstY0, dstX1, dstY1, + mask, filter); +} + +void WebGraphicsContext3DCommandBufferImpl:: + renderbufferStorageMultisampleCHROMIUM( + WGC3Denum target, WGC3Dsizei samples, WGC3Denum internalformat, + WGC3Dsizei width, WGC3Dsizei height) { + glRenderbufferStorageMultisampleEXT(target, samples, internalformat, + width, height); +} + +// Helper macros to reduce the amount of code. + +#define DELEGATE_TO_GL(name, glname) \ +void WebGraphicsContext3DCommandBufferImpl::name() { \ + makeContextCurrent(); \ + gl##glname(); \ +} + +#define DELEGATE_TO_GL_1(name, glname, t1) \ +void WebGraphicsContext3DCommandBufferImpl::name(t1 a1) { \ + makeContextCurrent(); \ + gl##glname(a1); \ +} + +#define DELEGATE_TO_GL_1R(name, glname, t1, rt) \ +rt WebGraphicsContext3DCommandBufferImpl::name(t1 a1) { \ + makeContextCurrent(); \ + return gl##glname(a1); \ +} + +#define DELEGATE_TO_GL_1RB(name, glname, t1, rt) \ +rt WebGraphicsContext3DCommandBufferImpl::name(t1 a1) { \ + makeContextCurrent(); \ + return gl##glname(a1) ? true : false; \ +} + +#define DELEGATE_TO_GL_2(name, glname, t1, t2) \ +void WebGraphicsContext3DCommandBufferImpl::name(t1 a1, t2 a2) { \ + makeContextCurrent(); \ + gl##glname(a1, a2); \ +} + +#define DELEGATE_TO_GL_2R(name, glname, t1, t2, rt) \ +rt WebGraphicsContext3DCommandBufferImpl::name(t1 a1, t2 a2) { \ + makeContextCurrent(); \ + return gl##glname(a1, a2); \ +} + +#define DELEGATE_TO_GL_3(name, glname, t1, t2, t3) \ +void WebGraphicsContext3DCommandBufferImpl::name(t1 a1, t2 a2, t3 a3) { \ + makeContextCurrent(); \ + gl##glname(a1, a2, a3); \ +} + +#define DELEGATE_TO_GL_4(name, glname, t1, t2, t3, t4) \ +void WebGraphicsContext3DCommandBufferImpl::name(t1 a1, t2 a2, t3 a3, t4 a4) { \ + makeContextCurrent(); \ + gl##glname(a1, a2, a3, a4); \ +} + +#define DELEGATE_TO_GL_5(name, glname, t1, t2, t3, t4, t5) \ +void WebGraphicsContext3DCommandBufferImpl::name(t1 a1, t2 a2, t3 a3, \ + t4 a4, t5 a5) { \ + makeContextCurrent(); \ + gl##glname(a1, a2, a3, a4, a5); \ +} + +#define DELEGATE_TO_GL_6(name, glname, t1, t2, t3, t4, t5, t6) \ +void WebGraphicsContext3DCommandBufferImpl::name(t1 a1, t2 a2, t3 a3, \ + t4 a4, t5 a5, t6 a6) { \ + makeContextCurrent(); \ + gl##glname(a1, a2, a3, a4, a5, a6); \ +} + +#define DELEGATE_TO_GL_7(name, glname, t1, t2, t3, t4, t5, t6, t7) \ +void WebGraphicsContext3DCommandBufferImpl::name(t1 a1, t2 a2, t3 a3, \ + t4 a4, t5 a5, t6 a6, t7 a7) { \ + makeContextCurrent(); \ + gl##glname(a1, a2, a3, a4, a5, a6, a7); \ +} + +#define DELEGATE_TO_GL_8(name, glname, t1, t2, t3, t4, t5, t6, t7, t8) \ +void WebGraphicsContext3DCommandBufferImpl::name(t1 a1, t2 a2, t3 a3, \ + t4 a4, t5 a5, t6 a6, \ + t7 a7, t8 a8) { \ + makeContextCurrent(); \ + gl##glname(a1, a2, a3, a4, a5, a6, a7, a8); \ +} + +#define DELEGATE_TO_GL_9(name, glname, t1, t2, t3, t4, t5, t6, t7, t8, t9) \ +void WebGraphicsContext3DCommandBufferImpl::name(t1 a1, t2 a2, t3 a3, \ + t4 a4, t5 a5, t6 a6, \ + t7 a7, t8 a8, t9 a9) { \ + makeContextCurrent(); \ + gl##glname(a1, a2, a3, a4, a5, a6, a7, a8, a9); \ +} + +DELEGATE_TO_GL_1(activeTexture, ActiveTexture, WGC3Denum) + +DELEGATE_TO_GL_2(attachShader, AttachShader, WebGLId, WebGLId) + +DELEGATE_TO_GL_3(bindAttribLocation, BindAttribLocation, WebGLId, + WGC3Duint, const WGC3Dchar*) + +DELEGATE_TO_GL_2(bindBuffer, BindBuffer, WGC3Denum, WebGLId) + +void WebGraphicsContext3DCommandBufferImpl::bindFramebuffer( + WGC3Denum target, + WebGLId framebuffer) { + makeContextCurrent(); + glBindFramebuffer(target, framebuffer); + bound_fbo_ = framebuffer; +} + +DELEGATE_TO_GL_2(bindRenderbuffer, BindRenderbuffer, WGC3Denum, WebGLId) + +DELEGATE_TO_GL_2(bindTexture, BindTexture, WGC3Denum, WebGLId) + +DELEGATE_TO_GL_4(blendColor, BlendColor, + WGC3Dclampf, WGC3Dclampf, WGC3Dclampf, WGC3Dclampf) + +DELEGATE_TO_GL_1(blendEquation, BlendEquation, WGC3Denum) + +DELEGATE_TO_GL_2(blendEquationSeparate, BlendEquationSeparate, + WGC3Denum, WGC3Denum) + +DELEGATE_TO_GL_2(blendFunc, BlendFunc, WGC3Denum, WGC3Denum) + +DELEGATE_TO_GL_4(blendFuncSeparate, BlendFuncSeparate, + WGC3Denum, WGC3Denum, WGC3Denum, WGC3Denum) + +DELEGATE_TO_GL_4(bufferData, BufferData, + WGC3Denum, WGC3Dsizeiptr, const void*, WGC3Denum) + +DELEGATE_TO_GL_4(bufferSubData, BufferSubData, + WGC3Denum, WGC3Dintptr, WGC3Dsizeiptr, const void*) + +DELEGATE_TO_GL_1R(checkFramebufferStatus, CheckFramebufferStatus, + WGC3Denum, WGC3Denum) + +DELEGATE_TO_GL_1(clear, Clear, WGC3Dbitfield) + +DELEGATE_TO_GL_4(clearColor, ClearColor, + WGC3Dclampf, WGC3Dclampf, WGC3Dclampf, WGC3Dclampf) + +DELEGATE_TO_GL_1(clearDepth, ClearDepthf, WGC3Dclampf) + +DELEGATE_TO_GL_1(clearStencil, ClearStencil, WGC3Dint) + +DELEGATE_TO_GL_4(colorMask, ColorMask, + WGC3Dboolean, WGC3Dboolean, WGC3Dboolean, WGC3Dboolean) + +DELEGATE_TO_GL_1(compileShader, CompileShader, WebGLId) + +DELEGATE_TO_GL_8(copyTexImage2D, CopyTexImage2D, + WGC3Denum, WGC3Dint, WGC3Denum, WGC3Dint, WGC3Dint, + WGC3Dsizei, WGC3Dsizei, WGC3Dint) + +DELEGATE_TO_GL_8(copyTexSubImage2D, CopyTexSubImage2D, + WGC3Denum, WGC3Dint, WGC3Dint, WGC3Dint, WGC3Dint, WGC3Dint, + WGC3Dsizei, WGC3Dsizei) + +DELEGATE_TO_GL_1(cullFace, CullFace, WGC3Denum) + +DELEGATE_TO_GL_1(depthFunc, DepthFunc, WGC3Denum) + +DELEGATE_TO_GL_1(depthMask, DepthMask, WGC3Dboolean) + +DELEGATE_TO_GL_2(depthRange, DepthRangef, WGC3Dclampf, WGC3Dclampf) + +DELEGATE_TO_GL_2(detachShader, DetachShader, WebGLId, WebGLId) + +DELEGATE_TO_GL_1(disable, Disable, WGC3Denum) + +DELEGATE_TO_GL_1(disableVertexAttribArray, DisableVertexAttribArray, + WGC3Duint) + +DELEGATE_TO_GL_3(drawArrays, DrawArrays, WGC3Denum, WGC3Dint, WGC3Dsizei) + +void WebGraphicsContext3DCommandBufferImpl::drawElements(WGC3Denum mode, + WGC3Dsizei count, + WGC3Denum type, + WGC3Dintptr offset) { + makeContextCurrent(); + glDrawElements(mode, count, type, + reinterpret_cast<void*>(static_cast<intptr_t>(offset))); +} + +DELEGATE_TO_GL_1(enable, Enable, WGC3Denum) + +DELEGATE_TO_GL_1(enableVertexAttribArray, EnableVertexAttribArray, + WGC3Duint) + +DELEGATE_TO_GL(finish, Finish) + +DELEGATE_TO_GL(flush, Flush) + +DELEGATE_TO_GL_4(framebufferRenderbuffer, FramebufferRenderbuffer, + WGC3Denum, WGC3Denum, WGC3Denum, WebGLId) + +DELEGATE_TO_GL_5(framebufferTexture2D, FramebufferTexture2D, + WGC3Denum, WGC3Denum, WGC3Denum, WebGLId, WGC3Dint) + +DELEGATE_TO_GL_1(frontFace, FrontFace, WGC3Denum) + +DELEGATE_TO_GL_1(generateMipmap, GenerateMipmap, WGC3Denum) + +bool WebGraphicsContext3DCommandBufferImpl::getActiveAttrib( + WebGLId program, WGC3Duint index, ActiveInfo& info) { + makeContextCurrent(); + if (!program) { + synthesizeGLError(GL_INVALID_VALUE); + return false; + } + GLint max_name_length = -1; + glGetProgramiv(program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &max_name_length); + if (max_name_length < 0) + return false; + scoped_array<GLchar> name(new GLchar[max_name_length]); + if (!name.get()) { + synthesizeGLError(GL_OUT_OF_MEMORY); + return false; + } + GLsizei length = 0; + GLint size = -1; + GLenum type = 0; + glGetActiveAttrib(program, index, max_name_length, + &length, &size, &type, name.get()); + if (size < 0) { + return false; + } + info.name = WebKit::WebString::fromUTF8(name.get(), length); + info.type = type; + info.size = size; + return true; +} + +bool WebGraphicsContext3DCommandBufferImpl::getActiveUniform( + WebGLId program, WGC3Duint index, ActiveInfo& info) { + makeContextCurrent(); + GLint max_name_length = -1; + glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_name_length); + if (max_name_length < 0) + return false; + scoped_array<GLchar> name(new GLchar[max_name_length]); + if (!name.get()) { + synthesizeGLError(GL_OUT_OF_MEMORY); + return false; + } + GLsizei length = 0; + GLint size = -1; + GLenum type = 0; + glGetActiveUniform(program, index, max_name_length, + &length, &size, &type, name.get()); + if (size < 0) { + return false; + } + info.name = WebKit::WebString::fromUTF8(name.get(), length); + info.type = type; + info.size = size; + return true; +} + +DELEGATE_TO_GL_4(getAttachedShaders, GetAttachedShaders, + WebGLId, WGC3Dsizei, WGC3Dsizei*, WebGLId*) + +DELEGATE_TO_GL_2R(getAttribLocation, GetAttribLocation, + WebGLId, const WGC3Dchar*, WGC3Dint) + +DELEGATE_TO_GL_2(getBooleanv, GetBooleanv, WGC3Denum, WGC3Dboolean*) + +DELEGATE_TO_GL_3(getBufferParameteriv, GetBufferParameteriv, + WGC3Denum, WGC3Denum, WGC3Dint*) + +WebKit::WebGraphicsContext3D::Attributes +WebGraphicsContext3DCommandBufferImpl::getContextAttributes() { + return attributes_; +} + +WGC3Denum WebGraphicsContext3DCommandBufferImpl::getError() { + if (!synthetic_errors_.empty()) { + std::vector<WGC3Denum>::iterator iter = synthetic_errors_.begin(); + WGC3Denum err = *iter; + synthetic_errors_.erase(iter); + return err; + } + + makeContextCurrent(); + return glGetError(); +} + +bool WebGraphicsContext3DCommandBufferImpl::isContextLost() { + return ggl::IsCommandBufferContextLost(context_); +} + +DELEGATE_TO_GL_2(getFloatv, GetFloatv, WGC3Denum, WGC3Dfloat*) + +DELEGATE_TO_GL_4(getFramebufferAttachmentParameteriv, + GetFramebufferAttachmentParameteriv, + WGC3Denum, WGC3Denum, WGC3Denum, WGC3Dint*) + +DELEGATE_TO_GL_2(getIntegerv, GetIntegerv, WGC3Denum, WGC3Dint*) + +DELEGATE_TO_GL_3(getProgramiv, GetProgramiv, WebGLId, WGC3Denum, WGC3Dint*) + +WebKit::WebString WebGraphicsContext3DCommandBufferImpl::getProgramInfoLog( + WebGLId program) { + makeContextCurrent(); + GLint logLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength); + if (!logLength) + return WebKit::WebString(); + scoped_array<GLchar> log(new GLchar[logLength]); + if (!log.get()) + return WebKit::WebString(); + GLsizei returnedLogLength = 0; + glGetProgramInfoLog(program, logLength, &returnedLogLength, log.get()); + DCHECK_EQ(logLength, returnedLogLength + 1); + WebKit::WebString res = + WebKit::WebString::fromUTF8(log.get(), returnedLogLength); + return res; +} + +DELEGATE_TO_GL_3(getRenderbufferParameteriv, GetRenderbufferParameteriv, + WGC3Denum, WGC3Denum, WGC3Dint*) + +DELEGATE_TO_GL_3(getShaderiv, GetShaderiv, WebGLId, WGC3Denum, WGC3Dint*) + +WebKit::WebString WebGraphicsContext3DCommandBufferImpl::getShaderInfoLog( + WebGLId shader) { + makeContextCurrent(); + GLint logLength = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); + if (!logLength) + return WebKit::WebString(); + scoped_array<GLchar> log(new GLchar[logLength]); + if (!log.get()) + return WebKit::WebString(); + GLsizei returnedLogLength = 0; + glGetShaderInfoLog(shader, logLength, &returnedLogLength, log.get()); + DCHECK_EQ(logLength, returnedLogLength + 1); + WebKit::WebString res = + WebKit::WebString::fromUTF8(log.get(), returnedLogLength); + return res; +} + +WebKit::WebString WebGraphicsContext3DCommandBufferImpl::getShaderSource( + WebGLId shader) { + makeContextCurrent(); + GLint logLength = 0; + glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &logLength); + if (!logLength) + return WebKit::WebString(); + scoped_array<GLchar> log(new GLchar[logLength]); + if (!log.get()) + return WebKit::WebString(); + GLsizei returnedLogLength = 0; + glGetShaderSource(shader, logLength, &returnedLogLength, log.get()); + DCHECK_EQ(logLength, returnedLogLength + 1); + WebKit::WebString res = + WebKit::WebString::fromUTF8(log.get(), returnedLogLength); + return res; +} + +WebKit::WebString WebGraphicsContext3DCommandBufferImpl::getString( + WGC3Denum name) { + makeContextCurrent(); + return WebKit::WebString::fromUTF8( + reinterpret_cast<const char*>(glGetString(name))); +} + +DELEGATE_TO_GL_3(getTexParameterfv, GetTexParameterfv, + WGC3Denum, WGC3Denum, WGC3Dfloat*) + +DELEGATE_TO_GL_3(getTexParameteriv, GetTexParameteriv, + WGC3Denum, WGC3Denum, WGC3Dint*) + +DELEGATE_TO_GL_3(getUniformfv, GetUniformfv, WebGLId, WGC3Dint, WGC3Dfloat*) + +DELEGATE_TO_GL_3(getUniformiv, GetUniformiv, WebGLId, WGC3Dint, WGC3Dint*) + +DELEGATE_TO_GL_2R(getUniformLocation, GetUniformLocation, + WebGLId, const WGC3Dchar*, WGC3Dint) + +DELEGATE_TO_GL_3(getVertexAttribfv, GetVertexAttribfv, + WGC3Duint, WGC3Denum, WGC3Dfloat*) + +DELEGATE_TO_GL_3(getVertexAttribiv, GetVertexAttribiv, + WGC3Duint, WGC3Denum, WGC3Dint*) + +WGC3Dsizeiptr WebGraphicsContext3DCommandBufferImpl::getVertexAttribOffset( + WGC3Duint index, WGC3Denum pname) { + makeContextCurrent(); + GLvoid* value = NULL; + // NOTE: If pname is ever a value that returns more then 1 element + // this will corrupt memory. + glGetVertexAttribPointerv(index, pname, &value); + return static_cast<WGC3Dsizeiptr>(reinterpret_cast<intptr_t>(value)); +} + +DELEGATE_TO_GL_2(hint, Hint, WGC3Denum, WGC3Denum) + +DELEGATE_TO_GL_1RB(isBuffer, IsBuffer, WebGLId, WGC3Dboolean) + +DELEGATE_TO_GL_1RB(isEnabled, IsEnabled, WGC3Denum, WGC3Dboolean) + +DELEGATE_TO_GL_1RB(isFramebuffer, IsFramebuffer, WebGLId, WGC3Dboolean) + +DELEGATE_TO_GL_1RB(isProgram, IsProgram, WebGLId, WGC3Dboolean) + +DELEGATE_TO_GL_1RB(isRenderbuffer, IsRenderbuffer, WebGLId, WGC3Dboolean) + +DELEGATE_TO_GL_1RB(isShader, IsShader, WebGLId, WGC3Dboolean) + +DELEGATE_TO_GL_1RB(isTexture, IsTexture, WebGLId, WGC3Dboolean) + +DELEGATE_TO_GL_1(lineWidth, LineWidth, WGC3Dfloat) + +DELEGATE_TO_GL_1(linkProgram, LinkProgram, WebGLId) + +DELEGATE_TO_GL_2(pixelStorei, PixelStorei, WGC3Denum, WGC3Dint) + +DELEGATE_TO_GL_2(polygonOffset, PolygonOffset, WGC3Dfloat, WGC3Dfloat) + +DELEGATE_TO_GL_7(readPixels, ReadPixels, + WGC3Dint, WGC3Dint, WGC3Dsizei, WGC3Dsizei, WGC3Denum, + WGC3Denum, void*) + +void WebGraphicsContext3DCommandBufferImpl::releaseShaderCompiler() { +} + +DELEGATE_TO_GL_4(renderbufferStorage, RenderbufferStorage, + WGC3Denum, WGC3Denum, WGC3Dsizei, WGC3Dsizei) + +DELEGATE_TO_GL_2(sampleCoverage, SampleCoverage, WGC3Dfloat, WGC3Dboolean) + +DELEGATE_TO_GL_4(scissor, Scissor, WGC3Dint, WGC3Dint, WGC3Dsizei, WGC3Dsizei) + +void WebGraphicsContext3DCommandBufferImpl::shaderSource( + WebGLId shader, const WGC3Dchar* string) { + makeContextCurrent(); + GLint length = strlen(string); + glShaderSource(shader, 1, &string, &length); +} + +DELEGATE_TO_GL_3(stencilFunc, StencilFunc, WGC3Denum, WGC3Dint, WGC3Duint) + +DELEGATE_TO_GL_4(stencilFuncSeparate, StencilFuncSeparate, + WGC3Denum, WGC3Denum, WGC3Dint, WGC3Duint) + +DELEGATE_TO_GL_1(stencilMask, StencilMask, WGC3Duint) + +DELEGATE_TO_GL_2(stencilMaskSeparate, StencilMaskSeparate, + WGC3Denum, WGC3Duint) + +DELEGATE_TO_GL_3(stencilOp, StencilOp, + WGC3Denum, WGC3Denum, WGC3Denum) + +DELEGATE_TO_GL_4(stencilOpSeparate, StencilOpSeparate, + WGC3Denum, WGC3Denum, WGC3Denum, WGC3Denum) + +DELEGATE_TO_GL_9(texImage2D, TexImage2D, + WGC3Denum, WGC3Dint, WGC3Denum, WGC3Dsizei, WGC3Dsizei, + WGC3Dint, WGC3Denum, WGC3Denum, const void*) + +DELEGATE_TO_GL_3(texParameterf, TexParameterf, + WGC3Denum, WGC3Denum, WGC3Dfloat); + +static const unsigned int kTextureWrapR = 0x8072; + +void WebGraphicsContext3DCommandBufferImpl::texParameteri( + WGC3Denum target, WGC3Denum pname, WGC3Dint param) { + // TODO(kbr): figure out whether the setting of TEXTURE_WRAP_R in + // GraphicsContext3D.cpp is strictly necessary to avoid seams at the + // edge of cube maps, and, if it is, push it into the GLES2 service + // side code. + if (pname == kTextureWrapR) { + return; + } + makeContextCurrent(); + glTexParameteri(target, pname, param); +} + +DELEGATE_TO_GL_9(texSubImage2D, TexSubImage2D, + WGC3Denum, WGC3Dint, WGC3Dint, WGC3Dint, WGC3Dsizei, + WGC3Dsizei, WGC3Denum, WGC3Denum, const void*) + +DELEGATE_TO_GL_2(uniform1f, Uniform1f, WGC3Dint, WGC3Dfloat) + +DELEGATE_TO_GL_3(uniform1fv, Uniform1fv, WGC3Dint, WGC3Dsizei, + const WGC3Dfloat*) + +DELEGATE_TO_GL_2(uniform1i, Uniform1i, WGC3Dint, WGC3Dint) + +DELEGATE_TO_GL_3(uniform1iv, Uniform1iv, WGC3Dint, WGC3Dsizei, const WGC3Dint*) + +DELEGATE_TO_GL_3(uniform2f, Uniform2f, WGC3Dint, WGC3Dfloat, WGC3Dfloat) + +DELEGATE_TO_GL_3(uniform2fv, Uniform2fv, WGC3Dint, WGC3Dsizei, + const WGC3Dfloat*) + +DELEGATE_TO_GL_3(uniform2i, Uniform2i, WGC3Dint, WGC3Dint, WGC3Dint) + +DELEGATE_TO_GL_3(uniform2iv, Uniform2iv, WGC3Dint, WGC3Dsizei, const WGC3Dint*) + +DELEGATE_TO_GL_4(uniform3f, Uniform3f, WGC3Dint, + WGC3Dfloat, WGC3Dfloat, WGC3Dfloat) + +DELEGATE_TO_GL_3(uniform3fv, Uniform3fv, WGC3Dint, WGC3Dsizei, + const WGC3Dfloat*) + +DELEGATE_TO_GL_4(uniform3i, Uniform3i, WGC3Dint, WGC3Dint, WGC3Dint, WGC3Dint) + +DELEGATE_TO_GL_3(uniform3iv, Uniform3iv, WGC3Dint, WGC3Dsizei, const WGC3Dint*) + +DELEGATE_TO_GL_5(uniform4f, Uniform4f, WGC3Dint, + WGC3Dfloat, WGC3Dfloat, WGC3Dfloat, WGC3Dfloat) + +DELEGATE_TO_GL_3(uniform4fv, Uniform4fv, WGC3Dint, WGC3Dsizei, + const WGC3Dfloat*) + +DELEGATE_TO_GL_5(uniform4i, Uniform4i, WGC3Dint, + WGC3Dint, WGC3Dint, WGC3Dint, WGC3Dint) + +DELEGATE_TO_GL_3(uniform4iv, Uniform4iv, WGC3Dint, WGC3Dsizei, const WGC3Dint*) + +DELEGATE_TO_GL_4(uniformMatrix2fv, UniformMatrix2fv, + WGC3Dint, WGC3Dsizei, WGC3Dboolean, const WGC3Dfloat*) + +DELEGATE_TO_GL_4(uniformMatrix3fv, UniformMatrix3fv, + WGC3Dint, WGC3Dsizei, WGC3Dboolean, const WGC3Dfloat*) + +DELEGATE_TO_GL_4(uniformMatrix4fv, UniformMatrix4fv, + WGC3Dint, WGC3Dsizei, WGC3Dboolean, const WGC3Dfloat*) + +DELEGATE_TO_GL_1(useProgram, UseProgram, WebGLId) + +DELEGATE_TO_GL_1(validateProgram, ValidateProgram, WebGLId) + +DELEGATE_TO_GL_2(vertexAttrib1f, VertexAttrib1f, WGC3Duint, WGC3Dfloat) + +DELEGATE_TO_GL_2(vertexAttrib1fv, VertexAttrib1fv, WGC3Duint, + const WGC3Dfloat*) + +DELEGATE_TO_GL_3(vertexAttrib2f, VertexAttrib2f, WGC3Duint, + WGC3Dfloat, WGC3Dfloat) + +DELEGATE_TO_GL_2(vertexAttrib2fv, VertexAttrib2fv, WGC3Duint, + const WGC3Dfloat*) + +DELEGATE_TO_GL_4(vertexAttrib3f, VertexAttrib3f, WGC3Duint, + WGC3Dfloat, WGC3Dfloat, WGC3Dfloat) + +DELEGATE_TO_GL_2(vertexAttrib3fv, VertexAttrib3fv, WGC3Duint, + const WGC3Dfloat*) + +DELEGATE_TO_GL_5(vertexAttrib4f, VertexAttrib4f, WGC3Duint, + WGC3Dfloat, WGC3Dfloat, WGC3Dfloat, WGC3Dfloat) + +DELEGATE_TO_GL_2(vertexAttrib4fv, VertexAttrib4fv, WGC3Duint, + const WGC3Dfloat*) + +void WebGraphicsContext3DCommandBufferImpl::vertexAttribPointer( + WGC3Duint index, WGC3Dint size, WGC3Denum type, WGC3Dboolean normalized, + WGC3Dsizei stride, WGC3Dintptr offset) { + makeContextCurrent(); + + glVertexAttribPointer(index, size, type, normalized, stride, + reinterpret_cast<void*>( + static_cast<intptr_t>(offset))); +} + +DELEGATE_TO_GL_4(viewport, Viewport, + WGC3Dint, WGC3Dint, WGC3Dsizei, WGC3Dsizei) + +WebGLId WebGraphicsContext3DCommandBufferImpl::createBuffer() { + makeContextCurrent(); + GLuint o; + glGenBuffers(1, &o); + return o; +} + +WebGLId WebGraphicsContext3DCommandBufferImpl::createFramebuffer() { + makeContextCurrent(); + GLuint o = 0; + glGenFramebuffers(1, &o); + return o; +} + +WebGLId WebGraphicsContext3DCommandBufferImpl::createProgram() { + makeContextCurrent(); + return glCreateProgram(); +} + +WebGLId WebGraphicsContext3DCommandBufferImpl::createRenderbuffer() { + makeContextCurrent(); + GLuint o; + glGenRenderbuffers(1, &o); + return o; +} + +DELEGATE_TO_GL_1R(createShader, CreateShader, WGC3Denum, WebGLId); + +WebGLId WebGraphicsContext3DCommandBufferImpl::createTexture() { + makeContextCurrent(); + GLuint o; + glGenTextures(1, &o); + return o; +} + +void WebGraphicsContext3DCommandBufferImpl::deleteBuffer(WebGLId buffer) { + makeContextCurrent(); + glDeleteBuffers(1, &buffer); +} + +void WebGraphicsContext3DCommandBufferImpl::deleteFramebuffer( + WebGLId framebuffer) { + makeContextCurrent(); + glDeleteFramebuffers(1, &framebuffer); +} + +void WebGraphicsContext3DCommandBufferImpl::deleteProgram(WebGLId program) { + makeContextCurrent(); + glDeleteProgram(program); +} + +void WebGraphicsContext3DCommandBufferImpl::deleteRenderbuffer( + WebGLId renderbuffer) { + makeContextCurrent(); + glDeleteRenderbuffers(1, &renderbuffer); +} + +void WebGraphicsContext3DCommandBufferImpl::deleteShader(WebGLId shader) { + makeContextCurrent(); + glDeleteShader(shader); +} + +void WebGraphicsContext3DCommandBufferImpl::deleteTexture(WebGLId texture) { + makeContextCurrent(); + glDeleteTextures(1, &texture); +} + +void WebGraphicsContext3DCommandBufferImpl::copyTextureToCompositor( + WebGLId texture, WebGLId parentTexture) { + makeContextCurrent(); + glCopyTextureToParentTextureCHROMIUM(texture, parentTexture); + glFlush(); +} + +void WebGraphicsContext3DCommandBufferImpl::OnSwapBuffers() { + // This may be called after tear-down of the RenderView. + RenderView* renderview = + web_view_ ? RenderView::FromWebView(web_view_) : NULL; + if (renderview) + renderview->DidFlushPaint(); +} + +void WebGraphicsContext3DCommandBufferImpl::setContextLostCallback( + WebGraphicsContext3D::WebGraphicsContextLostCallback* cb) +{ + context_lost_callback_ = cb; +} + +void WebGraphicsContext3DCommandBufferImpl::OnContextLost() { + if (context_lost_callback_) { + context_lost_callback_->onContextLost(); + } +} + +#endif // defined(ENABLE_GPU) diff --git a/content/renderer/webgraphicscontext3d_command_buffer_impl.h b/content/renderer/webgraphicscontext3d_command_buffer_impl.h new file mode 100644 index 0000000..122023a --- /dev/null +++ b/content/renderer/webgraphicscontext3d_command_buffer_impl.h @@ -0,0 +1,461 @@ +// 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 CONTENT_RENDERER_WEBGRAPHICSCONTEXT3D_COMMAND_BUFFER_IMPL_H_ +#define CONTENT_RENDERER_WEBGRAPHICSCONTEXT3D_COMMAND_BUFFER_IMPL_H_ +#pragma once + +#if defined(ENABLE_GPU) + +#include <vector> + +#include "base/scoped_ptr.h" +#include "content/renderer/ggl.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebGraphicsContext3D.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "ui/gfx/native_widget_types.h" + +#if !defined(OS_MACOSX) +#define FLIP_FRAMEBUFFER_VERTICALLY +#endif + +class GpuChannelHost; +class CommandBufferProxy; + +namespace gpu { +namespace gles2 { +class GLES2Implementation; +} +} + +using WebKit::WebGLId; + +using WebKit::WGC3Dchar; +using WebKit::WGC3Denum; +using WebKit::WGC3Dboolean; +using WebKit::WGC3Dbitfield; +using WebKit::WGC3Dint; +using WebKit::WGC3Dsizei; +using WebKit::WGC3Duint; +using WebKit::WGC3Dfloat; +using WebKit::WGC3Dclampf; +using WebKit::WGC3Dintptr; +using WebKit::WGC3Dsizeiptr; + +class WebGraphicsContext3DCommandBufferImpl + : public WebKit::WebGraphicsContext3D { + public: + + WebGraphicsContext3DCommandBufferImpl(); + virtual ~WebGraphicsContext3DCommandBufferImpl(); + + //---------------------------------------------------------------------- + // WebGraphicsContext3D methods + virtual bool initialize(WebGraphicsContext3D::Attributes attributes, + WebKit::WebView*, + bool renderDirectlyToWebView); + + virtual bool makeContextCurrent(); + + virtual int width(); + virtual int height(); + + virtual bool isGLES2Compliant(); + + virtual void reshape(int width, int height); + + virtual bool readBackFramebuffer(unsigned char* pixels, size_t buffer_size); + + virtual WebGLId getPlatformTextureId(); + virtual void prepareTexture(); + + virtual void activeTexture(WGC3Denum texture); + virtual void attachShader(WebGLId program, WebGLId shader); + virtual void bindAttribLocation(WebGLId program, WGC3Duint index, + const WGC3Dchar* name); + virtual void bindBuffer(WGC3Denum target, WebGLId buffer); + virtual void bindFramebuffer(WGC3Denum target, WebGLId framebuffer); + virtual void bindRenderbuffer(WGC3Denum target, WebGLId renderbuffer); + virtual void bindTexture(WGC3Denum target, WebGLId texture); + virtual void blendColor(WGC3Dclampf red, WGC3Dclampf green, + WGC3Dclampf blue, WGC3Dclampf alpha); + virtual void blendEquation(WGC3Denum mode); + virtual void blendEquationSeparate(WGC3Denum modeRGB, + WGC3Denum modeAlpha); + virtual void blendFunc(WGC3Denum sfactor, WGC3Denum dfactor); + virtual void blendFuncSeparate(WGC3Denum srcRGB, + WGC3Denum dstRGB, + WGC3Denum srcAlpha, + WGC3Denum dstAlpha); + + virtual void bufferData(WGC3Denum target, WGC3Dsizeiptr size, + const void* data, WGC3Denum usage); + virtual void bufferSubData(WGC3Denum target, WGC3Dintptr offset, + WGC3Dsizeiptr size, const void* data); + + virtual WGC3Denum checkFramebufferStatus(WGC3Denum target); + virtual void clear(WGC3Dbitfield mask); + virtual void clearColor(WGC3Dclampf red, WGC3Dclampf green, + WGC3Dclampf blue, WGC3Dclampf alpha); + virtual void clearDepth(WGC3Dclampf depth); + virtual void clearStencil(WGC3Dint s); + virtual void colorMask(WGC3Dboolean red, WGC3Dboolean green, + WGC3Dboolean blue, WGC3Dboolean alpha); + virtual void compileShader(WebGLId shader); + + virtual void copyTexImage2D(WGC3Denum target, + WGC3Dint level, + WGC3Denum internalformat, + WGC3Dint x, + WGC3Dint y, + WGC3Dsizei width, + WGC3Dsizei height, + WGC3Dint border); + virtual void copyTexSubImage2D(WGC3Denum target, + WGC3Dint level, + WGC3Dint xoffset, + WGC3Dint yoffset, + WGC3Dint x, + WGC3Dint y, + WGC3Dsizei width, + WGC3Dsizei height); + virtual void cullFace(WGC3Denum mode); + virtual void depthFunc(WGC3Denum func); + virtual void depthMask(WGC3Dboolean flag); + virtual void depthRange(WGC3Dclampf zNear, WGC3Dclampf zFar); + virtual void detachShader(WebGLId program, WebGLId shader); + virtual void disable(WGC3Denum cap); + virtual void disableVertexAttribArray(WGC3Duint index); + virtual void drawArrays(WGC3Denum mode, WGC3Dint first, WGC3Dsizei count); + virtual void drawElements(WGC3Denum mode, + WGC3Dsizei count, + WGC3Denum type, + WGC3Dintptr offset); + + virtual void enable(WGC3Denum cap); + virtual void enableVertexAttribArray(WGC3Duint index); + virtual void finish(); + virtual void flush(); + virtual void framebufferRenderbuffer(WGC3Denum target, + WGC3Denum attachment, + WGC3Denum renderbuffertarget, + WebGLId renderbuffer); + virtual void framebufferTexture2D(WGC3Denum target, + WGC3Denum attachment, + WGC3Denum textarget, + WebGLId texture, + WGC3Dint level); + virtual void frontFace(WGC3Denum mode); + virtual void generateMipmap(WGC3Denum target); + + virtual bool getActiveAttrib(WebGLId program, + WGC3Duint index, + ActiveInfo&); + virtual bool getActiveUniform(WebGLId program, + WGC3Duint index, + ActiveInfo&); + + virtual void getAttachedShaders(WebGLId program, + WGC3Dsizei maxCount, + WGC3Dsizei* count, + WebGLId* shaders); + + virtual WGC3Dint getAttribLocation(WebGLId program, const WGC3Dchar* name); + + virtual void getBooleanv(WGC3Denum pname, WGC3Dboolean* value); + + virtual void getBufferParameteriv(WGC3Denum target, + WGC3Denum pname, + WGC3Dint* value); + + virtual Attributes getContextAttributes(); + + virtual WGC3Denum getError(); + + virtual bool isContextLost(); + + virtual void getFloatv(WGC3Denum pname, WGC3Dfloat* value); + + virtual void getFramebufferAttachmentParameteriv(WGC3Denum target, + WGC3Denum attachment, + WGC3Denum pname, + WGC3Dint* value); + + virtual void getIntegerv(WGC3Denum pname, WGC3Dint* value); + + virtual void getProgramiv(WebGLId program, WGC3Denum pname, WGC3Dint* value); + + virtual WebKit::WebString getProgramInfoLog(WebGLId program); + + virtual void getRenderbufferParameteriv(WGC3Denum target, + WGC3Denum pname, + WGC3Dint* value); + + virtual void getShaderiv(WebGLId shader, WGC3Denum pname, WGC3Dint* value); + + virtual WebKit::WebString getShaderInfoLog(WebGLId shader); + + // TBD + // void glGetShaderPrecisionFormat (GLenum shadertype, + // GLenum precisiontype, + // GLint* range, + // GLint* precision); + + virtual WebKit::WebString getShaderSource(WebGLId shader); + virtual WebKit::WebString getString(WGC3Denum name); + + virtual void getTexParameterfv(WGC3Denum target, + WGC3Denum pname, + WGC3Dfloat* value); + virtual void getTexParameteriv(WGC3Denum target, + WGC3Denum pname, + WGC3Dint* value); + + virtual void getUniformfv(WebGLId program, + WGC3Dint location, + WGC3Dfloat* value); + virtual void getUniformiv(WebGLId program, + WGC3Dint location, + WGC3Dint* value); + + virtual WGC3Dint getUniformLocation(WebGLId program, const WGC3Dchar* name); + + virtual void getVertexAttribfv(WGC3Duint index, WGC3Denum pname, + WGC3Dfloat* value); + virtual void getVertexAttribiv(WGC3Duint index, WGC3Denum pname, + WGC3Dint* value); + + virtual WGC3Dsizeiptr getVertexAttribOffset(WGC3Duint index, WGC3Denum pname); + + virtual void hint(WGC3Denum target, WGC3Denum mode); + virtual WGC3Dboolean isBuffer(WebGLId buffer); + virtual WGC3Dboolean isEnabled(WGC3Denum cap); + virtual WGC3Dboolean isFramebuffer(WebGLId framebuffer); + virtual WGC3Dboolean isProgram(WebGLId program); + virtual WGC3Dboolean isRenderbuffer(WebGLId renderbuffer); + virtual WGC3Dboolean isShader(WebGLId shader); + virtual WGC3Dboolean isTexture(WebGLId texture); + virtual void lineWidth(WGC3Dfloat); + virtual void linkProgram(WebGLId program); + virtual void pixelStorei(WGC3Denum pname, WGC3Dint param); + virtual void polygonOffset(WGC3Dfloat factor, WGC3Dfloat units); + + virtual void readPixels(WGC3Dint x, + WGC3Dint y, + WGC3Dsizei width, + WGC3Dsizei height, + WGC3Denum format, + WGC3Denum type, + void* pixels); + + virtual void releaseShaderCompiler(); + virtual void renderbufferStorage(WGC3Denum target, + WGC3Denum internalformat, + WGC3Dsizei width, + WGC3Dsizei height); + virtual void sampleCoverage(WGC3Dfloat value, WGC3Dboolean invert); + virtual void scissor(WGC3Dint x, WGC3Dint y, + WGC3Dsizei width, WGC3Dsizei height); + virtual void shaderSource(WebGLId shader, const WGC3Dchar* string); + virtual void stencilFunc(WGC3Denum func, WGC3Dint ref, WGC3Duint mask); + virtual void stencilFuncSeparate(WGC3Denum face, + WGC3Denum func, + WGC3Dint ref, + WGC3Duint mask); + virtual void stencilMask(WGC3Duint mask); + virtual void stencilMaskSeparate(WGC3Denum face, WGC3Duint mask); + virtual void stencilOp(WGC3Denum fail, + WGC3Denum zfail, + WGC3Denum zpass); + virtual void stencilOpSeparate(WGC3Denum face, + WGC3Denum fail, + WGC3Denum zfail, + WGC3Denum zpass); + + virtual void texImage2D(WGC3Denum target, + WGC3Dint level, + WGC3Denum internalformat, + WGC3Dsizei width, + WGC3Dsizei height, + WGC3Dint border, + WGC3Denum format, + WGC3Denum type, + const void* pixels); + + virtual void texParameterf(WGC3Denum target, + WGC3Denum pname, + WGC3Dfloat param); + virtual void texParameteri(WGC3Denum target, + WGC3Denum pname, + WGC3Dint param); + + virtual void texSubImage2D(WGC3Denum target, + WGC3Dint level, + WGC3Dint xoffset, + WGC3Dint yoffset, + WGC3Dsizei width, + WGC3Dsizei height, + WGC3Denum format, + WGC3Denum type, + const void* pixels); + + virtual void uniform1f(WGC3Dint location, WGC3Dfloat x); + virtual void uniform1fv(WGC3Dint location, + WGC3Dsizei count, const WGC3Dfloat* v); + virtual void uniform1i(WGC3Dint location, WGC3Dint x); + virtual void uniform1iv(WGC3Dint location, + WGC3Dsizei count, const WGC3Dint* v); + virtual void uniform2f(WGC3Dint location, WGC3Dfloat x, WGC3Dfloat y); + virtual void uniform2fv(WGC3Dint location, + WGC3Dsizei count, const WGC3Dfloat* v); + virtual void uniform2i(WGC3Dint location, WGC3Dint x, WGC3Dint y); + virtual void uniform2iv(WGC3Dint location, + WGC3Dsizei count, const WGC3Dint* v); + virtual void uniform3f(WGC3Dint location, + WGC3Dfloat x, WGC3Dfloat y, WGC3Dfloat z); + virtual void uniform3fv(WGC3Dint location, + WGC3Dsizei count, const WGC3Dfloat* v); + virtual void uniform3i(WGC3Dint location, + WGC3Dint x, WGC3Dint y, WGC3Dint z); + virtual void uniform3iv(WGC3Dint location, + WGC3Dsizei count, const WGC3Dint* v); + virtual void uniform4f(WGC3Dint location, + WGC3Dfloat x, WGC3Dfloat y, + WGC3Dfloat z, WGC3Dfloat w); + virtual void uniform4fv(WGC3Dint location, + WGC3Dsizei count, const WGC3Dfloat* v); + virtual void uniform4i(WGC3Dint location, + WGC3Dint x, WGC3Dint y, WGC3Dint z, WGC3Dint w); + virtual void uniform4iv(WGC3Dint location, + WGC3Dsizei count, const WGC3Dint* v); + virtual void uniformMatrix2fv(WGC3Dint location, + WGC3Dsizei count, + WGC3Dboolean transpose, + const WGC3Dfloat* value); + virtual void uniformMatrix3fv(WGC3Dint location, + WGC3Dsizei count, + WGC3Dboolean transpose, + const WGC3Dfloat* value); + virtual void uniformMatrix4fv(WGC3Dint location, + WGC3Dsizei count, + WGC3Dboolean transpose, + const WGC3Dfloat* value); + + virtual void useProgram(WebGLId program); + virtual void validateProgram(WebGLId program); + + virtual void vertexAttrib1f(WGC3Duint index, WGC3Dfloat x); + virtual void vertexAttrib1fv(WGC3Duint index, const WGC3Dfloat* values); + virtual void vertexAttrib2f(WGC3Duint index, WGC3Dfloat x, WGC3Dfloat y); + virtual void vertexAttrib2fv(WGC3Duint index, const WGC3Dfloat* values); + virtual void vertexAttrib3f(WGC3Duint index, + WGC3Dfloat x, WGC3Dfloat y, WGC3Dfloat z); + virtual void vertexAttrib3fv(WGC3Duint index, const WGC3Dfloat* values); + virtual void vertexAttrib4f(WGC3Duint index, + WGC3Dfloat x, WGC3Dfloat y, + WGC3Dfloat z, WGC3Dfloat w); + virtual void vertexAttrib4fv(WGC3Duint index, const WGC3Dfloat* values); + virtual void vertexAttribPointer(WGC3Duint index, + WGC3Dint size, + WGC3Denum type, + WGC3Dboolean normalized, + WGC3Dsizei stride, + WGC3Dintptr offset); + + virtual void viewport(WGC3Dint x, WGC3Dint y, + WGC3Dsizei width, WGC3Dsizei height); + + // Support for buffer creation and deletion + virtual WebGLId createBuffer(); + virtual WebGLId createFramebuffer(); + virtual WebGLId createProgram(); + virtual WebGLId createRenderbuffer(); + virtual WebGLId createShader(WGC3Denum); + virtual WebGLId createTexture(); + + virtual void deleteBuffer(WebGLId); + virtual void deleteFramebuffer(WebGLId); + virtual void deleteProgram(WebGLId); + virtual void deleteRenderbuffer(WebGLId); + virtual void deleteShader(WebGLId); + virtual void deleteTexture(WebGLId); + + virtual void synthesizeGLError(WGC3Denum); + + virtual void* mapBufferSubDataCHROMIUM( + WGC3Denum target, WGC3Dintptr offset, + WGC3Dsizeiptr size, WGC3Denum access); + virtual void unmapBufferSubDataCHROMIUM(const void*); + virtual void* mapTexSubImage2DCHROMIUM( + WGC3Denum target, + WGC3Dint level, + WGC3Dint xoffset, + WGC3Dint yoffset, + WGC3Dsizei width, + WGC3Dsizei height, + WGC3Denum format, + WGC3Denum type, + WGC3Denum access); + virtual void unmapTexSubImage2DCHROMIUM(const void*); + + virtual void copyTextureToParentTextureCHROMIUM( + WebGLId texture, WebGLId parentTexture); + + virtual WebKit::WebString getRequestableExtensionsCHROMIUM(); + virtual void requestExtensionCHROMIUM(const char*); + + virtual void blitFramebufferCHROMIUM( + WGC3Dint srcX0, WGC3Dint srcY0, WGC3Dint srcX1, WGC3Dint srcY1, + WGC3Dint dstX0, WGC3Dint dstY0, WGC3Dint dstX1, WGC3Dint dstY1, + WGC3Dbitfield mask, WGC3Denum filter); + virtual void renderbufferStorageMultisampleCHROMIUM( + WGC3Denum target, WGC3Dsizei samples, WGC3Denum internalformat, + WGC3Dsizei width, WGC3Dsizei height); + + virtual WebGLId createCompositorTexture(WGC3Dsizei width, WGC3Dsizei height); + virtual void deleteCompositorTexture(WebGLId parent_texture); + virtual void copyTextureToCompositor(WebGLId texture, + WebGLId parent_texture); + + ggl::Context* context() { return context_; } + + virtual void setContextLostCallback( + WebGraphicsContext3D::WebGraphicsContextLostCallback* callback); + + private: + // SwapBuffers callback; + void OnSwapBuffers(); + virtual void OnContextLost(); + + // The GGL context we use for OpenGL rendering. + ggl::Context* context_; + // If rendering directly to WebView, weak pointer to it. + WebKit::WebView* web_view_; +#if defined(OS_MACOSX) + // "Fake" plugin window handle in browser process for the compositor's output. + gfx::PluginWindowHandle plugin_handle_; +#endif + WebGraphicsContext3D::WebGraphicsContextLostCallback* context_lost_callback_; + + WebKit::WebGraphicsContext3D::Attributes attributes_; + int cached_width_, cached_height_; + + // For tracking which FBO is bound. + WebGLId bound_fbo_; + + // Errors raised by synthesizeGLError(). + std::vector<WGC3Denum> synthetic_errors_; + +#ifdef FLIP_FRAMEBUFFER_VERTICALLY + scoped_ptr<uint8> scanline_; + void FlipVertically(uint8* framebuffer, + unsigned int width, + unsigned int height); +#endif +}; + +#endif // defined(ENABLE_GPU) +#endif // CONTENT_RENDERER_WEBGRAPHICSCONTEXT3D_COMMAND_BUFFER_IMPL_H_ + diff --git a/content/renderer/webplugin_delegate_proxy.cc b/content/renderer/webplugin_delegate_proxy.cc new file mode 100644 index 0000000..e8ac928 --- /dev/null +++ b/content/renderer/webplugin_delegate_proxy.cc @@ -0,0 +1,1430 @@ +// Copyright (c) 2011 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 "content/renderer/webplugin_delegate_proxy.h" + +#if defined(TOOLKIT_USES_GTK) +#include <gtk/gtk.h> +#endif + +#include <algorithm> + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "base/sys_info.h" +#include "base/utf_string_conversions.h" +#include "chrome/common/child_process_logging.h" +#include "chrome/common/render_messages.h" +#include "chrome/renderer/render_thread.h" +#include "chrome/renderer/render_view.h" +#include "content/common/plugin_messages.h" +#include "content/plugin/npobject_proxy.h" +#include "content/plugin/npobject_stub.h" +#include "content/plugin/npobject_util.h" +#include "content/renderer/command_buffer_proxy.h" +#include "content/renderer/plugin_channel_host.h" +//#include "grit/renderer_resources.h" +#include "ipc/ipc_channel_handle.h" +#include "net/base/mime_util.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebCursorInfo.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDragData.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/blit.h" +#include "ui/gfx/canvas_skia.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/size.h" +#include "webkit/plugins/npapi/webplugin.h" +#include "webkit/glue/webkit_glue.h" + +#if defined(OS_POSIX) +#include "ipc/ipc_channel_posix.h" +#endif + +#if defined(OS_WIN) +#include "printing/native_metafile_factory.h" +#include "printing/native_metafile.h" +#endif + +using WebKit::WebBindings; +using WebKit::WebCursorInfo; +using WebKit::WebDragData; +using WebKit::WebInputEvent; +using WebKit::WebString; +using WebKit::WebView; + +// Proxy for WebPluginResourceClient. The object owns itself after creation, +// deleting itself after its callback has been called. +class ResourceClientProxy : public webkit::npapi::WebPluginResourceClient { + public: + ResourceClientProxy(PluginChannelHost* channel, int instance_id) + : channel_(channel), instance_id_(instance_id), resource_id_(0), + multibyte_response_expected_(false) { + } + + ~ResourceClientProxy() { + } + + void Initialize(unsigned long resource_id, const GURL& url, int notify_id) { + resource_id_ = resource_id; + channel_->Send(new PluginMsg_HandleURLRequestReply( + instance_id_, resource_id, url, notify_id)); + } + + void InitializeForSeekableStream(unsigned long resource_id, + int range_request_id) { + resource_id_ = resource_id; + multibyte_response_expected_ = true; + channel_->Send(new PluginMsg_HTTPRangeRequestReply( + instance_id_, resource_id, range_request_id)); + } + + // PluginResourceClient implementation: + void WillSendRequest(const GURL& url, int http_status_code) { + DCHECK(channel_ != NULL); + channel_->Send(new PluginMsg_WillSendRequest(instance_id_, resource_id_, + url, http_status_code)); + } + + void DidReceiveResponse(const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified, + bool request_is_seekable) { + DCHECK(channel_ != NULL); + PluginMsg_DidReceiveResponseParams params; + params.id = resource_id_; + params.mime_type = mime_type; + params.headers = headers; + params.expected_length = expected_length; + params.last_modified = last_modified; + params.request_is_seekable = request_is_seekable; + // Grab a reference on the underlying channel so it does not get + // deleted from under us. + scoped_refptr<PluginChannelHost> channel_ref(channel_); + channel_->Send(new PluginMsg_DidReceiveResponse(instance_id_, params)); + } + + void DidReceiveData(const char* buffer, int length, int data_offset) { + DCHECK(channel_ != NULL); + DCHECK_GT(length, 0); + std::vector<char> data; + data.resize(static_cast<size_t>(length)); + memcpy(&data.front(), buffer, length); + // Grab a reference on the underlying channel so it does not get + // deleted from under us. + scoped_refptr<PluginChannelHost> channel_ref(channel_); + channel_->Send(new PluginMsg_DidReceiveData(instance_id_, resource_id_, + data, data_offset)); + } + + void DidFinishLoading() { + DCHECK(channel_ != NULL); + channel_->Send(new PluginMsg_DidFinishLoading(instance_id_, resource_id_)); + channel_ = NULL; + MessageLoop::current()->DeleteSoon(FROM_HERE, this); + } + + void DidFail() { + DCHECK(channel_ != NULL); + channel_->Send(new PluginMsg_DidFail(instance_id_, resource_id_)); + channel_ = NULL; + MessageLoop::current()->DeleteSoon(FROM_HERE, this); + } + + bool IsMultiByteResponseExpected() { + return multibyte_response_expected_; + } + + int ResourceId() { + return resource_id_; + } + + private: + scoped_refptr<PluginChannelHost> channel_; + int instance_id_; + unsigned long resource_id_; + // Set to true if the response expected is a multibyte response. + // For e.g. response for a HTTP byte range request. + bool multibyte_response_expected_; +}; + +#if defined(OS_MACOSX) +static void ReleaseTransportDIB(TransportDIB* dib) { + if (dib) { + IPC::Message* message = new ViewHostMsg_FreeTransportDIB(dib->id()); + RenderThread::current()->Send(message); + } +} +#endif + +WebPluginDelegateProxy::WebPluginDelegateProxy( + const std::string& mime_type, + const base::WeakPtr<RenderView>& render_view) + : render_view_(render_view), + plugin_(NULL), + uses_shared_bitmaps_(false), + window_(gfx::kNullPluginWindow), + mime_type_(mime_type), + instance_id_(MSG_ROUTING_NONE), + npobject_(NULL), + sad_plugin_(NULL), + invalidate_pending_(false), + transparent_(false), + page_url_(render_view_->webview()->mainFrame()->url()) { +} + +WebPluginDelegateProxy::~WebPluginDelegateProxy() { +#if defined(OS_MACOSX) + // Ask the browser to release old TransportDIB objects for which no + // PluginHostMsg_UpdateGeometry_ACK was ever received from the plugin + // process. + for (OldTransportDIBMap::iterator iterator = old_transport_dibs_.begin(); + iterator != old_transport_dibs_.end(); + ++iterator) { + ReleaseTransportDIB(iterator->second.get()); + } + + // Ask the browser to release the "live" TransportDIB object. + ReleaseTransportDIB(transport_store_.get()); + DCHECK(!background_store_.get()); +#endif +} + +void WebPluginDelegateProxy::PluginDestroyed() { +#if defined(OS_MACOSX) + // Ensure that the renderer doesn't think the plugin still has focus. + if (render_view_) + render_view_->PluginFocusChanged(false, instance_id_); +#endif + + if (window_) + WillDestroyWindow(); + + if (render_view_) + render_view_->UnregisterPluginDelegate(this); + + if (channel_host_) { + Send(new PluginMsg_DestroyInstance(instance_id_)); + + // Must remove the route after sending the destroy message, since + // RemoveRoute can lead to all the outstanding NPObjects being told the + // channel went away if this was the last instance. + channel_host_->RemoveRoute(instance_id_); + + // Release the channel host now. If we are is the last reference to the + // channel, this avoids a race where this renderer asks a new connection to + // the same plugin between now and the time 'this' is actually deleted. + // Destroying the channel host is what releases the channel name -> FD + // association on POSIX, and if we ask for a new connection before it is + // released, the plugin will give us a new FD, and we'll assert when trying + // to associate it with the channel name. + channel_host_ = NULL; + } + + if (window_script_object_) { + // The ScriptController deallocates this object independent of its ref count + // to avoid leaks if the plugin forgets to release it. So mark the object + // invalid to avoid accessing it past this point. Note: only do this after + // the DestroyInstance message in case the window object is scripted by the + // plugin in NPP_Destroy. + window_script_object_->OnPluginDestroyed(); + } + + plugin_ = NULL; + + MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +// Returns true if the given Silverlight 'background' value corresponds to +// one that should make the plugin transparent. See: +// http://msdn.microsoft.com/en-us/library/cc838148(VS.95).aspx +// for possible values. +static bool SilverlightColorIsTransparent(const std::string& color) { + if (StartsWithASCII(color, "#", false)) { + // If it's #ARGB or #AARRGGBB check the alpha; if not it's an RGB form and + // it's not transparent. + if ((color.length() == 5 && !StartsWithASCII(color, "#F", false)) || + (color.length() == 9 && !StartsWithASCII(color, "#FF", false))) + return true; + } else if (StartsWithASCII(color, "sc#", false)) { + // It's either sc#A,R,G,B or sc#R,G,B; if the former, check the alpha. + if (color.length() < 4) + return false; + std::string value_string = color.substr(3, std::string::npos); + std::vector<std::string> components; + base::SplitString(value_string, ',', &components); + if (components.size() == 4 && !StartsWithASCII(components[0], "1", false)) + return true; + } else if (LowerCaseEqualsASCII(color, "transparent")) { + return true; + } + // Anything else is a named, opaque color or an RGB form with no alpha. + return false; +} + +bool WebPluginDelegateProxy::Initialize( + const GURL& url, + const std::vector<std::string>& arg_names, + const std::vector<std::string>& arg_values, + webkit::npapi::WebPlugin* plugin, + bool load_manually) { + IPC::ChannelHandle channel_handle; + if (!RenderThread::current()->Send(new ViewHostMsg_OpenChannelToPlugin( + render_view_->routing_id(), url, mime_type_, &channel_handle, + &info_))) { + return false; + } + + if (channel_handle.name.empty()) { + // We got an invalid handle. Either the plugin couldn't be found (which + // shouldn't happen, since if we got here the plugin should exist) or the + // plugin crashed on initialization. + if (!info_.path.empty()) { + render_view_->PluginCrashed(info_.path); + + // Return true so that the plugin widget is created and we can paint the + // crashed plugin there. + return true; + } + return false; + } + + scoped_refptr<PluginChannelHost> channel_host( + PluginChannelHost::GetPluginChannelHost( + channel_handle, ChildProcess::current()->io_message_loop())); + if (!channel_host.get()) + return false; + + int instance_id; + bool result = channel_host->Send(new PluginMsg_CreateInstance( + mime_type_, &instance_id)); + if (!result) + return false; + + channel_host_ = channel_host; + instance_id_ = instance_id; + + channel_host_->AddRoute(instance_id_, this, NULL); + + // Now tell the PluginInstance in the plugin process to initialize. + PluginMsg_Init_Params params; + params.containing_window = render_view_->host_window(); + params.url = url; + params.page_url = page_url_; + params.arg_names = arg_names; + params.arg_values = arg_values; + params.host_render_view_routing_id = render_view_->routing_id(); + + bool flash = + LowerCaseEqualsASCII(mime_type_, "application/x-shockwave-flash"); + bool silverlight = + StartsWithASCII(mime_type_, "application/x-silverlight", false); + for (size_t i = 0; i < arg_names.size(); ++i) { + if ((flash && LowerCaseEqualsASCII(arg_names[i], "wmode") && + LowerCaseEqualsASCII(arg_values[i], "transparent")) || + (silverlight && LowerCaseEqualsASCII(arg_names[i], "background") && + SilverlightColorIsTransparent(arg_values[i]))) { + transparent_ = true; + } + } +#if defined(OS_MACOSX) + // Unless we have a real way to support accelerated (3D) drawing on Macs + // (which for now at least means the Core Animation drawing model), ask + // Flash to use windowless mode so that it use CoreGraphics instead of opening + // OpenGL contexts overlaying the browser window (which requires a very + // expensive extra copy for us). + if (!transparent_ && mime_type_ == "application/x-shockwave-flash") { + bool force_opaque_mode = false; + if (StartsWith(info_.version, ASCIIToUTF16("10.0"), false) || + StartsWith(info_.version, ASCIIToUTF16("9."), false)) { + // Older versions of Flash don't support CA (and they assume QuickDraw + // support, so we can't rely on negotiation to do the right thing). + force_opaque_mode = true; + } else { + // Flash 10.1 doesn't respect QuickDraw negotiation either, so we still + // have to force opaque mode on 10.5 (where it doesn't use CA). + int32 major, minor, bugfix; + base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix); + if (major < 10 || (major == 10 && minor < 6)) + force_opaque_mode = true; + } + + if (force_opaque_mode) { + params.arg_names.push_back("wmode"); + params.arg_values.push_back("opaque"); + } + } +#endif + params.load_manually = load_manually; + + plugin_ = plugin; + + result = false; + IPC::Message* msg = new PluginMsg_Init(instance_id_, params, &result); + Send(msg); + + render_view_->RegisterPluginDelegate(this); + + return result; +} + +bool WebPluginDelegateProxy::Send(IPC::Message* msg) { + if (!channel_host_) { + DLOG(WARNING) << "dropping message because channel host is null"; + delete msg; + return false; + } + + return channel_host_->Send(msg); +} + +void WebPluginDelegateProxy::SendJavaScriptStream(const GURL& url, + const std::string& result, + bool success, + int notify_id) { + Send(new PluginMsg_SendJavaScriptStream( + instance_id_, url, result, success, notify_id)); +} + +void WebPluginDelegateProxy::DidReceiveManualResponse( + const GURL& url, const std::string& mime_type, + const std::string& headers, uint32 expected_length, + uint32 last_modified) { + PluginMsg_DidReceiveResponseParams params; + params.id = 0; + params.mime_type = mime_type; + params.headers = headers; + params.expected_length = expected_length; + params.last_modified = last_modified; + Send(new PluginMsg_DidReceiveManualResponse(instance_id_, url, params)); +} + +void WebPluginDelegateProxy::DidReceiveManualData(const char* buffer, + int length) { + DCHECK_GT(length, 0); + std::vector<char> data; + data.resize(static_cast<size_t>(length)); + memcpy(&data.front(), buffer, length); + Send(new PluginMsg_DidReceiveManualData(instance_id_, data)); +} + +void WebPluginDelegateProxy::DidFinishManualLoading() { + Send(new PluginMsg_DidFinishManualLoading(instance_id_)); +} + +void WebPluginDelegateProxy::DidManualLoadFail() { + Send(new PluginMsg_DidManualLoadFail(instance_id_)); +} + +void WebPluginDelegateProxy::InstallMissingPlugin() { + Send(new PluginMsg_InstallMissingPlugin(instance_id_)); +} + +bool WebPluginDelegateProxy::OnMessageReceived(const IPC::Message& msg) { + child_process_logging::SetActiveURL(page_url_); + + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(WebPluginDelegateProxy, msg) + IPC_MESSAGE_HANDLER(PluginHostMsg_SetWindow, OnSetWindow) +#if defined(OS_WIN) + IPC_MESSAGE_HANDLER(PluginHostMsg_SetWindowlessPumpEvent, + OnSetWindowlessPumpEvent) +#endif + IPC_MESSAGE_HANDLER(PluginHostMsg_CancelResource, OnCancelResource) + IPC_MESSAGE_HANDLER(PluginHostMsg_InvalidateRect, OnInvalidateRect) + IPC_MESSAGE_HANDLER(PluginHostMsg_GetWindowScriptNPObject, + OnGetWindowScriptNPObject) + IPC_MESSAGE_HANDLER(PluginHostMsg_GetPluginElement, + OnGetPluginElement) + IPC_MESSAGE_HANDLER(PluginHostMsg_SetCookie, OnSetCookie) + IPC_MESSAGE_HANDLER(PluginHostMsg_GetCookies, OnGetCookies) + IPC_MESSAGE_HANDLER(PluginHostMsg_MissingPluginStatus, + OnMissingPluginStatus) + IPC_MESSAGE_HANDLER(PluginHostMsg_URLRequest, OnHandleURLRequest) + IPC_MESSAGE_HANDLER(PluginHostMsg_CancelDocumentLoad, OnCancelDocumentLoad) + IPC_MESSAGE_HANDLER(PluginHostMsg_InitiateHTTPRangeRequest, + OnInitiateHTTPRangeRequest) + IPC_MESSAGE_HANDLER(PluginHostMsg_DeferResourceLoading, + OnDeferResourceLoading) + +#if defined(OS_MACOSX) + IPC_MESSAGE_HANDLER(PluginHostMsg_FocusChanged, + OnFocusChanged); + IPC_MESSAGE_HANDLER(PluginHostMsg_StartIme, + OnStartIme); + IPC_MESSAGE_HANDLER(PluginHostMsg_BindFakePluginWindowHandle, + OnBindFakePluginWindowHandle); + IPC_MESSAGE_HANDLER(PluginHostMsg_UpdateGeometry_ACK, + OnUpdateGeometry_ACK) + // Used only on 10.6 and later. + IPC_MESSAGE_HANDLER(PluginHostMsg_AcceleratedSurfaceSetIOSurface, + OnAcceleratedSurfaceSetIOSurface) + // Used on 10.5 and earlier. + IPC_MESSAGE_HANDLER(PluginHostMsg_AcceleratedSurfaceSetTransportDIB, + OnAcceleratedSurfaceSetTransportDIB) + IPC_MESSAGE_HANDLER(PluginHostMsg_AllocTransportDIB, + OnAcceleratedSurfaceAllocTransportDIB) + IPC_MESSAGE_HANDLER(PluginHostMsg_FreeTransportDIB, + OnAcceleratedSurfaceFreeTransportDIB) + IPC_MESSAGE_HANDLER(PluginHostMsg_AcceleratedSurfaceBuffersSwapped, + OnAcceleratedSurfaceBuffersSwapped) +#endif + IPC_MESSAGE_HANDLER(PluginHostMsg_URLRedirectResponse, + OnURLRedirectResponse) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + DCHECK(handled); + return handled; +} + +void WebPluginDelegateProxy::OnChannelError() { + if (plugin_) { + if (window_) { + // The actual WebPluginDelegate never got a chance to tell the WebPlugin + // its window was going away. Do it on its behalf. + WillDestroyWindow(); + } + plugin_->Invalidate(); + } + if (!channel_host_->expecting_shutdown()) + render_view_->PluginCrashed(info_.path); + +#if defined(OS_MACOSX) + // Ensure that the renderer doesn't think the plugin still has focus. + if (render_view_) + render_view_->PluginFocusChanged(false, instance_id_); +#endif +} + +void WebPluginDelegateProxy::UpdateGeometry(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + // window_rect becomes either a window in native windowing system + // coords, or a backing buffer. In either case things will go bad + // if the rectangle is very large. + if (window_rect.width() < 0 || window_rect.width() > (1<<15) || + window_rect.height() < 0 || window_rect.height() > (1<<15) || + // Clip to 8m pixels; we know this won't overflow due to above checks. + window_rect.width() * window_rect.height() > (8<<20)) { + return; + } + + plugin_rect_ = window_rect; + clip_rect_ = clip_rect; + + bool bitmaps_changed = false; + + PluginMsg_UpdateGeometry_Param param; +#if defined(OS_MACOSX) + param.ack_key = -1; +#endif + + if (uses_shared_bitmaps_) { + if (!backing_store_canvas_.get() || + (window_rect.width() != backing_store_canvas_->getDevice()->width() || + window_rect.height() != backing_store_canvas_->getDevice()->height())) + { + bitmaps_changed = true; + + bool needs_background_store = transparent_; +#if defined(OS_MACOSX) + // We don't support transparency under QuickDraw, and CoreGraphics + // preserves transparency information (and does the compositing itself) + // so plugins don't need access to the page background. + needs_background_store = false; + if (transport_store_.get()) { + // ResetWindowlessBitmaps inserts the old TransportDIBs into + // old_transport_dibs_ using the transport store's file descriptor as + // the key. The constraints on the keys are that -1 is reserved + // to mean "no ACK required," and in-flight keys must be unique. + // File descriptors will never be -1, and because they won't be closed + // until receipt of the ACK, they're unique. + param.ack_key = transport_store_->handle().fd; + } +#endif + + // Create a shared memory section that the plugin paints into + // asynchronously. + ResetWindowlessBitmaps(); + if (!window_rect.IsEmpty()) { + if (!CreateSharedBitmap(&transport_store_, &transport_store_canvas_) || +#if defined(OS_WIN) + !CreateSharedBitmap(&backing_store_, &backing_store_canvas_) || +#else + !CreateLocalBitmap(&backing_store_, &backing_store_canvas_) || +#endif + (needs_background_store && + !CreateSharedBitmap(&background_store_, + &background_store_canvas_))) { + DCHECK(false); + ResetWindowlessBitmaps(); + return; + } + } + } + } + + param.window_rect = window_rect; + param.clip_rect = clip_rect; + param.windowless_buffer = TransportDIB::DefaultHandleValue(); + param.background_buffer = TransportDIB::DefaultHandleValue(); + param.transparent = transparent_; + +#if defined(OS_POSIX) + // If we're using POSIX mmap'd TransportDIBs, sending the handle across + // IPC establishes a new mapping rather than just sending a window ID, + // so only do so if we've actually recreated the shared memory bitmaps. + if (bitmaps_changed) +#endif + { + if (transport_store_.get()) + param.windowless_buffer = transport_store_->handle(); + + if (background_store_.get()) + param.background_buffer = background_store_->handle(); + } + + IPC::Message* msg; +#if defined (OS_WIN) + if (UseSynchronousGeometryUpdates()) { + msg = new PluginMsg_UpdateGeometrySync(instance_id_, param); + } else // NOLINT +#endif + { + msg = new PluginMsg_UpdateGeometry(instance_id_, param); + msg->set_unblock(true); + } + + Send(msg); +} + +void WebPluginDelegateProxy::ResetWindowlessBitmaps() { +#if defined(OS_MACOSX) + DCHECK(!background_store_.get()); + // The Mac TransportDIB implementation uses base::SharedMemory, which + // cannot be disposed of if an in-flight UpdateGeometry message refers to + // the shared memory file descriptor. The old_transport_dibs_ map holds + // old TransportDIBs waiting to die, keyed by the |ack_key| values used in + // UpdateGeometry messages. When an UpdateGeometry_ACK message arrives, + // the associated TransportDIB can be released. + if (transport_store_.get()) { + int ack_key = transport_store_->handle().fd; + + DCHECK_NE(ack_key, -1); + + // DCHECK_EQ does not work with base::hash_map. + DCHECK(old_transport_dibs_.find(ack_key) == old_transport_dibs_.end()); + + // Stash the old TransportDIB in the map. It'll be released when an + // ACK message comes in. + old_transport_dibs_[ack_key] = + linked_ptr<TransportDIB>(transport_store_.release()); + } +#else + transport_store_.reset(); + background_store_.reset(); +#endif +#if defined(OS_WIN) + backing_store_.reset(); +#else + backing_store_.resize(0); +#endif + + backing_store_canvas_.reset(); + transport_store_canvas_.reset(); + background_store_canvas_.reset(); + backing_store_painted_ = gfx::Rect(); +} + +static size_t BitmapSizeForPluginRect(const gfx::Rect& plugin_rect) { + const size_t stride = + skia::PlatformCanvas::StrideForWidth(plugin_rect.width()); + return stride * plugin_rect.height(); +} + +#if !defined(OS_WIN) +bool WebPluginDelegateProxy::CreateLocalBitmap( + std::vector<uint8>* memory, + scoped_ptr<skia::PlatformCanvas>* canvas) { + const size_t size = BitmapSizeForPluginRect(plugin_rect_); + memory->resize(size); + if (memory->size() != size) + return false; + canvas->reset(new skia::PlatformCanvas( + plugin_rect_.width(), plugin_rect_.height(), true, &((*memory)[0]))); + return true; +} +#endif + +bool WebPluginDelegateProxy::CreateSharedBitmap( + scoped_ptr<TransportDIB>* memory, + scoped_ptr<skia::PlatformCanvas>* canvas) { + const size_t size = BitmapSizeForPluginRect(plugin_rect_); +#if defined(OS_POSIX) && !defined(OS_MACOSX) + memory->reset(TransportDIB::Create(size, 0)); + if (!memory->get()) + return false; +#endif +#if defined(OS_MACOSX) + TransportDIB::Handle handle; + IPC::Message* msg = new ViewHostMsg_AllocTransportDIB(size, true, &handle); + if (!RenderThread::current()->Send(msg)) + return false; + if (handle.fd < 0) + return false; + memory->reset(TransportDIB::Map(handle)); +#else + static uint32 sequence_number = 0; + memory->reset(TransportDIB::Create(size, sequence_number++)); +#endif + canvas->reset((*memory)->GetPlatformCanvas(plugin_rect_.width(), + plugin_rect_.height())); + return !!canvas->get(); +} + +#if defined(OS_MACOSX) +// Flips |rect| vertically within an enclosing rect with height |height|. +// Intended for converting rects between flipped and non-flipped contexts. +static void FlipRectVerticallyWithHeight(gfx::Rect* rect, int height) { + rect->set_y(height - rect->y() - rect->height()); +} +#endif + +void WebPluginDelegateProxy::Paint(WebKit::WebCanvas* canvas, + const gfx::Rect& damaged_rect) { + // Limit the damaged rectangle to whatever is contained inside the plugin + // rectangle, as that's the rectangle that we'll actually draw. + gfx::Rect rect = damaged_rect.Intersect(plugin_rect_); + + // If the plugin is no longer connected (channel crashed) draw a crashed + // plugin bitmap + if (!channel_host_ || !channel_host_->channel_valid()) { + PaintSadPlugin(canvas, rect); + return; + } + + if (!uses_shared_bitmaps_) + return; + + // We got a paint before the plugin's coordinates, so there's no buffer to + // copy from. + if (!backing_store_canvas_.get()) + return; + + // We're using the native OS APIs from here on out. +#if WEBKIT_USING_SKIA + gfx::NativeDrawingContext context = canvas->beginPlatformPaint(); +#elif WEBKIT_USING_CG + gfx::NativeDrawingContext context = canvas; +#endif + + gfx::Rect offset_rect = rect; + offset_rect.Offset(-plugin_rect_.x(), -plugin_rect_.y()); + gfx::Rect canvas_rect = offset_rect; +#if defined(OS_MACOSX) + // The canvases are flipped relative to the context, so flip the rect too. + FlipRectVerticallyWithHeight(&canvas_rect, plugin_rect_.height()); +#endif + + bool background_changed = false; + if (background_store_canvas_.get() && BackgroundChanged(context, rect)) { + background_changed = true; + gfx::Rect flipped_offset_rect = offset_rect; + BlitContextToCanvas(background_store_canvas_.get(), canvas_rect, + context, rect.origin()); + } + + if (background_changed || !backing_store_painted_.Contains(offset_rect)) { + Send(new PluginMsg_Paint(instance_id_, offset_rect)); + CopyFromTransportToBacking(offset_rect); + } + + BlitCanvasToContext(context, rect, backing_store_canvas_.get(), + canvas_rect.origin()); + + if (invalidate_pending_) { + // Only send the PaintAck message if this paint is in response to an + // invalidate from the plugin, since this message acts as an access token + // to ensure only one process is using the transport dib at a time. + invalidate_pending_ = false; + Send(new PluginMsg_DidPaint(instance_id_)); + } + +#if WEBKIT_USING_SKIA + canvas->endPlatformPaint(); +#endif +} + +bool WebPluginDelegateProxy::BackgroundChanged( + gfx::NativeDrawingContext context, + const gfx::Rect& rect) { +#if defined(OS_WIN) + HBITMAP hbitmap = static_cast<HBITMAP>(GetCurrentObject(context, OBJ_BITMAP)); + if (hbitmap == NULL) { + NOTREACHED(); + return true; + } + + BITMAP bitmap = { 0 }; + int result = GetObject(hbitmap, sizeof(bitmap), &bitmap); + if (!result) { + NOTREACHED(); + return true; + } + + XFORM xf; + if (!GetWorldTransform(context, &xf)) { + NOTREACHED(); + return true; + } + + // The damaged rect that we're given can be larger than the bitmap, so + // intersect their rects first. + gfx::Rect bitmap_rect(static_cast<int>(-xf.eDx), static_cast<int>(-xf.eDy), + bitmap.bmWidth, bitmap.bmHeight); + gfx::Rect check_rect = rect.Intersect(bitmap_rect); + int row_byte_size = check_rect.width() * (bitmap.bmBitsPixel / 8); + for (int y = check_rect.y(); y < check_rect.bottom(); y++) { + char* hdc_row_start = static_cast<char*>(bitmap.bmBits) + + (y + static_cast<int>(xf.eDy)) * bitmap.bmWidthBytes + + (check_rect.x() + static_cast<int>(xf.eDx)) * (bitmap.bmBitsPixel / 8); + + // getAddr32 doesn't use the translation units, so we have to subtract + // the plugin origin from the coordinates. + uint32_t* canvas_row_start = + background_store_canvas_->getDevice()->accessBitmap(true).getAddr32( + check_rect.x() - plugin_rect_.x(), y - plugin_rect_.y()); + if (memcmp(hdc_row_start, canvas_row_start, row_byte_size) != 0) + return true; + } +#else +#if defined(OS_MACOSX) + // If there is a translation on the content area context, we need to account + // for it; the context may be a subset of the full content area with a + // transform that makes the coordinates work out. + CGAffineTransform transform = CGContextGetCTM(context); + bool flipped = fabs(transform.d + 1) < 0.0001; + CGFloat context_offset_x = -transform.tx; + CGFloat context_offset_y = flipped ? transform.ty - + CGBitmapContextGetHeight(context) + : -transform.ty; + gfx::Rect full_content_rect(context_offset_x, context_offset_y, + CGBitmapContextGetWidth(context), + CGBitmapContextGetHeight(context)); +#else + cairo_surface_t* page_surface = cairo_get_target(context); + DCHECK_EQ(cairo_surface_get_type(page_surface), CAIRO_SURFACE_TYPE_IMAGE); + DCHECK_EQ(cairo_image_surface_get_format(page_surface), CAIRO_FORMAT_ARGB32); + + // Transform context coordinates into surface coordinates. + double page_x_double = rect.x(); + double page_y_double = rect.y(); + cairo_user_to_device(context, &page_x_double, &page_y_double); + gfx::Rect full_content_rect(0, 0, + cairo_image_surface_get_width(page_surface), + cairo_image_surface_get_height(page_surface)); +#endif + // According to comments in the Windows code, the damage rect that we're given + // may project outside the image, so intersect their rects. + gfx::Rect content_rect = rect.Intersect(full_content_rect); + +#if defined(OS_MACOSX) + const unsigned char* page_bytes = static_cast<const unsigned char*>( + CGBitmapContextGetData(context)); + int page_stride = CGBitmapContextGetBytesPerRow(context); + int page_start_x = content_rect.x() - context_offset_x; + int page_start_y = content_rect.y() - context_offset_y; + + CGContextRef bg_context = + background_store_canvas_->getTopPlatformDevice().GetBitmapContext(); + DCHECK_EQ(CGBitmapContextGetBitsPerPixel(context), + CGBitmapContextGetBitsPerPixel(bg_context)); + const unsigned char* bg_bytes = static_cast<const unsigned char*>( + CGBitmapContextGetData(bg_context)); + int full_bg_width = CGBitmapContextGetWidth(bg_context); + int full_bg_height = CGBitmapContextGetHeight(bg_context); + int bg_stride = CGBitmapContextGetBytesPerRow(bg_context); + int bg_last_row = CGBitmapContextGetHeight(bg_context) - 1; + + int bytes_per_pixel = CGBitmapContextGetBitsPerPixel(context) / 8; +#else + cairo_surface_flush(page_surface); + const unsigned char* page_bytes = cairo_image_surface_get_data(page_surface); + int page_stride = cairo_image_surface_get_stride(page_surface); + int page_start_x = static_cast<int>(page_x_double); + int page_start_y = static_cast<int>(page_y_double); + + skia::PlatformDevice& device = + background_store_canvas_->getTopPlatformDevice(); + cairo_surface_t* bg_surface = cairo_get_target(device.beginPlatformPaint()); + DCHECK_EQ(cairo_surface_get_type(bg_surface), CAIRO_SURFACE_TYPE_IMAGE); + DCHECK_EQ(cairo_image_surface_get_format(bg_surface), CAIRO_FORMAT_ARGB32); + cairo_surface_flush(bg_surface); + const unsigned char* bg_bytes = cairo_image_surface_get_data(bg_surface); + int full_bg_width = cairo_image_surface_get_width(bg_surface); + int full_bg_height = cairo_image_surface_get_height(bg_surface); + int bg_stride = cairo_image_surface_get_stride(bg_surface); + + int bytes_per_pixel = 4; // ARGB32 = 4 bytes per pixel. +#endif + + int damage_width = content_rect.width(); + int damage_height = content_rect.height(); + + int bg_start_x = rect.x() - plugin_rect_.x(); + int bg_start_y = rect.y() - plugin_rect_.y(); + // The damage rect is supposed to have been intersected with the plugin rect; + // double-check, since if it hasn't we'll walk off the end of the buffer. + DCHECK_LE(bg_start_x + damage_width, full_bg_width); + DCHECK_LE(bg_start_y + damage_height, full_bg_height); + + int bg_x_byte_offset = bg_start_x * bytes_per_pixel; + int page_x_byte_offset = page_start_x * bytes_per_pixel; + for (int row = 0; row < damage_height; ++row) { + int page_offset = page_stride * (page_start_y + row) + page_x_byte_offset; + int bg_y = bg_start_y + row; +#if defined(OS_MACOSX) + // The background buffer is upside down relative to the content. + bg_y = bg_last_row - bg_y; +#endif + int bg_offset = bg_stride * bg_y + bg_x_byte_offset; + if (memcmp(page_bytes + page_offset, + bg_bytes + bg_offset, + damage_width * bytes_per_pixel) != 0) + return true; + } +#endif + + return false; +} + +void WebPluginDelegateProxy::Print(gfx::NativeDrawingContext context) { + base::SharedMemoryHandle shared_memory; + uint32 size; + if (!Send(new PluginMsg_Print(instance_id_, &shared_memory, &size))) + return; + + base::SharedMemory memory(shared_memory, true); + if (!memory.Map(size)) { + NOTREACHED(); + return; + } + +#if defined(OS_WIN) + scoped_ptr<printing::NativeMetafile> metafile( + printing::NativeMetafileFactory::CreateMetafile()); + if (!metafile->Init(memory.memory(), size)) { + NOTREACHED(); + return; + } + // Playback the buffer. + metafile->Playback(context, NULL); +#else + // TODO(port): plugin printing. + NOTIMPLEMENTED(); +#endif +} + +NPObject* WebPluginDelegateProxy::GetPluginScriptableObject() { + if (npobject_) + return WebBindings::retainObject(npobject_); + + int route_id = MSG_ROUTING_NONE; + Send(new PluginMsg_GetPluginScriptableObject(instance_id_, &route_id)); + if (route_id == MSG_ROUTING_NONE) + return NULL; + + npobject_ = NPObjectProxy::Create( + channel_host_.get(), route_id, 0, page_url_); + + return WebBindings::retainObject(npobject_); +} + +void WebPluginDelegateProxy::DidFinishLoadWithReason( + const GURL& url, NPReason reason, int notify_id) { + Send(new PluginMsg_DidFinishLoadWithReason( + instance_id_, url, reason, notify_id)); +} + +void WebPluginDelegateProxy::SetFocus(bool focused) { + Send(new PluginMsg_SetFocus(instance_id_, focused)); +} + +bool WebPluginDelegateProxy::HandleInputEvent( + const WebInputEvent& event, + WebCursorInfo* cursor_info) { + bool handled; + WebCursor cursor; + // A windowless plugin can enter a modal loop in the context of a + // NPP_HandleEvent call, in which case we need to pump messages to + // the plugin. We pass of the corresponding event handle to the + // plugin process, which is set if the plugin does enter a modal loop. + IPC::SyncMessage* message = new PluginMsg_HandleInputEvent( + instance_id_, &event, &handled, &cursor); + message->set_pump_messages_event(modal_loop_pump_messages_event_.get()); + Send(message); + cursor.GetCursorInfo(cursor_info); + return handled; +} + +int WebPluginDelegateProxy::GetProcessId() { + return channel_host_->peer_pid(); +} + +void WebPluginDelegateProxy::SetContentAreaFocus(bool has_focus) { + IPC::Message* msg = new PluginMsg_SetContentAreaFocus(instance_id_, + has_focus); + // Make sure focus events are delivered in the right order relative to + // sync messages they might interact with (Paint, HandleEvent, etc.). + msg->set_unblock(true); + Send(msg); +} + +#if defined(OS_MACOSX) +void WebPluginDelegateProxy::SetWindowFocus(bool window_has_focus) { + IPC::Message* msg = new PluginMsg_SetWindowFocus(instance_id_, + window_has_focus); + // Make sure focus events are delivered in the right order relative to + // sync messages they might interact with (Paint, HandleEvent, etc.). + msg->set_unblock(true); + Send(msg); +} + +void WebPluginDelegateProxy::SetContainerVisibility(bool is_visible) { + IPC::Message* msg; + if (is_visible) { + gfx::Rect window_frame = render_view_->rootWindowRect(); + gfx::Rect view_frame = render_view_->windowRect(); + WebKit::WebView* webview = render_view_->webview(); + msg = new PluginMsg_ContainerShown(instance_id_, window_frame, view_frame, + webview && webview->isActive()); + } else { + msg = new PluginMsg_ContainerHidden(instance_id_); + } + // Make sure visibility events are delivered in the right order relative to + // sync messages they might interact with (Paint, HandleEvent, etc.). + msg->set_unblock(true); + Send(msg); +} + +void WebPluginDelegateProxy::WindowFrameChanged(gfx::Rect window_frame, + gfx::Rect view_frame) { + IPC::Message* msg = new PluginMsg_WindowFrameChanged(instance_id_, + window_frame, + view_frame); + // Make sure frame events are delivered in the right order relative to + // sync messages they might interact with (e.g., HandleEvent). + msg->set_unblock(true); + Send(msg); +} +void WebPluginDelegateProxy::ImeCompositionCompleted(const string16& text, + int plugin_id) { + // If the message isn't intended for this plugin, there's nothing to do. + if (instance_id_ != plugin_id) + return; + + IPC::Message* msg = new PluginMsg_ImeCompositionCompleted(instance_id_, + text); + // Order relative to other key events is important. + msg->set_unblock(true); + Send(msg); +} +#endif // OS_MACOSX + +void WebPluginDelegateProxy::OnSetWindow(gfx::PluginWindowHandle window) { + uses_shared_bitmaps_ = !window; + window_ = window; + if (plugin_) + plugin_->SetWindow(window); +} + +void WebPluginDelegateProxy::WillDestroyWindow() { + DCHECK(window_); + plugin_->WillDestroyWindow(window_); +#if defined(OS_MACOSX) + if (window_) { + // This is actually a "fake" window handle only for the GPU + // plugin. Deallocate it on the browser side. + if (render_view_) + render_view_->DestroyFakePluginWindowHandle(window_); + } +#endif + window_ = gfx::kNullPluginWindow; +} + +#if defined(OS_WIN) +void WebPluginDelegateProxy::OnSetWindowlessPumpEvent( + HANDLE modal_loop_pump_messages_event) { + DCHECK(modal_loop_pump_messages_event_ == NULL); + + // Bug 25583: this can be null because some "virus scanners" block the + // DuplicateHandle call in the plugin process. + if (!modal_loop_pump_messages_event) + return; + + modal_loop_pump_messages_event_.reset( + new base::WaitableEvent(modal_loop_pump_messages_event)); +} +#endif + +void WebPluginDelegateProxy::OnCancelResource(int id) { + if (plugin_) + plugin_->CancelResource(id); +} + +void WebPluginDelegateProxy::OnInvalidateRect(const gfx::Rect& rect) { + if (!plugin_) + return; + + // Clip the invalidation rect to the plugin bounds; the plugin may have been + // resized since the invalidate message was sent. + const gfx::Rect clipped_rect(rect.Intersect(gfx::Rect(plugin_rect_.size()))); + + invalidate_pending_ = true; + CopyFromTransportToBacking(clipped_rect); + plugin_->InvalidateRect(clipped_rect); +} + +void WebPluginDelegateProxy::OnGetWindowScriptNPObject( + int route_id, bool* success) { + *success = false; + NPObject* npobject = NULL; + if (plugin_) + npobject = plugin_->GetWindowScriptNPObject(); + + if (!npobject) + return; + + // The stub will delete itself when the proxy tells it that it's released, or + // otherwise when the channel is closed. + window_script_object_ = (new NPObjectStub( + npobject, channel_host_.get(), route_id, 0, page_url_))->AsWeakPtr(); + *success = true; +} + +void WebPluginDelegateProxy::OnGetPluginElement(int route_id, bool* success) { + *success = false; + NPObject* npobject = NULL; + if (plugin_) + npobject = plugin_->GetPluginElement(); + if (!npobject) + return; + + // The stub will delete itself when the proxy tells it that it's released, or + // otherwise when the channel is closed. + new NPObjectStub( + npobject, channel_host_.get(), route_id, 0, page_url_); + *success = true; +} + +void WebPluginDelegateProxy::OnSetCookie(const GURL& url, + const GURL& first_party_for_cookies, + const std::string& cookie) { + if (plugin_) + plugin_->SetCookie(url, first_party_for_cookies, cookie); +} + +void WebPluginDelegateProxy::OnGetCookies(const GURL& url, + const GURL& first_party_for_cookies, + std::string* cookies) { + DCHECK(cookies); + if (plugin_) + *cookies = plugin_->GetCookies(url, first_party_for_cookies); +} + +void WebPluginDelegateProxy::OnMissingPluginStatus(int status) { + if (render_view_) + render_view_->OnMissingPluginStatus(this, status); +} + +void WebPluginDelegateProxy::PaintSadPlugin(WebKit::WebCanvas* native_context, + const gfx::Rect& rect) { + // Lazily load the sad plugin image. + /* temporarily disabled by jam + if (!sad_plugin_) { + sad_plugin_ = ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_SAD_PLUGIN); + } + */ + if (!sad_plugin_) + return; + + // Make a temporary canvas for the background image. + const int width = plugin_rect_.width(); + const int height = plugin_rect_.height(); + gfx::CanvasSkia canvas(width, height, false); +#if defined(OS_MACOSX) + // Flip the canvas, since the context expects flipped data. + canvas.translate(0, height); + canvas.scale(1, -1); +#endif + SkPaint paint; + + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(SK_ColorBLACK); + canvas.drawRectCoords(0, 0, SkIntToScalar(width), SkIntToScalar(height), + paint); + canvas.DrawBitmapInt(*sad_plugin_, + std::max(0, (width - sad_plugin_->width())/2), + std::max(0, (height - sad_plugin_->height())/2)); + + // It's slightly less code to make a big SkBitmap of the sad tab image and + // then copy that to the screen than to use the native APIs. The small speed + // penalty is not important when drawing crashed plugins. +#if WEBKIT_USING_SKIA + gfx::NativeDrawingContext context = native_context->beginPlatformPaint(); + BlitCanvasToContext(context, plugin_rect_, &canvas, gfx::Point(0, 0)); + native_context->endPlatformPaint(); +#elif WEBKIT_USING_CG + BlitCanvasToContext(native_context, plugin_rect_, &canvas, gfx::Point(0, 0)); +#endif +} + +void WebPluginDelegateProxy::CopyFromTransportToBacking(const gfx::Rect& rect) { + if (!backing_store_canvas_.get()) { + return; + } + + // Copy the damaged rect from the transport bitmap to the backing store. +#if defined(OS_MACOSX) + // Blitting the bits directly is much faster than going through CG, and since + // since the goal is just to move the raw pixels between two bitmaps with the + // same pixel format (no compositing, color correction, etc.), it's safe. + const size_t stride = + skia::PlatformCanvas::StrideForWidth(plugin_rect_.width()); + const size_t chunk_size = 4 * rect.width(); + uint8* source_data = static_cast<uint8*>(transport_store_->memory()) + + rect.y() * stride + 4 * rect.x(); + // The two bitmaps are flipped relative to each other. + int dest_starting_row = plugin_rect_.height() - rect.y() - 1; + DCHECK(!backing_store_.empty()); + uint8* target_data = &(backing_store_[0]) + dest_starting_row * stride + + 4 * rect.x(); + for (int row = 0; row < rect.height(); ++row) { + memcpy(target_data, source_data, chunk_size); + source_data += stride; + target_data -= stride; + } +#else + BlitCanvasToCanvas(backing_store_canvas_.get(), rect, + transport_store_canvas_.get(), rect.origin()); +#endif + backing_store_painted_ = backing_store_painted_.Union(rect); +} + +void WebPluginDelegateProxy::OnHandleURLRequest( + const PluginHostMsg_URLRequest_Params& params) { + const char* data = NULL; + if (params.buffer.size()) + data = ¶ms.buffer[0]; + + const char* target = NULL; + if (params.target.length()) + target = params.target.c_str(); + + plugin_->HandleURLRequest( + params.url.c_str(), params.method.c_str(), target, data, + static_cast<unsigned int>(params.buffer.size()), params.notify_id, + params.popups_allowed, params.notify_redirects); +} + +webkit::npapi::WebPluginResourceClient* +WebPluginDelegateProxy::CreateResourceClient( + unsigned long resource_id, const GURL& url, int notify_id) { + if (!channel_host_) + return NULL; + + ResourceClientProxy* proxy = new ResourceClientProxy(channel_host_, + instance_id_); + proxy->Initialize(resource_id, url, notify_id); + return proxy; +} + +webkit::npapi::WebPluginResourceClient* +WebPluginDelegateProxy::CreateSeekableResourceClient( + unsigned long resource_id, int range_request_id) { + if (!channel_host_) + return NULL; + + ResourceClientProxy* proxy = new ResourceClientProxy(channel_host_, + instance_id_); + proxy->InitializeForSeekableStream(resource_id, range_request_id); + return proxy; +} + +#if defined(OS_MACOSX) +void WebPluginDelegateProxy::OnFocusChanged(bool focused) { + if (render_view_) + render_view_->PluginFocusChanged(focused, instance_id_); +} + +void WebPluginDelegateProxy::OnStartIme() { + if (render_view_) + render_view_->StartPluginIme(); +} + +void WebPluginDelegateProxy::OnBindFakePluginWindowHandle(bool opaque) { + BindFakePluginWindowHandle(opaque); +} + +// Synthesize a fake window handle for the plug-in to identify the instance +// to the browser, allowing mapping to a surface for hardware acceleration +// of plug-in content. The browser generates the handle which is then set on +// the plug-in. Returns true if it successfully sets the window handle on the +// plug-in. +bool WebPluginDelegateProxy::BindFakePluginWindowHandle(bool opaque) { + gfx::PluginWindowHandle fake_window = NULL; + if (render_view_) + fake_window = render_view_->AllocateFakePluginWindowHandle(opaque, false); + // If we aren't running on 10.6, this allocation will fail. + if (!fake_window) + return false; + OnSetWindow(fake_window); + if (!Send(new PluginMsg_SetFakeAcceleratedSurfaceWindowHandle(instance_id_, + fake_window))) { + return false; + } + + // Since this isn't a real window, it doesn't get initial size and location + // information the way a real windowed plugin would, so we need to feed it its + // starting geometry. + webkit::npapi::WebPluginGeometry geom; + geom.window = fake_window; + geom.window_rect = plugin_rect_; + geom.clip_rect = clip_rect_; + geom.rects_valid = true; + geom.visible = true; + render_view_->DidMovePlugin(geom); + // Invalidate the plugin region to ensure that the move event actually gets + // dispatched (for a plugin on an otherwise static page). + render_view_->didInvalidateRect(WebKit::WebRect(plugin_rect_.x(), + plugin_rect_.y(), + plugin_rect_.width(), + plugin_rect_.height())); + + return true; +} +#endif + +gfx::PluginWindowHandle WebPluginDelegateProxy::GetPluginWindowHandle() { + return window_; +} + +void WebPluginDelegateProxy::OnCancelDocumentLoad() { + plugin_->CancelDocumentLoad(); +} + +void WebPluginDelegateProxy::OnInitiateHTTPRangeRequest( + const std::string& url, + const std::string& range_info, + int range_request_id) { + plugin_->InitiateHTTPRangeRequest( + url.c_str(), range_info.c_str(), range_request_id); +} + +void WebPluginDelegateProxy::OnDeferResourceLoading(unsigned long resource_id, + bool defer) { + plugin_->SetDeferResourceLoading(resource_id, defer); +} + +#if defined(OS_MACOSX) +void WebPluginDelegateProxy::OnUpdateGeometry_ACK(int ack_key) { + DCHECK_NE(ack_key, -1); + + OldTransportDIBMap::iterator iterator = old_transport_dibs_.find(ack_key); + + // DCHECK_NE does not work with base::hash_map. + DCHECK(iterator != old_transport_dibs_.end()); + + // Now that the ACK has been received, the TransportDIB that was used + // prior to the UpdateGeometry message now being acknowledged is known to + // be no longer needed. Release it, and take the stale entry out of the map. + ReleaseTransportDIB(iterator->second.get()); + + old_transport_dibs_.erase(iterator); +} + +void WebPluginDelegateProxy::OnAcceleratedSurfaceSetIOSurface( + gfx::PluginWindowHandle window, + int32 width, + int32 height, + uint64 io_surface_identifier) { + if (render_view_) + render_view_->AcceleratedSurfaceSetIOSurface(window, width, height, + io_surface_identifier); +} + +void WebPluginDelegateProxy::OnAcceleratedSurfaceSetTransportDIB( + gfx::PluginWindowHandle window, + int32 width, + int32 height, + TransportDIB::Handle transport_dib) { + if (render_view_) + render_view_->AcceleratedSurfaceSetTransportDIB(window, width, height, + transport_dib); +} + +void WebPluginDelegateProxy::OnAcceleratedSurfaceAllocTransportDIB( + size_t size, + TransportDIB::Handle* dib_handle) { + if (render_view_) + *dib_handle = render_view_->AcceleratedSurfaceAllocTransportDIB(size); + else + *dib_handle = TransportDIB::DefaultHandleValue(); +} + +void WebPluginDelegateProxy::OnAcceleratedSurfaceFreeTransportDIB( + TransportDIB::Id dib_id) { + if (render_view_) + render_view_->AcceleratedSurfaceFreeTransportDIB(dib_id); +} + +void WebPluginDelegateProxy::OnAcceleratedSurfaceBuffersSwapped( + gfx::PluginWindowHandle window, uint64 surface_id) { + if (render_view_) + render_view_->AcceleratedSurfaceBuffersSwapped(window, surface_id); +} +#endif + +#if defined(OS_WIN) +bool WebPluginDelegateProxy::UseSynchronousGeometryUpdates() { + // Need to update geometry synchronously with WMP, otherwise if a site + // scripts the plugin to start playing while it's in the middle of handling + // an update geometry message, videos don't play. See urls in bug 20260. + if (info_.name.find(ASCIIToUTF16("Windows Media Player")) != string16::npos) + return true; + + // The move networks plugin needs to be informed of geometry updates + // synchronously. + std::vector<webkit::npapi::WebPluginMimeType>::iterator index; + for (index = info_.mime_types.begin(); index != info_.mime_types.end(); + index++) { + if (index->mime_type == "application/x-vnd.moveplayer.qm" || + index->mime_type == "application/x-vnd.moveplay2.qm" || + index->mime_type == "application/x-vnd.movenetworks.qm" || + index->mime_type == "application/x-vnd.mnplayer.qm") { + return true; + } + } + return false; +} +#endif + +void WebPluginDelegateProxy::OnURLRedirectResponse(bool allow, + int resource_id) { + if (!plugin_) + return; + + plugin_->URLRedirectResponse(allow, resource_id); +} diff --git a/content/renderer/webplugin_delegate_proxy.h b/content/renderer/webplugin_delegate_proxy.h new file mode 100644 index 0000000..f495b89 --- /dev/null +++ b/content/renderer/webplugin_delegate_proxy.h @@ -0,0 +1,275 @@ +// 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 CONTENT_RENDERER_WEBPLUGIN_DELEGATE_PROXY_H_ +#define CONTENT_RENDERER_WEBPLUGIN_DELEGATE_PROXY_H_ +#pragma once + +#include <string> +#include <vector> + +#include "app/surface/transport_dib.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/weak_ptr.h" +#include "googleurl/src/gurl.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_message.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/rect.h" +#include "webkit/plugins/npapi/webplugininfo.h" +#include "webkit/plugins/npapi/webplugin_delegate.h" + +#if defined(OS_MACOSX) +#include "base/hash_tables.h" +#include "base/linked_ptr.h" +#endif + +struct NPObject; +class NPObjectStub; +struct NPVariant_Param; +class PluginChannelHost; +struct PluginHostMsg_URLRequest_Params; +class RenderView; +class SkBitmap; + +namespace base { +class SharedMemory; +class WaitableEvent; +} + +namespace skia { +class PlatformCanvas; +} + +namespace webkit { +namespace npapi { +class WebPlugin; +} +} + +// An implementation of WebPluginDelegate that proxies all calls to +// the plugin process. +class WebPluginDelegateProxy + : public webkit::npapi::WebPluginDelegate, + public IPC::Channel::Listener, + public IPC::Message::Sender, + public base::SupportsWeakPtr<WebPluginDelegateProxy> { + public: + WebPluginDelegateProxy(const std::string& mime_type, + const base::WeakPtr<RenderView>& render_view); + + // WebPluginDelegate implementation: + virtual void PluginDestroyed(); + virtual bool Initialize(const GURL& url, + const std::vector<std::string>& arg_names, + const std::vector<std::string>& arg_values, + webkit::npapi::WebPlugin* plugin, + bool load_manually); + virtual void UpdateGeometry(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect); + virtual void Paint(WebKit::WebCanvas* canvas, const gfx::Rect& rect); + virtual void Print(gfx::NativeDrawingContext context); + virtual NPObject* GetPluginScriptableObject(); + virtual void DidFinishLoadWithReason(const GURL& url, NPReason reason, + int notify_id); + virtual void SetFocus(bool focused); + virtual bool HandleInputEvent(const WebKit::WebInputEvent& event, + WebKit::WebCursorInfo* cursor); + virtual int GetProcessId(); + + // Informs the plugin that its containing content view has gained or lost + // first responder status. + virtual void SetContentAreaFocus(bool has_focus); +#if defined(OS_MACOSX) + // Informs the plugin that its enclosing window has gained or lost focus. + virtual void SetWindowFocus(bool window_has_focus); + // Informs the plugin that its container (window/tab) has changed visibility. + virtual void SetContainerVisibility(bool is_visible); + // Informs the plugin that its enclosing window's frame has changed. + virtual void WindowFrameChanged(gfx::Rect window_frame, gfx::Rect view_frame); + // Informs the plugin that plugin IME has completed. + // If |text| is empty, composition was cancelled. + virtual void ImeCompositionCompleted(const string16& text, int plugin_id); +#endif + + // IPC::Channel::Listener implementation: + virtual bool OnMessageReceived(const IPC::Message& msg); + virtual void OnChannelError(); + + // IPC::Message::Sender implementation: + virtual bool Send(IPC::Message* msg); + + virtual void SendJavaScriptStream(const GURL& url, + const std::string& result, + bool success, + int notify_id); + + virtual void DidReceiveManualResponse(const GURL& url, + const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified); + virtual void DidReceiveManualData(const char* buffer, int length); + virtual void DidFinishManualLoading(); + virtual void DidManualLoadFail(); + virtual void InstallMissingPlugin(); + virtual webkit::npapi::WebPluginResourceClient* CreateResourceClient( + unsigned long resource_id, const GURL& url, int notify_id); + virtual webkit::npapi::WebPluginResourceClient* CreateSeekableResourceClient( + unsigned long resource_id, int range_request_id); + + gfx::PluginWindowHandle GetPluginWindowHandle(); + + protected: + template<class WebPluginDelegateProxy> friend class DeleteTask; + ~WebPluginDelegateProxy(); + + private: + // Message handlers for messages that proxy WebPlugin methods, which + // we translate into calls to the real WebPlugin. + void OnSetWindow(gfx::PluginWindowHandle window); +#if defined(OS_WIN) + void OnSetWindowlessPumpEvent(HANDLE modal_loop_pump_messages_event); +#endif + void OnCompleteURL(const std::string& url_in, std::string* url_out, + bool* result); + void OnHandleURLRequest(const PluginHostMsg_URLRequest_Params& params); + void OnCancelResource(int id); + void OnInvalidateRect(const gfx::Rect& rect); + void OnGetWindowScriptNPObject(int route_id, bool* success); + void OnGetPluginElement(int route_id, bool* success); + void OnSetCookie(const GURL& url, + const GURL& first_party_for_cookies, + const std::string& cookie); + void OnGetCookies(const GURL& url, const GURL& first_party_for_cookies, + std::string* cookies); + void OnMissingPluginStatus(int status); + void OnCancelDocumentLoad(); + void OnInitiateHTTPRangeRequest(const std::string& url, + const std::string& range_info, + int range_request_id); + void OnDeferResourceLoading(unsigned long resource_id, bool defer); + +#if defined(OS_MACOSX) + void OnFocusChanged(bool focused); + void OnStartIme(); + void OnBindFakePluginWindowHandle(bool opaque); + void OnUpdateGeometry_ACK(int ack_key); + void OnAcceleratedSurfaceSetIOSurface(gfx::PluginWindowHandle window, + int32 width, + int32 height, + uint64 io_surface_identifier); + void OnAcceleratedSurfaceSetTransportDIB(gfx::PluginWindowHandle window, + int32 width, + int32 height, + TransportDIB::Handle transport_dib); + void OnAcceleratedSurfaceAllocTransportDIB(size_t size, + TransportDIB::Handle* dib_handle); + void OnAcceleratedSurfaceFreeTransportDIB(TransportDIB::Id dib_id); + void OnAcceleratedSurfaceBuffersSwapped(gfx::PluginWindowHandle window, + uint64 surface_id); +#endif + + void OnURLRedirectResponse(bool allow, int resource_id); + + // Draw a graphic indicating a crashed plugin. + void PaintSadPlugin(WebKit::WebCanvas* canvas, const gfx::Rect& rect); + + // Returns true if the given rectangle is different in the native drawing + // context and the current background bitmap. + bool BackgroundChanged(gfx::NativeDrawingContext context, + const gfx::Rect& rect); + + // Copies the given rectangle from the transport bitmap to the backing store. + void CopyFromTransportToBacking(const gfx::Rect& rect); + + // Clears the shared memory section and canvases used for windowless plugins. + void ResetWindowlessBitmaps(); + +#if !defined(OS_WIN) + // Creates a process-local memory section and canvas. PlatformCanvas on + // Windows only works with a DIB, not arbitrary memory. + bool CreateLocalBitmap(std::vector<uint8>* memory, + scoped_ptr<skia::PlatformCanvas>* canvas); +#endif + + // Creates a shared memory section and canvas. + bool CreateSharedBitmap(scoped_ptr<TransportDIB>* memory, + scoped_ptr<skia::PlatformCanvas>* canvas); + + // Called for cleanup during plugin destruction. Normally right before the + // plugin window gets destroyed, or when the plugin has crashed (at which + // point the window has already been destroyed). + void WillDestroyWindow(); + +#if defined(OS_MACOSX) + // Synthesize a fake window handle for the plug-in to identify the instance + // to the browser, allowing mapping to a surface for hardware acceleration + // of plug-in content. The browser generates the handle which is then set on + // the plug-in. Returns true if it successfully sets the window handle on the + // plug-in. + bool BindFakePluginWindowHandle(bool opaque); + + typedef base::hash_map<int, linked_ptr<TransportDIB> > OldTransportDIBMap; + + OldTransportDIBMap old_transport_dibs_; +#endif // OS_MACOSX + +#if defined(OS_WIN) + // Returns true if we should update the plugin geometry synchronously. + bool UseSynchronousGeometryUpdates(); +#endif + + base::WeakPtr<RenderView> render_view_; + webkit::npapi::WebPlugin* plugin_; + bool uses_shared_bitmaps_; + gfx::PluginWindowHandle window_; + scoped_refptr<PluginChannelHost> channel_host_; + std::string mime_type_; + int instance_id_; + webkit::npapi::WebPluginInfo info_; + + gfx::Rect plugin_rect_; + gfx::Rect clip_rect_; + + NPObject* npobject_; + base::WeakPtr<NPObjectStub> window_script_object_; + + // Event passed in by the plugin process and is used to decide if + // messages need to be pumped in the NPP_HandleEvent sync call. + scoped_ptr<base::WaitableEvent> modal_loop_pump_messages_event_; + + // Bitmap for crashed plugin + SkBitmap* sad_plugin_; + + // True if we got an invalidate from the plugin and are waiting for a paint. + bool invalidate_pending_; + + // Used to desynchronize windowless painting. When WebKit paints, we bitblt + // from our backing store of what the plugin rectangle looks like. The + // plugin paints into the transport store, and we copy that to our backing + // store when we get an invalidate from it. The background bitmap is used + // for transparent plugins, as they need the backgroud data during painting. + bool transparent_; +#if defined(OS_WIN) + scoped_ptr<TransportDIB> backing_store_; +#else + std::vector<uint8> backing_store_; +#endif + scoped_ptr<skia::PlatformCanvas> backing_store_canvas_; + scoped_ptr<TransportDIB> transport_store_; + scoped_ptr<skia::PlatformCanvas> transport_store_canvas_; + scoped_ptr<TransportDIB> background_store_; + scoped_ptr<skia::PlatformCanvas> background_store_canvas_; + // This lets us know which portion of the backing store has been painted into. + gfx::Rect backing_store_painted_; + + // The url of the main frame hosting the plugin. + GURL page_url_; + + DISALLOW_COPY_AND_ASSIGN(WebPluginDelegateProxy); +}; + +#endif // CONTENT_RENDERER_WEBPLUGIN_DELEGATE_PROXY_H_ diff --git a/content/renderer/websharedworker_proxy.cc b/content/renderer/websharedworker_proxy.cc new file mode 100644 index 0000000..4af93e5 --- /dev/null +++ b/content/renderer/websharedworker_proxy.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2009 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 "content/renderer/websharedworker_proxy.h" + +#include "chrome/common/render_messages.h" +#include "content/common/webmessageportchannel_impl.h" +#include "content/common/worker_messages.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURL.h" + +WebSharedWorkerProxy::WebSharedWorkerProxy(ChildThread* child_thread, + unsigned long long document_id, + bool exists, + int route_id, + int render_view_route_id) + : WebWorkerBase(child_thread, + document_id, + exists ? route_id : MSG_ROUTING_NONE, + render_view_route_id, + 0), + pending_route_id_(route_id), + connect_listener_(NULL) { +} + +bool WebSharedWorkerProxy::isStarted() { + return IsStarted(); +} + +void WebSharedWorkerProxy::startWorkerContext( + const WebKit::WebURL& script_url, + const WebKit::WebString& name, + const WebKit::WebString& user_agent, + const WebKit::WebString& source_code, + long long script_resource_appcache_id) { + DCHECK(!isStarted()); + CreateSharedWorkerContext(script_url, name, user_agent, source_code, + pending_route_id_, script_resource_appcache_id); +} + +void WebSharedWorkerProxy::terminateWorkerContext() { + // This API should only be invoked from worker context. + NOTREACHED(); +} + +void WebSharedWorkerProxy::clientDestroyed() { + // This API should only be invoked from worker context. + NOTREACHED(); +} + +void WebSharedWorkerProxy::connect(WebKit::WebMessagePortChannel* channel, + ConnectListener* listener) { + WebMessagePortChannelImpl* webchannel = + static_cast<WebMessagePortChannelImpl*>(channel); + + int message_port_id = webchannel->message_port_id(); + DCHECK(message_port_id != MSG_ROUTING_NONE); + webchannel->QueueMessages(); + + Send(new WorkerMsg_Connect(route_id_, message_port_id, MSG_ROUTING_NONE)); + if (HasQueuedMessages()) { + connect_listener_ = listener; + } else { + listener->connected(); + // The listener may free this object, so do not access the object after + // this point. + } +} + +bool WebSharedWorkerProxy::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(WebSharedWorkerProxy, message) + IPC_MESSAGE_HANDLER(ViewMsg_WorkerCreated, OnWorkerCreated) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void WebSharedWorkerProxy::OnWorkerCreated() { + // The worker is created - now send off the CreateWorkerContext message and + // any other queued messages + SendQueuedMessages(); + + // Inform any listener that the pending connect event has been sent + // (this can result in this object being freed). + if (connect_listener_) { + connect_listener_->connected(); + } +} diff --git a/content/renderer/websharedworker_proxy.h b/content/renderer/websharedworker_proxy.h new file mode 100644 index 0000000..acc8dca --- /dev/null +++ b/content/renderer/websharedworker_proxy.h @@ -0,0 +1,58 @@ +// Copyright (c) 2009 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 CONTENT_RENDERER_WEBSHAREDWORKER_PROXY_H_ +#define CONTENT_RENDERER_WEBSHAREDWORKER_PROXY_H_ +#pragma once + +#include "base/basictypes.h" +#include "content/renderer/webworker_base.h" +#include "googleurl/src/gurl.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSharedWorker.h" + +class ChildThread; + +// Implementation of the WebSharedWorker APIs. This object is intended to only +// live long enough to allow the caller to send a "connect" event to the worker +// thread. Once the connect event has been sent, all future communication will +// happen via the WebMessagePortChannel, and the WebSharedWorker instance will +// be freed. +class WebSharedWorkerProxy : public WebKit::WebSharedWorker, + private WebWorkerBase { + public: + // If the worker not loaded yet, route_id == MSG_ROUTING_NONE + WebSharedWorkerProxy(ChildThread* child_thread, + unsigned long long document_id, + bool exists, + int route_id, + int render_view_route_id); + + // Implementations of WebSharedWorker APIs + virtual bool isStarted(); + virtual void connect(WebKit::WebMessagePortChannel* channel, + ConnectListener* listener); + virtual void startWorkerContext(const WebKit::WebURL& script_url, + const WebKit::WebString& name, + const WebKit::WebString& user_agent, + const WebKit::WebString& source_code, + long long script_resource_appcache_id); + virtual void terminateWorkerContext(); + virtual void clientDestroyed(); + + // IPC::Channel::Listener implementation. + virtual bool OnMessageReceived(const IPC::Message& message); + + private: + void OnWorkerCreated(); + + // The id for the placeholder worker instance we've stored on the + // browser process (we need to pass this same route id back in when creating + // the worker). + int pending_route_id_; + ConnectListener* connect_listener_; + + DISALLOW_COPY_AND_ASSIGN(WebSharedWorkerProxy); +}; + +#endif // CONTENT_RENDERER_WEBSHAREDWORKER_PROXY_H_ diff --git a/content/renderer/websharedworkerrepository_impl.cc b/content/renderer/websharedworkerrepository_impl.cc new file mode 100644 index 0000000..76c9bf1 --- /dev/null +++ b/content/renderer/websharedworkerrepository_impl.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2009 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 "content/renderer/websharedworkerrepository_impl.h" + +#include "chrome/common/render_messages.h" +#include "chrome/renderer/render_thread.h" +#include "content/renderer/websharedworker_proxy.h" + +WebSharedWorkerRepositoryImpl::WebSharedWorkerRepositoryImpl() {} + +WebSharedWorkerRepositoryImpl::~WebSharedWorkerRepositoryImpl() {} + +void WebSharedWorkerRepositoryImpl::addSharedWorker( + WebKit::WebSharedWorker* worker, DocumentID document) { + shared_worker_parents_.insert(document); +} + +void WebSharedWorkerRepositoryImpl::documentDetached(DocumentID document) { + DocumentSet::iterator iter = shared_worker_parents_.find(document); + if (iter != shared_worker_parents_.end()) { + // Notify the browser process that the document has shut down. + ChildThread::current()->Send(new ViewHostMsg_DocumentDetached(document)); + shared_worker_parents_.erase(iter); + } +} + +bool WebSharedWorkerRepositoryImpl::hasSharedWorkers(DocumentID document) { + return shared_worker_parents_.find(document) != shared_worker_parents_.end(); +} diff --git a/content/renderer/websharedworkerrepository_impl.h b/content/renderer/websharedworkerrepository_impl.h new file mode 100644 index 0000000..8bd615c --- /dev/null +++ b/content/renderer/websharedworkerrepository_impl.h @@ -0,0 +1,35 @@ +// 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 CONTENT_RENDERER_WEBSHAREDWORKERREPOSITORY_IMPL_H_ +#define CONTENT_RENDERER_WEBSHAREDWORKERREPOSITORY_IMPL_H_ +#pragma once + +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSharedWorkerRepository.h" + +#include "base/hash_tables.h" + +namespace WebKit { +class WebSharedWorker; +} + +class WebSharedWorkerRepositoryImpl : public WebKit::WebSharedWorkerRepository { + public: + WebSharedWorkerRepositoryImpl(); + virtual ~WebSharedWorkerRepositoryImpl(); + + virtual void addSharedWorker(WebKit::WebSharedWorker*, DocumentID document); + virtual void documentDetached(DocumentID document); + + // Returns true if the document has created a SharedWorker (used by the + // WebKit code to determine if the document can be suspended). + virtual bool hasSharedWorkers(DocumentID document); + + private: + // The set of documents that have created a SharedWorker. + typedef base::hash_set<DocumentID> DocumentSet; + DocumentSet shared_worker_parents_; +}; + +#endif // CONTENT_RENDERER_WEBSHAREDWORKERREPOSITORY_IMPL_H_ diff --git a/content/renderer/webworker_base.cc b/content/renderer/webworker_base.cc new file mode 100644 index 0000000..0741e79 --- /dev/null +++ b/content/renderer/webworker_base.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2009 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 "content/renderer/webworker_base.h" + +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "content/common/child_thread.h" +#include "content/common/webmessageportchannel_impl.h" +#include "content/common/worker_messages.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebWorkerClient.h" + +using WebKit::WebMessagePortChannel; +using WebKit::WebMessagePortChannelArray; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebWorkerClient; + +WebWorkerBase::WebWorkerBase( + ChildThread* child_thread, + unsigned long long document_id, + int route_id, + int render_view_route_id, + int parent_appcache_host_id) + : route_id_(route_id), + render_view_route_id_(render_view_route_id), + child_thread_(child_thread), + document_id_(document_id), + parent_appcache_host_id_(parent_appcache_host_id) { + if (route_id_ != MSG_ROUTING_NONE) + child_thread_->AddRoute(route_id_, this); +} + +WebWorkerBase::~WebWorkerBase() { + Disconnect(); + + // Free up any unsent queued messages. + for (size_t i = 0; i < queued_messages_.size(); ++i) + delete queued_messages_[i]; +} + +void WebWorkerBase::Disconnect() { + if (route_id_ == MSG_ROUTING_NONE) + return; + + // So the messages from WorkerContext (like WorkerContextDestroyed) do not + // come after nobody is listening. Since Worker and WorkerContext can + // terminate independently, already sent messages may still be in the pipe. + child_thread_->RemoveRoute(route_id_); + + route_id_ = MSG_ROUTING_NONE; +} + +void WebWorkerBase::CreateWorkerContext(const GURL& script_url, + bool is_shared, + const string16& name, + const string16& user_agent, + const string16& source_code, + int pending_route_id, + int64 script_resource_appcache_id) { + DCHECK(route_id_ == MSG_ROUTING_NONE); + ViewHostMsg_CreateWorker_Params params; + params.url = script_url; + params.is_shared = is_shared; + params.name = name; + params.document_id = document_id_; + params.render_view_route_id = render_view_route_id_; + params.route_id = pending_route_id; + params.parent_appcache_host_id = parent_appcache_host_id_; + params.script_resource_appcache_id = script_resource_appcache_id; + IPC::Message* create_message = new ViewHostMsg_CreateWorker( + params, &route_id_); + child_thread_->Send(create_message); + if (route_id_ == MSG_ROUTING_NONE) + return; + + child_thread_->AddRoute(route_id_, this); + + // We make sure that the start message is the first, since postMessage or + // connect might have already been called. + queued_messages_.insert(queued_messages_.begin(), + new WorkerMsg_StartWorkerContext( + route_id_, script_url, user_agent, source_code)); +} + +bool WebWorkerBase::IsStarted() { + // Worker is started if we have a route ID and there are no queued messages + // (meaning we've sent the WorkerMsg_StartWorkerContext already). + return (route_id_ != MSG_ROUTING_NONE && queued_messages_.empty()); +} + +bool WebWorkerBase::Send(IPC::Message* message) { + // It's possible that messages will be sent before the worker is created, in + // which case route_id_ will be none. Or the worker object can be interacted + // with before the browser process told us that it started, in which case we + // also want to queue the message. + if (!IsStarted()) { + queued_messages_.push_back(message); + return true; + } + + // For now we proxy all messages to the worker process through the browser. + // Revisit if we find this slow. + // TODO(jabdelmalek): handle sync messages if we need them. + IPC::Message* wrapped_msg = new ViewHostMsg_ForwardToWorker(*message); + delete message; + return child_thread_->Send(wrapped_msg); +} + +void WebWorkerBase::SendQueuedMessages() { + DCHECK(queued_messages_.size()); + std::vector<IPC::Message*> queued_messages = queued_messages_; + queued_messages_.clear(); + for (size_t i = 0; i < queued_messages.size(); ++i) { + queued_messages[i]->set_routing_id(route_id_); + Send(queued_messages[i]); + } +} diff --git a/content/renderer/webworker_base.h b/content/renderer/webworker_base.h new file mode 100644 index 0000000..7ea07b3 --- /dev/null +++ b/content/renderer/webworker_base.h @@ -0,0 +1,99 @@ +// Copyright (c) 2009 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 CONTENT_RENDERER_WEBWORKER_BASE_H_ +#define CONTENT_RENDERER_WEBWORKER_BASE_H_ +#pragma once + +#include <vector> + +#include "base/basictypes.h" +#include "ipc/ipc_channel.h" + +class ChildThread; +class GURL; + +// WebWorkerBase is the common base class used by both WebWorkerProxy and +// WebSharedWorker. It contains logic to support starting up both dedicated +// and shared workers, and handling message queueing while waiting for the +// worker process to start. +class WebWorkerBase : public IPC::Channel::Listener { + public: + virtual ~WebWorkerBase(); + + // Creates and initializes a new dedicated worker context. + void CreateDedicatedWorkerContext(const GURL& script_url, + const string16& user_agent, + const string16& source_code) { + CreateWorkerContext(script_url, false, string16(), user_agent, + source_code, MSG_ROUTING_NONE, 0); + } + + // Creates and initializes a new shared worker context. + void CreateSharedWorkerContext(const GURL& script_url, + const string16& name, + const string16& user_agent, + const string16& source_code, + int pending_route_id, + int64 script_resource_appcache_id) { + CreateWorkerContext(script_url, true, name, user_agent, + source_code, pending_route_id, + script_resource_appcache_id); + } + + // Returns true if the worker is running (can send messages to it). + bool IsStarted(); + + // Disconnects the worker (stops listening for incoming messages). + void Disconnect(); + + // Sends a message to the worker thread (forwarded via the RenderViewHost). + // If WorkerStarted() has not yet been called, message is queued. + bool Send(IPC::Message*); + + // Returns true if there are queued messages. + bool HasQueuedMessages() { return !queued_messages_.empty(); } + + // Sends any messages currently in the queue. + void SendQueuedMessages(); + + protected: + WebWorkerBase(ChildThread* child_thread, + unsigned long long document_id, + int route_id, + int render_view_route_id, + int parent_appcache_host_id); + + // Routing id associated with this worker - used to receive messages from the + // worker, and also to route messages to the worker (WorkerService contains + // a map that maps between these renderer-side route IDs and worker-side + // routing ids). + int route_id_; + + // The routing id for the RenderView that created this worker. + int render_view_route_id_; + + ChildThread* child_thread_; + + private: + void CreateWorkerContext(const GURL& script_url, + bool is_shared, + const string16& name, + const string16& user_agent, + const string16& source_code, + int pending_route_id, + int64 script_resource_appcache_id); + + // ID of our parent document (used to shutdown workers when the parent + // document is detached). + unsigned long long document_id_; + + // ID of our parent's appcache host, only valid for dedicated workers. + int parent_appcache_host_id_; + + // Stores messages that were sent before the StartWorkerContext message. + std::vector<IPC::Message*> queued_messages_; +}; + +#endif // CONTENT_RENDERER_WEBWORKER_BASE_H_ diff --git a/content/renderer/webworker_proxy.cc b/content/renderer/webworker_proxy.cc new file mode 100644 index 0000000..53f97df --- /dev/null +++ b/content/renderer/webworker_proxy.cc @@ -0,0 +1,140 @@ +// Copyright (c) 2009 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 "content/renderer/webworker_proxy.h" + +#include "chrome/common/render_messages.h" +#include "content/common/child_thread.h" +#include "content/common/webmessageportchannel_impl.h" +#include "content/common/worker_messages.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebWorkerClient.h" + +using WebKit::WebCommonWorkerClient; +using WebKit::WebMessagePortChannel; +using WebKit::WebMessagePortChannelArray; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebWorkerClient; + +WebWorkerProxy::WebWorkerProxy( + WebWorkerClient* client, + ChildThread* child_thread, + int render_view_route_id, + int parent_appcache_host_id) + : WebWorkerBase(child_thread, 0, MSG_ROUTING_NONE, render_view_route_id, + parent_appcache_host_id), + client_(client) { + // TODO(atwilson): Change to pass in a real document_id when we support nested + // workers. +} + +WebWorkerProxy::~WebWorkerProxy() { + // If we're midway through starting a worker, cancel it. + CancelCreation(); +} + +void WebWorkerProxy::CancelCreation() { + if (route_id_ == MSG_ROUTING_NONE) + return; + + // Tell the browser to not start our queued worker. + if (!IsStarted()) + child_thread_->Send(new ViewHostMsg_CancelCreateDedicatedWorker(route_id_)); +} + +void WebWorkerProxy::startWorkerContext( + const WebURL& script_url, + const WebString& user_agent, + const WebString& source_code) { + CreateDedicatedWorkerContext(script_url, user_agent, source_code); +} + +void WebWorkerProxy::terminateWorkerContext() { + if (route_id_ != MSG_ROUTING_NONE) { + Send(new WorkerMsg_TerminateWorkerContext(route_id_)); + CancelCreation(); + Disconnect(); + } +} + +void WebWorkerProxy::postMessageToWorkerContext( + const WebString& message, const WebMessagePortChannelArray& channels) { + std::vector<int> message_port_ids(channels.size()); + std::vector<int> routing_ids(channels.size()); + for (size_t i = 0; i < channels.size(); ++i) { + WebMessagePortChannelImpl* webchannel = + static_cast<WebMessagePortChannelImpl*>(channels[i]); + message_port_ids[i] = webchannel->message_port_id(); + webchannel->QueueMessages(); + routing_ids[i] = MSG_ROUTING_NONE; + DCHECK(message_port_ids[i] != MSG_ROUTING_NONE); + } + + Send(new WorkerMsg_PostMessage( + route_id_, message, message_port_ids, routing_ids)); +} + +void WebWorkerProxy::workerObjectDestroyed() { + Send(new WorkerMsg_WorkerObjectDestroyed(route_id_)); + delete this; +} + +void WebWorkerProxy::clientDestroyed() { +} + +bool WebWorkerProxy::OnMessageReceived(const IPC::Message& message) { + if (!client_) + return false; + + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(WebWorkerProxy, message) + IPC_MESSAGE_HANDLER(ViewMsg_WorkerCreated, OnWorkerCreated) + IPC_MESSAGE_HANDLER(WorkerMsg_PostMessage, OnPostMessage) + IPC_MESSAGE_FORWARD(WorkerHostMsg_PostExceptionToWorkerObject, + client_, + WebWorkerClient::postExceptionToWorkerObject) + IPC_MESSAGE_HANDLER(WorkerHostMsg_PostConsoleMessageToWorkerObject, + OnPostConsoleMessageToWorkerObject) + IPC_MESSAGE_FORWARD(WorkerHostMsg_ConfirmMessageFromWorkerObject, + client_, + WebWorkerClient::confirmMessageFromWorkerObject) + IPC_MESSAGE_FORWARD(WorkerHostMsg_ReportPendingActivity, + client_, + WebWorkerClient::reportPendingActivity) + IPC_MESSAGE_FORWARD(WorkerHostMsg_WorkerContextDestroyed, + static_cast<WebCommonWorkerClient*>(client_), + WebCommonWorkerClient::workerContextDestroyed) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void WebWorkerProxy::OnWorkerCreated() { + // The worker is created - now send off the CreateWorkerContext message and + // any other queued messages + SendQueuedMessages(); +} + +void WebWorkerProxy::OnPostMessage( + const string16& message, + const std::vector<int>& sent_message_port_ids, + const std::vector<int>& new_routing_ids) { + DCHECK(new_routing_ids.size() == sent_message_port_ids.size()); + WebMessagePortChannelArray channels(sent_message_port_ids.size()); + for (size_t i = 0; i < sent_message_port_ids.size(); ++i) { + channels[i] = new WebMessagePortChannelImpl( + new_routing_ids[i], sent_message_port_ids[i]); + } + + client_->postMessageToWorkerObject(message, channels); +} + +void WebWorkerProxy::OnPostConsoleMessageToWorkerObject( + const WorkerHostMsg_PostConsoleMessageToWorkerObject_Params& params) { + client_->postConsoleMessageToWorkerObject(params.source_identifier, + params.message_type, params.message_level, + params.message, params.line_number, params.source_url); +} + diff --git a/content/renderer/webworker_proxy.h b/content/renderer/webworker_proxy.h new file mode 100644 index 0000000..9151193 --- /dev/null +++ b/content/renderer/webworker_proxy.h @@ -0,0 +1,66 @@ +// Copyright (c) 2009 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 CONTENT_RENDERER_WEBWORKER_PROXY_H_ +#define CONTENT_RENDERER_WEBWORKER_PROXY_H_ +#pragma once + +#include <vector> + +#include "base/basictypes.h" +#include "content/renderer/webworker_base.h" +#include "ipc/ipc_channel.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebWorker.h" + +class ChildThread; +class GURL; +class RenderView; +struct WorkerHostMsg_PostConsoleMessageToWorkerObject_Params; + +// This class provides an implementation of WebWorker that the renderer provides +// to the glue. This class converts function calls to IPC messages that are +// dispatched in the worker process by WebWorkerClientProxy. It also receives +// IPC messages from WebWorkerClientProxy which it converts to function calls to +// WebWorkerClient. +class WebWorkerProxy : public WebKit::WebWorker, private WebWorkerBase { + public: + WebWorkerProxy(WebKit::WebWorkerClient* client, + ChildThread* child_thread, + int render_view_route_id, + int parent_appcache_host_id); + ~WebWorkerProxy(); + + // WebWorker implementation. + virtual void startWorkerContext(const WebKit::WebURL& script_url, + const WebKit::WebString& user_agent, + const WebKit::WebString& source_code); + virtual void terminateWorkerContext(); + virtual void postMessageToWorkerContext( + const WebKit::WebString& message, + const WebKit::WebMessagePortChannelArray& channel_array); + virtual void workerObjectDestroyed(); + virtual void clientDestroyed(); + + // IPC::Channel::Listener implementation. + virtual bool OnMessageReceived(const IPC::Message& message); + + private: + void CancelCreation(); + void OnWorkerCreated(); + void OnWorkerContextDestroyed(); + void OnPostMessage(const string16& message, + const std::vector<int>& sent_message_port_ids, + const std::vector<int>& new_routing_ids); + void OnPostConsoleMessageToWorkerObject( + const WorkerHostMsg_PostConsoleMessageToWorkerObject_Params& params); + + + // Used to communicate to the WebCore::Worker object in response to IPC + // messages. + WebKit::WebWorkerClient* client_; + + DISALLOW_COPY_AND_ASSIGN(WebWorkerProxy); +}; + +#endif // CONTENT_RENDERER_WEBWORKER_PROXY_H_ |