text: // 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/tests/test_websocket.h"
  
  #include <stdio.h>
  #include <string.h>
  
  #include <algorithm>
  #include <memory>
  #include <string>
  #include <vector>
  
  #include "ppapi/c/pp_bool.h"
  #include "ppapi/c/pp_completion_callback.h"
  #include "ppapi/c/pp_errors.h"
  #include "ppapi/c/pp_instance.h"
  #include "ppapi/c/pp_resource.h"
  #include "ppapi/c/pp_var.h"
  #include "ppapi/c/ppb_core.h"
  #include "ppapi/c/ppb_var.h"
  #include "ppapi/c/ppb_var_array_buffer.h"
  #include "ppapi/c/ppb_websocket.h"
  #include "ppapi/c/private/ppb_testing_private.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"
  
  // net::SpawnedTestServer serves WebSocket service for testing.
  // Following URLs are handled by pywebsocket handlers in
  // net/data/websocket/*_wsh.py.
  const char kEchoServerURL[] = "echo-with-no-extension";
  const char kCloseServerURL[] = "close";
  const char kCloseWithCodeAndReasonServerURL[] = "close-code-and-reason";
  const char kProtocolTestServerURL[] = "protocol-test?protocol=";
  
  const char* const kInvalidURLs[] = {
    "http://www.google.com/invalid_scheme",
    "ws://www.google.com/invalid#fragment",
    "ws://www.google.com:65535/invalid_port",
    NULL
  };
  
  // Internal packet sizes.
  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 ReleaseResourceDelegate : public TestCompletionCallback::Delegate {
   public:
    explicit ReleaseResourceDelegate(const PPB_Core* core_interface,
                                     PP_Resource resource)
        : core_interface_(core_interface),
          resource_(resource) {
    }
  
    // TestCompletionCallback::Delegate implementation.
    virtual void OnCallback(void* user_data, int32_t result) {
      if (resource_)
        core_interface_->ReleaseResource(resource_);
    }
  
   private:
    const PPB_Core* core_interface_;
    PP_Resource resource_;
  };
  
  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() {
    websocket_interface_ = static_cast<const PPB_WebSocket*>(
        pp::Module::Get()->GetBrowserInterface(PPB_WEBSOCKET_INTERFACE));
    var_interface_ = static_cast<const PPB_Var*>(
        pp::Module::Get()->GetBrowserInterface(PPB_VAR_INTERFACE));
    arraybuffer_interface_ = static_cast<const PPB_VarArrayBuffer*>(
        pp::Module::Get()->GetBrowserInterface(
            PPB_VAR_ARRAY_BUFFER_INTERFACE));
    core_interface_ = static_cast<const PPB_Core*>(
        pp::Module::Get()->GetBrowserInterface(PPB_CORE_INTERFACE));
    if (!websocket_interface_ || !var_interface_ || !arraybuffer_interface_ ||
        !core_interface_)
      return false;
  
    return CheckTestingInterface();
  }
  
  void TestWebSocket::RunTests(const std::string& filter) {
    RUN_TEST_WITH_REFERENCE_CHECK(IsWebSocket, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(UninitializedPropertiesAccess, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(InvalidConnect, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(Protocols, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(GetURL, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(ValidConnect, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(InvalidClose, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(ValidClose, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(GetProtocol, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(TextSendReceive, filter);
    RUN_TEST_BACKGROUND(TestWebSocket, TextSendReceiveTwice, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(BinarySendReceive, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(StressedSendReceive, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(BufferedAmount, filter);
    // PP_Resource for WebSocket may be released later because of an internal
    // reference for asynchronous IPC handling. So, suppress reference check on
    // the following AbortCallsWithCallback test.
    RUN_TEST(AbortCallsWithCallback, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(AbortSendMessageCall, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(AbortCloseCall, filter);
    RUN_TEST_WITH_REFERENCE_CHECK(AbortReceiveMessageCall, 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);
  }
  
  std::string TestWebSocket::GetFullURL(const char* url) {
    std::string rv = "ws://";
    // Some WebSocket tests don't start the server so there'll be no host and
    // port.
    if (instance_->websocket_host().empty())
      rv += "127.0.0.1";
    else
      rv += instance_->websocket_host();
    if (instance_->websocket_port() != -1) {
      char buffer[10];
      sprintf(buffer, ":%d", instance_->websocket_port());
      rv += std::string(buffer);
    }
    rv += "/";
    rv += url;
    return rv;
  }
  
  PP_Var TestWebSocket::CreateVarString(const std::string& string) {
    return var_interface_->VarFromUtf8(string.c_str(), string.size());
  }
  
  PP_Var TestWebSocket::CreateVarBinary(const std::vector<uint8_t>& binary) {
    PP_Var var = arraybuffer_interface_->Create(binary.size());
    uint8_t* var_data = static_cast<uint8_t*>(arraybuffer_interface_->Map(var));
    std::copy(binary.begin(), binary.end(), var_data);
    return var;
  }
  
  void TestWebSocket::ReleaseVar(const PP_Var& var) {
    var_interface_->Release(var);
  }
  
  bool TestWebSocket::AreEqualWithString(const PP_Var& var,
                                         const std::string& string) {
    if (var.type != PP_VARTYPE_STRING)
      return false;
    uint32_t utf8_length;
    const char* utf8 = var_interface_->VarToUtf8(var, &utf8_length);
    if (utf8_length != string.size())
      return false;
    if (string.compare(utf8))
      return false;
    return true;
  }
  
  bool TestWebSocket::AreEqualWithBinary(const PP_Var& var,
                                         const std::vector<uint8_t>& binary) {
    uint32_t buffer_size = 0;
    PP_Bool success = arraybuffer_interface_->ByteLength(var, &buffer_size);
    if (!success || buffer_size != binary.size())
      return false;
    if (!std::equal(binary.begin(), binary.end(),
        static_cast<uint8_t*>(arraybuffer_interface_->Map(var))))
      return false;
    return true;
  }
  
  PP_Resource TestWebSocket::Connect(const std::string& url,
                                     int32_t* result,
                                     const std::string& protocol) {
    PP_Var protocols[] = { PP_MakeUndefined() };
    PP_Resource ws = websocket_interface_->Create(instance_->pp_instance());
    if (!ws)
      return 0;
    PP_Var url_var = CreateVarString(url);
    TestCompletionCallback callback(instance_->pp_instance(), callback_type());
    uint32_t protocol_count = 0U;
    if (protocol.size()) {
      protocols[0] = CreateVarString(protocol);
      protocol_count = 1U;
    }
    callback.WaitForResult(websocket_interface_->Connect(
        ws, url_var, protocols, protocol_count,
        callback.GetCallback().pp_completion_callback()));
    ReleaseVar(url_var);
    if (protocol.size())
      ReleaseVar(protocols[0]);
    *result = callback.result();
    return ws;
  }
  
  void TestWebSocket::Send(int32_t /* result */, PP_Resource ws,
                           const std::string& message) {
    PP_Var message_var = CreateVarString(message);
    websocket_interface_->SendMessage(ws, message_var);
    ReleaseVar(message_var);
  }
  
  std::string TestWebSocket::TestIsWebSocket() {
    // Test that a NULL resource isn't a websocket.
    pp::Resource null_resource;
    PP_Bool result =
        websocket_interface_->IsWebSocket(null_resource.pp_resource());
    ASSERT_FALSE(result);
  
    PP_Resource ws = websocket_interface_->Create(instance_->pp_instance());
    ASSERT_TRUE(ws);
  
    result = websocket_interface_->IsWebSocket(ws);
    ASSERT_TRUE(result);
  
    core_interface_->ReleaseResource(ws);
  
    PASS();
  }
  
  std::string TestWebSocket::TestUninitializedPropertiesAccess() {
    PP_Resource ws = websocket_interface_->Create(instance_->pp_instance());
    ASSERT_TRUE(ws);
  
    uint64_t bufferedAmount = websocket_interface_->GetBufferedAmount(ws);
    ASSERT_EQ(0U, bufferedAmount);
  
    uint16_t close_code = websocket_interface_->GetCloseCode(ws);
    ASSERT_EQ(0U, close_code);
  
    PP_Var close_reason = websocket_interface_->GetCloseReason(ws);
    ASSERT_TRUE(AreEqualWithString(close_reason, std::string()));
    ReleaseVar(close_reason);
  
    PP_Bool close_was_clean = websocket_interface_->GetCloseWasClean(ws);
    ASSERT_EQ(PP_FALSE, close_was_clean);
  
    PP_Var extensions = websocket_interface_->GetExtensions(ws);
    ASSERT_TRUE(AreEqualWithString(extensions, std::string()));
    ReleaseVar(extensions);
  
    PP_Var protocol = websocket_interface_->GetProtocol(ws);
    ASSERT_TRUE(AreEqualWithString(protocol, std::string()));
    ReleaseVar(protocol);
  
    PP_WebSocketReadyState ready_state =
        websocket_interface_->GetReadyState(ws);
    ASSERT_EQ(PP_WEBSOCKETREADYSTATE_INVALID, ready_state);
  
    PP_Var url = websocket_interface_->GetURL(ws);
    ASSERT_TRUE(AreEqualWithString(url, std::string()));
    ReleaseVar(url);
  
    core_interface_->ReleaseResource(ws);
  
    PASS();
  }
  
  std::string TestWebSocket::TestInvalidConnect() {
    PP_Var protocols[] = { PP_MakeUndefined() };
  
    PP_Resource ws = websocket_interface_->Create(instance_->pp_instance());
    ASSERT_TRUE(ws);
  
    TestCompletionCallback callback(instance_->pp_instance(), callback_type());
    callback.WaitForResult(websocket_interface_->Connect(
        ws, PP_MakeUndefined(), protocols, 1U,
        callback.GetCallback().pp_completion_callback()));
    ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
  
    callback.WaitForResult(websocket_interface_->Connect(
        ws, PP_MakeUndefined(), protocols, 1U,
        callback.GetCallback().pp_completion_callback()));
    ASSERT_EQ(PP_ERROR_INPROGRESS, callback.result());
  
    core_interface_->ReleaseResource(ws);
  
    for (int i = 0; kInvalidURLs[i]; ++i) {
      int32_t result;
      ws = Connect(kInvalidURLs[i], &result, std::string());
      ASSERT_TRUE(ws);
      ASSERT_EQ(PP_ERROR_BADARGUMENT, result);
  
      core_interface_->ReleaseResource(ws);
    }
  
    PASS();
  }
  
  std::string TestWebSocket::TestProtocols() {
    PP_Var url = CreateVarString(GetFullURL(kEchoServerURL).c_str());
    PP_Var bad_protocols[] = {
      CreateVarString("x-test"),
      CreateVarString("x-test")
    };
    PP_Var good_protocols[] = {
      CreateVarString("x-test"),
      CreateVarString("x-yatest")
    };
  
    PP_Resource ws = websocket_interface_->Create(instance_->pp_instance());
    ASSERT_TRUE(ws);
    TestCompletionCallback callback(instance_->pp_instance(), callback_type());
    callback.WaitForResult(websocket_interface_->Connect(
        ws, url, bad_protocols, 2U,
        callback.GetCallback().pp_completion_callback()));
    ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
    core_interface_->ReleaseResource(ws);
  
    ws = websocket_interface_->Create(instance_->pp_instance());
    ASSERT_TRUE(ws);
    int32_t result = websocket_interface_->Connect(
        ws, url, good_protocols, 2U, PP_BlockUntilComplete());
    ASSERT_EQ(PP_ERROR_BLOCKS_MAIN_THREAD, result);
    core_interface_->ReleaseResource(ws);
  
    ReleaseVar(url);
    for (int i = 0; i < 2; ++i) {
      ReleaseVar(bad_protocols[i]);
      ReleaseVar(good_protocols[i]);
    }
    core_interface_->ReleaseResource(ws);
  
    PASS();
  }
  
  std::string TestWebSocket::TestGetURL() {
    for (int i = 0; kInvalidURLs[i]; ++i) {
      int32_t result;
      PP_Resource ws = Connect(kInvalidURLs[i], &result, std::string());
      ASSERT_TRUE(ws);
      PP_Var url = websocket_interface_->GetURL(ws);
      ASSERT_TRUE(AreEqualWithString(url, kInvalidURLs[i]));
      ASSERT_EQ(PP_ERROR_BADARGUMENT, result);
  
      ReleaseVar(url);
      core_interface_->ReleaseResource(ws);
    }
  
    PASS();
  }
  
  std::string TestWebSocket::TestValidConnect() {
    int32_t result;
    PP_Resource ws = Connect(GetFullURL(kEchoServerURL), &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    PP_Var extensions = websocket_interface_->GetExtensions(ws);
    ASSERT_TRUE(AreEqualWithString(extensions, std::string()));
    core_interface_->ReleaseResource(ws);
    ReleaseVar(extensions);
  
    PASS();
  }
  
  std::string TestWebSocket::TestInvalidClose() {
    PP_Var reason = CreateVarString("close for test");
    TestCompletionCallback callback(instance_->pp_instance(), callback_type());
    TestCompletionCallback async_callback(instance_->pp_instance(), PP_REQUIRED);
  
    // Close before connect.
    PP_Resource ws = websocket_interface_->Create(instance_->pp_instance());
    callback.WaitForResult(websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason,
        callback.GetCallback().pp_completion_callback()));
    ASSERT_EQ(PP_ERROR_FAILED, callback.result());
    core_interface_->ReleaseResource(ws);
  
    // Close with bad arguments.
    int32_t result;
    ws = Connect(GetFullURL(kEchoServerURL), &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    callback.WaitForResult(websocket_interface_->Close(
        ws, 1U, reason, callback.GetCallback().pp_completion_callback()));
    ASSERT_EQ(PP_ERROR_NOACCESS, callback.result());
    core_interface_->ReleaseResource(ws);
  
    // Close with PP_VARTYPE_NULL.
    ws = Connect(GetFullURL(kEchoServerURL), &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    callback.WaitForResult(websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, PP_MakeNull(),
        callback.GetCallback().pp_completion_callback()));
    ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
    core_interface_->ReleaseResource(ws);
  
    // Close with PP_VARTYPE_NULL and ongoing receive message.
    ws = Connect(GetFullURL(kEchoServerURL), &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    PP_Var receive_message_var;
    result = websocket_interface_->ReceiveMessage(
        ws, &receive_message_var,
        async_callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    callback.WaitForResult(websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, PP_MakeNull(),
        callback.GetCallback().pp_completion_callback()));
    ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
    const char* send_message = "hi";
    PP_Var send_message_var = CreateVarString(send_message);
    result = websocket_interface_->SendMessage(ws, send_message_var);
    ReleaseVar(send_message_var);
    ASSERT_EQ(PP_OK, result);
    async_callback.WaitForResult(PP_OK_COMPLETIONPENDING);
    ASSERT_EQ(PP_OK, async_callback.result());
    ASSERT_TRUE(AreEqualWithString(receive_message_var, send_message));
    ReleaseVar(receive_message_var);
    core_interface_->ReleaseResource(ws);
  
    // Close twice.
    ws = Connect(GetFullURL(kEchoServerURL), &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    result = websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason,
        async_callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    // Call another Close() before previous one is in progress.
    result = websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason,
        callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_ERROR_INPROGRESS, result);
    async_callback.WaitForResult(PP_OK_COMPLETIONPENDING);
    ASSERT_EQ(PP_OK, async_callback.result());
    // Call another Close() after previous one is completed.
    // This Close() must do nothing and reports no error.
    callback.WaitForResult(websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason,
        callback.GetCallback().pp_completion_callback()));
    ASSERT_EQ(PP_OK, callback.result());
    core_interface_->ReleaseResource(ws);
  
    ReleaseVar(reason);
  
    PASS();
  }
  
  std::string TestWebSocket::TestValidClose() {
    PP_Var reason = CreateVarString("close for test");
    PP_Var url = CreateVarString(GetFullURL(kEchoServerURL).c_str());
    PP_Var protocols[] = { PP_MakeUndefined() };
    TestCompletionCallback callback(instance_->pp_instance(), callback_type());
    TestCompletionCallback another_callback(
        instance_->pp_instance(), callback_type());
  
    // Close.
    int32_t result;
    PP_Resource ws = Connect(GetFullURL(kEchoServerURL), &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    callback.WaitForResult(websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason,
        callback.GetCallback().pp_completion_callback()));
    CHECK_CALLBACK_BEHAVIOR(callback);
    ASSERT_EQ(PP_OK, callback.result());
    core_interface_->ReleaseResource(ws);
  
    // Close without code and reason.
    ws = Connect(GetFullURL(kEchoServerURL), &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    callback.WaitForResult(websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED, reason,
        callback.GetCallback().pp_completion_callback()));
    ASSERT_EQ(PP_OK, callback.result());
    core_interface_->ReleaseResource(ws);
  
    // Close with PP_VARTYPE_UNDEFINED.
    ws = Connect(GetFullURL(kEchoServerURL), &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    callback.WaitForResult(websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, PP_MakeUndefined(),
        callback.GetCallback().pp_completion_callback()));
    CHECK_CALLBACK_BEHAVIOR(callback);
    ASSERT_EQ(PP_OK, callback.result());
    core_interface_->ReleaseResource(ws);
  
    // Close in connecting.
    // The ongoing connect failed with PP_ERROR_ABORTED, then the close is done
    // successfully.
    ws = websocket_interface_->Create(instance_->pp_instance());
    result = websocket_interface_->Connect(
        ws, url, protocols, 0U, callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    result = websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason,
        another_callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    callback.WaitForResult(PP_OK_COMPLETIONPENDING);
    ASSERT_EQ(PP_ERROR_ABORTED, callback.result());
    another_callback.WaitForResult(PP_OK_COMPLETIONPENDING);
    ASSERT_EQ(PP_OK, another_callback.result());
    core_interface_->ReleaseResource(ws);
  
    // Close in closing.
    // The first close will be done successfully, then the second one failed with
    // with PP_ERROR_INPROGRESS immediately.
    ws = Connect(GetFullURL(kEchoServerURL), &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    result = websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason,
        callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    result = websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason,
        another_callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_ERROR_INPROGRESS, result);
    callback.WaitForResult(PP_OK_COMPLETIONPENDING);
    ASSERT_EQ(PP_OK, callback.result());
    core_interface_->ReleaseResource(ws);
  
    // Close with ongoing receive message.
    ws = Connect(GetFullURL(kEchoServerURL), &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    PP_Var receive_message_var;
    result = websocket_interface_->ReceiveMessage(
        ws, &receive_message_var,
        callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    result = websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason,
        another_callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    callback.WaitForResult(PP_OK_COMPLETIONPENDING);
    ASSERT_EQ(PP_ERROR_ABORTED, callback.result());
    another_callback.WaitForResult(PP_OK_COMPLETIONPENDING);
    ASSERT_EQ(PP_OK, another_callback.result());
    core_interface_->ReleaseResource(ws);
  
    // Close with PP_VARTYPE_UNDEFINED and ongoing receive message.
    ws = Connect(GetFullURL(kEchoServerURL), &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    result = websocket_interface_->ReceiveMessage(
        ws, &receive_message_var,
        callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    result = websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, PP_MakeUndefined(),
        another_callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    callback.WaitForResult(PP_OK_COMPLETIONPENDING);
    ASSERT_EQ(PP_ERROR_ABORTED, callback.result());
    another_callback.WaitForResult(PP_OK_COMPLETIONPENDING);
    ASSERT_EQ(PP_OK, another_callback.result());
    core_interface_->ReleaseResource(ws);
  
    // Server initiated closing handshake.
    ws = Connect(
        GetFullURL(kCloseWithCodeAndReasonServerURL), &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    // Text messsage "1000 bye" requests the server to initiate closing handshake
    // with code being 1000 and reason being "bye".
    PP_Var close_request_var = CreateVarString("1000 bye");
    result = websocket_interface_->SendMessage(ws, close_request_var);
    ReleaseVar(close_request_var);
    callback.WaitForResult(websocket_interface_->ReceiveMessage(
        ws, &receive_message_var,
        callback.GetCallback().pp_completion_callback()));
    ASSERT_EQ(PP_ERROR_FAILED, callback.result());
    core_interface_->ReleaseResource(ws);
  
    ReleaseVar(reason);
    ReleaseVar(url);
  
    PASS();
  }
  
  std::string TestWebSocket::TestGetProtocol() {
    const char* expected_protocols[] = {
      "x-chat",
      "hoehoe",
      NULL
    };
    for (int i = 0; expected_protocols[i]; ++i) {
      std::string url(GetFullURL(kProtocolTestServerURL));
      url += expected_protocols[i];
      int32_t result;
      PP_Resource ws = Connect(url.c_str(), &result, expected_protocols[i]);
      ASSERT_TRUE(ws);
      ASSERT_EQ(PP_OK, result);
  
      PP_Var protocol = websocket_interface_->GetProtocol(ws);
      ASSERT_TRUE(AreEqualWithString(protocol, expected_protocols[i]));
  
      ReleaseVar(protocol);
      core_interface_->ReleaseResource(ws);
    }
  
    PASS();
  }
  
  std::string TestWebSocket::TestTextSendReceive() {
    // Connect to test echo server.
    int32_t connect_result;
    PP_Resource ws =
        Connect(GetFullURL(kEchoServerURL), &connect_result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, connect_result);
  
    // Send 'hello pepper' text message.
    const char* message = "hello pepper";
    PP_Var message_var = CreateVarString(message);
    int32_t result = websocket_interface_->SendMessage(ws, message_var);
    ReleaseVar(message_var);
    ASSERT_EQ(PP_OK, result);
  
    // Receive echoed 'hello pepper'.
    TestCompletionCallback callback(instance_->pp_instance(), callback_type());
    PP_Var received_message;
    callback.WaitForResult(websocket_interface_->ReceiveMessage(
        ws, &received_message, callback.GetCallback().pp_completion_callback()));
    ASSERT_EQ(PP_OK, callback.result());
    ASSERT_TRUE(AreEqualWithString(received_message, message));
    ReleaseVar(received_message);
    core_interface_->ReleaseResource(ws);
  
    PASS();
  }
  
  // Run as a BACKGROUND test.
  std::string TestWebSocket::TestTextSendReceiveTwice() {
    // Connect to test echo server.
    int32_t connect_result;
    PP_Resource ws =
        Connect(GetFullURL(kEchoServerURL), &connect_result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, connect_result);
    pp::MessageLoop message_loop = pp::MessageLoop::GetCurrent();
    pp::CompletionCallbackFactory<TestWebSocket> factory(this);
  
    message_loop.PostWork(factory.NewCallback(&TestWebSocket::Send,
                                              ws, std::string("hello")));
    // When the server receives 'Goodbye', it closes the session.
    message_loop.PostWork(factory.NewCallback(&TestWebSocket::Send,
                                              ws, std::string("Goodbye")));
    message_loop.PostQuit(false);
    message_loop.Run();
  
    TestCompletionCallback callback(instance_->pp_instance(), callback_type());
    PP_Var received_message;
    int32_t result = websocket_interface_->ReceiveMessage(
        ws, &received_message, callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK, result);
    // Since we don't run the message loop, the callback will stay
    // "pending and scheduled to run" state.
  
    // Waiting for the connection close which will be done by the server.
    while (true) {
      PP_WebSocketReadyState ready_state =
          websocket_interface_->GetReadyState(ws);
      if (ready_state != PP_WEBSOCKETREADYSTATE_CONNECTING &&
          ready_state != PP_WEBSOCKETREADYSTATE_OPEN) {
        break;
      }
      PlatformSleep(100);  // 100ms
    }
  
    // Cleanup the message loop
    message_loop.PostQuit(false);
    message_loop.Run();
  
    ASSERT_EQ(PP_OK, callback.result());
    ASSERT_TRUE(AreEqualWithString(received_message, "hello"));
    ReleaseVar(received_message);
    core_interface_->ReleaseResource(ws);
    PASS();
  }
  
  std::string TestWebSocket::TestBinarySendReceive() {
    // Connect to test echo server.
    int32_t connect_result;
    PP_Resource ws =
        Connect(GetFullURL(kEchoServerURL), &connect_result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, connect_result);
  
    // Send binary message.
    std::vector<uint8_t> binary(256);
    for (uint32_t i = 0; i < binary.size(); ++i)
      binary[i] = i;
    PP_Var message_var = CreateVarBinary(binary);
    int32_t result = websocket_interface_->SendMessage(ws, message_var);
    ReleaseVar(message_var);
    ASSERT_EQ(PP_OK, result);
  
    // Receive echoed binary.
    TestCompletionCallback callback(instance_->pp_instance(), callback_type());
    PP_Var received_message;
    callback.WaitForResult(websocket_interface_->ReceiveMessage(
        ws, &received_message, callback.GetCallback().pp_completion_callback()));
    ASSERT_EQ(PP_OK, callback.result());
    ASSERT_TRUE(AreEqualWithBinary(received_message, binary));
    ReleaseVar(received_message);
    core_interface_->ReleaseResource(ws);
  
    PASS();
  }
  
  std::string TestWebSocket::TestStressedSendReceive() {
    // Connect to test echo server.
    int32_t connect_result;
    PP_Resource ws =
        Connect(GetFullURL(kEchoServerURL), &connect_result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, connect_result);
  
    // Prepare PP_Var objects to send.
    const char* text = "hello pepper";
    PP_Var text_var = CreateVarString(text);
    std::vector<uint8_t> binary(256);
    for (uint32_t i = 0; i < binary.size(); ++i)
      binary[i] = i;
    PP_Var binary_var = CreateVarBinary(binary);
    // Prepare very large binary data over 64KiB. Object serializer in
    // ppapi_proxy has a limitation of 64KiB as maximum return PP_Var data size
    // to SRPC. In case received data over 64KiB exists, a specific code handles
    // this large data via asynchronous callback from main thread. This data
    // intends to test the code.
    std::vector<uint8_t> large_binary(65 * 1024);
    for (uint32_t i = 0; i < large_binary.size(); ++i)
      large_binary[i] = i & 0xff;
    PP_Var large_binary_var = CreateVarBinary(large_binary);
  
    // Send many messages.
    int32_t result;
    for (int i = 0; i < 256; ++i) {
      result = websocket_interface_->SendMessage(ws, text_var);
      ASSERT_EQ(PP_OK, result);
      result = websocket_interface_->SendMessage(ws, binary_var);
      ASSERT_EQ(PP_OK, result);
    }
    result = websocket_interface_->SendMessage(ws, large_binary_var);
    ASSERT_EQ(PP_OK, result);
    ReleaseVar(text_var);
    ReleaseVar(binary_var);
    ReleaseVar(large_binary_var);
  
    // Receive echoed data.
    TestCompletionCallback callback(instance_->pp_instance(), callback_type());
    for (int i = 0; i <= 512; ++i) {
      PP_Var received_message;
      callback.WaitForResult(websocket_interface_->ReceiveMessage(
          ws, &received_message,
          callback.GetCallback().pp_completion_callback()));
      ASSERT_EQ(PP_OK, callback.result());
      if (i == 512) {
        ASSERT_TRUE(AreEqualWithBinary(received_message, large_binary));
      } else if (i & 1) {
        ASSERT_TRUE(AreEqualWithBinary(received_message, binary));
      } else {
        ASSERT_TRUE(AreEqualWithString(received_message, text));
      }
      ReleaseVar(received_message);
    }
    core_interface_->ReleaseResource(ws);
  
    PASS();
  }
  
  std::string TestWebSocket::TestBufferedAmount() {
    // Connect to test echo server.
    int32_t connect_result;
    PP_Resource ws =
        Connect(GetFullURL(kEchoServerURL), &connect_result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, connect_result);
  
    // Prepare a large message that is not aligned with the internal buffer
    // sizes.
    std::string message(8193, 'x');
    PP_Var message_var = CreateVarString(message);
  
    uint64_t buffered_amount = 0;
    int32_t result;
    for (int i = 0; i < 100; i++) {
      result = websocket_interface_->SendMessage(ws, message_var);
      ASSERT_EQ(PP_OK, result);
      buffered_amount = websocket_interface_->GetBufferedAmount(ws);
      // 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";
    PP_Var reason = CreateVarString(reason_str.c_str());
    TestCompletionCallback callback(instance_->pp_instance(), callback_type());
    result = websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason,
        callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    ASSERT_EQ(PP_WEBSOCKETREADYSTATE_CLOSING,
        websocket_interface_->GetReadyState(ws));
  
    callback.WaitForResult(result);
    ASSERT_EQ(PP_OK, callback.result());
    ASSERT_EQ(PP_WEBSOCKETREADYSTATE_CLOSED,
        websocket_interface_->GetReadyState(ws));
  
    uint64_t base_buffered_amount = websocket_interface_->GetBufferedAmount(ws);
  
    // After connection closure, all sending requests fail and just increase
    // the bufferedAmount property.
    PP_Var empty_string = CreateVarString(std::string());
    result = websocket_interface_->SendMessage(ws, empty_string);
    ASSERT_EQ(PP_ERROR_FAILED, result);
    buffered_amount = websocket_interface_->GetBufferedAmount(ws);
    ASSERT_EQ(base_buffered_amount + kMessageFrameOverhead, buffered_amount);
    base_buffered_amount = buffered_amount;
  
    result = websocket_interface_->SendMessage(ws, reason);
    ASSERT_EQ(PP_ERROR_FAILED, result);
    buffered_amount = websocket_interface_->GetBufferedAmount(ws);
    uint64_t reason_frame_size = kMessageFrameOverhead + reason_str.length();
    ASSERT_EQ(base_buffered_amount + reason_frame_size, buffered_amount);
  
    ReleaseVar(message_var);
    ReleaseVar(reason);
    ReleaseVar(empty_string);
    core_interface_->ReleaseResource(ws);
  
    PASS();
  }
  
  // Test abort behaviors where a WebSocket PP_Resource is released while each
  // function is in-flight on the WebSocket PP_Resource.
  std::string TestWebSocket::TestAbortCallsWithCallback() {
    // Following tests make sure the behavior for functions which require a
    // callback. The callback must get a PP_ERROR_ABORTED.
  
    // Test the behavior for Connect().
    PP_Resource ws = websocket_interface_->Create(instance_->pp_instance());
    ASSERT_TRUE(ws);
    std::string url = GetFullURL(kEchoServerURL);
    PP_Var url_var = CreateVarString(url);
    TestCompletionCallback connect_callback(
        instance_->pp_instance(), callback_type());
    int32_t result = websocket_interface_->Connect(
        ws, url_var, NULL, 0,
        connect_callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    core_interface_->ReleaseResource(ws);
    connect_callback.WaitForResult(result);
    ASSERT_EQ(PP_ERROR_ABORTED, connect_callback.result());
  
    // Test the behavior for Close().
    ws = Connect(url, &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    PP_Var reason_var = CreateVarString("abort");
    TestCompletionCallback close_callback(
        instance_->pp_instance(), callback_type());
    result = websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason_var,
        close_callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    core_interface_->ReleaseResource(ws);
    close_callback.WaitForResult(result);
    ASSERT_EQ(PP_ERROR_ABORTED, close_callback.result());
    ReleaseVar(reason_var);
  
    // Test the behavior for ReceiveMessage().
    // Make sure the simplest case to wait for data which never arrives, here.
    ws = Connect(url, &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    PP_Var receive_var;
    TestCompletionCallback receive_callback(
        instance_->pp_instance(), callback_type());
    result = websocket_interface_->ReceiveMessage(
        ws, &receive_var,
        receive_callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    core_interface_->ReleaseResource(ws);
    receive_callback.WaitForResult(result);
    ASSERT_EQ(PP_ERROR_ABORTED, receive_callback.result());
  
    // Release the resource in the aborting receive completion callback which is
    // introduced by calling Close().
    ws = Connect(url, &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    result = websocket_interface_->ReceiveMessage(
        ws, &receive_var,
        receive_callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    ReleaseResourceDelegate receive_delegate(core_interface_, ws);
    receive_callback.SetDelegate(&receive_delegate);
    result = websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, PP_MakeUndefined(),
        close_callback.GetCallback().pp_completion_callback());
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    receive_callback.WaitForResult(result);
    CHECK_CALLBACK_BEHAVIOR(receive_callback);
    ASSERT_EQ(PP_ERROR_ABORTED, receive_callback.result());
    close_callback.WaitForResult(result);
    CHECK_CALLBACK_BEHAVIOR(close_callback);
    ASSERT_EQ(PP_ERROR_ABORTED, close_callback.result());
  
    ReleaseVar(url_var);
  
    PASS();
  }
  
  std::string TestWebSocket::TestAbortSendMessageCall() {
    // Test the behavior for SendMessage().
    // This function doesn't require a callback, but operation will be done
    // asynchronously in WebKit and browser process.
    std::vector<uint8_t> large_binary(65 * 1024);
    PP_Var large_var = CreateVarBinary(large_binary);
  
    int32_t result;
    std::string url = GetFullURL(kEchoServerURL);
    PP_Resource ws = Connect(url, &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    result = websocket_interface_->SendMessage(ws, large_var);
    ASSERT_EQ(PP_OK, result);
    core_interface_->ReleaseResource(ws);
    ReleaseVar(large_var);
  
    PASS();
  }
  
  std::string TestWebSocket::TestAbortCloseCall() {
    // Release the resource in the close completion callback.
    int32_t result;
    std::string url = GetFullURL(kEchoServerURL);
    PP_Resource ws = Connect(url, &result, std::string());
    ASSERT_TRUE(ws);
    ASSERT_EQ(PP_OK, result);
    TestCompletionCallback close_callback(
        instance_->pp_instance(), callback_type());
    result = websocket_interface_->Close(
        ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, PP_MakeUndefined(),
        close_callback.GetCallback().pp_completion_callback());
    ReleaseResourceDelegate close_delegate(core_interface_, ws);
    close_callback.SetDelegate(&close_delegate);
    close_callback.WaitForResult(result);
    CHECK_CALLBACK_BEHAVIOR(close_callback);
    ASSERT_EQ(PP_OK, close_callback.result());
  
    PASS();
  }
  
  std::string TestWebSocket::TestAbortReceiveMessageCall() {
    // Test the behavior where receive process might be in-flight.
    std::vector<uint8_t> large_binary(65 * 1024);
    PP_Var large_var = CreateVarBinary(large_binary);
    const char* text = "yukarin";
    PP_Var text_var = CreateVarString(text);
  
    std::string url = GetFullURL(kEchoServerURL);
    int32_t result;
    PP_Resource ws;
  
    // Each trial sends |trial_count| + 1 messages and receives just |trial|
    // number of message(s) before releasing the WebSocket. The WebSocket is
    // released while the next message is going to be received.
    const int trial_count = 8;
    for (int trial = 1; trial <= trial_count; trial++) {
      ws = Connect(url, &result, std::string());
      ASSERT_TRUE(ws);
      ASSERT_EQ(PP_OK, result);
      for (int i = 0; i <= trial_count; ++i) {
        result = websocket_interface_->SendMessage(ws, text_var);
        ASSERT_EQ(PP_OK, result);
      }
      TestCompletionCallback callback(instance_->pp_instance(), callback_type());
      PP_Var var;
      for (int i = 0; i < trial; ++i) {
        callback.WaitForResult(websocket_interface_->ReceiveMessage(
            ws, &var, callback.GetCallback().pp_completion_callback()));
        ASSERT_EQ(PP_OK, callback.result());
        ASSERT_TRUE(AreEqualWithString(var, text));
        ReleaseVar(var);
      }
      result = websocket_interface_->ReceiveMessage(
          ws, &var, callback.GetCallback().pp_completion_callback());
      core_interface_->ReleaseResource(ws);
      if (result != PP_OK) {
        callback.WaitForResult(result);
        ASSERT_EQ(PP_ERROR_ABORTED, callback.result());
      }
    }
    // Same test, but the last receiving message is large message over 64KiB.
    for (int trial = 1; trial <= trial_count; trial++) {
      ws = Connect(url, &result, std::string());
      ASSERT_TRUE(ws);
      ASSERT_EQ(PP_OK, result);
      for (int i = 0; i <= trial_count; ++i) {
        if (i == trial)
          result = websocket_interface_->SendMessage(ws, large_var);
        else
          result = websocket_interface_->SendMessage(ws, text_var);
        ASSERT_EQ(PP_OK, result);
      }
      TestCompletionCallback callback(instance_->pp_instance(), callback_type());
      PP_Var var;
      for (int i = 0; i < trial; ++i) {
        callback.WaitForResult(websocket_interface_->ReceiveMessage(
            ws, &var, callback.GetCallback().pp_completion_callback()));
        ASSERT_EQ(PP_OK, callback.result());
        ASSERT_TRUE(AreEqualWithString(var, text));
        ReleaseVar(var);
      }
      result = websocket_interface_->ReceiveMessage(
          ws, &var, callback.GetCallback().pp_completion_callback());
      core_interface_->ReleaseResource(ws);
      if (result != PP_OK) {
        callback.WaitForResult(result);
        ASSERT_EQ(PP_ERROR_ABORTED, callback.result());
      }
    }
  
    ReleaseVar(large_var);
    ReleaseVar(text_var);
  
    PASS();
  }
  
  std::string TestWebSocket::TestCcInterfaces() {
    // C++ bindings is simple straightforward, then just verifies interfaces work
    // as a interface bridge fine.
    pp::WebSocket ws(instance_);
  
    // Check uninitialized properties access.
    ASSERT_EQ(0, ws.GetBufferedAmount());
    ASSERT_EQ(0, ws.GetCloseCode());
    ASSERT_TRUE(AreEqualWithString(ws.GetCloseReason().pp_var(), std::string()));
    ASSERT_EQ(false, ws.GetCloseWasClean());
    ASSERT_TRUE(AreEqualWithString(ws.GetExtensions().pp_var(), std::string()));
    ASSERT_TRUE(AreEqualWithString(ws.GetProtocol().pp_var(), std::string()));
    ASSERT_EQ(PP_WEBSOCKETREADYSTATE_INVALID, ws.GetReadyState());
    ASSERT_TRUE(AreEqualWithString(ws.GetURL().pp_var(), std::string()));
  
    // Check communication interfaces (connect, send, receive, and close).
    TestCompletionCallback connect_callback(
        instance_->pp_instance(), callback_type());
    connect_callback.WaitForResult(ws.Connect(
        pp::Var(GetFullURL(kCloseServerURL)), NULL, 0U,
                connect_callback.GetCallback()));
    CHECK_CALLBACK_BEHAVIOR(connect_callback);
    ASSERT_EQ(PP_OK, connect_callback.result());
  
    std::string text_message("hello C++");
    int32_t result = ws.SendMessage(pp::Var(text_message));
    ASSERT_EQ(PP_OK, result);
  
    std::vector<uint8_t> binary(256);
    for (uint32_t i = 0; i < binary.size(); ++i)
      binary[i] = i;
    result = ws.SendMessage(
        pp::Var(pp::PASS_REF, CreateVarBinary(binary)));
    ASSERT_EQ(PP_OK, result);
  
    pp::Var text_receive_var;
    TestCompletionCallback text_receive_callback(
        instance_->pp_instance(), callback_type());
    text_receive_callback.WaitForResult(
        ws.ReceiveMessage(&text_receive_var,
                          text_receive_callback.GetCallback()));
    ASSERT_EQ(PP_OK, text_receive_callback.result());
    ASSERT_TRUE(
        AreEqualWithString(text_receive_var.pp_var(), text_message.c_str()));
  
    pp::Var binary_receive_var;
    TestCompletionCallback binary_receive_callback(
        instance_->pp_instance(), callback_type());
    binary_receive_callback.WaitForResult(
        ws.ReceiveMessage(&binary_receive_var,
                          binary_receive_callback.GetCallback()));
    ASSERT_EQ(PP_OK, binary_receive_callback.result());
    ASSERT_TRUE(AreEqualWithBinary(binary_receive_var.pp_var(), binary));
  
    TestCompletionCallback close_callback(
        instance_->pp_instance(), callback_type());
    std::string reason("bye");
    close_callback.WaitForResult(ws.Close(
        PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, pp::Var(reason),
        close_callback.GetCallback()));
    CHECK_CALLBACK_BEHAVIOR(close_callback);
    ASSERT_EQ(PP_OK, close_callback.result());
  
    // Check initialized properties access.
    ASSERT_EQ(0, ws.GetBufferedAmount());
    ASSERT_EQ(PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, ws.GetCloseCode());
    ASSERT_TRUE(
        AreEqualWithString(ws.GetCloseReason().pp_var(), reason.c_str()));
    ASSERT_EQ(true, ws.GetCloseWasClean());
    ASSERT_TRUE(AreEqualWithString(ws.GetProtocol().pp_var(), std::string()));
    ASSERT_EQ(PP_WEBSOCKETREADYSTATE_CLOSED, ws.GetReadyState());
    ASSERT_TRUE(AreEqualWithString(
        ws.GetURL().pp_var(), GetFullURL(kCloseServerURL).c_str()));
  
    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);
      if (result == PP_OK_COMPLETIONPENDING) {
        ws.WaitForClosed();
        const std::vector<WebSocketEvent>& events = ws.GetSeenEvents();
        ASSERT_EQ(WebSocketEvent::EVENT_ERROR, events[0].event_type);
        ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[1].event_type);
        ASSERT_EQ(2U, ws.GetSeenEvents().size());
      } else {
        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(GetFullURL(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(GetFullURL(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);
      if (result == PP_OK_COMPLETIONPENDING) {
        websocket.WaitForClosed();
        const std::vector<WebSocketEvent>& events = websocket.GetSeenEvents();
        ASSERT_EQ(WebSocketEvent::EVENT_ERROR, events[0].event_type);
        ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[1].event_type);
        ASSERT_EQ(2U, events.size());
      } else {
        ASSERT_EQ(PP_ERROR_BADARGUMENT, result);
        ASSERT_EQ(0U, websocket.GetSeenEvents().size());
      }
      pp::Var url = websocket.GetURL();
      ASSERT_TRUE(AreEqualWithString(url.pp_var(), kInvalidURLs[i]));
    }
  
    PASS();
  }
  
  std::string TestWebSocket::TestUtilityValidConnect() {
    const pp::Var protocols[] = { pp::Var() };
    TestWebSocketAPI websocket(instance_);
    int32_t result = websocket.Connect(
        pp::Var(GetFullURL(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);
    ASSERT_TRUE(
        AreEqualWithString(websocket.GetExtensions().pp_var(), std::string()));
  
    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(
          PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, 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(GetFullURL(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(GetFullURL(kCloseServerURL));
  
    // Close.
    {
      TestWebSocketAPI websocket(instance_);
      int32_t result = websocket.Connect(url, NULL, 0U);
      ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
      websocket.WaitForConnected();
      result = websocket.Close(
          PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, 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(PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, 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(
          PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, 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(
          PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, pp::Var(reason));
      ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
      result = websocket.Close(
          PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, 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(GetFullURL(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()));
  
    PASS();
  }
  
  std::string TestWebSocket::TestUtilityTextSendReceive() {
    const pp::Var protocols[] = { pp::Var() };
    TestWebSocketAPI websocket(instance_);
    int32_t result =
        websocket.Connect(pp::Var(GetFullURL(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(GetFullURL(kEchoServerURL)), protocols, 0U);
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    websocket.WaitForConnected();
  
    // Send binary message.
    uint32_t len = 256;
    std::vector<uint8_t> binary(len);
    for (uint32_t i = 0; i < len; ++i)
      binary[i] = i;
    pp::VarArrayBuffer message(len);
    uint8_t* var_data = static_cast<uint8_t*>(message.Map());
    std::copy(binary.begin(), binary.end(), var_data);
    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(), binary));
  
    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(GetFullURL(kEchoServerURL)), protocols, 0U);
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    websocket.WaitForConnected();
  
    // Prepare a large message that is not aligned with the internal buffer
    // sizes.
    std::string message(8193, 'x');
    uint64_t buffered_amount = 0;
    uint32_t sent;
    for (sent = 0; sent < 100; sent++) {
      result = websocket.Send(pp::Var(message));
      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 = "close while busy";
    result = websocket.Close(
        PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, pp::Var(reason));
    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));
    ASSERT_EQ(PP_ERROR_FAILED, result);
    buffered_amount = websocket.GetBufferedAmount();
    uint64_t reason_frame_size = kMessageFrameOverhead + reason.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));
    }
    ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[last_event].event_type);
    ASSERT_TRUE(events[last_event].was_clean);
  
    PASS();
  }
  