chromium/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc

// Copyright 2014 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/virtual_keyboard_private/chrome_virtual_keyboard_delegate.h"

#include <memory>
#include <string>
#include <utility>

#include "ash/clipboard/clipboard_history_item.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/clipboard_history_controller.h"
#include "ash/public/cpp/clipboard_image_model_factory.h"
#include "ash/public/cpp/keyboard/keyboard_types.h"
#include "ash/webui/settings/public/constants/routes.mojom-forward.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/values.h"
#include "chrome/browser/ash/login/lock/screen_locker.h"
#include "chrome/browser/ash/login/ui/user_adding_screen.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/crosapi/mojom/clipboard_history.mojom.h"
#include "chromeos/services/machine_learning/public/cpp/ml_switches.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/audio_service.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/event_router.h"
#include "extensions/common/api/virtual_keyboard.h"
#include "extensions/common/api/virtual_keyboard_private.h"
#include "media/audio/audio_system.h"
#include "ui/aura/event_injector.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/ime/ash/ime_bridge.h"
#include "ui/base/ime/constants.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"

namespace keyboard_api = extensions::api::virtual_keyboard_private;

namespace {

// The hotrod keyboard must be enabled for each session and will remain enabled
// until / unless it is explicitly disabled.
bool g_hotrod_keyboard_enabled = false;

std::string GenerateFeatureFlag(const std::string& feature, bool enabled) {
  return feature + (enabled ? "-enabled" : "-disabled");
}

keyboard::ContainerType ConvertKeyboardModeToContainerType(
    keyboard_api::KeyboardMode mode) {
  switch (mode) {
    case keyboard_api::KeyboardMode::kFullWidth:
      return keyboard::ContainerType::kFullWidth;
    case keyboard_api::KeyboardMode::kFloating:
      return keyboard::ContainerType::kFloating;
    case keyboard_api::KeyboardMode::kNone:
      break;
  }

  NOTREACHED_IN_MIGRATION();
  return keyboard::ContainerType::kFullWidth;
}

// Returns the ui::TextInputClient of the active InputMethod or nullptr.
ui::TextInputClient* GetFocusedTextInputClient() {
  ui::InputMethod* input_method =
      ash::IMEBridge::Get()->GetInputContextHandler()->GetInputMethod();
  if (!input_method)
    return nullptr;

  return input_method->GetTextInputClient();
}

const char kKeyDown[] = "keydown";
const char kKeyUp[] = "keyup";

void SendProcessKeyEvent(ui::EventType type, aura::WindowTreeHost* host) {
  ui::KeyEvent event(type, ui::VKEY_PROCESSKEY, ui::DomCode::NONE,
                     ui::EF_IS_SYNTHESIZED, ui::DomKey::PROCESS,
                     ui::EventTimeForNow());
  ui::EventDispatchDetails details = aura::EventInjector().Inject(host, &event);
  CHECK(!details.dispatcher_destroyed);
}

// Sends a fabricated key event, where |type| is the event type (which must be
// "keydown" or "keyup"), |key_value| is the unicode value of the character,
// |key_code| is the legacy key code value, |key_name| is the name of the key as
// defined in the DOM3 key event specification, and |modifier| indicates if any
// modifier keys are being virtually pressed. The event is dispatched to the
// active TextInputClient associated with |host|.
bool SendKeyEventImpl(const std::string& type,
                      int key_value,
                      int key_code,
                      const std::string& key_name,
                      int modifiers,
                      aura::WindowTreeHost* host) {
  ui::EventType event_type;
  if (type == kKeyDown)
    event_type = ui::EventType::kKeyPressed;
  else if (type == kKeyUp)
    event_type = ui::EventType::kKeyReleased;
  else
    return false;

  ui::KeyboardCode code = static_cast<ui::KeyboardCode>(key_code);

  if (code == ui::VKEY_UNKNOWN) {
    // Handling of special printable characters (e.g. accented characters) for
    // which there is no key code.
    if (event_type == ui::EventType::kKeyReleased) {
      // This can be null if no text input field is focused.
      ui::TextInputClient* tic = GetFocusedTextInputClient();

      SendProcessKeyEvent(ui::EventType::kKeyPressed, host);

      ui::KeyEvent char_event = ui::KeyEvent::FromCharacter(
          key_value, code, ui::DomCode::NONE, ui::EF_NONE);
      if (tic)
        tic->InsertChar(char_event);
      SendProcessKeyEvent(ui::EventType::kKeyReleased, host);
    }
    return true;
  }

  ui::DomCode dom_code = ui::KeycodeConverter::CodeStringToDomCode(key_name);
  if (dom_code == ui::DomCode::NONE)
    dom_code = ui::UsLayoutKeyboardCodeToDomCode(code);
  CHECK(dom_code != ui::DomCode::NONE);

  ui::KeyEvent event(event_type, code, dom_code, modifiers);

  // Indicate that the simulated key event is from the Virtual Keyboard.
  ui::Event::Properties properties;
  properties[ui::kPropertyFromVK] =
      std::vector<uint8_t>(ui::kPropertyFromVKSize);
  event.SetProperties(properties);

  ui::EventDispatchDetails details = aura::EventInjector().Inject(host, &event);
  CHECK(!details.dispatcher_destroyed);
  return true;
}

std::string GetKeyboardLayout() {
  // TODO(bshe): layout string is currently hard coded. We should use more
  // standard keyboard layouts.
  return ChromeKeyboardControllerClient::Get()->IsEnableFlagSet(
             keyboard::KeyboardEnableFlag::kAccessibilityEnabled)
             ? "system-qwerty"
             : "qwerty";
}

// Returns a nullptr if a router could not be found or if the the router does
// not have an event listener for the given `event_name`.
extensions::EventRouter* GetRouterForEventName(content::BrowserContext* context,
                                               const std::string& event_name) {
  extensions::EventRouter* router = extensions::EventRouter::Get(context);
  if (!router || !router->HasEventListener(event_name)) {
    return nullptr;
  }
  return router;
}

// Returns whether the `ondevice_handwriting` USE flag has been set.
// Adapted from
// `//chromeos/services/machine_learning/cpp/ash/handwriting_model_loader.cc`.
// This flag is set from the CrOS side in
// https://crsrc.org/o/src/platform2/login_manager/chrome_setup.cc;l=1014;drc=e44a81d180823c2a0758c52f0520862d0545b98d
bool IsOndeviceHandwritingEnabledViaCommandLine() {
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  return command_line->HasSwitch(::switches::kOndeviceHandwritingSwitch) &&
         command_line->GetSwitchValueASCII(
             ::switches::kOndeviceHandwritingSwitch) == "use_rootfs";
}

}  // namespace

