Chromium Code Reviews| Index: webkit/plugins/ppapi/ppb_websocket_impl.cc |
| diff --git a/webkit/plugins/ppapi/ppb_websocket_impl.cc b/webkit/plugins/ppapi/ppb_websocket_impl.cc |
| index f8c907c81d19b0657a23d022c6d92c640b5351ad..0adf2064b38d8cf5b4dc393c9dd9e0df603d1ee6 100644 |
| --- a/webkit/plugins/ppapi/ppb_websocket_impl.cc |
| +++ b/webkit/plugins/ppapi/ppb_websocket_impl.cc |
| @@ -4,19 +4,66 @@ |
| #include "webkit/plugins/ppapi/ppb_websocket_impl.h" |
| +#include <string> |
| + |
| +#include "base/logging.h" |
| +#include "googleurl/src/gurl.h" |
| +#include "net/base/net_util.h" |
| +#include "ppapi/c/pp_completion_callback.h" |
| #include "ppapi/c/pp_errors.h" |
| #include "ppapi/c/pp_var.h" |
| +#include "ppapi/c/ppb_var.h" |
| +#include "ppapi/cpp/module.h" |
| +#include "ppapi/shared_impl/var.h" |
| +#include "ppapi/shared_impl/var_tracker.h" |
| +#include "third_party/WebKit/Source/WebKit/chromium/public/WebData.h" |
| +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" |
| +#include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h" |
| +#include "third_party/WebKit/Source/WebKit/chromium/public/WebPluginContainer.h" |
| +#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h" |
| +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSocket.h" |
| +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURL.h" |
| +#include "webkit/plugins/ppapi/host_globals.h" |
| +#include "webkit/plugins/ppapi/ppapi_plugin_instance.h" |
| +#include "webkit/plugins/ppapi/resource_helper.h" |
| +using ppapi::PpapiGlobals; |
| +using ppapi::StringVar; |
| using ppapi::thunk::PPB_WebSocket_API; |
| +using ppapi::VarTracker; |
| +using WebKit::WebData; |
| +using WebKit::WebDocument; |
| +using WebKit::WebString; |
| +using WebKit::WebSocket; |
| +using WebKit::WebSocketClient; |
| +using WebKit::WebURL; |
| + |
| +static const uint32_t kMaxReasonSizeInBytes = 123; |
| namespace webkit { |
| namespace ppapi { |
| PPB_WebSocket_Impl::PPB_WebSocket_Impl(PP_Instance instance) |
| - : Resource(instance) { |
| + : Resource(instance), |
| + state_(PP_WEBSOCKETREADYSTATE_INVALID_DEV), |
| + receive_callback_var_(NULL), |
| + wait_for_receive_(false), |
| + close_code_(0), |
| + close_was_clean_(PP_FALSE) { |
| + empty_string_ = new StringVar(pp::Module::Get()->pp_module(), "", 0); |
| } |
| PPB_WebSocket_Impl::~PPB_WebSocket_Impl() { |
| + if (websocket_.get()) |
| + websocket_->disconnect(); |
| + |
| + // Clean up received and unread messages |
| + VarTracker* var_tracker = PpapiGlobals::Get()->GetVarTracker(); |
| + while (!received_messages_.empty()) { |
| + PP_Var var = received_messages_.front(); |
| + received_messages_.pop(); |
| + var_tracker->ReleaseVar(var); |
| + } |
| } |
| // static |
| @@ -33,66 +80,301 @@ int32_t PPB_WebSocket_Impl::Connect(PP_Var url, |
| const PP_Var protocols[], |
| uint32_t protocol_count, |
| PP_CompletionCallback callback) { |
| - // TODO(toyoshim): Implement it. |
| - return PP_ERROR_NOTSUPPORTED; |
| + // Check mandatory interfaces. |
| + PluginInstance* plugin_instance = ResourceHelper::GetPluginInstance(this); |
| + DCHECK(plugin_instance); |
| + if (!plugin_instance) |
| + return PP_ERROR_FAILED; |
| + |
| + // Connect() can be called at most once. |
| + if (websocket_.get()) |
| + return PP_ERROR_INPROGRESS; |
| + if (state_ != PP_WEBSOCKETREADYSTATE_INVALID_DEV) |
| + return PP_ERROR_INPROGRESS; |
| + state_ = PP_WEBSOCKETREADYSTATE_CLOSED_DEV; |
| + |
| + // Validate |callback| (Doesn't support blocking callback) |
| + if (!callback.func) |
| + return PP_ERROR_BLOCKS_MAIN_THREAD; |
| + |
| + // Validate url and convert it to WebURL. |
| + scoped_refptr<StringVar> url_string = StringVar::FromPPVar(url); |
| + if (!url_string) |
| + return PP_ERROR_BADARGUMENT; |
| + GURL gurl(url_string->value()); |
| + if (!gurl.is_valid()) |
| + return PP_ERROR_BADARGUMENT; |
| + if (!gurl.SchemeIs("ws") && !gurl.SchemeIs("wss")) |
| + return PP_ERROR_BADARGUMENT; |
| + if (gurl.has_ref()) |
| + return PP_ERROR_BADARGUMENT; |
| + if (!net::IsPortAllowedByDefault(gurl.IntPort())) |
| + return PP_ERROR_BADARGUMENT; |
| + WebURL web_url(gurl); |
| + |
| + // Validate protocols and convert it to WebString. |
| + // TODO(toyoshim): Detect duplicated protocols as error. |
| + std::string protocol_string; |
| + for (uint32_t i = 0; i < protocol_count; i++) { |
| + // TODO(toyoshim): Similar function exist in WebKit::WebSocket. |
| + // We must rearrange them into WebKit::WebChannel and share its protocol |
| + // related implementation via WebKit API. |
| + scoped_refptr<StringVar> string_var; |
| + string_var = StringVar::FromPPVar(protocols[i]); |
| + if (!string_var->value().length()) |
| + return PP_ERROR_BADARGUMENT; |
|
dmichael (off chromium)
2011/11/24 04:32:36
Shouldn't we also check that string_var is valid (
Takashi Toyoshima
2011/11/25 05:42:48
Done.
|
| + for (std::string::const_iterator it = string_var->value().begin(); |
| + it != string_var->value().end(); |
| + ++it) { |
| + uint8_t character = static_cast<uint8_t>(*it); |
| + // WebSocket specification says "(Subprotocol string must consist of) |
| + // characters in the range U+0021 to U+007E not including separator |
| + // characters as defined in [RFC2616]." |
| + const uint8_t minimumProtocolCharacter = '!'; // U+0021. |
| + const uint8_t maximumProtocolCharacter = '~'; // U+007E. |
| + if (character < minimumProtocolCharacter || |
| + character > maximumProtocolCharacter || |
| + character == '"' || character == '(' || character == ')' || |
| + character == ',' || character == '/' || |
| + (character >= ':' && character <= '@') || // U+003A - U+0040 |
| + (character >= '[' && character <= ']') || // U+005B - u+005D |
| + character == '{' || character == '}') |
| + return PP_ERROR_BADARGUMENT; |
| + } |
| + if (i != 0) |
| + protocol_string.append(","); |
| + protocol_string.append(string_var->value()); |
| + } |
| + WebString web_protocols = WebString::fromUTF8(protocol_string); |
| + |
| + // Create WebKit::WebSocket object. |
| + WebDocument document = plugin_instance->container()->element().document(); |
| + websocket_.reset(WebSocket::create(document, this)); |
| + DCHECK(websocket_.get()); |
| + if (!websocket_.get()) |
| + return PP_ERROR_NOTSUPPORTED; |
| + |
| + websocket_->connect(web_url, web_protocols); |
| + state_ = PP_WEBSOCKETREADYSTATE_CONNECTING_DEV; |
| + |
| + // Install callback. |
| + connect_callback_ = callback; |
| + |
| + return PP_OK_COMPLETIONPENDING; |
| } |
| int32_t PPB_WebSocket_Impl::Close(uint16_t code, |
| PP_Var reason, |
| PP_CompletionCallback callback) { |
| - // TODO(toyoshim): Implement it. |
| - return PP_ERROR_NOTSUPPORTED; |
| + // Check mandarory interfaces. |
| + if (!websocket_.get()) |
| + return PP_ERROR_FAILED; |
| + |
| + // Validate |callback| (Doesn't support blocking callback) |
| + if (!callback.func) |
| + return PP_ERROR_BLOCKS_MAIN_THREAD; |
| + |
| + // Validate |code|. |
| + if (code != WebSocket::CloseEventCodeNotSpecified) { |
| + if (!(code == WebSocket::CloseEventCodeNormalClosure || |
| + (WebSocket::CloseEventCodeMinimumUserDefined <= code && |
| + code <= WebSocket::CloseEventCodeMaximumUserDefined))) |
| + return PP_ERROR_NOACCESS; |
| + } |
| + // Validate |reason|. |
| + // TODO(toyoshim): Returns PP_ERROR_BADARGUMENT if |reason| contains any |
| + // surrogates. |
| + scoped_refptr<StringVar> reason_string = StringVar::FromPPVar(reason); |
| + if (!reason_string || reason_string->value().size() > kMaxReasonSizeInBytes) |
| + return PP_ERROR_BADARGUMENT; |
| + |
| + // Check state. |
| + if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING_DEV || |
| + state_ == PP_WEBSOCKETREADYSTATE_CLOSED_DEV) |
| + return PP_ERROR_INPROGRESS; |
| + |
| + // Install |callback|. |
| + close_callback_ = callback; |
| + |
| + if (state_ == PP_WEBSOCKETREADYSTATE_CONNECTING_DEV) { |
| + state_ = PP_WEBSOCKETREADYSTATE_CLOSING_DEV; |
| + PP_RunAndClearCompletionCallback(&connect_callback_, PP_ERROR_ABORTED); |
| + websocket_->fail( |
| + "WebSocket was closed before the connection was established."); |
| + return PP_OK_COMPLETIONPENDING; |
| + } |
| + |
| + // TODO(toyoshim): Handle bufferedAmount here. |
| + |
| + state_ = PP_WEBSOCKETREADYSTATE_CLOSING_DEV; |
| + WebString web_reason = WebString::fromUTF8(reason_string->value()); |
| + websocket_->close(code, web_reason); |
| + |
| + return PP_OK_COMPLETIONPENDING; |
| } |
| int32_t PPB_WebSocket_Impl::ReceiveMessage(PP_Var* message, |
| PP_CompletionCallback callback) { |
| - // TODO(toyoshim): Implement it. |
| - return PP_ERROR_NOTSUPPORTED; |
| + // Check state. |
| + if (state_ == PP_WEBSOCKETREADYSTATE_INVALID_DEV || |
| + state_ == PP_WEBSOCKETREADYSTATE_CONNECTING_DEV) |
| + return PP_ERROR_BADARGUMENT; |
| + |
| + // Just return received message if any received message is queued. |
| + if (!received_messages_.empty()) |
| + return DoReceive(); |
| + |
| + // Or retain |message| as buffer to store and install |callback|. |
| + wait_for_receive_ = true; |
| + receive_callback_var_ = message; |
| + receive_callback_ = callback; |
| + |
| + return PP_OK_COMPLETIONPENDING; |
| } |
| int32_t PPB_WebSocket_Impl::SendMessage(PP_Var message) { |
| - // TODO(toyoshim): Implement it. |
| - return PP_ERROR_NOTSUPPORTED; |
| + // Check mandatory interfaces. |
| + if (!websocket_.get()) |
| + return PP_ERROR_FAILED; |
| + |
| + // Check state. |
| + if (state_ == PP_WEBSOCKETREADYSTATE_INVALID_DEV || |
| + state_ == PP_WEBSOCKETREADYSTATE_CONNECTING_DEV) |
| + return PP_ERROR_BADARGUMENT; |
| + |
| + if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING_DEV || |
| + state_ == PP_WEBSOCKETREADYSTATE_CLOSED_DEV) { |
| + // TODO(toyoshim): Handle bufferedAmount here. |
| + } |
| + |
| + if (message.type != PP_VARTYPE_STRING) { |
| + // TODO(toyoshim): Support binary data. |
| + return PP_ERROR_NOTSUPPORTED; |
| + } |
| + |
| + // Convert message to WebString. |
| + scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message); |
| + if (!message_string) |
| + return PP_ERROR_BADARGUMENT; |
| + WebString web_message = WebString::fromUTF8(message_string->value()); |
| + if (!websocket_->sendText(web_message)) |
| + return PP_ERROR_BADARGUMENT; |
| + |
| + return PP_OK; |
| } |
| uint64_t PPB_WebSocket_Impl::GetBufferedAmount() { |
| - // TODO(toyoshim): Implement it. |
| + // TODO(toyoshim): Implement. |
| return 0; |
| } |
| uint16_t PPB_WebSocket_Impl::GetCloseCode() { |
| - // TODO(toyoshim): Implement it. |
| - return 0; |
| + return close_code_; |
| } |
| PP_Var PPB_WebSocket_Impl::GetCloseReason() { |
| - // TODO(toyoshim): Implement it. |
| - return PP_MakeUndefined(); |
| + if (!close_reason_) |
| + return empty_string_->GetPPVar(); |
| + return close_reason_->GetPPVar(); |
| } |
| PP_Bool PPB_WebSocket_Impl::GetCloseWasClean() { |
| - // TODO(toyoshim): Implement it. |
| - return PP_FALSE; |
| + return close_was_clean_; |
| } |
| PP_Var PPB_WebSocket_Impl::GetExtensions() { |
| - // TODO(toyoshim): Implement it. |
| - return PP_MakeUndefined(); |
| + // TODO(toyoshim): For now, always returns empty string. |
| + if (!extensions_) |
| + return empty_string_->GetPPVar(); |
| + return extensions_->GetPPVar(); |
| } |
| PP_Var PPB_WebSocket_Impl::GetProtocol() { |
| - // TODO(toyoshim): Implement it. |
| - return PP_MakeUndefined(); |
| + // TODO(toyoshim): Implement. |
| + if (!protocol_) |
| + return empty_string_->GetPPVar(); |
| + return protocol_->GetPPVar(); |
| } |
| PP_WebSocketReadyState_Dev PPB_WebSocket_Impl::GetReadyState() { |
| - // TODO(toyoshim): Implement it. |
| - return PP_WEBSOCKETREADYSTATE_INVALID_DEV; |
| + return state_; |
| } |
| PP_Var PPB_WebSocket_Impl::GetURL() { |
| - // TODO(toyoshim): Implement it. |
| - return PP_MakeUndefined(); |
| + // TODO(toyoshim): For now, always returns empty string. |
| + if (!url_) |
| + return empty_string_->GetPPVar(); |
| + return url_->GetPPVar(); |
| +} |
| + |
| +void PPB_WebSocket_Impl::didConnect() { |
| + DCHECK_EQ(PP_WEBSOCKETREADYSTATE_CONNECTING_DEV, state_); |
| + state_ = PP_WEBSOCKETREADYSTATE_OPEN_DEV; |
| + PP_RunAndClearCompletionCallback(&connect_callback_, PP_OK); |
| +} |
| + |
| +void PPB_WebSocket_Impl::didReceiveMessage(const WebString& message) { |
| + // Append received data to queue. |
| + std::string string = message.utf8(); |
| + PP_Var var = StringVar::StringToPPVar( |
| + pp::Module::Get()->pp_module(), string); |
| + received_messages_.push(var); |
| + |
| + if (!wait_for_receive_) |
| + return; |
| + |
| + PP_RunAndClearCompletionCallback(&receive_callback_, DoReceive()); |
| +} |
| + |
| +void PPB_WebSocket_Impl::didReceiveBinaryData(const WebData& binaryData) { |
| + DLOG(INFO) << "didReceiveBinaryData is not implemented yet."; |
| + // TODO(toyoshim): Support to receive binary data. |
| +} |
| + |
| +void PPB_WebSocket_Impl::didReceiveMessageError() { |
| + // TODO(toyoshim): Must implement. |
| + DLOG(INFO) << "didReceiveMessageError is not implemented yet."; |
| +} |
| + |
| +void PPB_WebSocket_Impl::didStartClosingHandshake() { |
| + // TODO(toyoshim): Must implement. |
| + DLOG(INFO) << "didStartClosingHandshake is not implemented yet."; |
| +} |
| + |
| +void PPB_WebSocket_Impl::didClose(unsigned long bufferedAmount, |
| + ClosingHandshakeCompletionStatus status, |
| + unsigned short code, |
| + const WebString& reason) { |
| + // Store code and reason. |
| + close_code_ = code; |
| + std::string reason_string = reason.utf8(); |
| + close_reason_ = new StringVar(pp::Module::Get()->pp_module(), reason_string); |
| + |
| + // TODO(toyoshim): Set close_was_clean_. |
| + |
| + // Handle state transition and invoking callback. |
| + DCHECK_NE(PP_WEBSOCKETREADYSTATE_CLOSED_DEV, state_); |
| + PP_WebSocketReadyState_Dev state = state_; |
| + state_ = PP_WEBSOCKETREADYSTATE_CLOSED_DEV; |
| + |
| + if (state == PP_WEBSOCKETREADYSTATE_CONNECTING_DEV) |
| + PP_RunAndClearCompletionCallback(&connect_callback_, PP_OK); |
| + |
| + if (state == PP_WEBSOCKETREADYSTATE_CLOSING_DEV) |
| + PP_RunAndClearCompletionCallback(&close_callback_, PP_OK); |
| +} |
| + |
| +int32_t PPB_WebSocket_Impl::DoReceive() { |
| + // TODO(toyoshim): Check state. |
| + |
| + if (!receive_callback_var_) |
| + return PP_OK; |
| + |
| + *receive_callback_var_ = received_messages_.front(); |
| + received_messages_.pop(); |
| + receive_callback_var_ = NULL; |
| + wait_for_receive_ = false; |
| + return PP_OK; |
| } |
| } // namespace ppapi |