// 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_ime_input_event.h" #include "ppapi/c/pp_errors.h" #include "ppapi/c/ppb_input_event.h" #include "ppapi/cpp/input_event.h" #include "ppapi/cpp/module.h" #include "ppapi/tests/test_utils.h" #include "ppapi/tests/testing_instance.h" REGISTER_TEST_CASE(ImeInputEvent); namespace { // Japanese Kanji letters const char* kCompositionChar[] = { "\xE6\x96\x87", // An example character of normal unicode. "\xF0\xA0\xAE\x9F", // An example character of surrogate pair. "\xF0\x9F\x98\x81" // An example character of surrogate pair(emoji). }; const char kCompositionText[] = "\xE6\x96\x87\xF0\xA0\xAE\x9F\xF0\x9F\x98\x81"; #define FINISHED_WAITING_MESSAGE "TEST_IME_INPUT_EVENT_FINISHED_WAITING" } // namespace TestImeInputEvent::TestImeInputEvent(TestingInstance* instance) : TestCase(instance), input_event_interface_(NULL), keyboard_input_event_interface_(NULL), ime_input_event_interface_(NULL), received_unexpected_event_(true), received_finish_message_(false) { } TestImeInputEvent::~TestImeInputEvent() { // Remove the special listener that only responds to a // FINISHED_WAITING_MESSAGE 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); } void TestImeInputEvent::RunTests(const std::string& filter) { RUN_TEST(ImeCommit, filter); RUN_TEST(ImeCancel, filter); RUN_TEST(ImeUnawareCommit, filter); RUN_TEST(ImeUnawareCancel, filter); } bool TestImeInputEvent::Init() { input_event_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface(PPB_INPUT_EVENT_INTERFACE)); keyboard_input_event_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface( PPB_KEYBOARD_INPUT_EVENT_INTERFACE)); ime_input_event_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface( PPB_IME_INPUT_EVENT_INTERFACE)); bool success = input_event_interface_ && keyboard_input_event_interface_ && ime_input_event_interface_ && CheckTestingInterface(); // Set up a listener for our message that signals that all input events have // been received. // 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. std::string 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); return success; } bool TestImeInputEvent::HandleInputEvent(const pp::InputEvent& input_event) { // Check whether the IME related events comes in the expected order. switch (input_event.GetType()) { case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: case PP_INPUTEVENT_TYPE_IME_TEXT: case PP_INPUTEVENT_TYPE_CHAR: if (expected_events_.empty()) { received_unexpected_event_ = true; } else { received_unexpected_event_ = !AreEquivalentEvents(input_event.pp_resource(), expected_events_.front().pp_resource()); expected_events_.erase(expected_events_.begin()); } break; default: // Don't care for any other input event types for this test. break; } // Handle all input events. return true; } void TestImeInputEvent::HandleMessage(const pp::Var& message_data) { if (message_data.is_string() && (message_data.AsString() == FINISHED_WAITING_MESSAGE)) { testing_interface_->QuitMessageLoop(instance_->pp_instance()); received_finish_message_ = true; } } void TestImeInputEvent::DidChangeView(const pp::View& view) { view_rect_ = view.GetRect(); } pp::InputEvent TestImeInputEvent::CreateImeCompositionStartEvent() { return pp::IMEInputEvent( instance_, PP_INPUTEVENT_TYPE_IME_COMPOSITION_START, 100, // time_stamp pp::Var(""), std::vector(), -1, // target_segment std::make_pair(0U, 0U) // selection ); } pp::InputEvent TestImeInputEvent::CreateImeCompositionUpdateEvent( const std::string& text, const std::vector& segments, int32_t target_segment, const std::pair& selection) { return pp::IMEInputEvent( instance_, PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE, 100, // time_stamp text, segments, target_segment, selection ); } pp::InputEvent TestImeInputEvent::CreateImeCompositionEndEvent( const std::string& text) { return pp::IMEInputEvent( instance_, PP_INPUTEVENT_TYPE_IME_COMPOSITION_END, 100, // time_stamp pp::Var(text), std::vector(), -1, // target_segment std::make_pair(0U, 0U) // selection ); } pp::InputEvent TestImeInputEvent::CreateImeTextEvent(const std::string& text) { return pp::IMEInputEvent( instance_, PP_INPUTEVENT_TYPE_IME_TEXT, 100, // time_stamp pp::Var(text), std::vector(), -1, // target_segment std::make_pair(0U, 0U) // selection ); } pp::InputEvent TestImeInputEvent::CreateCharEvent(const std::string& text) { return pp::KeyboardInputEvent( instance_, PP_INPUTEVENT_TYPE_CHAR, 100, // time_stamp 0, // modifiers 0, // keycode pp::Var(text), pp::Var()); } void TestImeInputEvent::GetFocusBySimulatingMouseClick() { // For receiving IME events, the plugin DOM node needs to be focused. // The following code is for achieving that by simulating a mouse click event. input_event_interface_->RequestInputEvents(instance_->pp_instance(), PP_INPUTEVENT_CLASS_MOUSE); SimulateInputEvent(pp::MouseInputEvent( instance_, PP_INPUTEVENT_TYPE_MOUSEDOWN, 100, // time_stamp 0, // modifiers PP_INPUTEVENT_MOUSEBUTTON_LEFT, pp::Point( view_rect_.x() + view_rect_.width() / 2, view_rect_.y() + view_rect_.height() / 2), 1, // click count pp::Point())); // movement } // Simulates the input event and calls PostMessage to let us know when // we have received all resulting events from the browser. bool TestImeInputEvent::SimulateInputEvent(const pp::InputEvent& input_event) { received_unexpected_event_ = false; received_finish_message_ = false; testing_interface_->SimulateInputEvent(instance_->pp_instance(), input_event.pp_resource()); instance_->PostMessage(pp::Var(FINISHED_WAITING_MESSAGE)); testing_interface_->RunMessageLoop(instance_->pp_instance()); return received_finish_message_ && !received_unexpected_event_; } bool TestImeInputEvent::AreEquivalentEvents(PP_Resource received, PP_Resource expected) { if (!input_event_interface_->IsInputEvent(received) || !input_event_interface_->IsInputEvent(expected)) { return false; } // Test common fields, except modifiers and time stamp, which may be changed // by the browser. int32_t received_type = input_event_interface_->GetType(received); int32_t expected_type = input_event_interface_->GetType(expected); if (received_type != expected_type) return false; // Test event type-specific fields. switch (received_type) { case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: // COMPOSITION_START does not convey further information. break; case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: case PP_INPUTEVENT_TYPE_IME_TEXT: // For COMPOSITION_END and TEXT, GetText() has meaning. return pp::Var(pp::PASS_REF, ime_input_event_interface_->GetText(received)) == pp::Var(pp::PASS_REF, ime_input_event_interface_->GetText(expected)); case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: // For COMPOSITION_UPDATE, all fields must be checked. { uint32_t received_segment_number = ime_input_event_interface_->GetSegmentNumber(received); uint32_t expected_segment_number = ime_input_event_interface_->GetSegmentNumber(expected); if (received_segment_number != expected_segment_number) return false; // The "<=" is not a bug. i-th segment is represented as the pair of // i-th and (i+1)-th offsets in Pepper IME API. for (uint32_t i = 0; i <= received_segment_number; ++i) { if (ime_input_event_interface_->GetSegmentOffset(received, i) != ime_input_event_interface_->GetSegmentOffset(expected, i)) return false; } uint32_t received_selection_start = 0; uint32_t received_selection_end = 0; uint32_t expected_selection_start = 0; uint32_t expected_selection_end = 0; ime_input_event_interface_->GetSelection( received, &received_selection_start, &received_selection_end); ime_input_event_interface_->GetSelection( expected, &expected_selection_start, &expected_selection_end); if (received_selection_start != expected_selection_start || received_selection_end != expected_selection_end) { return true; } return pp::Var(pp::PASS_REF, ime_input_event_interface_->GetText(received)) == pp::Var(pp::PASS_REF, ime_input_event_interface_->GetText(expected)) && ime_input_event_interface_->GetTargetSegment(received) == ime_input_event_interface_->GetTargetSegment(expected); } case PP_INPUTEVENT_TYPE_CHAR: return keyboard_input_event_interface_->GetKeyCode(received) == keyboard_input_event_interface_->GetKeyCode(expected) && pp::Var(pp::PASS_REF, keyboard_input_event_interface_->GetCharacterText(received)) == pp::Var(pp::PASS_REF, keyboard_input_event_interface_->GetCharacterText(expected)); default: break; } return true; } std::string TestImeInputEvent::TestImeCommit() { GetFocusBySimulatingMouseClick(); input_event_interface_->RequestInputEvents(instance_->pp_instance(), PP_INPUTEVENT_CLASS_KEYBOARD | PP_INPUTEVENT_CLASS_IME); std::vector segments; segments.push_back(0U); segments.push_back(3U); segments.push_back(7U); segments.push_back(11U); pp::InputEvent update_event = CreateImeCompositionUpdateEvent( kCompositionText, segments, 1, std::make_pair(3U, 7U)); expected_events_.clear(); expected_events_.push_back(CreateImeCompositionStartEvent()); expected_events_.push_back(update_event); expected_events_.push_back(CreateImeCompositionEndEvent(kCompositionText)); expected_events_.push_back(CreateImeTextEvent(kCompositionText)); // Simulate the case when IME successfully committed some text. ASSERT_TRUE(SimulateInputEvent(update_event)); ASSERT_TRUE(SimulateInputEvent(CreateImeTextEvent(kCompositionText))); ASSERT_TRUE(expected_events_.empty()); PASS(); } std::string TestImeInputEvent::TestImeCancel() { GetFocusBySimulatingMouseClick(); input_event_interface_->RequestInputEvents(instance_->pp_instance(), PP_INPUTEVENT_CLASS_KEYBOARD | PP_INPUTEVENT_CLASS_IME); std::vector segments; segments.push_back(0U); segments.push_back(3U); segments.push_back(7U); segments.push_back(11U); pp::InputEvent update_event = CreateImeCompositionUpdateEvent( kCompositionText, segments, 1, std::make_pair(3U, 7U)); expected_events_.clear(); expected_events_.push_back(CreateImeCompositionStartEvent()); expected_events_.push_back(update_event); expected_events_.push_back(CreateImeCompositionEndEvent(std::string())); // Simulate the case when IME canceled composition. ASSERT_TRUE(SimulateInputEvent(update_event)); ASSERT_TRUE(SimulateInputEvent(CreateImeCompositionEndEvent(std::string()))); ASSERT_TRUE(expected_events_.empty()); PASS(); } std::string TestImeInputEvent::TestImeUnawareCommit() { GetFocusBySimulatingMouseClick(); input_event_interface_->ClearInputEventRequest(instance_->pp_instance(), PP_INPUTEVENT_CLASS_IME); input_event_interface_->RequestInputEvents(instance_->pp_instance(), PP_INPUTEVENT_CLASS_KEYBOARD); std::vector segments; segments.push_back(0U); segments.push_back(3U); segments.push_back(7U); segments.push_back(11U); pp::InputEvent update_event = CreateImeCompositionUpdateEvent( kCompositionText, segments, 1, std::make_pair(3U, 7U)); expected_events_.clear(); expected_events_.push_back(CreateCharEvent(kCompositionChar[0])); expected_events_.push_back(CreateCharEvent(kCompositionChar[1])); expected_events_.push_back(CreateCharEvent(kCompositionChar[2])); // Test for IME-unaware plugins. Commit event is translated to char events. ASSERT_TRUE(SimulateInputEvent(update_event)); ASSERT_TRUE(SimulateInputEvent(CreateImeTextEvent(kCompositionText))); ASSERT_TRUE(expected_events_.empty()); PASS(); } std::string TestImeInputEvent::TestImeUnawareCancel() { GetFocusBySimulatingMouseClick(); input_event_interface_->ClearInputEventRequest(instance_->pp_instance(), PP_INPUTEVENT_CLASS_IME); input_event_interface_->RequestInputEvents(instance_->pp_instance(), PP_INPUTEVENT_CLASS_KEYBOARD); std::vector segments; segments.push_back(0U); segments.push_back(3U); segments.push_back(7U); segments.push_back(11U); pp::InputEvent update_event = CreateImeCompositionUpdateEvent( kCompositionText, segments, 1, std::make_pair(3U, 7U)); expected_events_.clear(); // Test for IME-unaware plugins. Cancel won't issue any events. ASSERT_TRUE(SimulateInputEvent(update_event)); ASSERT_TRUE(SimulateInputEvent(CreateImeCompositionEndEvent(std::string()))); ASSERT_TRUE(expected_events_.empty()); PASS(); }