namespace extensions {

ChromeVirtualKeyboardDelegate::ChromeVirtualKeyboardDelegate(
    content::BrowserContext* browser_context)
    : browser_context_(browser_context) {
  weak_this_ = weak_factory_.GetWeakPtr();

  ash::ClipboardHistoryController* clipboard_history_controller =
      ash::ClipboardHistoryController::Get();
  if (clipboard_history_controller) {
    clipboard_history_controller->AddObserver(this);
  }
}

ChromeVirtualKeyboardDelegate::~ChromeVirtualKeyboardDelegate() {
  ash::ClipboardHistoryController* clipboard_history_controller =
      ash::ClipboardHistoryController::Get();
  if (clipboard_history_controller)
    clipboard_history_controller->RemoveObserver(this);
}

void ChromeVirtualKeyboardDelegate::GetKeyboardConfig(
    OnKeyboardSettingsCallback on_settings_callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!audio_system_)
    audio_system_ = content::CreateAudioSystemForAudioService();
  audio_system_->HasInputDevices(
      base::BindOnce(&ChromeVirtualKeyboardDelegate::OnHasInputDevices,
                     weak_this_, std::move(on_settings_callback)));
}

void ChromeVirtualKeyboardDelegate::OnKeyboardConfigChanged() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  GetKeyboardConfig(base::BindOnce(
      &ChromeVirtualKeyboardDelegate::DispatchConfigChangeEvent, weak_this_));
}

