chromium/chrome/browser/extensions/api/input_ime/input_ime_api.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/api/input_ime/input_ime_api.h"

#include <utility>
#include "base/lazy_instance.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/common/extensions/api/input_method_private.h"
#include "extensions/browser/extension_registry.h"
#include "ui/base/ime/ash/ime_keymap.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/keycode_converter.h"

namespace {

namespace input_ime = extensions::api::input_ime;
namespace KeyEventHandled = extensions::api::input_ime::KeyEventHandled;
namespace SetComposition = extensions::api::input_ime::SetComposition;
namespace CommitText = extensions::api::input_ime::CommitText;
namespace SendKeyEvents = extensions::api::input_ime::SendKeyEvents;

const char kErrorRouterNotAvailable[] = "The router is not available.";
const char kErrorSetKeyEventsFail[] = "Could not send key events.";

using ::ash::input_method::InputMethodEngine;

InputMethodEngine* GetEngineIfActive(Profile* profile,
                                     const std::string& extension_id,
                                     std::string* error) {
  extensions::InputImeEventRouter* event_router =
      extensions::GetInputImeEventRouter(profile);
  DCHECK(event_router) << kErrorRouterNotAvailable;
  InputMethodEngine* engine =
      event_router->GetEngineIfActive(extension_id, error);
  return engine;
}

ui::KeyEvent ConvertKeyboardEventToUIKeyEvent(
    const input_ime::KeyboardEvent& event) {
  const ui::EventType type =
      event.type == input_ime::KeyboardEventType::kKeydown
          ? ui::EventType::kKeyPressed
          : ui::EventType::kKeyReleased;

  const auto key_code = static_cast<ui::KeyboardCode>(
      event.key_code && *event.key_code != ui::VKEY_UNKNOWN
          ? *event.key_code
          : ash::DomKeycodeToKeyboardCode(event.code));

  int flags = ui::EF_NONE;
  flags |= event.alt_key && *event.alt_key ? ui::EF_ALT_DOWN : ui::EF_NONE;
  flags |=
      event.altgr_key && *event.altgr_key ? ui::EF_ALTGR_DOWN : ui::EF_NONE;
  flags |=
      event.ctrl_key && *event.ctrl_key ? ui::EF_CONTROL_DOWN : ui::EF_NONE;
  flags |=
      event.shift_key && *event.shift_key ? ui::EF_SHIFT_DOWN : ui::EF_NONE;
  flags |=
      event.caps_lock && *event.caps_lock ? ui::EF_CAPS_LOCK_ON : ui::EF_NONE;

  return ui::KeyEvent(type, key_code,
                      ui::KeycodeConverter::CodeStringToDomCode(event.code),
                      flags, ui::KeycodeConverter::KeyStringToDomKey(event.key),
                      ui::EventTimeForNow());
}

}  // namespace

