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..23d2991ac33ca71a034f98a6ec52b72c11f994bc 100644 |
--- a/webkit/plugins/ppapi/ppb_websocket_impl.cc |
+++ b/webkit/plugins/ppapi/ppb_websocket_impl.cc |
@@ -4,19 +4,76 @@ |
#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 "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/ppapi_plugin_instance.h" |
+#include "webkit/plugins/ppapi/resource_helper.h" |
using ppapi::thunk::PPB_WebSocket_API; |
+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_reason_(PP_MakeUndefined()), |
+ close_was_clean_(PP_FALSE), |
+ extensions_(PP_MakeUndefined()), |
+ protocol_(PP_MakeUndefined()), |
+ url_(PP_MakeUndefined()) { |
+ var_interface_ = reinterpret_cast<const PPB_Var*>( |
+ pp::Module::Get()->GetBrowserInterface(PPB_VAR_INTERFACE)); |
dmichael (off chromium)
2011/11/23 17:41:24
In implementation code, we should access the VarTr
Takashi Toyoshima
2011/11/24 03:55:49
Done.
|
+ DCHECK(var_interface_); |
+ if (!var_interface_) |
+ empty_var_ = PP_MakeUndefined(); |
dmichael (off chromium)
2011/11/23 17:41:24
And maybe empty_var_ should be a scoped_refptr<Str
Takashi Toyoshima
2011/11/24 03:55:49
Done.
|
+ else |
+ empty_var_ = var_interface_->VarFromUtf8( |
+ pp::Module::Get()->pp_module(), "", 0); |
} |
PPB_WebSocket_Impl::~PPB_WebSocket_Impl() { |
+ if (websocket_.get()) |
+ websocket_->disconnect(); |
+ |
+ if (!var_interface_) |
+ return; |
+ |
+ if (empty_var_.type == PP_VARTYPE_STRING) |
+ var_interface_->Release(empty_var_); |
+ |
+ // Clean up received and unread messages |
+ while (!received_messages_.empty()) { |
+ PP_Var var = received_messages_.front(); |
+ received_messages_.pop(); |
+ var_interface_->Release(var); |
dmichael (off chromium)
2011/11/23 17:41:24
*if* each received_message is really just a string
Takashi Toyoshima
2011/11/24 03:55:49
For now, it is true.
But, we must handle binary da
dmichael (off chromium)
2011/11/24 04:32:36
The binary data will be ArrayBuffer and Blob, and
Takashi Toyoshima
2011/11/25 05:42:48
Oh, it sounds very nice that ArrayBuffer and Blob
|
+ } |
} |
// static |
@@ -33,66 +90,317 @@ int32_t PPB_WebSocket_Impl::Connect(PP_Var url, |
const PP_Var protocols[], |
uint32_t protocol_count, |
PP_CompletionCallback callback) { |
dmichael (off chromium)
2011/11/23 18:29:57
If callback is a blocking completion callback, you
Takashi Toyoshima
2011/11/24 03:55:49
I'd like to valid after changing state_, because I
|
- // TODO(toyoshim): Implement it. |
- return PP_ERROR_NOTSUPPORTED; |
+ // Check mandatory interfaces. |
+ if (!var_interface_) |
+ return PP_ERROR_FAILED; |
+ |
+ 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; |
+ |
+ // Type of |url| and |protocols| must be PP_VARTYPE_STRING. |
+ if (url.type != PP_VARTYPE_STRING) |
+ return PP_ERROR_BADARGUMENT; |
+ for (uint32_t i = 0; i < protocol_count; i++) |
+ if (protocols[i].type != PP_VARTYPE_STRING) |
+ return PP_ERROR_BADARGUMENT; |
+ |
+ // Validate url and convert it to WebURL. |
+ uint32_t utf8_length; |
+ const char* utf8 = var_interface_->VarToUtf8(url, &utf8_length); |
+ std::string url_string(utf8, utf8_length); |
+ |
+ GURL gurl(url_string); |
+ 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. |
+ utf8 = var_interface_->VarToUtf8(protocols[i], &utf8_length); |
+ if (!utf8_length) |
+ return PP_ERROR_BADARGUMENT; |
+ for (uint32_t caret = 0; caret < utf8_length; caret++) { |
+ uint8_t character = static_cast<uint8_t>(utf8[caret]); |
+ // 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(utf8, utf8_length); |
+ } |
+ WebString web_protocols = |
+ WebString::fromUTF8(protocol_string.data(), protocol_string.length()); |
+ |
+ // 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) { |
dmichael (off chromium)
2011/11/23 18:29:57
Same as above, you should not allow a blocking com
Takashi Toyoshima
2011/11/24 03:55:49
Done.
|
- // TODO(toyoshim): Implement it. |
- return PP_ERROR_NOTSUPPORTED; |
+ // Check mandarory interfaces. |
+ if (!var_interface_) |
+ return PP_ERROR_FAILED; |
+ |
+ if (!websocket_.get()) |
+ return PP_ERROR_FAILED; |
+ |
+ // 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. |
+ if (reason.type != PP_VARTYPE_STRING) |
+ return PP_ERROR_BADARGUMENT; |
+ uint32_t utf8_length; |
+ const char* utf8 = var_interface_->VarToUtf8(reason, &utf8_length); |
+ if (utf8_length > 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; |
+ websocket_->fail( |
+ "WebSocket is closed before the connection is established."); |
dmichael (off chromium)
2011/11/23 18:29:57
is->was, is->was ?
Takashi Toyoshima
2011/11/24 03:55:49
OK, I fixed.
Yuta,
I think WebCore::WebSocket has
|
+ return PP_OK_COMPLETIONPENDING; |
+ } |
+ |
+ // TODO(toyoshim): Handle bufferedAmount here. |
+ |
+ state_ = PP_WEBSOCKETREADYSTATE_CLOSING_DEV; |
+ WebString web_reason = WebString::fromUTF8(utf8, utf8_length); |
+ 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 mandatory interfaces. |
+ if (!var_interface_) |
+ return PP_ERROR_FAILED; |
+ |
+ // 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 |callbak|. |
dmichael (off chromium)
2011/11/23 18:29:57
nit: callbak->callback
Takashi Toyoshima
2011/11/24 03:55:49
Done.
|
+ wait_for_receive_ = true; |
+ receive_callback_var_ = message; |
+ receive_callback_ = callback; |
dmichael (off chromium)
2011/11/23 18:29:57
I don't see where these 3 get cleared. Should that
Takashi Toyoshima
2011/11/24 03:55:49
Done.
|
+ |
+ 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 (!var_interface_) |
+ return PP_ERROR_FAILED; |
+ |
+ 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. |
+ // TODO(toyoshim): Check message is valid utf8. |
+ uint32_t utf8_length; |
+ const char* utf8 = var_interface_->VarToUtf8(message, &utf8_length); |
+ WebString web_message = WebString::fromUTF8(utf8, utf8_length); |
+ 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(); |
+ return ReturnVarString(close_reason_); |
} |
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. |
+ return ReturnVarString(extensions_); |
} |
PP_Var PPB_WebSocket_Impl::GetProtocol() { |
- // TODO(toyoshim): Implement it. |
- return PP_MakeUndefined(); |
+ // TODO(toyoshim): Implement. |
+ return ReturnVarString(protocol_); |
} |
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(); |
+ return ReturnVarString(url_); |
+} |
+ |
+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) { |
+ // Check mandatory interface and states. |
+ if (!var_interface_) |
+ return; |
+ |
+ // Append received data to queue. |
+ std::string string = message.utf8(); |
+ PP_Var var = var_interface_->VarFromUtf8( |
+ pp::Module::Get()->pp_module(), string.data(), string.length()); |
dmichael (off chromium)
2011/11/23 17:41:24
Does the message really just represent a string? O
Takashi Toyoshima
2011/11/24 03:55:49
In the future, we must handle binary data in didRe
|
+ received_messages_.push(var); |
+ |
+ if (!wait_for_receive_) |
+ return; |
+ |
+ PP_RunAndClearCompletionCallback(&receive_callback_, DoReceive()); |
+} |
+ |
+void PPB_WebSocket_Impl::didReceiveBinaryData(const WebData& binaryData) { |
+ DLOG(WARNING) << "Not implemented yet."; |
+ // TODO(toyoshim): Support to receive binary data. |
+} |
+ |
+void PPB_WebSocket_Impl::didReceiveMessageError() { |
+ // TODO(toyoshim): Must implement. |
+ puts("didReceiveMessageError"); |
dmichael (off chromium)
2011/11/23 18:29:57
please don't use stdio directly. DLOG(INFO)?
Takashi Toyoshima
2011/11/24 03:55:49
Sorry, I miss removing them.
|
+} |
+ |
+void PPB_WebSocket_Impl::didStartClosingHandshake() { |
+ // TODO(toyoshim): Must implement. |
+ puts("didStartClosingHandshake"); |
+} |
+ |
+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_ = var_interface_->VarFromUtf8( |
+ pp::Module::Get()->pp_module(), |
+ reason_string.data(), |
+ reason_string.length()); |
+ |
+ // TODO(toyoshim): Set close_was_clean_. |
+ |
+ if (state_ == PP_WEBSOCKETREADYSTATE_CONNECTING_DEV) |
+ PP_RunAndClearCompletionCallback(&connect_callback_, PP_OK); |
+ |
+ DCHECK_NE(PP_WEBSOCKETREADYSTATE_CLOSED_DEV, state_); |
+ state_ = PP_WEBSOCKETREADYSTATE_CLOSED_DEV; |
dmichael (off chromium)
2011/11/23 18:29:57
I don't see where you ever invoke close_callback_?
Takashi Toyoshima
2011/11/24 03:55:49
Oh, sorry.
I must invoke around here.
|
+} |
+ |
+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(); |
+ return PP_OK; |
+} |
+ |
+PP_Var PPB_WebSocket_Impl::ReturnVarString(PP_Var var) { |
+ if (!var_interface_) |
+ return PP_MakeUndefined(); |
+ |
+ if (var.type == PP_VARTYPE_STRING) { |
+ var_interface_->AddRef(var); |
+ return var; |
+ } |
+ var_interface_->AddRef(empty_var_); |
+ return empty_var_; |
} |
} // namespace ppapi |