bool ChromeVirtualKeyboardDelegate::HideKeyboard() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  auto* keyboard_client = ChromeKeyboardControllerClient::Get();
  if (!keyboard_client->is_keyboard_enabled())
    return false;

  // Pass HIDE_REASON_MANUAL since calls to HideKeyboard as part of this API
  // would be user generated.
  keyboard_client->HideKeyboard(ash::HideReason::kUser);
  return true;
}

bool ChromeVirtualKeyboardDelegate::InsertText(const std::u16string& text) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  ui::TextInputClient* tic = GetFocusedTextInputClient();
  if (!tic || tic->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE)
    return false;

  tic->InsertText(
      text,
      ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
  return true;
}

bool ChromeVirtualKeyboardDelegate::OnKeyboardLoaded() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  base::RecordAction(base::UserMetricsAction("VirtualKeyboardLoaded"));
  return true;
}

void ChromeVirtualKeyboardDelegate::SetHotrodKeyboard(bool enable) {
  if (g_hotrod_keyboard_enabled == enable)
    return;

  g_hotrod_keyboard_enabled = enable;

  // This reloads virtual keyboard even if it exists. This ensures virtual
  // keyboard gets the correct state of the hotrod keyboard through
  // chrome.virtualKeyboardPrivate.getKeyboardConfig.
  ChromeKeyboardControllerClient::Get()->RebuildKeyboardIfEnabled();
}

bool ChromeVirtualKeyboardDelegate::LockKeyboard(bool state) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  auto* keyboard_client = ChromeKeyboardControllerClient::Get();
  if (!keyboard_client->is_keyboard_enabled())
    return false;

  keyboard_client->SetKeyboardLocked(state);
  return true;
}

bool ChromeVirtualKeyboardDelegate::SendKeyEvent(const std::string& type,
                                                 int char_value,
                                                 int key_code,
                                                 const std::string& key_name,
                                                 int modifiers) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  aura::Window* window =
      ChromeKeyboardControllerClient::Get()->GetKeyboardWindow();
  return window && SendKeyEventImpl(type, char_value, key_code, key_name,
                                    modifiers, window->GetHost());
}

bool ChromeVirtualKeyboardDelegate::ShowLanguageSettings() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  auto* keyboard_client = ChromeKeyboardControllerClient::Get();
  if (keyboard_client->is_keyboard_enabled())
    keyboard_client->HideKeyboard(ash::HideReason::kUser);

  base::RecordAction(
      base::UserMetricsAction("VirtualKeyboard.OpenLanguageSettings"));
  chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
      ProfileManager::GetActiveUserProfile(),
      chromeos::settings::mojom::kInputSubpagePath);
  return true;
}

bool ChromeVirtualKeyboardDelegate::ShowSuggestionSettings() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  auto* keyboard_client = ChromeKeyboardControllerClient::Get();
  if (keyboard_client->is_keyboard_enabled())
    keyboard_client->HideKeyboard(ash::HideReason::kUser);

  base::RecordAction(
      base::UserMetricsAction("VirtualKeyboard.OpenSuggestionSettings"));
  chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
      ProfileManager::GetActiveUserProfile(),
      chromeos::settings::mojom::kInputSubpagePath);
  return true;
}

bool ChromeVirtualKeyboardDelegate::SetVirtualKeyboardMode(
    keyboard_api::KeyboardMode mode,
    gfx::Rect target_bounds,
    OnSetModeCallback on_set_mode_callback) {
  auto* keyboard_client = ChromeKeyboardControllerClient::Get();
  if (!keyboard_client->is_keyboard_enabled())
    return false;

  keyboard_client->SetContainerType(ConvertKeyboardModeToContainerType(mode),
                                    target_bounds,
                                    std::move(on_set_mode_callback));
  return true;
}

bool ChromeVirtualKeyboardDelegate::SetOccludedBounds(
    const std::vector<gfx::Rect>& bounds) {
  auto* keyboard_client = ChromeKeyboardControllerClient::Get();
  if (!keyboard_client->is_keyboard_enabled())
    return false;

  keyboard_client->SetOccludedBounds(bounds);
  return true;
}

