diff options
-rw-r--r-- | chrome/test/ui/ppapi_uitest.cc | 20 | ||||
-rw-r--r-- | ppapi/ppapi_sources.gypi | 2 | ||||
-rw-r--r-- | ppapi/tests/test_websocket.cc | 460 | ||||
-rw-r--r-- | ppapi/tests/test_websocket.h | 11 | ||||
-rw-r--r-- | ppapi/utility/websocket/websocket_api.cc | 143 | ||||
-rw-r--r-- | ppapi/utility/websocket/websocket_api.h | 130 |
6 files changed, 766 insertions, 0 deletions
diff --git a/chrome/test/ui/ppapi_uitest.cc b/chrome/test/ui/ppapi_uitest.cc index 03c40ae..ca57676 100644 --- a/chrome/test/ui/ppapi_uitest.cc +++ b/chrome/test/ui/ppapi_uitest.cc @@ -763,6 +763,16 @@ TEST_PPAPI_IN_PROCESS_WITH_WS(WebSocket_TextSendReceive) TEST_PPAPI_IN_PROCESS_WITH_WS(WebSocket_BinarySendReceive) TEST_PPAPI_IN_PROCESS_WITH_WS(WebSocket_BufferedAmount) TEST_PPAPI_IN_PROCESS_WITH_WS(MAYBE_WebSocket_CcInterfaces) +TEST_PPAPI_IN_PROCESS(WebSocket_UtilityInvalidConnect) +TEST_PPAPI_IN_PROCESS(WebSocket_UtilityProtocols) +TEST_PPAPI_IN_PROCESS(WebSocket_UtilityGetURL) +TEST_PPAPI_IN_PROCESS_WITH_WS(WebSocket_UtilityValidConnect) +TEST_PPAPI_IN_PROCESS_WITH_WS(WebSocket_UtilityInvalidClose) +TEST_PPAPI_IN_PROCESS_WITH_WS(WebSocket_UtilityValidClose) +TEST_PPAPI_IN_PROCESS_WITH_WS(WebSocket_UtilityGetProtocol) +TEST_PPAPI_IN_PROCESS_WITH_WS(WebSocket_UtilityTextSendReceive) +TEST_PPAPI_IN_PROCESS_WITH_WS(WebSocket_UtilityBinarySendReceive) +TEST_PPAPI_IN_PROCESS_WITH_WS(WebSocket_UtilityBufferedAmount) TEST_PPAPI_NACL_VIA_HTTP(WebSocket_IsWebSocket) TEST_PPAPI_NACL_VIA_HTTP(WebSocket_UninitializedPropertiesAccess) TEST_PPAPI_NACL_VIA_HTTP(WebSocket_InvalidConnect) @@ -776,6 +786,16 @@ TEST_PPAPI_NACL_VIA_HTTP_WITH_WS(WebSocket_TextSendReceive) TEST_PPAPI_NACL_VIA_HTTP_WITH_WS(WebSocket_BinarySendReceive) TEST_PPAPI_NACL_VIA_HTTP_WITH_WS(WebSocket_BufferedAmount) TEST_PPAPI_NACL_VIA_HTTP_WITH_WS(MAYBE_WebSocket_CcInterfaces) +TEST_PPAPI_NACL_VIA_HTTP(WebSocket_UtilityInvalidConnect) +TEST_PPAPI_NACL_VIA_HTTP(WebSocket_UtilityProtocols) +TEST_PPAPI_NACL_VIA_HTTP(WebSocket_UtilityGetURL) +TEST_PPAPI_NACL_VIA_HTTP_WITH_WS(WebSocket_UtilityValidConnect) +TEST_PPAPI_NACL_VIA_HTTP_WITH_WS(WebSocket_UtilityInvalidClose) +TEST_PPAPI_NACL_VIA_HTTP_WITH_WS(WebSocket_UtilityValidClose) +TEST_PPAPI_NACL_VIA_HTTP_WITH_WS(WebSocket_UtilityGetProtocol) +TEST_PPAPI_NACL_VIA_HTTP_WITH_WS(WebSocket_UtilityTextSendReceive) +TEST_PPAPI_NACL_VIA_HTTP_WITH_WS(WebSocket_UtilityBinarySendReceive) +TEST_PPAPI_NACL_VIA_HTTP_WITH_WS(WebSocket_UtilityBufferedAmount) TEST_PPAPI_IN_PROCESS(AudioConfig_ValidConfigs) TEST_PPAPI_IN_PROCESS(AudioConfig_InvalidConfigs) diff --git a/ppapi/ppapi_sources.gypi b/ppapi/ppapi_sources.gypi index cce3a54..b99e463 100644 --- a/ppapi/ppapi_sources.gypi +++ b/ppapi/ppapi_sources.gypi @@ -277,6 +277,8 @@ 'utility/graphics/paint_manager.h', 'utility/threading/simple_thread.cc', 'utility/threading/simple_thread.h', + 'utility/websocket/websocket_api.cc', + 'utility/websocket/websocket_api.h', ], # # Common Testing source for trusted and untrusted (NaCl) pugins. diff --git a/ppapi/tests/test_websocket.cc b/ppapi/tests/test_websocket.cc index b705ad5..ce01d67 100644 --- a/ppapi/tests/test_websocket.cc +++ b/ppapi/tests/test_websocket.cc @@ -17,10 +17,15 @@ #include "ppapi/c/ppb_websocket.h" #include "ppapi/cpp/instance.h" #include "ppapi/cpp/module.h" +#include "ppapi/cpp/var_array_buffer.h" #include "ppapi/cpp/websocket.h" #include "ppapi/tests/test_utils.h" #include "ppapi/tests/testing_instance.h" +#include "ppapi/utility/websocket/websocket_api.h" +// These servers are provided by pywebsocket server side handlers in +// LayoutTests/http/tests/websocket/tests/hybi/*_wsh. +// pywebsocket server itself is launched in ppapi_ui_test.cc. const char kEchoServerURL[] = "ws://localhost:8880/websocket/tests/hybi/echo"; @@ -46,6 +51,119 @@ const uint16_t kCloseCodeNormalClosure = 1000U; const uint64_t kCloseFrameSize = 6; const uint64_t kMessageFrameOverhead = 6; +namespace { + +struct WebSocketEvent { + enum EventType { + EVENT_OPEN, + EVENT_MESSAGE, + EVENT_ERROR, + EVENT_CLOSE + }; + + WebSocketEvent(EventType type, + bool was_clean, + uint16_t close_code, + const pp::Var& var) + : event_type(type), + was_clean(was_clean), + close_code(close_code), + var(var) {} + EventType event_type; + bool was_clean; + uint16_t close_code; + pp::Var var; +}; + +class TestWebSocketAPI : public pp::WebSocketAPI { + public: + explicit TestWebSocketAPI(pp::Instance* instance) + : pp::WebSocketAPI(instance), + connected_(false), + received_(false), + closed_(false), + wait_for_connected_(false), + wait_for_received_(false), + wait_for_closed_(false), + instance_(instance->pp_instance()) {} + + virtual void WebSocketDidOpen() { + events_.push_back( + WebSocketEvent(WebSocketEvent::EVENT_OPEN, true, 0U, pp::Var())); + connected_ = true; + if (wait_for_connected_) { + GetTestingInterface()->QuitMessageLoop(instance_); + wait_for_connected_ = false; + } + } + + virtual void WebSocketDidClose( + bool was_clean, uint16_t code, const pp::Var& reason) { + events_.push_back( + WebSocketEvent(WebSocketEvent::EVENT_CLOSE, was_clean, code, reason)); + connected_ = true; + closed_ = true; + if (wait_for_connected_ || wait_for_closed_) { + GetTestingInterface()->QuitMessageLoop(instance_); + wait_for_connected_ = false; + wait_for_closed_ = false; + } + } + + virtual void HandleWebSocketMessage(const pp::Var &message) { + events_.push_back( + WebSocketEvent(WebSocketEvent::EVENT_MESSAGE, true, 0U, message)); + received_ = true; + if (wait_for_received_) { + GetTestingInterface()->QuitMessageLoop(instance_); + wait_for_received_ = false; + received_ = false; + } + } + + virtual void HandleWebSocketError() { + events_.push_back( + WebSocketEvent(WebSocketEvent::EVENT_ERROR, true, 0U, pp::Var())); + } + + void WaitForConnected() { + if (!connected_) { + wait_for_connected_ = true; + GetTestingInterface()->RunMessageLoop(instance_); + } + } + + void WaitForReceived() { + if (!received_) { + wait_for_received_ = true; + GetTestingInterface()->RunMessageLoop(instance_); + } + } + + void WaitForClosed() { + if (!closed_) { + wait_for_closed_ = true; + GetTestingInterface()->RunMessageLoop(instance_); + } + } + + const std::vector<WebSocketEvent>& GetSeenEvents() const { + return events_; + } + + private: + std::vector<WebSocketEvent> events_; + bool connected_; + bool received_; + bool closed_; + bool wait_for_connected_; + bool wait_for_received_; + bool wait_for_closed_; + PP_Instance instance_; +}; + +} // namespace + REGISTER_TEST_CASE(WebSocket); bool TestWebSocket::Init() { @@ -80,6 +198,17 @@ void TestWebSocket::RunTests(const std::string& filter) { RUN_TEST_WITH_REFERENCE_CHECK(BufferedAmount, filter); RUN_TEST_WITH_REFERENCE_CHECK(CcInterfaces, filter); + + RUN_TEST_WITH_REFERENCE_CHECK(UtilityInvalidConnect, filter); + RUN_TEST_WITH_REFERENCE_CHECK(UtilityProtocols, filter); + RUN_TEST_WITH_REFERENCE_CHECK(UtilityGetURL, filter); + RUN_TEST_WITH_REFERENCE_CHECK(UtilityValidConnect, filter); + RUN_TEST_WITH_REFERENCE_CHECK(UtilityInvalidClose, filter); + RUN_TEST_WITH_REFERENCE_CHECK(UtilityValidClose, filter); + RUN_TEST_WITH_REFERENCE_CHECK(UtilityGetProtocol, filter); + RUN_TEST_WITH_REFERENCE_CHECK(UtilityTextSendReceive, filter); + RUN_TEST_WITH_REFERENCE_CHECK(UtilityBinarySendReceive, filter); + RUN_TEST_WITH_REFERENCE_CHECK(UtilityBufferedAmount, filter); } PP_Var TestWebSocket::CreateVarString(const char* string) { @@ -624,3 +753,334 @@ std::string TestWebSocket::TestCcInterfaces() { PASS(); } + +std::string TestWebSocket::TestUtilityInvalidConnect() { + const pp::Var protocols[] = { pp::Var() }; + + TestWebSocketAPI websocket(instance_); + int32_t result = websocket.Connect(pp::Var(), protocols, 1U); + ASSERT_EQ(PP_ERROR_BADARGUMENT, result); + ASSERT_EQ(0U, websocket.GetSeenEvents().size()); + + result = websocket.Connect(pp::Var(), protocols, 1U); + ASSERT_EQ(PP_ERROR_INPROGRESS, result); + ASSERT_EQ(0U, websocket.GetSeenEvents().size()); + + for (int i = 0; kInvalidURLs[i]; ++i) { + TestWebSocketAPI ws(instance_); + result = ws.Connect(pp::Var(std::string(kInvalidURLs[i])), protocols, 0U); + ASSERT_EQ(PP_ERROR_BADARGUMENT, result); + ASSERT_EQ(0U, ws.GetSeenEvents().size()); + } + + PASS(); +} + +std::string TestWebSocket::TestUtilityProtocols() { + const pp::Var bad_protocols[] = { + pp::Var(std::string("x-test")), pp::Var(std::string("x-test")) }; + const pp::Var good_protocols[] = { + pp::Var(std::string("x-test")), pp::Var(std::string("x-yatest")) }; + + { + TestWebSocketAPI websocket(instance_); + int32_t result = websocket.Connect( + pp::Var(std::string(kEchoServerURL)), bad_protocols, 2U); + ASSERT_EQ(PP_ERROR_BADARGUMENT, result); + ASSERT_EQ(0U, websocket.GetSeenEvents().size()); + } + + { + TestWebSocketAPI websocket(instance_); + int32_t result = websocket.Connect( + pp::Var(std::string(kEchoServerURL)), good_protocols, 2U); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + websocket.WaitForConnected(); + const std::vector<WebSocketEvent>& events = websocket.GetSeenEvents(); + // Protocol arguments are valid, but this test run without a WebSocket + // server. As a result, OnError() and OnClose() are invoked because of + // a connection establishment failure. + ASSERT_EQ(2U, events.size()); + ASSERT_EQ(WebSocketEvent::EVENT_ERROR, events[0].event_type); + ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[1].event_type); + ASSERT_FALSE(events[1].was_clean); + } + + PASS(); +} + +std::string TestWebSocket::TestUtilityGetURL() { + const pp::Var protocols[] = { pp::Var() }; + + for (int i = 0; kInvalidURLs[i]; ++i) { + TestWebSocketAPI websocket(instance_); + int32_t result = websocket.Connect( + pp::Var(std::string(kInvalidURLs[i])), protocols, 0U); + ASSERT_EQ(PP_ERROR_BADARGUMENT, result); + pp::Var url = websocket.GetURL(); + ASSERT_TRUE(AreEqualWithString(url.pp_var(), kInvalidURLs[i])); + ASSERT_EQ(0U, websocket.GetSeenEvents().size()); + } + + PASS(); +} + +std::string TestWebSocket::TestUtilityValidConnect() { + const pp::Var protocols[] = { pp::Var() }; + TestWebSocketAPI websocket(instance_); + int32_t result = websocket.Connect( + pp::Var(std::string(kEchoServerURL)), protocols, 0U); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + websocket.WaitForConnected(); + const std::vector<WebSocketEvent>& events = websocket.GetSeenEvents(); + ASSERT_EQ(1U, events.size()); + ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[0].event_type); + + PASS(); +} + +std::string TestWebSocket::TestUtilityInvalidClose() { + const pp::Var reason = pp::Var(std::string("close for test")); + + // Close before connect. + { + TestWebSocketAPI websocket(instance_); + int32_t result = websocket.Close(kCloseCodeNormalClosure, reason); + ASSERT_EQ(PP_ERROR_FAILED, result); + ASSERT_EQ(0U, websocket.GetSeenEvents().size()); + } + + // Close with bad arguments. + { + TestWebSocketAPI websocket(instance_); + int32_t result = websocket.Connect(pp::Var(std::string(kEchoServerURL)), + NULL, 0); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + websocket.WaitForConnected(); + result = websocket.Close(1U, reason); + ASSERT_EQ(PP_ERROR_NOACCESS, result); + const std::vector<WebSocketEvent>& events = websocket.GetSeenEvents(); + ASSERT_EQ(1U, events.size()); + ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[0].event_type); + } + + PASS(); +} + +std::string TestWebSocket::TestUtilityValidClose() { + std::string reason("close for test"); + pp::Var url = pp::Var(std::string(kCloseServerURL)); + + // Close. + { + TestWebSocketAPI websocket(instance_); + int32_t result = websocket.Connect(url, NULL, 0U); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + websocket.WaitForConnected(); + result = websocket.Close(kCloseCodeNormalClosure, pp::Var(reason)); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + websocket.WaitForClosed(); + const std::vector<WebSocketEvent>& events = websocket.GetSeenEvents(); + ASSERT_EQ(2U, events.size()); + ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[0].event_type); + ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[1].event_type); + ASSERT_TRUE(events[1].was_clean); + ASSERT_EQ(kCloseCodeNormalClosure, events[1].close_code); + ASSERT_TRUE(AreEqualWithString(events[1].var.pp_var(), reason.c_str())); + } + + // Close in connecting. + // The ongoing connect failed with PP_ERROR_ABORTED, then the close is done + // successfully. + { + TestWebSocketAPI websocket(instance_); + int32_t result = websocket.Connect(url, NULL, 0U); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + result = websocket.Close(kCloseCodeNormalClosure, pp::Var(reason)); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + websocket.WaitForClosed(); + const std::vector<WebSocketEvent>& events = websocket.GetSeenEvents(); + ASSERT_TRUE(events.size() == 2 || events.size() == 3); + int index = 0; + if (events.size() == 3) + ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[index++].event_type); + ASSERT_EQ(WebSocketEvent::EVENT_ERROR, events[index++].event_type); + ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[index].event_type); + ASSERT_FALSE(events[index].was_clean); + } + + // Close in closing. + // The first close will be done successfully, then the second one failed with + // with PP_ERROR_INPROGRESS immediately. + { + TestWebSocketAPI websocket(instance_); + int32_t result = websocket.Connect(url, NULL, 0U); + result = websocket.Close(kCloseCodeNormalClosure, pp::Var(reason)); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + result = websocket.Close(kCloseCodeNormalClosure, pp::Var(reason)); + ASSERT_EQ(PP_ERROR_INPROGRESS, result); + websocket.WaitForClosed(); + const std::vector<WebSocketEvent>& events = websocket.GetSeenEvents(); + ASSERT_TRUE(events.size() == 2 || events.size() == 3) + int index = 0; + if (events.size() == 3) + ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[index++].event_type); + ASSERT_EQ(WebSocketEvent::EVENT_ERROR, events[index++].event_type); + ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[index].event_type); + ASSERT_FALSE(events[index].was_clean); + } + + PASS(); +} + +std::string TestWebSocket::TestUtilityGetProtocol() { + const std::string protocol("x-chat"); + const pp::Var protocols[] = { pp::Var(protocol) }; + std::string url(kProtocolTestServerURL); + url += protocol; + TestWebSocketAPI websocket(instance_); + int32_t result = websocket.Connect(pp::Var(url), protocols, 1U); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + websocket.WaitForReceived(); + ASSERT_TRUE(AreEqualWithString( + websocket.GetProtocol().pp_var(), protocol.c_str())); + const std::vector<WebSocketEvent>& events = websocket.GetSeenEvents(); + // The server to which this test connect returns the decided protocol as a + // text frame message. So the WebSocketEvent records EVENT_MESSAGE event + // after EVENT_OPEN event. + ASSERT_EQ(2U, events.size()); + ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[0].event_type); + ASSERT_EQ(WebSocketEvent::EVENT_MESSAGE, events[1].event_type); + ASSERT_TRUE(AreEqualWithString(events[1].var.pp_var(), protocol.c_str())); + ASSERT_TRUE(events[1].was_clean); + + PASS(); +} + +std::string TestWebSocket::TestUtilityTextSendReceive() { + const pp::Var protocols[] = { pp::Var() }; + TestWebSocketAPI websocket(instance_); + int32_t result = + websocket.Connect(pp::Var(std::string(kEchoServerURL)), protocols, 0U); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + websocket.WaitForConnected(); + + // Send 'hello pepper'. + std::string message1("hello pepper"); + result = websocket.Send(pp::Var(std::string(message1))); + ASSERT_EQ(PP_OK, result); + + // Receive echoed 'hello pepper'. + websocket.WaitForReceived(); + + // Send 'goodbye pepper'. + std::string message2("goodbye pepper"); + result = websocket.Send(pp::Var(std::string(message2))); + + // Receive echoed 'goodbye pepper'. + websocket.WaitForReceived(); + + const std::vector<WebSocketEvent>& events = websocket.GetSeenEvents(); + ASSERT_EQ(3U, events.size()); + ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[0].event_type); + ASSERT_EQ(WebSocketEvent::EVENT_MESSAGE, events[1].event_type); + ASSERT_TRUE(AreEqualWithString(events[1].var.pp_var(), message1.c_str())); + ASSERT_EQ(WebSocketEvent::EVENT_MESSAGE, events[2].event_type); + ASSERT_TRUE(AreEqualWithString(events[2].var.pp_var(), message2.c_str())); + + PASS(); +} + +std::string TestWebSocket::TestUtilityBinarySendReceive() { + const pp::Var protocols[] = { pp::Var() }; + TestWebSocketAPI websocket(instance_); + int32_t result = + websocket.Connect(pp::Var(std::string(kEchoServerURL)), protocols, 0U); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + websocket.WaitForConnected(); + + // Send binary message. + pp::VarArrayBuffer message(256); + uint32_t len = message.ByteLength(); + uint8_t* data = static_cast<uint8_t*>(message.Map()); + for (uint32_t i = 0; i < len; ++i) + data[i] = i; + result = websocket.Send(message); + ASSERT_EQ(PP_OK, result); + + // Receive echoed binary message. + websocket.WaitForReceived(); + + const std::vector<WebSocketEvent>& events = websocket.GetSeenEvents(); + ASSERT_EQ(2U, events.size()); + ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[0].event_type); + ASSERT_EQ(WebSocketEvent::EVENT_MESSAGE, events[1].event_type); + ASSERT_TRUE(AreEqualWithBinary(events[1].var.pp_var(), data, len)); + + PASS(); +} + +std::string TestWebSocket::TestUtilityBufferedAmount() { + // Connect to test echo server. + const pp::Var protocols[] = { pp::Var() }; + TestWebSocketAPI websocket(instance_); + int32_t result = + websocket.Connect(pp::Var(std::string(kEchoServerURL)), protocols, 0U); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + websocket.WaitForConnected(); + + // Prepare a large message that is not aligned with the internal buffer + // sizes. + char message_char[8194]; + memset(message_char, 'x', 8193); + message_char[8193] = 0; + std::string message_str(message_char); + + uint64_t buffered_amount = 0; + uint32_t sent; + for (sent = 0; sent < 100; sent++) { + result = websocket.Send(pp::Var(message_str)); + ASSERT_EQ(PP_OK, result); + buffered_amount = websocket.GetBufferedAmount(); + // Buffered amount size 262144 is too big for the internal buffer size. + if (buffered_amount > 262144) + break; + } + + // Close connection. + std::string reason_str = "close while busy"; + result = websocket.Close(kCloseCodeNormalClosure, pp::Var(reason_str)); + ASSERT_EQ(PP_WEBSOCKETREADYSTATE_CLOSING, websocket.GetReadyState()); + websocket.WaitForClosed(); + ASSERT_EQ(PP_WEBSOCKETREADYSTATE_CLOSED, websocket.GetReadyState()); + + uint64_t base_buffered_amount = websocket.GetBufferedAmount(); + size_t events_on_closed = websocket.GetSeenEvents().size(); + + // After connection closure, all sending requests fail and just increase + // the bufferedAmount property. + result = websocket.Send(pp::Var(std::string(""))); + ASSERT_EQ(PP_ERROR_FAILED, result); + buffered_amount = websocket.GetBufferedAmount(); + ASSERT_EQ(base_buffered_amount + kMessageFrameOverhead, buffered_amount); + base_buffered_amount = buffered_amount; + + result = websocket.Send(pp::Var(reason_str)); + ASSERT_EQ(PP_ERROR_FAILED, result); + buffered_amount = websocket.GetBufferedAmount(); + uint64_t reason_frame_size = kMessageFrameOverhead + reason_str.length(); + ASSERT_EQ(base_buffered_amount + reason_frame_size, buffered_amount); + + const std::vector<WebSocketEvent>& events = websocket.GetSeenEvents(); + ASSERT_EQ(events_on_closed, events.size()); + ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[0].event_type); + size_t last_event = events_on_closed - 1; + for (uint32_t i = 1; i < last_event; ++i) { + ASSERT_EQ(WebSocketEvent::EVENT_MESSAGE, events[i].event_type); + ASSERT_TRUE(AreEqualWithString(events[i].var.pp_var(), message_char)); + } + ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[last_event].event_type); + ASSERT_TRUE(events[last_event].was_clean); + + PASS(); +} diff --git a/ppapi/tests/test_websocket.h b/ppapi/tests/test_websocket.h index f63d717..8024bb7 100644 --- a/ppapi/tests/test_websocket.h +++ b/ppapi/tests/test_websocket.h @@ -47,6 +47,17 @@ class TestWebSocket : public TestCase { std::string TestCcInterfaces(); + std::string TestUtilityInvalidConnect(); + std::string TestUtilityProtocols(); + std::string TestUtilityGetURL(); + std::string TestUtilityValidConnect(); + std::string TestUtilityInvalidClose(); + std::string TestUtilityValidClose(); + std::string TestUtilityGetProtocol(); + std::string TestUtilityTextSendReceive(); + std::string TestUtilityBinarySendReceive(); + std::string TestUtilityBufferedAmount(); + // Used by the tests that access the C API directly. const PPB_WebSocket* websocket_interface_; const PPB_Var* var_interface_; diff --git a/ppapi/utility/websocket/websocket_api.cc b/ppapi/utility/websocket/websocket_api.cc new file mode 100644 index 0000000..db4e01a --- /dev/null +++ b/ppapi/utility/websocket/websocket_api.cc @@ -0,0 +1,143 @@ +// 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/utility/websocket/websocket_api.h" + +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/pp_macros.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/module_impl.h" +#include "ppapi/cpp/var.h" +#include "ppapi/cpp/websocket.h" +#include "ppapi/utility/completion_callback_factory.h" + +namespace pp { + +class WebSocketAPI::Implement : public WebSocket { + public: + Implement(Instance* instance, WebSocketAPI* api) + : WebSocket(instance), + api_(api), + callback_factory_(PP_ALLOW_THIS_IN_INITIALIZER_LIST(this)) { + } + + virtual ~Implement() {} + + int32_t Connect(const Var& url, const Var protocols[], + uint32_t protocol_count) { + CompletionCallback callback = + callback_factory_.NewOptionalCallback(&Implement::DidConnect); + int32_t result = + WebSocket::Connect(url, protocols, protocol_count, callback); + if (result != PP_OK_COMPLETIONPENDING) { + // In synchronous cases, consumes callback here and invokes callback + // with PP_ERROR_ABORTED instead of result in order to avoid side effects + // in DidConnect. DidConnect ignores this invocation and doesn't call + // any delegate virtual method. + callback.Run(PP_ERROR_ABORTED); + } + return result; + } + + int32_t Close(uint16_t code, const Var& reason) { + CompletionCallback callback = + callback_factory_.NewOptionalCallback(&Implement::DidClose); + int32_t result = WebSocket::Close(code, reason, callback); + if (result != PP_OK_COMPLETIONPENDING) { + // In synchronous cases, consumes callback here and invokes callback + // with PP_ERROR_ABORTED instead of result in order to avoid side effects + // in DidConnect. DidConnect ignores this invocation and doesn't call + // any delegate virtual method. + callback.Run(PP_ERROR_ABORTED); + } + return result; + } + + void Receive() { + int32_t result; + do { + CompletionCallback callback = + callback_factory_.NewOptionalCallback(&Implement::DidReceive); + result = WebSocket::ReceiveMessage(&receive_message_var_, callback); + if (result != PP_OK_COMPLETIONPENDING) + callback.Run(result); + } while (result == PP_OK); + } + + void DidConnect(int32_t result) { + if (result == PP_OK) { + api_->WebSocketDidOpen(); + Receive(); + } else if (result != PP_ERROR_ABORTED) { + DidClose(result); + } + } + + void DidReceive(int32_t result) { + if (result == PP_OK) { + api_->HandleWebSocketMessage(receive_message_var_); + Receive(); + } else if (result != PP_ERROR_ABORTED) { + DidClose(result); + } + } + + void DidClose(int32_t result) { + if (result == PP_ERROR_ABORTED) + return; + bool was_clean = GetCloseWasClean(); + if (!was_clean) + api_->HandleWebSocketError(); + api_->WebSocketDidClose(was_clean, GetCloseCode(), GetCloseReason()); + } + + private: + WebSocketAPI* api_; + CompletionCallbackFactory<Implement> callback_factory_; + Var receive_message_var_; +}; + +WebSocketAPI::WebSocketAPI(Instance* instance) + : impl_(new Implement(instance, PP_ALLOW_THIS_IN_INITIALIZER_LIST(this))) { +} + +WebSocketAPI::~WebSocketAPI() { + delete impl_; +} + +int32_t WebSocketAPI::Connect(const Var& url, const Var protocols[], + uint32_t protocol_count) { + return impl_->Connect(url, protocols, protocol_count); +} + +int32_t WebSocketAPI::Close(uint16_t code, const Var& reason) { + return impl_->Close(code, reason); +} + +int32_t WebSocketAPI::Send(const Var& data) { + return impl_->SendMessage(data); +} + +uint64_t WebSocketAPI::GetBufferedAmount() { + return impl_->GetBufferedAmount(); +} + +Var WebSocketAPI::GetExtensions() { + return impl_->GetExtensions(); +} + +Var WebSocketAPI::GetProtocol() { + return impl_->GetProtocol(); +} + +PP_WebSocketReadyState WebSocketAPI::GetReadyState() { + return impl_->GetReadyState(); +} + +Var WebSocketAPI::GetURL() { + return impl_->GetURL(); +} + +} // namespace pp diff --git a/ppapi/utility/websocket/websocket_api.h b/ppapi/utility/websocket/websocket_api.h new file mode 100644 index 0000000..f733b1c --- /dev/null +++ b/ppapi/utility/websocket/websocket_api.h @@ -0,0 +1,130 @@ +// 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_UTILITY_WEBSOCKET_WEBSOCKET_API_H_ +#define PPAPI_UTILITY_WEBSOCKET_WEBSOCKET_API_H_ + +#include "ppapi/c/ppb_websocket.h" + +/// @file +/// This file defines the WebSocketAPI interface. + +namespace pp { + +class CompletionCallback; +class Instance; +class Var; + +/// The <code>WebSocketAPI</code> class +class WebSocketAPI { + public: + /// Constructs a WebSocketAPI object. + explicit WebSocketAPI(Instance* instance); + + /// Destructs a WebSocketAPI object. + virtual ~WebSocketAPI(); + + /// Connect() connects to the specified WebSocket server. Caller can call + /// this method at most once. + /// + /// @param[in] url A <code>Var</code> of string type representing a WebSocket + /// server URL. + /// @param[in] protocols A pointer to an array of string type + /// <code>Var</code> specifying sub-protocols. Each <code>Var</code> + /// represents one sub-protocol and its <code>PP_VarType</code> must be + /// <code>PP_VARTYPE_STRING</code>. This argument can be null only if + /// <code>protocol_count</code> is 0. + /// @param[in] protocol_count The number of sub-protocols in + /// <code>protocols</code>. + /// + /// @return An int32_t containing an error code from + /// <code>pp_errors.h</code>. + /// See also <code>pp::WebSocket::Connect</code>. + int32_t Connect(const Var& url, const Var protocols[], + uint32_t protocol_count); + + /// Close() closes the specified WebSocket connection by specifying + /// <code>code</code> and <code>reason</code>. + /// + /// @param[in] code The WebSocket close code. Ignored if it is 0. + /// @param[in] reason A <code>Var</code> of string type which represents the + /// WebSocket close reason. Ignored if it is undefined type. + /// + /// @return An int32_t containing an error code from + /// <code>pp_errors.h</code>. + /// See also <code>pp::WebSocket::Close</code>. + int32_t Close(uint16_t code, const Var& reason); + + /// Send() sends a message to the WebSocket server. + /// + /// @param[in] data A message to send. The message is copied to internal + /// buffer. So caller can free <code>data</code> safely after returning + /// from the function. + /// + /// @return An int32_t containing an error code from + /// <code>pp_errors.h</code>. + /// See also <code>pp::WebSocket::SendMessage</code>. + int32_t Send(const Var& data); + + /// GetBufferedAmount() returns the number of bytes of text and binary + /// messages that have been queued for the WebSocket connection to send but + /// have not been transmitted to the network yet. + /// + /// @return Returns the number of bytes. + uint64_t GetBufferedAmount(); + + /// GetExtensions() returns the extensions selected by the server for the + /// specified WebSocket connection. + /// + /// @return Returns a <code>Var</code> of string type. If called before the + /// connection is established, its data is empty string. + /// Currently its data is always an empty string. + Var GetExtensions(); + + /// GetProtocol() returns the sub-protocol chosen by the server for the + /// specified WebSocket connection. + /// + /// @return Returns a <code>Var</code> of string type. If called before the + /// connection is established, it contains the empty string. + Var GetProtocol(); + + /// GetReadyState() returns the ready state of the specified WebSocket + /// connection. + /// + /// @return Returns <code>PP_WEBSOCKETREADYSTATE_INVALID</code> if called + /// before connect() is called. + PP_WebSocketReadyState GetReadyState(); + + /// GetURL() returns the URL associated with specified WebSocket connection. + /// + /// @return Returns a <code>Var</code> of string type. If called before the + /// connection is established, it contains the empty string. + Var GetURL(); + + /// WebSocketDidOpen() is invoked when the connection is established by + /// Connect(). + virtual void WebSocketDidOpen() = 0; + + /// WebSocketDidClose() is invoked when the connection is closed by errors or + /// Close(). + virtual void WebSocketDidClose(bool wasClean, + uint16_t code, + const Var& reason) = 0; + + /// HandleWebSocketMessage() is invoked when a message is received. + virtual void HandleWebSocketMessage(const Var& message) = 0; + + /// HandleWebSocketError() is invoked if the user agent was required to fail + /// the WebSocket connection or the WebSocket connection is closed with + /// prejudice. DidClose() always follows HandleError(). + virtual void HandleWebSocketError() = 0; + + private: + class Implement; + Implement* impl_; +}; + +} // namespace pp + +#endif // PPAPI_UTILITY_WEBSOCKET_WEBSOCKET_API_H_ |