summaryrefslogtreecommitdiffstats
path: root/ppapi/proxy
diff options
context:
space:
mode:
authortoyoshim@chromium.org <toyoshim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-09 03:43:48 +0000
committertoyoshim@chromium.org <toyoshim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-09 03:43:48 +0000
commit9d5eadf3af4cdd5cb2fd9bce44b87532e919ec82 (patch)
treed441385646386a78ec7adf995b6965081c7bfbd6 /ppapi/proxy
parent7f06df0de9f7d7ee95d8205be4cff7a7617813e7 (diff)
downloadchromium_src-9d5eadf3af4cdd5cb2fd9bce44b87532e919ec82.zip
chromium_src-9d5eadf3af4cdd5cb2fd9bce44b87532e919ec82.tar.gz
chromium_src-9d5eadf3af4cdd5cb2fd9bce44b87532e919ec82.tar.bz2
Pepper WebSocket API: Implement new design Chrome IPC.
This change implements new Chrome IPC for PPB_WebSocket. After this change, all mode including out of process will work with new design. It doesn't depend on old SRPC design any more. BUG=87310,116317 Review URL: https://chromiumcodereview.appspot.com/10944005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@160783 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ppapi/proxy')
-rw-r--r--ppapi/proxy/plugin_dispatcher.cc3
-rw-r--r--ppapi/proxy/ppapi_messages.h80
-rw-r--r--ppapi/proxy/resource_creation_proxy.cc10
-rw-r--r--ppapi/proxy/resource_creation_proxy.h2
-rw-r--r--ppapi/proxy/websocket_resource.cc509
-rw-r--r--ppapi/proxy/websocket_resource.h157
-rw-r--r--ppapi/proxy/websocket_resource_unittest.cc168
7 files changed, 922 insertions, 7 deletions
diff --git a/ppapi/proxy/plugin_dispatcher.cc b/ppapi/proxy/plugin_dispatcher.cc
index 51a8ff2..51272fd 100644
--- a/ppapi/proxy/plugin_dispatcher.cc
+++ b/ppapi/proxy/plugin_dispatcher.cc
@@ -272,7 +272,8 @@ void PluginDispatcher::DispatchResourceReply(
Resource* resource = PpapiGlobals::Get()->GetResourceTracker()->GetResource(
reply_params.pp_resource());
if (!resource) {
- NOTREACHED();
+ if (reply_params.sequence())
+ NOTREACHED();
return;
}
resource->OnReplyReceived(reply_params, nested_msg);
diff --git a/ppapi/proxy/ppapi_messages.h b/ppapi/proxy/ppapi_messages.h
index 8ac0886..3e9dca2 100644
--- a/ppapi/proxy/ppapi_messages.h
+++ b/ppapi/proxy/ppapi_messages.h
@@ -1582,3 +1582,83 @@ IPC_MESSAGE_CONTROL0(PpapiHostMsg_Printing_Create)
IPC_MESSAGE_CONTROL0(PpapiHostMsg_Printing_GetDefaultPrintSettings)
IPC_MESSAGE_CONTROL1(PpapiPluginMsg_Printing_GetDefaultPrintSettingsReply,
PP_PrintSettings_Dev /* print_settings */)
+
+// WebSocket ------------------------------------------------------------------
+
+IPC_MESSAGE_CONTROL0(PpapiHostMsg_WebSocket_Create)
+
+// Establishes the connection to a server. This message requires
+// WebSocket_ConnectReply as a reply message.
+IPC_MESSAGE_CONTROL2(PpapiHostMsg_WebSocket_Connect,
+ std::string /* url */,
+ std::vector<std::string> /* protocols */)
+
+// Closes established connection with graceful closing handshake. This message
+// requires WebSocket_CloseReply as a reply message.
+IPC_MESSAGE_CONTROL2(PpapiHostMsg_WebSocket_Close,
+ int32_t /* code */,
+ std::string /* reason */)
+
+// Sends a text frame to the server. No reply is defined.
+IPC_MESSAGE_CONTROL1(PpapiHostMsg_WebSocket_SendText,
+ std::string /* message */)
+
+// Sends a binary frame to the server. No reply is defined.
+IPC_MESSAGE_CONTROL1(PpapiHostMsg_WebSocket_SendBinary,
+ std::vector<uint8_t> /* message */)
+
+// Fails the connection. This message invokes RFC6455 defined
+// _Fail the WebSocket Connection_ operation. No reply is defined.
+IPC_MESSAGE_CONTROL1(PpapiHostMsg_WebSocket_Fail,
+ std::string /* message */)
+
+// This message is a reply to WebSocket_Connect. If the |url| and |protocols|
+// are invalid, WebSocket_ConnectReply is issued immediately and it contains
+// proper error code in its result. Otherwise, WebSocket_ConnectReply is sent
+// with valid |url|, |protocol|, and result PP_OK. |protocol| is not a passed
+// |protocols|, but a result of opening handshake negotiation. If the
+// connection can not be established successfully, WebSocket_ConnectReply is
+// not issued, but WebSocket_ClosedReply is sent instead.
+IPC_MESSAGE_CONTROL2(PpapiPluginMsg_WebSocket_ConnectReply,
+ std::string /* url */,
+ std::string /* protocol */)
+
+// This message is a reply to WebSocket_Close. If the operation fails,
+// WebSocket_CloseReply is issued immediately and it contains PP_ERROR_FAILED.
+// Otherwise, CloseReply will be issued after the closing handshake is
+// finished. All arguments will be valid iff the result is PP_OK and it means
+// that the client initiated closing handshake is finished gracefully.
+IPC_MESSAGE_CONTROL4(PpapiPluginMsg_WebSocket_CloseReply,
+ unsigned long /* buffered_amount */,
+ bool /* was_clean */,
+ unsigned short /* code */,
+ std::string /* reason */)
+
+// Unsolicited reply message to transmit a receiving text frame.
+IPC_MESSAGE_CONTROL1(PpapiPluginMsg_WebSocket_ReceiveTextReply,
+ std::string /* message */)
+
+// Unsolicited reply message to transmit a receiving binary frame.
+IPC_MESSAGE_CONTROL1(PpapiPluginMsg_WebSocket_ReceiveBinaryReply,
+ std::vector<uint8_t> /* message */)
+
+// Unsolicited reply message to notify a error on underlying network connetion.
+IPC_MESSAGE_CONTROL0(PpapiPluginMsg_WebSocket_ErrorReply)
+
+// Unsolicited reply message to update the buffered amount value.
+IPC_MESSAGE_CONTROL1(PpapiPluginMsg_WebSocket_BufferedAmountReply,
+ unsigned long /* buffered_amount */)
+
+// Unsolicited reply message to update |state| because of incoming external
+// events, e.g., protocol error, or unexpected network closure.
+IPC_MESSAGE_CONTROL1(PpapiPluginMsg_WebSocket_StateReply,
+ int32_t /* state */)
+
+// Unsolicited reply message to notify that the connection is closed without
+// any WebSocket_Close request. Server initiated closing handshake or
+// unexpected network errors will invoke this message.
+IPC_MESSAGE_CONTROL4(PpapiPluginMsg_WebSocket_ClosedReply,
+ unsigned long /* buffered_amount */,
+ bool /* was_clean */,
+ unsigned short /* code */,
+ std::string /* reason */)
diff --git a/ppapi/proxy/resource_creation_proxy.cc b/ppapi/proxy/resource_creation_proxy.cc
index 373b9d7..78123a6 100644
--- a/ppapi/proxy/resource_creation_proxy.cc
+++ b/ppapi/proxy/resource_creation_proxy.cc
@@ -39,6 +39,7 @@
#include "ppapi/proxy/ppb_x509_certificate_private_proxy.h"
#include "ppapi/proxy/printing_resource.h"
#include "ppapi/proxy/url_request_info_resource.h"
+#include "ppapi/proxy/websocket_resource.h"
#include "ppapi/shared_impl/api_id.h"
#include "ppapi/shared_impl/host_resource.h"
#include "ppapi/shared_impl/ppb_audio_config_shared.h"
@@ -256,6 +257,10 @@ PP_Resource ResourceCreationProxy::CreateUDPSocketPrivate(
return PPB_UDPSocket_Private_Proxy::CreateProxyResource(instance);
}
+PP_Resource ResourceCreationProxy::CreateWebSocket(PP_Instance instance) {
+ return (new WebSocketResource(GetConnection(), instance))->GetReference();
+}
+
PP_Resource ResourceCreationProxy::CreateX509CertificatePrivate(
PP_Instance instance) {
return PPB_X509Certificate_Private_Proxy::CreateProxyResource(instance);
@@ -351,11 +356,6 @@ PP_Resource ResourceCreationProxy::CreateVideoDecoder(
instance, context3d_id, profile);
}
-PP_Resource ResourceCreationProxy::CreateWebSocket(PP_Instance instance) {
- NOTIMPLEMENTED();
- return 0;
-}
-
#endif // !defined(OS_NACL)
bool ResourceCreationProxy::Send(IPC::Message* msg) {
diff --git a/ppapi/proxy/resource_creation_proxy.h b/ppapi/proxy/resource_creation_proxy.h
index 7ab07e0..4860cbf 100644
--- a/ppapi/proxy/resource_creation_proxy.h
+++ b/ppapi/proxy/resource_creation_proxy.h
@@ -122,6 +122,7 @@ class ResourceCreationProxy : public InterfaceProxy,
PP_Instance instance) OVERRIDE;
virtual PP_Resource CreateTCPSocketPrivate(PP_Instance instance) OVERRIDE;
virtual PP_Resource CreateUDPSocketPrivate(PP_Instance instance) OVERRIDE;
+ virtual PP_Resource CreateWebSocket(PP_Instance instance) OVERRIDE;
virtual PP_Resource CreateX509CertificatePrivate(
PP_Instance instance) OVERRIDE;
#if !defined(OS_NACL)
@@ -155,7 +156,6 @@ class ResourceCreationProxy : public InterfaceProxy,
PP_Instance instance,
PP_Resource context3d_id,
PP_VideoDecoder_Profile profile) OVERRIDE;
- virtual PP_Resource CreateWebSocket(PP_Instance instance) OVERRIDE;
#endif // !defined(OS_NACL)
virtual bool Send(IPC::Message* msg) OVERRIDE;
diff --git a/ppapi/proxy/websocket_resource.cc b/ppapi/proxy/websocket_resource.cc
new file mode 100644
index 0000000..de9ee2b
--- /dev/null
+++ b/ppapi/proxy/websocket_resource.cc
@@ -0,0 +1,509 @@
+// Copyright (c) 2012 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 "ppapi/proxy/websocket_resource.h"
+
+#include <set>
+#include <vector>
+
+#include "base/bind.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/ppapi_globals.h"
+#include "ppapi/shared_impl/var.h"
+#include "ppapi/shared_impl/var_tracker.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebSocket.h"
+
+namespace {
+
+const uint32_t kMaxReasonSizeInBytes = 123;
+const size_t kBaseFramingOverhead = 2;
+const size_t kMaskingKeyLength = 4;
+const size_t kMinimumPayloadSizeWithTwoByteExtendedPayloadLength = 126;
+const size_t kMinimumPayloadSizeWithEightByteExtendedPayloadLength = 0x10000;
+
+uint64_t SaturateAdd(uint64_t a, uint64_t b) {
+ if (kuint64max - a < b)
+ return kuint64max;
+ return a + b;
+}
+
+uint64_t GetFrameSize(uint64_t payload_size) {
+ uint64_t overhead = kBaseFramingOverhead + kMaskingKeyLength;
+ if (payload_size > kMinimumPayloadSizeWithEightByteExtendedPayloadLength)
+ overhead += 8;
+ else if (payload_size > kMinimumPayloadSizeWithTwoByteExtendedPayloadLength)
+ overhead += 2;
+ return SaturateAdd(payload_size, overhead);
+}
+
+bool InValidStateToReceive(PP_WebSocketReadyState state) {
+ return state == PP_WEBSOCKETREADYSTATE_OPEN ||
+ state == PP_WEBSOCKETREADYSTATE_CLOSING;
+}
+
+} // namespace
+
+
+namespace ppapi {
+namespace proxy {
+
+WebSocketResource::WebSocketResource(Connection connection,
+ PP_Instance instance)
+ : PluginResource(connection, instance),
+ state_(PP_WEBSOCKETREADYSTATE_INVALID),
+ error_was_received_(false),
+ receive_callback_var_(NULL),
+ empty_string_(new StringVar(std::string())),
+ close_code_(0),
+ close_reason_(NULL),
+ close_was_clean_(PP_FALSE),
+ extensions_(NULL),
+ protocol_(NULL),
+ url_(NULL),
+ buffered_amount_(0),
+ buffered_amount_after_close_(0) {
+}
+
+WebSocketResource::~WebSocketResource() {
+}
+
+thunk::PPB_WebSocket_API* WebSocketResource::AsPPB_WebSocket_API() {
+ return this;
+}
+
+int32_t WebSocketResource::Connect(
+ const PP_Var& url,
+ const PP_Var protocols[],
+ uint32_t protocol_count,
+ scoped_refptr<TrackedCallback> callback) {
+ if (TrackedCallback::IsPending(connect_callback_))
+ return PP_ERROR_INPROGRESS;
+
+ // Connect() can be called at most once.
+ if (state_ != PP_WEBSOCKETREADYSTATE_INVALID)
+ return PP_ERROR_INPROGRESS;
+ state_ = PP_WEBSOCKETREADYSTATE_CLOSED;
+
+ // Get the URL.
+ url_ = StringVar::FromPPVar(url);
+ if (!url_)
+ return PP_ERROR_BADARGUMENT;
+
+ // Get the protocols.
+ std::set<std::string> protocol_set;
+ std::vector<std::string> protocol_strings;
+ protocol_strings.reserve(protocol_count);
+ for (uint32_t i = 0; i < protocol_count; ++i) {
+ scoped_refptr<StringVar> protocol(StringVar::FromPPVar(protocols[i]));
+
+ // Check invalid and empty entries.
+ if (!protocol || !protocol->value().length())
+ return PP_ERROR_BADARGUMENT;
+
+ // Check duplicated protocol entries.
+ if (protocol_set.find(protocol->value()) != protocol_set.end())
+ return PP_ERROR_BADARGUMENT;
+ protocol_set.insert(protocol->value());
+
+ protocol_strings.push_back(protocol->value());
+ }
+
+ // Install callback.
+ connect_callback_ = callback;
+
+ // Create remote host in the renderer, then request to check the URL and
+ // establish the connection.
+ state_ = PP_WEBSOCKETREADYSTATE_CONNECTING;
+ SendCreateToRenderer(PpapiHostMsg_WebSocket_Create());
+ PpapiHostMsg_WebSocket_Connect msg(url_->value(), protocol_strings);
+ CallRenderer<PpapiPluginMsg_WebSocket_ConnectReply>(msg,
+ base::Bind(&WebSocketResource::OnPluginMsgConnectReply, this));
+
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t WebSocketResource::Close(uint16_t code,
+ const PP_Var& reason,
+ scoped_refptr<TrackedCallback> callback) {
+ if (TrackedCallback::IsPending(close_callback_))
+ return PP_ERROR_INPROGRESS;
+ if (state_ == PP_WEBSOCKETREADYSTATE_INVALID)
+ return PP_ERROR_FAILED;
+
+ // Validate |code| and |reason|.
+ scoped_refptr<StringVar> reason_string_var;
+ std::string reason_string;
+ WebKit::WebSocket::CloseEventCode event_code =
+ static_cast<WebKit::WebSocket::CloseEventCode>(code);
+ if (code == PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED) {
+ // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED and CloseEventCodeNotSpecified are
+ // assigned to different values. A conversion is needed if
+ // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED is specified.
+ event_code = WebKit::WebSocket::CloseEventCodeNotSpecified;
+ } else {
+ if (!(code == PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE ||
+ (PP_WEBSOCKETSTATUSCODE_USER_REGISTERED_MIN <= code &&
+ code <= PP_WEBSOCKETSTATUSCODE_USER_PRIVATE_MAX)))
+ // RFC 6455 limits applications to use reserved connection close code in
+ // section 7.4.2.. The WebSocket API (http://www.w3.org/TR/websockets/)
+ // defines this out of range error as InvalidAccessError in JavaScript.
+ return PP_ERROR_NOACCESS;
+
+ // |reason| must be ignored if it is PP_VARTYPE_UNDEFINED or |code| is
+ // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED.
+ if (reason.type != PP_VARTYPE_UNDEFINED) {
+ // Validate |reason|.
+ reason_string_var = StringVar::FromPPVar(reason);
+ if (!reason_string_var ||
+ reason_string_var->value().size() > kMaxReasonSizeInBytes)
+ return PP_ERROR_BADARGUMENT;
+ reason_string = reason_string_var->value();
+ }
+ }
+
+ // Check state.
+ if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING)
+ return PP_ERROR_INPROGRESS;
+ if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
+ return PP_OK;
+
+ // Install |callback|.
+ close_callback_ = callback;
+
+ // Abort ongoing connect.
+ if (TrackedCallback::IsPending(connect_callback_)) {
+ state_ = PP_WEBSOCKETREADYSTATE_CLOSING;
+ // Need to do a "Post" to avoid reentering the plugin.
+ connect_callback_->PostAbort();
+ connect_callback_ = NULL;
+ PostToRenderer(PpapiHostMsg_WebSocket_Fail(
+ "WebSocket was closed before the connection was established."));
+ return PP_OK_COMPLETIONPENDING;
+ }
+
+ // Abort ongoing receive.
+ if (TrackedCallback::IsPending(receive_callback_)) {
+ receive_callback_var_ = NULL;
+ // Need to do a "Post" to avoid reentering the plugin.
+ receive_callback_->PostAbort();
+ receive_callback_ = NULL;
+ }
+
+ // Close connection.
+ state_ = PP_WEBSOCKETREADYSTATE_CLOSING;
+ PpapiHostMsg_WebSocket_Close msg(static_cast<int32_t>(event_code),
+ reason_string);
+ CallRenderer<PpapiPluginMsg_WebSocket_CloseReply>(msg,
+ base::Bind(&WebSocketResource::OnPluginMsgCloseReply, this));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t WebSocketResource::ReceiveMessage(
+ PP_Var* message,
+ scoped_refptr<TrackedCallback> callback) {
+ if (TrackedCallback::IsPending(receive_callback_))
+ return PP_ERROR_INPROGRESS;
+
+ // Check state.
+ if (state_ == PP_WEBSOCKETREADYSTATE_INVALID ||
+ state_ == PP_WEBSOCKETREADYSTATE_CONNECTING)
+ return PP_ERROR_BADARGUMENT;
+
+ // Just return received message if any received message is queued.
+ if (!received_messages_.empty()) {
+ receive_callback_var_ = message;
+ return DoReceive();
+ }
+
+ // Check state again. In CLOSED state, no more messages will be received.
+ if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
+ return PP_ERROR_BADARGUMENT;
+
+ // Returns PP_ERROR_FAILED after an error is received and received messages
+ // is exhausted.
+ if (error_was_received_)
+ return PP_ERROR_FAILED;
+
+ // Or retain |message| as buffer to store and install |callback|.
+ receive_callback_var_ = message;
+ receive_callback_ = callback;
+
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t WebSocketResource::SendMessage(const PP_Var& message) {
+ // Check state.
+ if (state_ == PP_WEBSOCKETREADYSTATE_INVALID ||
+ state_ == PP_WEBSOCKETREADYSTATE_CONNECTING)
+ return PP_ERROR_BADARGUMENT;
+
+ if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING ||
+ state_ == PP_WEBSOCKETREADYSTATE_CLOSED) {
+ // Handle buffered_amount_after_close_.
+ uint64_t payload_size = 0;
+ if (message.type == PP_VARTYPE_STRING) {
+ scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message);
+ if (message_string)
+ payload_size += message_string->value().length();
+ } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) {
+ scoped_refptr<ArrayBufferVar> message_array_buffer =
+ ArrayBufferVar::FromPPVar(message);
+ if (message_array_buffer)
+ payload_size += message_array_buffer->ByteLength();
+ } else {
+ // TODO(toyoshim): Support Blob.
+ return PP_ERROR_NOTSUPPORTED;
+ }
+
+ buffered_amount_after_close_ =
+ SaturateAdd(buffered_amount_after_close_, GetFrameSize(payload_size));
+
+ return PP_ERROR_FAILED;
+ }
+
+ // Send the message.
+ if (message.type == PP_VARTYPE_STRING) {
+ // Convert message to std::string, then send it.
+ scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message);
+ if (!message_string)
+ return PP_ERROR_BADARGUMENT;
+ PostToRenderer(PpapiHostMsg_WebSocket_SendText(message_string->value()));
+ } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) {
+ // Convert message to std::vector<uint8_t>, then send it.
+ scoped_refptr<ArrayBufferVar> message_arraybuffer =
+ ArrayBufferVar::FromPPVar(message);
+ if (!message_arraybuffer)
+ return PP_ERROR_BADARGUMENT;
+ uint8_t* message_data = static_cast<uint8_t*>(message_arraybuffer->Map());
+ uint32 message_length = message_arraybuffer->ByteLength();
+ std::vector<uint8_t> message_vector(message_data,
+ message_data + message_length);
+ PostToRenderer(PpapiHostMsg_WebSocket_SendBinary(message_vector));
+ } else {
+ // TODO(toyoshim): Support Blob.
+ return PP_ERROR_NOTSUPPORTED;
+ }
+ return PP_OK;
+}
+
+uint64_t WebSocketResource::GetBufferedAmount() {
+ return SaturateAdd(buffered_amount_, buffered_amount_after_close_);
+}
+
+uint16_t WebSocketResource::GetCloseCode() {
+ return close_code_;
+}
+
+PP_Var WebSocketResource::GetCloseReason() {
+ if (!close_reason_)
+ return empty_string_->GetPPVar();
+ return close_reason_->GetPPVar();
+}
+
+PP_Bool WebSocketResource::GetCloseWasClean() {
+ return close_was_clean_;
+}
+
+PP_Var WebSocketResource::GetExtensions() {
+ return StringVar::StringToPPVar(std::string());
+}
+
+PP_Var WebSocketResource::GetProtocol() {
+ if (!protocol_)
+ return empty_string_->GetPPVar();
+ return protocol_->GetPPVar();
+}
+
+PP_WebSocketReadyState WebSocketResource::GetReadyState() {
+ return state_;
+}
+
+PP_Var WebSocketResource::GetURL() {
+ if (!url_)
+ return empty_string_->GetPPVar();
+ return url_->GetPPVar();
+}
+
+void WebSocketResource::OnReplyReceived(
+ const ResourceMessageReplyParams& params,
+ const IPC::Message& msg) {
+ if (params.sequence())
+ return PluginResource::OnReplyReceived(params, msg);
+
+ // TODO(toyoshim): Currently, following unsolicited reply IPCs are handled
+ // manually. We should introduce more useful mechanism for that.
+ switch (msg.type()) {
+ case PpapiPluginMsg_WebSocket_ReceiveTextReply::ID: {
+ PpapiPluginMsg_WebSocket_ReceiveTextReply::Schema::Param p;
+ if (PpapiPluginMsg_WebSocket_ReceiveTextReply::Read(&msg, &p))
+ OnPluginMsgReceiveTextReply(params, p.a);
+ else
+ NOTREACHED();
+ break;
+ }
+ case PpapiPluginMsg_WebSocket_ReceiveBinaryReply::ID: {
+ PpapiPluginMsg_WebSocket_ReceiveBinaryReply::Schema::Param p;
+ if (PpapiPluginMsg_WebSocket_ReceiveBinaryReply::Read(&msg, &p))
+ OnPluginMsgReceiveBinaryReply(params, p.a);
+ else
+ NOTREACHED();
+ break;
+ }
+ case PpapiPluginMsg_WebSocket_ErrorReply::ID: {
+ OnPluginMsgErrorReply(params);
+ break;
+ }
+ case PpapiPluginMsg_WebSocket_BufferedAmountReply::ID: {
+ PpapiPluginMsg_WebSocket_BufferedAmountReply::Schema::Param p;
+ if (PpapiPluginMsg_WebSocket_BufferedAmountReply::Read(&msg, &p))
+ OnPluginMsgBufferedAmountReply(params, p.a);
+ else
+ NOTREACHED();
+ break;
+ }
+ case PpapiPluginMsg_WebSocket_StateReply::ID: {
+ PpapiPluginMsg_WebSocket_StateReply::Schema::Param p;
+ if (PpapiPluginMsg_WebSocket_StateReply::Read(&msg, &p))
+ OnPluginMsgStateReply(params, p.a);
+ else
+ NOTREACHED();
+ break;
+ }
+ case PpapiPluginMsg_WebSocket_ClosedReply::ID: {
+ PpapiPluginMsg_WebSocket_ClosedReply::Schema::Param p;
+ if (PpapiPluginMsg_WebSocket_ClosedReply::Read(&msg, &p))
+ OnPluginMsgClosedReply(params, p.a, p.b, p.c, p.d);
+ else
+ NOTREACHED();
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+}
+
+void WebSocketResource::OnPluginMsgConnectReply(
+ const ResourceMessageReplyParams& params,
+ const std::string& url,
+ const std::string& protocol) {
+ if (!TrackedCallback::IsPending(connect_callback_))
+ return;
+
+ int32_t result = params.result();
+ if (result == PP_OK) {
+ state_ = PP_WEBSOCKETREADYSTATE_OPEN;
+ protocol_ = new StringVar(protocol);
+ url_ = new StringVar(url);
+ }
+ TrackedCallback::ClearAndRun(&connect_callback_, params.result());
+}
+
+void WebSocketResource::OnPluginMsgCloseReply(
+ const ResourceMessageReplyParams& params,
+ unsigned long buffered_amount,
+ bool was_clean,
+ unsigned short code,
+ const std::string& reason) {
+ // Set close related properties.
+ state_ = PP_WEBSOCKETREADYSTATE_CLOSED;
+ buffered_amount_ = buffered_amount;
+ close_was_clean_ = PP_FromBool(was_clean);
+ close_code_ = code;
+ close_reason_ = new StringVar(reason);
+
+ if (TrackedCallback::IsPending(receive_callback_)) {
+ receive_callback_var_ = NULL;
+ receive_callback_->PostRun(PP_ERROR_FAILED);
+ receive_callback_ = NULL;
+ }
+
+ if (TrackedCallback::IsPending(close_callback_)) {
+ close_callback_->PostRun(params.result());
+ close_callback_ = NULL;
+ }
+}
+
+void WebSocketResource::OnPluginMsgReceiveTextReply(
+ const ResourceMessageReplyParams& params,
+ const std::string& message) {
+ // Dispose packets after receiving an error or in invalid state.
+ if (error_was_received_ || !InValidStateToReceive(state_))
+ return;
+
+ // Append received data to queue.
+ received_messages_.push(scoped_refptr<Var>(new StringVar(message)));
+
+ if (!TrackedCallback::IsPending(receive_callback_))
+ return;
+
+ TrackedCallback::ClearAndRun(&receive_callback_, DoReceive());
+}
+
+void WebSocketResource::OnPluginMsgReceiveBinaryReply(
+ const ResourceMessageReplyParams& params,
+ const std::vector<uint8_t>& message) {
+ // Dispose packets after receiving an error or in invalid state.
+ if (error_was_received_ || !InValidStateToReceive(state_))
+ return;
+
+ // Append received data to queue.
+ scoped_refptr<Var> message_var(ArrayBufferVar::FromPPVar(
+ PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferPPVar(
+ message.size(),
+ &message.front())));
+ received_messages_.push(message_var);
+
+ if (!TrackedCallback::IsPending(receive_callback_))
+ return;
+
+ TrackedCallback::ClearAndRun(&receive_callback_, DoReceive());
+}
+
+void WebSocketResource::OnPluginMsgErrorReply(
+ const ResourceMessageReplyParams& params) {
+ error_was_received_ = true;
+
+ if (!TrackedCallback::IsPending(receive_callback_))
+ return;
+
+ // No more text or binary messages will be received. If there is ongoing
+ // ReceiveMessage(), we must invoke the callback with error code here.
+ receive_callback_var_ = NULL;
+ TrackedCallback::ClearAndRun(&receive_callback_, PP_ERROR_FAILED);
+}
+
+void WebSocketResource::OnPluginMsgBufferedAmountReply(
+ const ResourceMessageReplyParams& params,
+ unsigned long buffered_amount) {
+ buffered_amount_ = buffered_amount;
+}
+
+void WebSocketResource::OnPluginMsgStateReply(
+ const ResourceMessageReplyParams& params,
+ int32_t state) {
+ state_ = static_cast<PP_WebSocketReadyState>(state);
+}
+
+void WebSocketResource::OnPluginMsgClosedReply(
+ const ResourceMessageReplyParams& params,
+ unsigned long buffered_amount,
+ bool was_clean,
+ unsigned short code,
+ const std::string& reason) {
+ OnPluginMsgCloseReply(params, buffered_amount, was_clean, code, reason);
+}
+
+int32_t WebSocketResource::DoReceive() {
+ if (!receive_callback_var_)
+ return PP_OK;
+
+ *receive_callback_var_ = received_messages_.front()->GetPPVar();
+ received_messages_.pop();
+ receive_callback_var_ = NULL;
+ return PP_OK;
+}
+
+} // namespace proxy
+} // namespace ppapi
diff --git a/ppapi/proxy/websocket_resource.h b/ppapi/proxy/websocket_resource.h
new file mode 100644
index 0000000..49353e4
--- /dev/null
+++ b/ppapi/proxy/websocket_resource.h
@@ -0,0 +1,157 @@
+// Copyright (c) 2012 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 PPAPI_PROXY_WEBSOCKET_RESOURCE_H_
+#define PPAPI_PROXY_WEBSOCKET_RESOURCE_H_
+
+#include <queue>
+
+#include "ppapi/c/ppb_websocket.h"
+#include "ppapi/proxy/plugin_resource.h"
+#include "ppapi/shared_impl/tracked_callback.h"
+#include "ppapi/thunk/ppb_websocket_api.h"
+
+namespace ppapi {
+
+class StringVar;
+class Var;
+
+namespace proxy {
+
+// This class contains protocol checks which doesn't affect security when it
+// run with untrusted code.
+class PPAPI_PROXY_EXPORT WebSocketResource
+ : public PluginResource,
+ public NON_EXPORTED_BASE(thunk::PPB_WebSocket_API) {
+ public:
+ WebSocketResource(Connection connection, PP_Instance instance);
+ virtual ~WebSocketResource();
+
+ // PluginResource implementation.
+ virtual thunk::PPB_WebSocket_API* AsPPB_WebSocket_API() OVERRIDE;
+
+ // PPB_WebSocket_API implementation.
+ virtual int32_t Connect(const PP_Var& url,
+ const PP_Var protocols[],
+ uint32_t protocol_count,
+ scoped_refptr<TrackedCallback> callback) OVERRIDE;
+ virtual int32_t Close(uint16_t code,
+ const PP_Var& reason,
+ scoped_refptr<TrackedCallback> callback) OVERRIDE;
+ virtual int32_t ReceiveMessage(
+ PP_Var* message,
+ scoped_refptr<TrackedCallback> callback) OVERRIDE;
+ virtual int32_t SendMessage(const PP_Var& message) OVERRIDE;
+ virtual uint64_t GetBufferedAmount() OVERRIDE;
+ virtual uint16_t GetCloseCode() OVERRIDE;
+ virtual PP_Var GetCloseReason() OVERRIDE;
+ virtual PP_Bool GetCloseWasClean() OVERRIDE;
+ virtual PP_Var GetExtensions() OVERRIDE;
+ virtual PP_Var GetProtocol() OVERRIDE;
+ virtual PP_WebSocketReadyState GetReadyState() OVERRIDE;
+ virtual PP_Var GetURL() OVERRIDE;
+
+ private:
+ // PluginResource override.
+ virtual void OnReplyReceived(const ResourceMessageReplyParams& params,
+ const IPC::Message& msg) OVERRIDE;
+
+ // IPC message handlers.
+ void OnPluginMsgConnectReply(const ResourceMessageReplyParams& params,
+ const std::string& url,
+ const std::string& protocol);
+ void OnPluginMsgCloseReply(const ResourceMessageReplyParams& params,
+ unsigned long buffered_amount,
+ bool was_clean,
+ unsigned short code,
+ const std::string& reason);
+ void OnPluginMsgReceiveTextReply(const ResourceMessageReplyParams& params,
+ const std::string& message);
+ void OnPluginMsgReceiveBinaryReply(const ResourceMessageReplyParams& params,
+ const std::vector<uint8_t>& message);
+ void OnPluginMsgErrorReply(const ResourceMessageReplyParams& params);
+ void OnPluginMsgBufferedAmountReply(const ResourceMessageReplyParams& params,
+ unsigned long buffered_amount);
+ void OnPluginMsgStateReply(const ResourceMessageReplyParams& params,
+ int32_t state);
+ void OnPluginMsgClosedReply(const ResourceMessageReplyParams& params,
+ unsigned long buffered_amount,
+ bool was_clean,
+ unsigned short code,
+ const std::string& reason);
+
+ // Picks up a received message and moves it to user receiving buffer. This
+ // function is used in both ReceiveMessage for fast returning path, and
+ // OnPluginMsgReceiveTextReply and OnPluginMsgReceiveBinaryReply for delayed
+ // callback invocations.
+ int32_t DoReceive();
+
+ // Holds user callbacks to invoke later.
+ scoped_refptr<TrackedCallback> connect_callback_;
+ scoped_refptr<TrackedCallback> close_callback_;
+ scoped_refptr<TrackedCallback> receive_callback_;
+
+ // Represents readyState described in the WebSocket API specification. It can
+ // be read via GetReadyState().
+ PP_WebSocketReadyState state_;
+
+ // Becomes true if any error is detected. Incoming data will be disposed
+ // if this variable is true, then ReceiveMessage() returns PP_ERROR_FAILED
+ // after returning all received data.
+ bool error_was_received_;
+
+ // Keeps a pointer to PP_Var which is provided via ReceiveMessage().
+ // Received data will be copied to this PP_Var on ready.
+ PP_Var* receive_callback_var_;
+
+ // Keeps received data until ReceiveMessage() requests.
+ std::queue<scoped_refptr<Var> > received_messages_;
+
+ // Keeps empty string for functions to return empty string.
+ scoped_refptr<StringVar> empty_string_;
+
+ // Keeps the status code field of closing handshake. It can be read via
+ // GetCloseCode().
+ uint16_t close_code_;
+
+ // Keeps the reason field of closing handshake. It can be read via
+ // GetCloseReason().
+ scoped_refptr<StringVar> close_reason_;
+
+ // Becomes true when closing handshake is performed successfully. It can be
+ // read via GetCloseWasClean().
+ PP_Bool close_was_clean_;
+
+ // Represents extensions described in the WebSocket API specification. It can
+ // be read via GetExtensions().
+ scoped_refptr<StringVar> extensions_;
+
+ // Represents protocol described in the WebSocket API specification. It can be
+ // read via GetProtocol().
+ scoped_refptr<StringVar> protocol_;
+
+ // Represents url described in the WebSocket API specification. It can be
+ // read via GetURL().
+ scoped_refptr<StringVar> url_;
+
+ // Keeps the number of bytes of application data that have been queued using
+ // SendMessage(). WebKit side implementation calculates the actual amount.
+ // This is a cached value which is notified through a WebKit callback.
+ // This value is used to calculate bufferedAmount in the WebSocket API
+ // specification. The calculated value can be read via GetBufferedAmount().
+ uint64_t buffered_amount_;
+
+ // Keeps the number of bytes of application data that have been ignored
+ // because the connection was already closed.
+ // This value is used to calculate bufferedAmount in the WebSocket API
+ // specification. The calculated value can be read via GetBufferedAmount().
+ uint64_t buffered_amount_after_close_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketResource);
+};
+
+} // namespace proxy
+} // namespace ppapi
+
+#endif // PPAPI_PROXY_WEBSOCKET_RESOURCE_H_
diff --git a/ppapi/proxy/websocket_resource_unittest.cc b/ppapi/proxy/websocket_resource_unittest.cc
new file mode 100644
index 0000000..61e8925
--- /dev/null
+++ b/ppapi/proxy/websocket_resource_unittest.cc
@@ -0,0 +1,168 @@
+// Copyright (c) 2012 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/memory/ref_counted.h"
+#include "base/message_loop.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/ppb_websocket.h"
+#include "ppapi/c/ppb_var.h"
+#include "ppapi/proxy/websocket_resource.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/proxy/ppapi_proxy_test.h"
+#include "ppapi/shared_impl/ppb_var_shared.h"
+#include "ppapi/shared_impl/scoped_pp_resource.h"
+#include "ppapi/shared_impl/scoped_pp_var.h"
+#include "ppapi/shared_impl/tracked_callback.h"
+#include "ppapi/shared_impl/var.h"
+#include "ppapi/thunk/thunk.h"
+
+namespace ppapi {
+namespace proxy {
+
+namespace {
+
+typedef PluginProxyTest WebSocketResourceTest;
+
+bool g_callback_called;
+int32_t g_callback_result;
+const PPB_Var* ppb_var_ = NULL;
+
+void Callback(void* user_data, int32_t result) {
+ g_callback_called = true;
+ g_callback_result = result;
+}
+
+PP_CompletionCallback MakeCallback() {
+ g_callback_called = false;
+ g_callback_result = PP_OK;
+ return PP_MakeCompletionCallback(Callback, NULL);
+}
+
+PP_Var MakeStringVar(const std::string& string) {
+ if (!ppb_var_)
+ ppb_var_ = ppapi::PPB_Var_Shared::GetVarInterface1_1();
+ return ppb_var_->VarFromUtf8(string.c_str(), string.length());
+}
+
+} // namespace
+
+
+// Does a test of Connect().
+TEST_F(WebSocketResourceTest, Connect) {
+ const PPB_WebSocket_1_0* websocket_iface =
+ thunk::GetPPB_WebSocket_1_0_Thunk();
+
+ std::string url("ws://ws.google.com");
+ std::string protocol0("x-foo");
+ std::string protocol1("x-bar");
+ PP_Var url_var = MakeStringVar(url);
+ PP_Var protocols[] = { MakeStringVar(protocol0), MakeStringVar(protocol1) };
+
+ ScopedPPResource res(ScopedPPResource::PassRef(),
+ websocket_iface->Create(pp_instance()));
+
+ int32_t result =
+ websocket_iface->Connect(res, url_var, protocols, 2, MakeCallback());
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
+
+ // Should be sent a "Connect" message.
+ ResourceMessageCallParams params;
+ IPC::Message msg;
+ ASSERT_TRUE(sink().GetFirstResourceCallMatching(
+ PpapiHostMsg_WebSocket_Connect::ID, &params, &msg));
+ PpapiHostMsg_WebSocket_Connect::Schema::Param p;
+ PpapiHostMsg_WebSocket_Connect::Read(&msg, &p);
+ EXPECT_EQ(url, p.a);
+ EXPECT_EQ(protocol0, p.b[0]);
+ EXPECT_EQ(protocol1, p.b[1]);
+
+ // Synthesize a response.
+ ResourceMessageReplyParams reply_params(params.pp_resource(),
+ params.sequence());
+ reply_params.set_result(PP_OK);
+ ASSERT_TRUE(plugin_dispatcher()->OnMessageReceived(
+ PpapiPluginMsg_ResourceReply(reply_params,
+ PpapiPluginMsg_WebSocket_ConnectReply(url, protocol1))));
+
+ EXPECT_EQ(PP_OK, g_callback_result);
+ EXPECT_EQ(true, g_callback_called);
+}
+
+// Does a test for unsolicited replies.
+TEST_F(WebSocketResourceTest, UnsolicitedReplies) {
+ const PPB_WebSocket_1_0* websocket_iface =
+ thunk::GetPPB_WebSocket_1_0_Thunk();
+
+ ScopedPPResource res(ScopedPPResource::PassRef(),
+ websocket_iface->Create(pp_instance()));
+
+ // Check if BufferedAmountReply is handled.
+ ResourceMessageReplyParams reply_params(res, 0);
+ reply_params.set_result(PP_OK);
+ ASSERT_TRUE(plugin_dispatcher()->OnMessageReceived(
+ PpapiPluginMsg_ResourceReply(
+ reply_params,
+ PpapiPluginMsg_WebSocket_BufferedAmountReply(19760227u))));
+
+ uint64_t amount = websocket_iface->GetBufferedAmount(res);
+ EXPECT_EQ(19760227u, amount);
+
+ // Check if StateReply is handled.
+ ASSERT_TRUE(plugin_dispatcher()->OnMessageReceived(
+ PpapiPluginMsg_ResourceReply(
+ reply_params,
+ PpapiPluginMsg_WebSocket_StateReply(
+ static_cast<int32_t>(PP_WEBSOCKETREADYSTATE_CLOSING)))));
+
+ PP_WebSocketReadyState state = websocket_iface->GetReadyState(res);
+ EXPECT_EQ(PP_WEBSOCKETREADYSTATE_CLOSING, state);
+}
+
+TEST_F(WebSocketResourceTest, MessageError) {
+ const PPB_WebSocket_1_0* websocket_iface =
+ thunk::GetPPB_WebSocket_1_0_Thunk();
+
+ std::string url("ws://ws.google.com");
+ PP_Var url_var = MakeStringVar(url);
+
+ ScopedPPResource res(ScopedPPResource::PassRef(),
+ websocket_iface->Create(pp_instance()));
+
+ // Establish the connection virtually.
+ int32_t result =
+ websocket_iface->Connect(res, url_var, NULL, 0, MakeCallback());
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
+
+ ResourceMessageCallParams params;
+ IPC::Message msg;
+ ASSERT_TRUE(sink().GetFirstResourceCallMatching(
+ PpapiHostMsg_WebSocket_Connect::ID, &params, &msg));
+
+ ResourceMessageReplyParams connect_reply_params(params.pp_resource(),
+ params.sequence());
+ connect_reply_params.set_result(PP_OK);
+ ASSERT_TRUE(plugin_dispatcher()->OnMessageReceived(
+ PpapiPluginMsg_ResourceReply(connect_reply_params,
+ PpapiPluginMsg_WebSocket_ConnectReply(url, std::string()))));
+
+ EXPECT_EQ(PP_OK, g_callback_result);
+ EXPECT_EQ(true, g_callback_called);
+
+ PP_Var message;
+ result = websocket_iface->ReceiveMessage(res, &message, MakeCallback());
+ EXPECT_EQ(false, g_callback_called);
+
+ // Synthesize a WebSocket_ErrorReply message.
+ ResourceMessageReplyParams error_reply_params(res, 0);
+ error_reply_params.set_result(PP_OK);
+ ASSERT_TRUE(plugin_dispatcher()->OnMessageReceived(
+ PpapiPluginMsg_ResourceReply(error_reply_params,
+ PpapiPluginMsg_WebSocket_ErrorReply())));
+
+ EXPECT_EQ(PP_ERROR_FAILED, g_callback_result);
+ EXPECT_EQ(true, g_callback_called);
+}
+
+} // namespace proxy
+} // namespace ppapi