// 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_post_message.h" #include #include #include #include #include "ppapi/c/pp_var.h" #include "ppapi/c/ppb_file_io.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/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(PostMessage); namespace { const char kTestFilename[] = "testfile.txt"; const char kTestString[] = "Hello world!"; const bool kTestBool = true; const int32_t kTestInt = 42; const double kTestDouble = 42.0; // On Windows XP bots, the NonMainThread test can run very slowly. So we dial // back the number of threads & messages when running on Windows. #ifdef PPAPI_OS_WIN const int32_t kThreadsToRun = 2; const int32_t kMessagesToSendPerThread = 5; #else const int32_t kThreadsToRun = 4; const int32_t kMessagesToSendPerThread = 10; #endif // The struct that invoke_post_message_thread_func expects for its argument. // It includes the instance on which to invoke PostMessage, and the value to // pass to PostMessage. struct InvokePostMessageThreadArg { InvokePostMessageThreadArg(pp::Instance* i, const pp::Var& v) : instance(i), value_to_send(v) {} pp::Instance* instance; pp::Var value_to_send; }; void InvokePostMessageThreadFunc(void* user_data) { InvokePostMessageThreadArg* arg = static_cast(user_data); for (int32_t i = 0; i < kMessagesToSendPerThread; ++i) arg->instance->PostMessage(arg->value_to_send); delete arg; } // TODO(raymes): Consider putting something like this into pp::Var. bool VarsEqual(const pp::Var& expected, const pp::Var& actual, std::map* visited_ids) { if (expected.pp_var().type != actual.pp_var().type) { if (!expected.is_number() && !actual.is_number()) return false; } // TODO(raymes): Implement a pp::Var::IsRefCounted() function. if (expected.pp_var().type > PP_VARTYPE_DOUBLE) { std::map::const_iterator it = visited_ids->find(expected.pp_var().value.as_id); if (it != visited_ids->end()) { if (it->second == actual.pp_var().value.as_id) return true; return false; } // Round-tripping reference graphs with strings will not necessarily // result in isomorphic graphs. This is because string vars are converted // to string primitives in JS which cannot be referenced. if (!expected.is_string()) { (*visited_ids)[expected.pp_var().value.as_id] = actual.pp_var().value.as_id; } } if (expected.is_number()) { return fabs(expected.AsDouble() - actual.AsDouble()) < 1.0e-4; } else if (expected.is_array()) { pp::VarArray expected_array(expected); pp::VarArray actual_array(actual); if (expected_array.GetLength() != actual_array.GetLength()) return false; for (uint32_t i = 0; i < expected_array.GetLength(); ++i) { if (!VarsEqual(expected_array.Get(i), actual_array.Get(i), visited_ids)) return false; } return true; } else if (expected.is_dictionary()) { pp::VarDictionary expected_dict(expected); pp::VarDictionary actual_dict(actual); if (expected_dict.GetKeys().GetLength() != actual_dict.GetKeys().GetLength()) { return false; } for (uint32_t i = 0; i < expected_dict.GetKeys().GetLength(); ++i) { pp::Var key = expected_dict.GetKeys().Get(i); if (!actual_dict.HasKey(key)) return false; if (!VarsEqual(expected_dict.Get(key), actual_dict.Get(key), visited_ids)) return false; } return true; } else { return expected == actual; } } bool VarsEqual(const pp::Var& expected, const pp::Var& actual) { std::map visited_ids; return VarsEqual(expected, actual, &visited_ids); } class ScopedArrayBufferSizeSetter { public: ScopedArrayBufferSizeSetter(const PPB_Testing_Private* interface, PP_Instance instance, uint32_t threshold) : interface_(interface), instance_(instance) { interface_->SetMinimumArrayBufferSizeForShmem(instance_, threshold); } ~ScopedArrayBufferSizeSetter() { interface_->SetMinimumArrayBufferSizeForShmem(instance_, 0); } private: const PPB_Testing_Private* interface_; PP_Instance instance_; }; #define FINISHED_WAITING_MESSAGE "TEST_POST_MESSAGE_FINISHED_WAITING" } // namespace TestPostMessage::TestPostMessage(TestingInstance* instance) : TestCase(instance) { } TestPostMessage::~TestPostMessage() { instance_->PostMessage(pp::Var("This isn't guaranteed to be received, but " "shouldn't cause a crash.")); // Remove the special listener that only responds to a FINISHED_WAITING // string. See Init for where it gets added. std::string js_code; js_code += "var plugin = document.getElementById('plugin');" "plugin.removeEventListener('message'," " plugin.wait_for_messages_handler);" "delete plugin.wait_for_messages_handler;"; instance_->EvalScript(js_code); } bool TestPostMessage::Init() { bool success = CheckTestingInterface(); // Add a post condition to tests which caches the postMessage function and // then calls it after the instance is destroyed. The ensures that no UAF // occurs because the MessageChannel may still be alive after the plugin // instance is destroyed (it will get garbage collected eventually). instance_->EvalScript("window.pluginPostMessage = " "document.getElementById('plugin').postMessage"); instance_->AddPostCondition("window.pluginPostMessage('') === undefined"); // Set up a special listener that only responds to a FINISHED_WAITING string. // This is for use by WaitForMessages. std::string js_code; // Note the following code is dependent on some features of test_case.html. // E.g., it is assumed that the DOM element where the plugin is embedded has // an id of 'plugin', and there is a function 'IsTestingMessage' that allows // us to ignore the messages that are intended for use by the testing // framework itself. js_code += "var plugin = document.getElementById('plugin');" "var wait_for_messages_handler = function(message_event) {" " if (!IsTestingMessage(message_event.data) &&" " message_event.data === '" FINISHED_WAITING_MESSAGE "') {" " plugin.postMessage('" FINISHED_WAITING_MESSAGE "');" " }" "};" "plugin.addEventListener('message', wait_for_messages_handler);" // Stash it on the plugin so we can remove it in the destructor. "plugin.wait_for_messages_handler = wait_for_messages_handler;"; instance_->EvalScript(js_code); // Set up the JavaScript message event listener to echo the data part of the // message event back to us. success = success && AddEchoingListener("message_event.data"); message_data_.clear(); // Send a message that the first test will expect to receive. This is to // verify that we can send messages when the 'Instance::Init' function is on // the stack. instance_->PostMessage(pp::Var(kTestString)); return success; } void TestPostMessage::RunTests(const std::string& filter) { // Note: SendInInit must be first, because it expects to receive a message // that was sent in Init above. RUN_TEST(SendInInit, filter); RUN_TEST(SendingData, filter); RUN_TEST(SendingString, filter); RUN_TEST(SendingArrayBuffer, filter); RUN_TEST(SendingArray, filter); RUN_TEST(SendingDictionary, filter); RUN_TEST(SendingResource, filter); RUN_TEST(SendingComplexVar, filter); RUN_TEST(MessageEvent, filter); RUN_TEST(NoHandler, filter); RUN_TEST(ExtraParam, filter); if (testing_interface_->IsOutOfProcess()) RUN_TEST(NonMainThread, filter); } void TestPostMessage::HandleMessage(const pp::Var& message_data) { if (message_data.is_string() && (message_data.AsString() == FINISHED_WAITING_MESSAGE)) testing_interface_->QuitMessageLoop(instance_->pp_instance()); else message_data_.push_back(message_data); } bool TestPostMessage::AddEchoingListener(const std::string& expression) { std::string js_code; // Note the following code is dependent on some features of test_case.html. // E.g., it is assumed that the DOM element where the plugin is embedded has // an id of 'plugin', and there is a function 'IsTestingMessage' that allows // us to ignore the messages that are intended for use by the testing // framework itself. js_code += "var plugin = document.getElementById('plugin');" "var message_handler = function(message_event) {" " if (!IsTestingMessage(message_event.data) &&" " !(message_event.data === '" FINISHED_WAITING_MESSAGE "')) {" " plugin.postMessage("; js_code += expression; js_code += " );" " }" "};" "plugin.addEventListener('message', message_handler);" // Maintain an array of all event listeners, attached to the // plugin. This is so that we can easily remove them later (see // ClearListeners()). "if (!plugin.eventListeners) plugin.eventListeners = [];" "plugin.eventListeners.push(message_handler);"; instance_->EvalScript(js_code); return true; } bool TestPostMessage::PostMessageFromJavaScript(const std::string& func) { std::string js_code; js_code += "var plugin = document.getElementById('plugin');" "plugin.postMessage("; js_code += func + "()"; js_code += " );"; instance_->EvalScript(js_code); return true; } bool TestPostMessage::ClearListeners() { std::string js_code; js_code += "var plugin = document.getElementById('plugin');" "while (plugin.eventListeners.length) {" " plugin.removeEventListener('message'," " plugin.eventListeners.pop());" "}"; instance_->EvalScript(js_code); return true; } int TestPostMessage::WaitForMessages() { size_t message_size_before = message_data_.size(); // We first post a FINISHED_WAITING_MESSAGE. This should be guaranteed to // come back _after_ any other incoming messages that were already pending. instance_->PostMessage(pp::Var(FINISHED_WAITING_MESSAGE)); testing_interface_->RunMessageLoop(instance_->pp_instance()); // Now that the FINISHED_WAITING_MESSAGE has been echoed back to us, we know // that all pending messages have been slurped up. Return the number we // received (which may be zero). return static_cast(message_data_.size() - message_size_before); } std::string TestPostMessage::CheckMessageProperties( const pp::Var& test_data, const std::vector& properties_to_check) { typedef std::vector::const_iterator Iterator; for (Iterator iter = properties_to_check.begin(); iter != properties_to_check.end(); ++iter) { ASSERT_TRUE(AddEchoingListener(*iter)); message_data_.clear(); instance_->PostMessage(test_data); ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(1, WaitForMessages()); ASSERT_TRUE(message_data_.back().is_bool()); if (!message_data_.back().AsBool()) return std::string("Failed: ") + *iter; ASSERT_TRUE(message_data_.back().AsBool()); ASSERT_TRUE(ClearListeners()); } PASS(); } std::string TestPostMessage::TestSendInInit() { // Wait for the messages from Init() to be guaranteed to be sent. WaitForMessages(); // This test assumes Init already sent a message. ASSERT_EQ(1, message_data_.size()); ASSERT_TRUE(message_data_.back().is_string()); ASSERT_EQ(kTestString, message_data_.back().AsString()); message_data_.clear(); PASS(); } std::string TestPostMessage::TestSendingData() { // Clean up after previous tests. This also swallows the message sent by Init // if we didn't run the 'SendInInit' test. All tests other than 'SendInInit' // should start with these. WaitForMessages(); ASSERT_TRUE(ClearListeners()); // Set up the JavaScript message event listener to echo the data part of the // message event back to us. ASSERT_TRUE(AddEchoingListener("message_event.data")); // Test sending a message to JavaScript for each supported type. The JS sends // the data back to us, and we check that they match. message_data_.clear(); instance_->PostMessage(pp::Var(kTestBool)); ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(1, WaitForMessages()); ASSERT_TRUE(message_data_.back().is_bool()); ASSERT_EQ(message_data_.back().AsBool(), kTestBool); message_data_.clear(); instance_->PostMessage(pp::Var(kTestInt)); ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(1, WaitForMessages()); ASSERT_TRUE(message_data_.back().is_number()); ASSERT_DOUBLE_EQ(static_cast(kTestInt), message_data_.back().AsDouble()); message_data_.clear(); instance_->PostMessage(pp::Var(kTestDouble)); ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(1, WaitForMessages()); ASSERT_TRUE(message_data_.back().is_number()); ASSERT_DOUBLE_EQ(message_data_.back().AsDouble(), kTestDouble); message_data_.clear(); instance_->PostMessage(pp::Var()); ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(1, WaitForMessages()); ASSERT_TRUE(message_data_.back().is_undefined()); message_data_.clear(); instance_->PostMessage(pp::Var(pp::Var::Null())); ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(1, WaitForMessages()); ASSERT_TRUE(message_data_.back().is_null()); message_data_.clear(); ASSERT_TRUE(ClearListeners()); PASS(); } std::string TestPostMessage::TestSendingString() { // Clean up after previous tests. This also swallows the message sent by Init // if we didn't run the 'SendInInit' test. All tests other than 'SendInInit' // should start with these. WaitForMessages(); ASSERT_TRUE(ClearListeners()); // Test that a string var is converted to a primitive JS string. message_data_.clear(); std::vector properties_to_check; properties_to_check.push_back( "typeof message_event.data === 'string'"); ASSERT_SUBTEST_SUCCESS(CheckMessageProperties(kTestString, properties_to_check)); ASSERT_TRUE(AddEchoingListener("message_event.data")); message_data_.clear(); instance_->PostMessage(pp::Var(kTestString)); // PostMessage is asynchronous, so we should not receive a response yet. ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(1, WaitForMessages()); ASSERT_TRUE(message_data_.back().is_string()); ASSERT_EQ(message_data_.back().AsString(), kTestString); message_data_.clear(); ASSERT_TRUE(ClearListeners()); PASS(); } std::string TestPostMessage::TestSendingArrayBuffer() { // Clean up after previous tests. This also swallows the message sent by Init // if we didn't run the 'SendInInit' test. All tests other than 'SendInInit' // should start with these. WaitForMessages(); ASSERT_TRUE(ClearListeners()); // TODO(sehr,dmichael): Add testing of longer array buffers when // crbug.com/110086 is fixed. ScopedArrayBufferSizeSetter setter(testing_interface_, instance_->pp_instance(), 200); uint32_t sizes[] = { 0, 100, 1000, 10000 }; for (size_t i = 0; i < sizeof(sizes)/sizeof(sizes[i]); ++i) { std::ostringstream size_stream; size_stream << sizes[i]; const std::string kSizeAsString(size_stream.str()); // Create an appropriately sized array buffer with test_data[i] == i. pp::VarArrayBuffer test_data(sizes[i]); if (sizes[i] > 0) ASSERT_NE(NULL, test_data.Map()); // Make sure we can Unmap/Map successfully (there's not really any way to // detect if it's unmapped, so we just re-map before getting the pointer to // the buffer). test_data.Unmap(); test_data.Map(); ASSERT_EQ(sizes[i], test_data.ByteLength()); unsigned char* buff = static_cast(test_data.Map()); const uint32_t kByteLength = test_data.ByteLength(); for (size_t j = 0; j < kByteLength; ++j) buff[j] = static_cast(j % 256u); // Have the listener test some properties of the ArrayBuffer. std::vector properties_to_check; properties_to_check.push_back( "message_event.data.constructor.name === 'ArrayBuffer'"); properties_to_check.push_back( std::string("message_event.data.byteLength === ") + kSizeAsString); if (sizes[i] > 0) { properties_to_check.push_back( "(new DataView(message_event.data)).getUint8(0) == 0"); // Checks that the last element has the right value: (byteLength-1)%256. std::string received_byte("(new DataView(message_event.data)).getUint8(" " message_event.data.byteLength-1)"); std::string expected_byte("(message_event.data.byteLength-1)%256"); properties_to_check.push_back(received_byte + " == " + expected_byte); } ASSERT_SUBTEST_SUCCESS(CheckMessageProperties(test_data, properties_to_check)); // Set up the JavaScript message event listener to echo the data part of the // message event back to us. ASSERT_TRUE(AddEchoingListener("message_event.data")); message_data_.clear(); instance_->PostMessage(test_data); // PostMessage is asynchronous, so we should not receive a response yet. ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(1, WaitForMessages()); ASSERT_TRUE(message_data_.back().is_array_buffer()); pp::VarArrayBuffer received(message_data_.back()); message_data_.clear(); ASSERT_EQ(test_data.ByteLength(), received.ByteLength()); unsigned char* received_buff = static_cast(received.Map()); // The buffer should be copied, so this should be a distinct buffer. When // 'transferrables' are implemented for PPAPI, we'll also want to test that // we get the _same_ buffer back when it's transferred. if (sizes[i] > 0) ASSERT_NE(buff, received_buff); for (size_t i = 0; i < test_data.ByteLength(); ++i) ASSERT_EQ(buff[i], received_buff[i]); message_data_.clear(); ASSERT_TRUE(ClearListeners()); } PASS(); } std::string TestPostMessage::TestSendingArray() { // Clean up after previous tests. This also swallows the message sent by Init // if we didn't run the 'SendInInit' test. All tests other than 'SendInInit' // should start with these. WaitForMessages(); ASSERT_TRUE(ClearListeners()); pp::VarArray array; array.Set(0, pp::Var(kTestBool)); array.Set(1, pp::Var(kTestString)); // Purposely leave index 2 empty. array.Set(3, pp::Var(kTestInt)); array.Set(4, pp::Var(kTestDouble)); std::stringstream ss; ss << array.GetLength(); std::string length_as_string(ss.str()); // Have the listener test some properties of the Array. std::vector properties_to_check; properties_to_check.push_back( "message_event.data.constructor.name === 'Array'"); properties_to_check.push_back( std::string("message_event.data.length === ") + length_as_string); // Check that the string is converted to a primitive JS string. properties_to_check.push_back( std::string("typeof message_event.data[1] === 'string'")); ASSERT_SUBTEST_SUCCESS(CheckMessageProperties(array, properties_to_check)); // Set up the JavaScript message event listener to echo the data part of the // message event back to us. ASSERT_TRUE(AddEchoingListener("message_event.data")); message_data_.clear(); instance_->PostMessage(array); // PostMessage is asynchronous, so we should not receive a response yet. ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(1, WaitForMessages()); ASSERT_TRUE(message_data_.back().is_array()); ASSERT_TRUE(VarsEqual(array, message_data_.back())); message_data_.clear(); ASSERT_TRUE(ClearListeners()); PASS(); } std::string TestPostMessage::TestSendingDictionary() { // Clean up after previous tests. This also swallows the message sent by Init // if we didn't run the 'SendInInit' test. All tests other than 'SendInInit' // should start with these. WaitForMessages(); ASSERT_TRUE(ClearListeners()); pp::VarDictionary dictionary; dictionary.Set(pp::Var("foo"), pp::Var(kTestBool)); dictionary.Set(pp::Var("bar"), pp::Var(kTestString)); dictionary.Set(pp::Var("abc"), pp::Var(kTestInt)); dictionary.Set(pp::Var("def"), pp::Var()); std::stringstream ss; ss << dictionary.GetKeys().GetLength(); std::string length_as_string(ss.str()); // Have the listener test some properties of the Dictionary. std::vector properties_to_check; properties_to_check.push_back( "message_event.data.constructor.name === 'Object'"); properties_to_check.push_back( std::string("Object.keys(message_event.data).length === ") + length_as_string); // Check that the string is converted to a primitive JS string. properties_to_check.push_back( std::string("typeof message_event.data['bar'] === 'string'")); ASSERT_SUBTEST_SUCCESS(CheckMessageProperties(dictionary, properties_to_check)); // Set up the JavaScript message event listener to echo the data part of the // message event back to us. ASSERT_TRUE(AddEchoingListener("message_event.data")); message_data_.clear(); instance_->PostMessage(dictionary); // PostMessage is asynchronous, so we should not receive a response yet. ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(1, WaitForMessages()); ASSERT_TRUE(message_data_.back().is_dictionary()); ASSERT_TRUE(VarsEqual(dictionary, message_data_.back())); message_data_.clear(); ASSERT_TRUE(ClearListeners()); PASS(); } std::string TestPostMessage::TestSendingResource() { // Clean up after previous tests. This also swallows the message sent by Init // if we didn't run the 'SendInInit' test. All tests other than 'SendInInit' // should start with these. WaitForMessages(); message_data_.clear(); ASSERT_TRUE(ClearListeners()); std::string file_path("/"); file_path += kTestFilename; int content_length = static_cast(strlen(kTestString)); // Create a file in the HTML5 temporary file system, in the Pepper plugin. TestCompletionCallback callback(instance_->pp_instance(), callback_type()); pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); callback.WaitForResult(file_system.Open(1024, callback.GetCallback())); CHECK_CALLBACK_BEHAVIOR(callback); ASSERT_EQ(PP_OK, callback.result()); pp::FileRef write_file_ref(file_system, file_path.c_str()); // Write to the file. pp::FileIO write_file_io(instance_); ASSERT_NE(0, write_file_io.pp_resource()); callback.WaitForResult( write_file_io.Open(write_file_ref, PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_CREATE, callback.GetCallback())); CHECK_CALLBACK_BEHAVIOR(callback); ASSERT_EQ(PP_OK, callback.result()); callback.WaitForResult(write_file_io.Write( 0, kTestString, content_length, callback.GetCallback())); CHECK_CALLBACK_BEHAVIOR(callback); ASSERT_EQ(callback.result(), content_length); write_file_io.Close(); // Pass the file system to JavaScript and have the listener test some // properties of the file system. pp::Var file_system_var(file_system); std::vector properties_to_check; properties_to_check.push_back( "message_event.data.constructor.name === 'DOMFileSystem'"); properties_to_check.push_back( "message_event.data.root.constructor.name === 'DirectoryEntry'"); properties_to_check.push_back( "message_event.data.name.indexOf(" " ':Temporary'," " message_event.data.name.length - ':Temporary'.length) !== -1"); ASSERT_SUBTEST_SUCCESS(CheckMessageProperties(file_system_var, properties_to_check)); // Set up the JavaScript message event listener to echo the data part of the // message event back to us. ASSERT_TRUE(AddEchoingListener("message_event.data")); // Send the file system in a message from the Pepper plugin to JavaScript. message_data_.clear(); instance_->PostMessage(file_system_var); // PostMessage is asynchronous, so we should not receive a response yet. ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(1, WaitForMessages()); // The JavaScript should have posted the file system back to us. Verify that // it is a file system and read the file contents that we wrote earlier. pp::Var var = message_data_.back(); ASSERT_TRUE(var.is_resource()); pp::Resource result = var.AsResource(); ASSERT_TRUE(pp::FileSystem::IsFileSystem(result)); { pp::FileSystem received_file_system(result); pp::FileRef file_ref(received_file_system, file_path.c_str()); ASSERT_NE(0, file_ref.pp_resource()); // Ensure that the file can be queried. TestCompletionCallbackWithOutput cc(instance_->pp_instance(), callback_type()); cc.WaitForResult(file_ref.Query(cc.GetCallback())); CHECK_CALLBACK_BEHAVIOR(cc); ASSERT_EQ(PP_OK, cc.result()); ASSERT_EQ(cc.output().size, content_length); // Read the file and test that its contents match. pp::FileIO file_io(instance_); ASSERT_NE(0, file_io.pp_resource()); callback.WaitForResult( file_io.Open(file_ref, PP_FILEOPENFLAG_READ, callback.GetCallback())); CHECK_CALLBACK_BEHAVIOR(callback); ASSERT_EQ(PP_OK, callback.result()); std::vector buffer_vector(content_length); char* buffer = &buffer_vector[0]; // Note: Not null-terminated! callback.WaitForResult( file_io.Read(0, buffer, content_length, callback.GetCallback())); CHECK_CALLBACK_BEHAVIOR(callback); ASSERT_EQ(callback.result(), content_length); ASSERT_EQ(0, memcmp(buffer, kTestString, content_length)); } WaitForMessages(); message_data_.clear(); ASSERT_TRUE(ClearListeners()); PASS(); } std::string TestPostMessage::TestSendingComplexVar() { // Clean up after previous tests. This also swallows the message sent by Init // if we didn't run the 'SendInInit' test. All tests other than 'SendInInit' // should start with these. WaitForMessages(); message_data_.clear(); ASSERT_TRUE(ClearListeners()); pp::Var string(kTestString); pp::VarDictionary dictionary; dictionary.Set(pp::Var("foo"), pp::Var(kTestBool)); dictionary.Set(pp::Var("bar"), string); dictionary.Set(pp::Var("abc"), pp::Var(kTestInt)); dictionary.Set(pp::Var("def"), pp::Var()); // Reference to array. pp::VarArray array; array.Set(0, pp::Var(kTestBool)); array.Set(1, string); // Purposely leave index 2 empty (which will place an undefined var there). array.Set(3, pp::Var(kTestInt)); array.Set(4, pp::Var(kTestDouble)); dictionary.Set(pp::Var("array-ref1"), array); dictionary.Set(pp::Var("array-ref2"), array); // Set up the JavaScript message event listener to echo the data part of the // message event back to us. ASSERT_TRUE(AddEchoingListener("message_event.data")); instance_->PostMessage(dictionary); // PostMessage is asynchronous, so we should not receive a response yet. ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(1, WaitForMessages()); ASSERT_TRUE(message_data_.back().is_dictionary()); pp::VarDictionary result(message_data_.back()); ASSERT_TRUE(VarsEqual(dictionary, message_data_.back())); WaitForMessages(); message_data_.clear(); ASSERT_TRUE(ClearListeners()); // Set up a (dictionary -> array -> dictionary) cycle. Cycles shouldn't be // transmitted. pp::VarArray array2; array2.Set(0, dictionary); dictionary.Set(pp::Var("array2"), array2); ASSERT_TRUE(AddEchoingListener("message_event.data")); instance_->PostMessage(dictionary); // PostMessage is asynchronous, so we should not receive a response yet. ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(WaitForMessages(), 0); // Break the cycles. dictionary.Delete(pp::Var("array2")); WaitForMessages(); message_data_.clear(); ASSERT_TRUE(ClearListeners()); // Test sending a cycle from JavaScript to the plugin. ASSERT_TRUE(AddEchoingListener("message_event.data")); PostMessageFromJavaScript("function() { var x = []; x[0] = x; return x; }"); ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(WaitForMessages(), 0); WaitForMessages(); message_data_.clear(); ASSERT_TRUE(ClearListeners()); PASS(); } std::string TestPostMessage::TestMessageEvent() { // Set up the JavaScript message event listener to pass us some values from // the MessageEvent and make sure they match our expectations. WaitForMessages(); ASSERT_TRUE(ClearListeners()); // Have the listener pass back the class name of message_event and make sure // it's "MessageEvent". ASSERT_TRUE(AddEchoingListener("message_event.constructor.name")); message_data_.clear(); instance_->PostMessage(pp::Var(kTestInt)); ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(1, WaitForMessages()); ASSERT_TRUE(message_data_.back().is_string()); ASSERT_EQ(message_data_.back().AsString(), "MessageEvent"); ASSERT_TRUE(ClearListeners()); // Make sure all the non-data properties have the expected values. bool success = AddEchoingListener("((message_event.origin === '')" " && (message_event.lastEventId === '')" " && (message_event.source === null)" " && (message_event.ports.length === 0)" " && (message_event.bubbles === false)" " && (message_event.cancelable === false)" ")"); ASSERT_TRUE(success); message_data_.clear(); instance_->PostMessage(pp::Var(kTestInt)); ASSERT_EQ(0, message_data_.size()); ASSERT_EQ(1, WaitForMessages()); ASSERT_TRUE(message_data_.back().is_bool()); ASSERT_TRUE(message_data_.back().AsBool()); ASSERT_TRUE(ClearListeners()); // Add some event handlers to make sure they receive messages. ASSERT_TRUE(AddEchoingListener("1")); ASSERT_TRUE(AddEchoingListener("2")); ASSERT_TRUE(AddEchoingListener("3")); message_data_.clear(); instance_->PostMessage(pp::Var(kTestInt)); // Make sure we don't get a response in a re-entrant fashion. ASSERT_EQ(0, message_data_.size()); // We should get 3 messages. ASSERT_EQ(WaitForMessages(), 3); // Copy to a vector of doubles and sort; w3c does not specify the order for // event listeners. (Copying is easier than writing an operator< for pp::Var.) // // See http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html. VarVector::iterator iter(message_data_.begin()), the_end(message_data_.end()); std::vector double_vec; for (; iter != the_end; ++iter) { ASSERT_TRUE(iter->is_number()); double_vec.push_back(iter->AsDouble()); } std::sort(double_vec.begin(), double_vec.end()); ASSERT_DOUBLE_EQ(double_vec[0], 1.0); ASSERT_DOUBLE_EQ(double_vec[1], 2.0); ASSERT_DOUBLE_EQ(double_vec[2], 3.0); message_data_.clear(); ASSERT_TRUE(ClearListeners()); PASS(); } std::string TestPostMessage::TestNoHandler() { // Delete any lingering messages and event listeners. WaitForMessages(); ASSERT_TRUE(ClearListeners()); // Now send a message. We shouldn't get a response. message_data_.clear(); instance_->PostMessage(pp::Var()); ASSERT_EQ(WaitForMessages(), 0); ASSERT_TRUE(message_data_.empty()); PASS(); } std::string TestPostMessage::TestExtraParam() { // Delete any lingering messages and event listeners. WaitForMessages(); ASSERT_TRUE(ClearListeners()); // Add a listener that will respond with 1 and an empty array (where the // message port array would appear if it was Worker postMessage). ASSERT_TRUE(AddEchoingListener("1, []")); // Now send a message. We shouldn't get a response. message_data_.clear(); instance_->PostMessage(pp::Var()); ASSERT_EQ(WaitForMessages(), 0); ASSERT_TRUE(message_data_.empty()); ASSERT_TRUE(ClearListeners()); PASS(); } std::string TestPostMessage::TestNonMainThread() { WaitForMessages(); ASSERT_TRUE(ClearListeners()); ASSERT_TRUE(AddEchoingListener("message_event.data")); message_data_.clear(); // Set up a thread for each integer from 0 to (kThreadsToRun - 1). Make each // thread send the number that matches its index kMessagesToSendPerThread // times. For good measure, call postMessage from the main thread // kMessagesToSendPerThread times. At the end, we make sure we got all the // values we expected. PP_Thread threads[kThreadsToRun]; for (int32_t i = 0; i < kThreadsToRun; ++i) { // Set up a thread to send a value of i. void* arg = new InvokePostMessageThreadArg(instance_, pp::Var(i)); PP_CreateThread(&threads[i], &InvokePostMessageThreadFunc, arg); } // Invoke PostMessage right now to send a value of (kThreadsToRun). for (int32_t i = 0; i < kMessagesToSendPerThread; ++i) instance_->PostMessage(pp::Var(kThreadsToRun)); // Now join all threads. for (int32_t i = 0; i < kThreadsToRun; ++i) PP_JoinThread(threads[i]); // PostMessage is asynchronous, so we should not receive a response yet. ASSERT_EQ(0, message_data_.size()); // Make sure we got all values that we expected. Note that because it's legal // for the JavaScript engine to treat our integers as floating points, we // can't just use std::find or equality comparison. So we instead, we convert // each incoming value to an integer, and count them in received_counts. int32_t expected_num = (kThreadsToRun + 1) * kMessagesToSendPerThread; // Count how many we receive per-index. std::vector expected_counts(kThreadsToRun + 1, kMessagesToSendPerThread); std::vector received_counts(kThreadsToRun + 1, 0); ASSERT_EQ(expected_num, WaitForMessages()); for (int32_t i = 0; i < expected_num; ++i) { const pp::Var& latest_var(message_data_[i]); ASSERT_TRUE(latest_var.is_int() || latest_var.is_double()); int32_t received_value = -1; if (latest_var.is_int()) { received_value = latest_var.AsInt(); } else if (latest_var.is_double()) { received_value = static_cast(latest_var.AsDouble() + 0.5); } ASSERT_TRUE(received_value >= 0); ASSERT_TRUE(received_value <= kThreadsToRun); ++received_counts[received_value]; } ASSERT_EQ(expected_counts, received_counts); message_data_.clear(); ASSERT_TRUE(ClearListeners()); PASS(); }