bool ChromeVirtualKeyboardDelegate::SetHitTestBounds(
    const std::vector<gfx::Rect>& bounds) {
  auto* keyboard_client = ChromeKeyboardControllerClient::Get();
  if (!keyboard_client->is_keyboard_enabled())
    return false;

  keyboard_client->SetHitTestBounds(bounds);
  return true;
}

bool ChromeVirtualKeyboardDelegate::SetAreaToRemainOnScreen(
    const gfx::Rect& bounds) {
  auto* keyboard_client = ChromeKeyboardControllerClient::Get();
  if (!keyboard_client->is_keyboard_enabled())
    return false;

  return keyboard_client->SetAreaToRemainOnScreen(bounds);
}

bool ChromeVirtualKeyboardDelegate::SetWindowBoundsInScreen(
    const gfx::Rect& bounds_in_screen) {
  auto* keyboard_client = ChromeKeyboardControllerClient::Get();
  if (!keyboard_client->is_keyboard_enabled())
    return false;

  return keyboard_client->SetWindowBoundsInScreen(bounds_in_screen);
}

void ChromeVirtualKeyboardDelegate::GetClipboardHistory(
    OnGetClipboardHistoryCallback get_history_callback) {
  // Do not leak clipboard history items if the screen is locked.
  if (ash::ScreenLocker::default_screen_locker() &&
      ash::ScreenLocker::default_screen_locker()->locked()) {
    std::move(get_history_callback)
        .Run(std::vector<ash::ClipboardHistoryItem>());
    return;
  }

  ash::ClipboardHistoryController* clipboard_history_controller =
      ash::ClipboardHistoryController::Get();
  if (!clipboard_history_controller) {
    std::move(get_history_callback)
        .Run(std::vector<ash::ClipboardHistoryItem>());
    return;
  }

  // Begin renderng all items in the clipboard history. Current items will
  // render even if Deactivate() is called on the ClipboardImageModelFactory.
  if (ash::ClipboardImageModelFactory::Get()) {
    ash::ClipboardImageModelFactory::Get()->RenderCurrentPendingRequests();
  }

  clipboard_history_controller->GetHistoryValues(
      std::move(get_history_callback));
}

bool ChromeVirtualKeyboardDelegate::PasteClipboardItem(
    const std::string& clipboard_item_id) {
  ash::ClipboardHistoryController* clipboard_history_controller =
      ash::ClipboardHistoryController::Get();
  if (!clipboard_history_controller)
    return false;

  return clipboard_history_controller->PasteClipboardItemById(
      clipboard_item_id, ui::EF_NONE,
      crosapi::mojom::ClipboardHistoryControllerShowSource::kVirtualKeyboard);
}

bool ChromeVirtualKeyboardDelegate::DeleteClipboardItem(
    const std::string& clipboard_item_id) {
  ash::ClipboardHistoryController* clipboard_history_controller =
      ash::ClipboardHistoryController::Get();
  if (!clipboard_history_controller)
    return false;

  return clipboard_history_controller->DeleteClipboardItemById(
      clipboard_item_id);
}

bool ChromeVirtualKeyboardDelegate::SetDraggableArea(
    const api::virtual_keyboard_private::Bounds& rect) {
  auto* keyboard_client = ChromeKeyboardControllerClient::Get();
  // Since controller will be destroyed when system switch from VK to
  // physical keyboard, return true to avoid unnecessary exception.
  if (!keyboard_client->is_keyboard_enabled())
    return true;

  keyboard_client->SetDraggableArea(
      gfx::Rect(rect.left, rect.top, rect.width, rect.height));
  return true;
}

bool ChromeVirtualKeyboardDelegate::SetRequestedKeyboardState(
    keyboard_api::KeyboardState state) {
  using keyboard::KeyboardEnableFlag;
  auto* client = ChromeKeyboardControllerClient::Get();
  switch (state) {
    case keyboard_api::KeyboardState::kEnabled:
      client->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
      break;
    case keyboard_api::KeyboardState::kDisabled:
      client->SetEnableFlag(KeyboardEnableFlag::kExtensionDisabled);
      break;
    case keyboard_api::KeyboardState::kAuto:
    case keyboard_api::KeyboardState::kNone:
      client->ClearEnableFlag(KeyboardEnableFlag::kExtensionDisabled);
      client->ClearEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
      break;
  }
  return true;
}