namespace extensions {

InputImeEventRouterFactory* InputImeEventRouterFactory::GetInstance() {
  return base::Singleton<InputImeEventRouterFactory>::get();
}

InputImeEventRouterFactory::InputImeEventRouterFactory() = default;
InputImeEventRouterFactory::~InputImeEventRouterFactory() = default;

InputImeEventRouter* InputImeEventRouterFactory::GetRouter(Profile* profile) {
  if (!profile)
    return nullptr;
  // The |router_map_| is keyed by the original profile.
  // Refers to the comments in |RemoveProfile| method for the reason.
  profile = profile->GetOriginalProfile();
  InputImeEventRouter* router = router_map_[profile];
  if (!router) {
    // The router must attach to the profile from which the extension can
    // receive events. If |profile| has an off-the-record profile, attach the
    // off-the-record profile. e.g. In guest mode, the extension is running with
    // the incognito profile instead of its original profile.
    router = new InputImeEventRouter(
        profile->HasPrimaryOTRProfile()
            ? profile->GetPrimaryOTRProfile(/*create_if_needed=*/true)
            : profile);
    router_map_[profile] = router;
  }
  return router;
}

void InputImeEventRouterFactory::RemoveProfile(Profile* profile) {
  if (!profile || router_map_.empty())
    return;
  auto it = router_map_.find(profile);
  // The routers are common between an incognito profile and its original
  // profile, and are keyed on the original profiles.
  // When a profile is removed, exact matching is used to ensure that the router
  // is deleted only when the original profile is removed.
  if (it != router_map_.end() && it->first == profile) {
    delete it->second;
    router_map_.erase(it);
  }
}

ExtensionFunction::ResponseAction InputImeKeyEventHandledFunction::Run() {
  std::optional<KeyEventHandled::Params> params =
      KeyEventHandled::Params::Create(args());
  std::string error;
  InputMethodEngine* engine = GetEngineIfActive(
      Profile::FromBrowserContext(browser_context()), extension_id(), &error);
  if (!engine)
    return RespondNow(Error(InformativeError(error, static_function_name())));

  engine->KeyEventHandled(extension_id(), params->request_id, params->response);
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction InputImeSetCompositionFunction::Run() {
  std::string error;
  InputMethodEngine* engine = GetEngineIfActive(
      Profile::FromBrowserContext(browser_context()), extension_id(), &error);
  if (!engine)
    return RespondNow(Error(InformativeError(error, static_function_name())));

  std::optional<SetComposition::Params> parent_params =
      SetComposition::Params::Create(args());
  const SetComposition::Params::Parameters& params = parent_params->parameters;
  std::vector<InputMethodEngine::SegmentInfo> segments;
  if (params.segments) {
    for (const auto& segments_arg : *params.segments) {
      EXTENSION_FUNCTION_VALIDATE(segments_arg.style !=
                                  input_ime::UnderlineStyle::kNone);
      InputMethodEngine::SegmentInfo segment_info;
      segment_info.start = segments_arg.start;
      segment_info.end = segments_arg.end;
      if (segments_arg.style == input_ime::UnderlineStyle::kUnderline) {
        segment_info.style = InputMethodEngine::SEGMENT_STYLE_UNDERLINE;
      } else if (segments_arg.style ==
                 input_ime::UnderlineStyle::kDoubleUnderline) {
        segment_info.style = InputMethodEngine::SEGMENT_STYLE_DOUBLE_UNDERLINE;
      } else {
        segment_info.style = InputMethodEngine::SEGMENT_STYLE_NO_UNDERLINE;
      }
      segments.push_back(segment_info);
    }
  }
  int selection_start =
      params.selection_start ? *params.selection_start : params.cursor;
  int selection_end =
      params.selection_end ? *params.selection_end : params.cursor;
  if (!engine->SetComposition(params.context_id, params.text.c_str(),
                              selection_start, selection_end, params.cursor,
                              segments, &error)) {
    base::Value::List results;
    results.Append(false);
    return RespondNow(ErrorWithArguments(
        std::move(results), InformativeError(error, static_function_name())));
  }
  return RespondNow(WithArguments(true));
}

ExtensionFunction::ResponseAction InputImeCommitTextFunction::Run() {
  std::string error;
  InputMethodEngine* engine = GetEngineIfActive(
      Profile::FromBrowserContext(browser_context()), extension_id(), &error);
  if (!engine)
    return RespondNow(Error(InformativeError(error, static_function_name())));

  std::optional<CommitText::Params> parent_params =
      CommitText::Params::Create(args());
  const CommitText::Params::Parameters& params = parent_params->parameters;
  if (!engine->CommitText(params.context_id, base::UTF8ToUTF16(params.text),
                          &error)) {
    base::Value::List results;
    results.Append(false);
    return RespondNow(ErrorWithArguments(
        std::move(results), InformativeError(error, static_function_name())));
  }
  return RespondNow(WithArguments(true));
}

ExtensionFunction::ResponseAction InputImeSendKeyEventsFunction::Run() {
  std::string error;
  InputMethodEngine* engine = GetEngineIfActive(
      Profile::FromBrowserContext(browser_context()), extension_id(), &error);
  if (!engine)
    return RespondNow(Error(InformativeError(error, static_function_name())));

  std::optional<SendKeyEvents::Params> parent_params =
      SendKeyEvents::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(parent_params);
  const SendKeyEvents::Params::Parameters& params = parent_params->parameters;

  std::vector<ui::KeyEvent> key_data_out;
  key_data_out.reserve(params.key_data.size());
  for (const auto& key_event : params.key_data) {
    key_data_out.emplace_back(ConvertKeyboardEventToUIKeyEvent(key_event));
  }

  if (!engine->SendKeyEvents(params.context_id, key_data_out, &error))
    return RespondNow(Error(InformativeError(
        base::StringPrintf("%s %s", kErrorSetKeyEventsFail, error.c_str()),
        static_function_name())));
  return RespondNow(NoArguments());
}

InputImeAPI::InputImeAPI(content::BrowserContext* context)
    : browser_context_(context) {
  extension_registry_observation_.Observe(
      ExtensionRegistry::Get(browser_context_));

  EventRouter* event_router = EventRouter::Get(browser_context_);
  event_router->RegisterObserver(this, input_ime::OnFocus::kEventName);
  event_router->RegisterObserver(
      this, api::input_method_private::OnFocus::kEventName);
  event_router->RegisterObserver(this, input_ime::OnKeyEvent::kEventName);
}

InputImeAPI::~InputImeAPI() = default;

void InputImeAPI::Shutdown() {
  extension_registry_observation_.Reset();
  InputImeEventRouterFactory::GetInstance()->RemoveProfile(
      Profile::FromBrowserContext(browser_context_));
  EventRouter::Get(browser_context_)->UnregisterObserver(this);
}

static base::LazyInstance<BrowserContextKeyedAPIFactory<InputImeAPI>>::
    DestructorAtExit g_input_ime_factory = LAZY_INSTANCE_INITIALIZER;

// static
BrowserContextKeyedAPIFactory<InputImeAPI>* InputImeAPI::GetFactoryInstance() {
  return g_input_ime_factory.Pointer();
}

InputImeEventRouter* GetInputImeEventRouter(Profile* profile) {
  if (!profile)
    return nullptr;
  return InputImeEventRouterFactory::GetInstance()->GetRouter(profile);
}

std::string InformativeError(const std::string& error,
                             const char* function_name) {
  return base::StringPrintf("[%s]: %s", function_name, error.c_str());
}

}  // namespace extensions