root/ppapi/tests/test_ime_input_event.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. received_finish_message_
  2. RunTests
  3. Init
  4. HandleInputEvent
  5. HandleMessage
  6. DidChangeView
  7. CreateImeCompositionStartEvent
  8. CreateImeCompositionUpdateEvent
  9. CreateImeCompositionEndEvent
  10. CreateImeTextEvent
  11. CreateCharEvent
  12. GetFocusBySimulatingMouseClick
  13. SimulateInputEvent
  14. AreEquivalentEvents
  15. TestImeCommit
  16. TestImeCancel
  17. TestImeUnawareCommit
  18. TestImeUnawareCancel

// 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();
}

/* [<][>][^][v][top][bottom][index][help] */