| Index: ppapi/tests/test_message_handler.cc
|
| diff --git a/ppapi/tests/test_message_handler.cc b/ppapi/tests/test_message_handler.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..54474f7033c4b0ef39251da6ba3c84269f3412f7
|
| --- /dev/null
|
| +++ b/ppapi/tests/test_message_handler.cc
|
| @@ -0,0 +1,297 @@
|
| +// Copyright 2014 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_message_handler.h"
|
| +
|
| +#include <string.h>
|
| +#include <algorithm>
|
| +#include <map>
|
| +#include <sstream>
|
| +
|
| +#include "ppapi/c/pp_var.h"
|
| +#include "ppapi/c/ppb_file_io.h"
|
| +#include "ppapi/c/ppp_message_handler.h"
|
| +#include "ppapi/cpp/file_io.h"
|
| +#include "ppapi/cpp/file_ref.h"
|
| +#include "ppapi/cpp/file_system.h"
|
| +#include "ppapi/cpp/instance.h"
|
| +#include "ppapi/cpp/module_impl.h"
|
| +#include "ppapi/cpp/var.h"
|
| +#include "ppapi/cpp/var_array.h"
|
| +#include "ppapi/cpp/var_array_buffer.h"
|
| +#include "ppapi/cpp/var_dictionary.h"
|
| +#include "ppapi/tests/pp_thread.h"
|
| +#include "ppapi/tests/test_utils.h"
|
| +#include "ppapi/tests/testing_instance.h"
|
| +
|
| +// Windows defines 'PostMessage', so we have to undef it.
|
| +#ifdef PostMessage
|
| +#undef PostMessage
|
| +#endif
|
| +
|
| +REGISTER_TEST_CASE(MessageHandler);
|
| +
|
| +namespace {
|
| +
|
| +// Created and destroyed on the main thread. All public methods should be called
|
| +// on the main thread. Most data members are only accessed on the main thread.
|
| +// (Though it handles messages on the background thread).
|
| +class EchoingMessageHandler {
|
| + public:
|
| + explicit EchoingMessageHandler(PP_Instance instance,
|
| + const pp::MessageLoop& loop)
|
| + : pp_instance_(instance),
|
| + message_handler_loop_(loop),
|
| + ppb_messaging_if_(static_cast<const PPB_Messaging_1_1*>(
|
| + pp::Module::Get()->GetBrowserInterface(
|
| + PPB_MESSAGING_INTERFACE_1_1))),
|
| + ppp_message_handler_if_(),
|
| + is_registered_(false),
|
| + test_finished_event_(instance),
|
| + destroy_event_(instance) {
|
| + AssertOnMainThread();
|
| + ppp_message_handler_if_.HandleMessage = &HandleMessage;
|
| + ppp_message_handler_if_.HandleBlockingMessage = &HandleBlockingMessage;
|
| + ppp_message_handler_if_.Destroy = &Destroy;
|
| + }
|
| + void Register() {
|
| + AssertOnMainThread();
|
| + assert(!is_registered_);
|
| + int32_t result = ppb_messaging_if_->RegisterMessageHandler(
|
| + pp_instance_,
|
| + this,
|
| + &ppp_message_handler_if_,
|
| + message_handler_loop_.pp_resource());
|
| + if (result == PP_OK) {
|
| + is_registered_ = true;
|
| + } else {
|
| + std::ostringstream stream;
|
| + stream << "Failed to register message handler; got error " << result;
|
| + AddError(stream.str());
|
| + test_finished_event_.Signal();
|
| + }
|
| + // Note, at this point, we can't safely read or write errors_ until we wait
|
| + // on destroy_event_.
|
| + }
|
| + void Unregister() {
|
| + AssertOnMainThread();
|
| + assert(is_registered_);
|
| + ppb_messaging_if_->UnregisterMessageHandler(pp_instance_);
|
| + is_registered_ = false;
|
| + }
|
| + void WaitForTestFinishedMessage() {
|
| + test_finished_event_.Wait();
|
| + test_finished_event_.Reset();
|
| + }
|
| + // Wait for Destroy() to be called on the MessageHandler thread. When it's
|
| + // done, return any errors that occurred during the time the MessageHandler
|
| + // was getting messages.
|
| + std::string WaitForDestroy() {
|
| + AssertOnMainThread();
|
| + // If we haven't called Unregister, we'll be waiting forever.
|
| + assert(!is_registered_);
|
| + destroy_event_.Wait();
|
| + destroy_event_.Reset();
|
| + // Now that we know Destroy() has been called, we know errors_ isn't being
|
| + // written on the MessageHandler thread anymore. So we can safely read it
|
| + // here on the main thread (since destroy_event_ gave us a memory barrier).
|
| + std::string temp_errors;
|
| + errors_.swap(temp_errors);
|
| + return temp_errors;
|
| + }
|
| + private:
|
| + static void AssertOnMainThread() {
|
| + assert(pp::MessageLoop::GetForMainThread() ==
|
| + pp::MessageLoop::GetCurrent());
|
| + }
|
| + void AddError(const std::string& error) {
|
| + if (!error.empty()) {
|
| + if (!errors_.empty())
|
| + errors_ += "<p>";
|
| + errors_ += error;
|
| + }
|
| + }
|
| + static void HandleMessage(PP_Instance instance,
|
| + void* user_data,
|
| + struct PP_Var message_data) {
|
| + EchoingMessageHandler* thiz =
|
| + static_cast<EchoingMessageHandler*>(user_data);
|
| + if (pp::MessageLoop::GetCurrent() != thiz->message_handler_loop_)
|
| + thiz->AddError("HandleMessage was called on the wrong thread!");
|
| + if (instance != thiz->pp_instance_)
|
| + thiz->AddError("HandleMessage was passed the wrong instance!");
|
| + pp::Var var(message_data);
|
| + if (var.is_string() && var.AsString() == "FINISHED_TEST")
|
| + thiz->test_finished_event_.Signal();
|
| + else
|
| + thiz->ppb_messaging_if_->PostMessage(instance, message_data);
|
| + }
|
| +
|
| + static PP_Var HandleBlockingMessage(PP_Instance instance,
|
| + void* user_data,
|
| + struct PP_Var message_data) {
|
| + EchoingMessageHandler* thiz =
|
| + static_cast<EchoingMessageHandler*>(user_data);
|
| + if (pp::MessageLoop::GetCurrent() != thiz->message_handler_loop_)
|
| + thiz->AddError("HandleBlockingMessage was called on the wrong thread!");
|
| + if (instance != thiz->pp_instance_)
|
| + thiz->AddError("HandleBlockingMessage was passed the wrong instance!");
|
| +
|
| + // The PP_Var we are passed is an in-parameter, so the browser is not
|
| + // giving us a ref-count. The ref-count it has will be decremented after we
|
| + // return. But we need to add a ref when returning a PP_Var, to pass to the
|
| + // caller.
|
| + pp::Var take_ref(message_data);
|
| + take_ref.Detach();
|
| + return message_data;
|
| + }
|
| +
|
| + static void Destroy(PP_Instance instance, void* user_data) {
|
| + EchoingMessageHandler* thiz =
|
| + static_cast<EchoingMessageHandler*>(user_data);
|
| + if (pp::MessageLoop::GetCurrent() != thiz->message_handler_loop_)
|
| + thiz->AddError("Destroy was called on the wrong thread!");
|
| + if (instance != thiz->pp_instance_)
|
| + thiz->AddError("Destroy was passed the wrong instance!");
|
| + thiz->destroy_event_.Signal();
|
| + }
|
| +
|
| + // These data members are initialized on the main thread, but don't change for
|
| + // the life of the object, so are safe to access on the background thread,
|
| + // because there will be a memory barrier before the the MessageHandler calls
|
| + // are invoked.
|
| + const PP_Instance pp_instance_;
|
| + const pp::MessageLoop message_handler_loop_;
|
| + const pp::MessageLoop main_loop_;
|
| + const PPB_Messaging_1_1* const ppb_messaging_if_;
|
| + // Spiritually, this member is const, but we can't initialize it in C++03,
|
| + // so it has to be non-const to be set in the constructor body.
|
| + PPP_MessageHandler_0_1 ppp_message_handler_if_;
|
| +
|
| + // is_registered_ is only read/written on the main thread.
|
| + bool is_registered_;
|
| +
|
| + // errors_ is written on the MessageHandler thread. When Destroy() is
|
| + // called, we stop writing to errors_ and signal destroy_event_. This causes
|
| + // a memory barrier, so it's safe to read errors_ after that.
|
| + std::string errors_;
|
| + NestedEvent test_finished_event_;
|
| + NestedEvent destroy_event_;
|
| +
|
| + // Undefined & private to disallow copy and assign.
|
| + EchoingMessageHandler(const EchoingMessageHandler&);
|
| + EchoingMessageHandler& operator=(const EchoingMessageHandler&);
|
| +};
|
| +
|
| +void FakeHandleMessage(PP_Instance instance,
|
| + void* user_data,
|
| + struct PP_Var message_data) {}
|
| +PP_Var FakeHandleBlockingMessage(PP_Instance instance,
|
| + void* user_data,
|
| + struct PP_Var message_data) {
|
| + return PP_MakeUndefined();
|
| +}
|
| +void FakeDestroy(PP_Instance instance, void* user_data) {}
|
| +
|
| +} // namespace
|
| +
|
| +TestMessageHandler::TestMessageHandler(TestingInstance* instance)
|
| + : TestCase(instance),
|
| + ppb_messaging_if_(NULL),
|
| + handler_thread_(instance) {
|
| +}
|
| +
|
| +TestMessageHandler::~TestMessageHandler() {
|
| + handler_thread_.Join();
|
| +}
|
| +
|
| +bool TestMessageHandler::Init() {
|
| + ppb_messaging_if_ = static_cast<const PPB_Messaging_1_1*>(
|
| + pp::Module::Get()->GetBrowserInterface(PPB_MESSAGING_INTERFACE_1_1));
|
| + return ppb_messaging_if_ &&
|
| + CheckTestingInterface() &&
|
| + handler_thread_.Start();
|
| +}
|
| +
|
| +void TestMessageHandler::RunTests(const std::string& filter) {
|
| + RUN_TEST(RegisterErrorConditions, filter);
|
| + RUN_TEST(PostMessageAndAwaitResponse, filter);
|
| +}
|
| +
|
| +void TestMessageHandler::HandleMessage(const pp::Var& message_data) {
|
| + // All messages should go to the background thread message handler.
|
| + assert(false);
|
| +}
|
| +
|
| +std::string TestMessageHandler::TestRegisterErrorConditions() {
|
| + {
|
| + // Test registering with the main thread as the message loop.
|
| + PPP_MessageHandler_0_1 fake_ppp_message_handler = {
|
| + &FakeHandleMessage, &FakeHandleBlockingMessage, &FakeDestroy
|
| + };
|
| + pp::MessageLoop main_loop = pp::MessageLoop::GetForMainThread();
|
| + int32_t result = ppb_messaging_if_->RegisterMessageHandler(
|
| + instance()->pp_instance(),
|
| + reinterpret_cast<void*>(0xdeadbeef),
|
| + &fake_ppp_message_handler,
|
| + main_loop.pp_resource());
|
| + ASSERT_EQ(PP_ERROR_WRONG_THREAD, result);
|
| + }
|
| + {
|
| + // Test registering with incomplete PPP_Messaging interface.
|
| + PPP_MessageHandler_0_1 bad_ppp_ifs[] = {
|
| + { NULL, &FakeHandleBlockingMessage, &FakeDestroy },
|
| + { &FakeHandleMessage, NULL, &FakeDestroy },
|
| + { &FakeHandleMessage, &FakeHandleBlockingMessage, NULL }};
|
| + for (size_t i = 0; i < sizeof(bad_ppp_ifs)/sizeof(bad_ppp_ifs[0]); ++i) {
|
| + int32_t result = ppb_messaging_if_->RegisterMessageHandler(
|
| + instance()->pp_instance(),
|
| + reinterpret_cast<void*>(0xdeadbeef),
|
| + &bad_ppp_ifs[i],
|
| + handler_thread_.message_loop().pp_resource());
|
| + ASSERT_EQ(PP_ERROR_BADARGUMENT, result);
|
| + }
|
| + }
|
| + PASS();
|
| +}
|
| +
|
| +std::string TestMessageHandler::TestPostMessageAndAwaitResponse() {
|
| + EchoingMessageHandler handler(instance()->pp_instance(),
|
| + handler_thread_.message_loop());
|
| + handler.Register();
|
| + std::string js_code("var plugin = document.getElementById('plugin');\n");
|
| + js_code += "var result = undefined;\n";
|
| + const char* const values_to_test[] = {
|
| + "5",
|
| + "undefined",
|
| + "1.5",
|
| + "'hello'",
|
| + "{'key': 'value', 'array_key': [1, 2, 3, 4, 5]}",
|
| + NULL
|
| + };
|
| + for (size_t i = 0; values_to_test[i]; ++i) {
|
| + js_code += "result = plugin.postMessageAndAwaitResponse(";
|
| + js_code += values_to_test[i];
|
| + js_code += ");\n";
|
| + js_code += "if (!deepCompare(result, ";
|
| + js_code += values_to_test[i];
|
| + js_code += "))\n";
|
| + js_code += " InternalError(\" Failed postMessageAndAwaitResponse for: ";
|
| + js_code += values_to_test[i];
|
| + js_code += " result: \" + result);\n";
|
| + }
|
| + // TODO(dmichael): Setting a property uses GetInstanceObject, which sends sync
|
| + // message, which can get interrupted with message to eval script, etc.
|
| + // FINISHED_WAITING message can therefore jump ahead. This test is
|
| + // currently carefully crafted to avoid races by doing all the JS in one call.
|
| + // That should be fixed before this API goes to stable. See crbug.com/384528
|
| + js_code += "plugin.postMessage('FINISHED_TEST');\n";
|
| + instance_->EvalScript(js_code);
|
| + handler.WaitForTestFinishedMessage();
|
| + handler.Unregister();
|
| + ASSERT_SUBTEST_SUCCESS(handler.WaitForDestroy());
|
| +
|
| + PASS();
|
| +}
|
| +
|
|
|