bool ChromeVirtualKeyboardDelegate::IsSettingsEnabled() {
  return (user_manager::UserManager::Get()->IsUserLoggedIn() &&
          !ash::UserAddingScreen::Get()->IsRunning() &&
          !(ash::ScreenLocker::default_screen_locker() &&
            ash::ScreenLocker::default_screen_locker()->locked()));
}

void ChromeVirtualKeyboardDelegate::OnClipboardHistoryItemsUpdated() {
  // Clipboard history is only used for multipaste in the virtual keyboard, so
  // there is no need to act on clipboard history events when the virtual
  // keyboard is disabled.
  if (!ChromeKeyboardControllerClient::HasInstance() ||
      !ChromeKeyboardControllerClient::Get()->is_keyboard_enabled()) {
    return;
  }

  EventRouter* router = GetRouterForEventName(
      browser_context_, keyboard_api::OnClipboardHistoryChanged::kEventName);
  if (!router)
    return;

  ash::ClipboardHistoryController* clipboard_history_controller =
      ash::ClipboardHistoryController::Get();
  if (!clipboard_history_controller)
    return;

  auto item_ids = clipboard_history_controller->GetHistoryItemIds();

  auto ids = keyboard_api::OnClipboardHistoryChanged::Create(item_ids);

  auto event = std::make_unique<extensions::Event>(
      extensions::events::VIRTUAL_KEYBOARD_PRIVATE_ON_CLIPBOARD_HISTORY_CHANGED,
      keyboard_api::OnClipboardHistoryChanged::kEventName, std::move(ids),
      browser_context_);
  router->BroadcastEvent(std::move(event));
}

void ChromeVirtualKeyboardDelegate::OnHasInputDevices(
    OnKeyboardSettingsCallback on_settings_callback,
    bool has_audio_input_devices) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  auto* keyboard_client = ChromeKeyboardControllerClient::Get();

  base::Value::Dict results;
  results.Set("layout", GetKeyboardLayout());

  // TODO(bshe): Consolidate a11y, hotrod and normal mode into a mode enum. See
  // crbug.com/529474.
  results.Set("a11ymode",
              keyboard_client->IsEnableFlagSet(
                  keyboard::KeyboardEnableFlag::kAccessibilityEnabled));
  results.Set("hotrodmode", g_hotrod_keyboard_enabled);
  base::Value::List features;

  keyboard::KeyboardConfig config = keyboard_client->GetKeyboardConfig();
  // TODO(oka): Change this to use config.voice_input.
  features.Append(GenerateFeatureFlag(
      "voiceinput", has_audio_input_devices && config.voice_input));
  features.Append(GenerateFeatureFlag("autocomplete", config.auto_complete));
  features.Append(GenerateFeatureFlag("autocorrect", config.auto_correct));
  features.Append(GenerateFeatureFlag("spellcheck", config.spell_check));
  features.Append(GenerateFeatureFlag("handwriting", config.handwriting));
  features.Append(GenerateFeatureFlag(
      "hindiinscriptlayout",
      base::FeatureList::IsEnabled(ash::features::kHindiInscriptLayout)));
  features.Append(GenerateFeatureFlag(
      "multiword",
      base::FeatureList::IsEnabled(ash::features::kAssistMultiWord)));
  features.Append(GenerateFeatureFlag(
      "stylushandwriting",
      base::FeatureList::IsEnabled(ash::features::kImeStylusHandwriting)));
  features.Append(GenerateFeatureFlag(
      "roundCorners", base::FeatureList::IsEnabled(
                          ash::features::kVirtualKeyboardRoundCorners)));
  features.Append(
      GenerateFeatureFlag("systemjapanesephysicaltyping",
                          base::FeatureList::IsEnabled(
                              ash::features::kSystemJapanesePhysicalTyping)));
  features.Append(GenerateFeatureFlag(
      "autocorrectparamstuning",
      base::FeatureList::IsEnabled(ash::features::kAutocorrectParamsTuning)));
  features.Append(GenerateFeatureFlag("jelly", true));
  features.Append(GenerateFeatureFlag(
      "japanesefunctionrow",
      base::FeatureList::IsEnabled(ash::features::kJapaneseFunctionRow)));
  features.Append(GenerateFeatureFlag(
      "usemlservicefornonlongformhandwriting",
      base::FeatureList::IsEnabled(
          ash::features::kUseMlServiceForNonLongformHandwritingOnAllBoards) ||
          IsOndeviceHandwritingEnabledViaCommandLine()));

  results.Set("features", std::move(features));

  std::move(on_settings_callback).Run(std::move(results));
}

