| // 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<const PPB_InputEvent*>( |
| pp::Module::Get()->GetBrowserInterface(PPB_INPUT_EVENT_INTERFACE)); |
| keyboard_input_event_interface_ = |
| static_cast<const PPB_KeyboardInputEvent*>( |
| pp::Module::Get()->GetBrowserInterface( |
| PPB_KEYBOARD_INPUT_EVENT_INTERFACE)); |
| ime_input_event_interface_ = static_cast<const PPB_IMEInputEvent*>( |
| 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<uint32_t>(), |
| -1, // target_segment |
| std::make_pair(0U, 0U) // selection |
| ); |
| } |
| |
| pp::InputEvent TestImeInputEvent::CreateImeCompositionUpdateEvent( |
| const std::string& text, |
| const std::vector<uint32_t>& segments, |
| int32_t target_segment, |
| const std::pair<uint32_t, uint32_t>& 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<uint32_t>(), |
| -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<uint32_t>(), |
| -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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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(); |
| } |