void ChromeVirtualKeyboardDelegate::DispatchConfigChangeEvent(
    std::optional<base::Value::Dict> settings) {
  DCHECK(settings);

  EventRouter* router = GetRouterForEventName(
      browser_context_, keyboard_api::OnKeyboardConfigChanged::kEventName);
  if (!router)
    return;

  base::Value::List event_args;
  event_args.Append(std::move(*settings));

  auto event = std::make_unique<extensions::Event>(
      extensions::events::VIRTUAL_KEYBOARD_PRIVATE_ON_KEYBOARD_CONFIG_CHANGED,
      keyboard_api::OnKeyboardConfigChanged::kEventName, std::move(event_args),
      browser_context_);
  router->BroadcastEvent(std::move(event));
}

void ChromeVirtualKeyboardDelegate::RestrictFeatures(
    const api::virtual_keyboard::RestrictFeatures::Params& params,
    OnRestrictFeaturesCallback callback) {
  const api::virtual_keyboard::FeatureRestrictions& restrictions =
      params.restrictions;
  api::virtual_keyboard::FeatureRestrictions update;
  keyboard::KeyboardConfig current_config =
      ChromeKeyboardControllerClient::Get()->GetKeyboardConfig();
  keyboard::KeyboardConfig config(current_config);
  if (restrictions.spell_check_enabled &&
      config.spell_check != *restrictions.spell_check_enabled) {
    update.spell_check_enabled = *restrictions.spell_check_enabled;
    config.spell_check = *restrictions.spell_check_enabled;
  }
  if (restrictions.auto_complete_enabled &&
      config.auto_complete != *restrictions.auto_complete_enabled) {
    update.auto_complete_enabled = *restrictions.auto_complete_enabled;
    config.auto_complete = *restrictions.auto_complete_enabled;
  }
  if (restrictions.auto_correct_enabled &&
      config.auto_correct != *restrictions.auto_correct_enabled) {
    update.auto_correct_enabled = *restrictions.auto_correct_enabled;
    config.auto_correct = *restrictions.auto_correct_enabled;
  }
  if (restrictions.voice_input_enabled &&
      config.voice_input != *restrictions.voice_input_enabled) {
    update.voice_input_enabled = *restrictions.voice_input_enabled;
    config.voice_input = *restrictions.voice_input_enabled;
  }
  if (restrictions.handwriting_enabled &&
      config.handwriting != *restrictions.handwriting_enabled) {
    update.handwriting_enabled = *restrictions.handwriting_enabled;
    config.handwriting = *restrictions.handwriting_enabled;
  }

  if (config != current_config) {
    ChromeKeyboardControllerClient::Get()->SetKeyboardConfig(config);
    // This reloads the virtual keyboard (VK) even if it exists, so it can get
    // new restrictFeatures via chrome.virtualKeyboardPrivate.getKeyboardConfig.
    // However, this reload is unnecessary as the API specs do NOT require
    // restrictFeatures to take effect immediately midway through a VK session.
    // Keeping this unnecessary reload for now, just to avoid behaviour changes.
    ChromeKeyboardControllerClient::Get()->RebuildKeyboardIfEnabled();
  }
  std::move(callback).Run(std::move(update));
}

}  // namespace extensions