chromium/chromeos/ash/services/ime/public/mojom/mojom_traits.cc

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

#include "chromeos/ash/services/ime/public/mojom/mojom_traits.h"

#include "chromeos/ash/services/ime/public/mojom/input_method_host.mojom-shared.h"

namespace mojo {
namespace {

using AssistiveWindow = ash::ime::AssistiveWindow;
using AssistiveWindowDataView = ash::ime::mojom::AssistiveWindowDataView;
using AssistiveWindowType = ash::ime::AssistiveWindowType;
using AssistiveWindowTypeMojo = ash::ime::mojom::AssistiveWindowType;
using CompletionCandidateDataView =
    ash::ime::mojom::CompletionCandidateDataView;
using AssistiveSuggestionMode = ash::ime::AssistiveSuggestionMode;
using SuggestionMode = ash::ime::mojom::SuggestionMode;
using SuggestionType = ash::ime::mojom::SuggestionType;
using SuggestionsTextContextDataView =
    ash::ime::mojom::SuggestionsTextContextDataView;
using SuggestionsTextContext = ash::ime::SuggestionsTextContext;
using SuggestionCandidateDataView =
    ash::ime::mojom::SuggestionCandidateDataView;
using DecoderCompletionCandidate = ash::ime::DecoderCompletionCandidate;
using AssistiveSuggestionType = ash::ime::AssistiveSuggestionType;
using AssistiveSuggestion = ash::ime::AssistiveSuggestion;
using AutocorrectSuggestionProvider = ash::ime::AutocorrectSuggestionProvider;
using AutocorrectSuggestionProviderMojo =
    ash::ime::mojom::AutocorrectSuggestionProvider;

// Returns the histogram with the given parameters.
// If any of the parameters are invalid, returns nullptr.
base::Histogram* GetHistogramStrict(
    const std::string& name,
    ash::ime::mojom::HistogramBucketType bucket_type,
    const base::Histogram::Sample minimum,
    const base::Histogram::Sample maximum,
    const size_t bucket_count) {
  // When `InspectConstructionArguments` receives certain invalid parameters, it
  // will attempt to adjust them to be correct and return true. So in order to
  // be strict about the validity of the parameters, ensure that the parameters
  // are not adjusted by `InspectConstructionArguments`.
  auto adjusted_minimum = minimum;
  auto adjusted_maximum = maximum;
  auto adjusted_bucket_count = bucket_count;
  if (!base::Histogram::InspectConstructionArguments(
          name, &adjusted_minimum, &adjusted_maximum, &adjusted_bucket_count)) {
    return nullptr;
  }
  if (minimum != adjusted_minimum || maximum != adjusted_maximum ||
      bucket_count != adjusted_bucket_count) {
    return nullptr;
  }

  // Returns nullptr if validation failed in `FactoryGet`.
  // This also relies on `Histogram::InspectConstructionArguments` called from
  // `FactoryGet` to validate the bucket parameters a second time:
  // https://source.chromium.org/chromium/chromium/src/+/main:base/metrics/histogram.cc;l=422
  switch (bucket_type) {
    case ash::ime::mojom::HistogramBucketType::kExponential:
      return static_cast<base::Histogram*>(base::Histogram::FactoryGet(
          name.data(), minimum, maximum, bucket_count,
          base::HistogramBase::kUmaTargetedHistogramFlag));
    case ash::ime::mojom::HistogramBucketType::kLinear:
      return static_cast<base::Histogram*>(base::LinearHistogram::FactoryGet(
          name.data(), minimum, maximum, bucket_count,
          base::HistogramBase::kUmaTargetedHistogramFlag));
    default:
      return nullptr;
  }
}

}  // namespace

SuggestionMode EnumTraits<SuggestionMode, AssistiveSuggestionMode>::ToMojom(
    AssistiveSuggestionMode mode) {
  switch (mode) {
    case AssistiveSuggestionMode::kCompletion:
      return SuggestionMode::kCompletion;
    case AssistiveSuggestionMode::kPrediction:
      return SuggestionMode::kPrediction;
  }
}

bool EnumTraits<SuggestionMode, AssistiveSuggestionMode>::FromMojom(
    SuggestionMode input,
    AssistiveSuggestionMode* output) {
  switch (input) {
    case SuggestionMode::kUnknown:
      // The browser process should never receive an unknown suggestion mode.
      // When adding a new SuggestionMode, the Chromium side should be updated
      // first to handle it, before changing the other calling side to send the
      // new suggestion mode.
      return false;
    case SuggestionMode::kCompletion:
      *output = AssistiveSuggestionMode::kCompletion;
      return true;
    case SuggestionMode::kPrediction:
      *output = AssistiveSuggestionMode::kPrediction;
      return true;
  }
}

SuggestionType EnumTraits<SuggestionType, AssistiveSuggestionType>::ToMojom(
    AssistiveSuggestionType type) {
  switch (type) {
    case AssistiveSuggestionType::kAssistivePersonalInfo:
      return SuggestionType::kAssistivePersonalInfo;
    case AssistiveSuggestionType::kAssistiveEmoji:
      return SuggestionType::kAssistiveEmoji;
    case AssistiveSuggestionType::kMultiWord:
      return SuggestionType::kMultiWord;
    case AssistiveSuggestionType::kGrammar:
      return SuggestionType::kGrammar;
    case AssistiveSuggestionType::kLongpressDiacritic:
      return SuggestionType::kLongpressDiacritic;
  }
}

bool EnumTraits<SuggestionType, AssistiveSuggestionType>::FromMojom(
    SuggestionType input,
    AssistiveSuggestionType* output) {
  switch (input) {
    case SuggestionType::kUnknown:
      // The browser process should never receive an unknown suggestion type.
      // When adding a new SuggestionType, the Chromium side should be updated
      // first to handle it, before changing the other calling side to send the
      // new suggestion type.
      return false;
    case SuggestionType::kAssistivePersonalInfo:
      *output = AssistiveSuggestionType::kAssistivePersonalInfo;
      return true;
    case SuggestionType::kAssistiveEmoji:
      *output = AssistiveSuggestionType::kAssistiveEmoji;
      return true;
    case SuggestionType::kMultiWord:
      *output = AssistiveSuggestionType::kMultiWord;
      return true;
    case SuggestionType::kGrammar:
      *output = AssistiveSuggestionType::kGrammar;
      return true;
    case SuggestionType::kLongpressDiacritic:
      *output = AssistiveSuggestionType::kLongpressDiacritic;
      return true;
  }
}

bool StructTraits<SuggestionCandidateDataView, AssistiveSuggestion>::Read(
    SuggestionCandidateDataView input,
    AssistiveSuggestion* output) {
  if (!input.ReadMode(&output->mode)) {
    return false;
  }
  if (!input.ReadType(&output->type)) {
    return false;
  }
  if (!input.ReadText(&output->text)) {
    return false;
  }
  output->confirmed_length = input.confirmed_length();
  return true;
}

bool StructTraits<SuggestionsTextContextDataView, SuggestionsTextContext>::Read(
    SuggestionsTextContextDataView input,
    SuggestionsTextContext* output) {
  if (!input.ReadLastNChars(&output->last_n_chars)) {
    return false;
  }
  output->surrounding_text_length = input.surrounding_text_length();
  return true;
}

bool StructTraits<CompletionCandidateDataView, DecoderCompletionCandidate>::
    Read(CompletionCandidateDataView input,
         DecoderCompletionCandidate* output) {
  if (!input.ReadText(&output->text)) {
    return false;
  }
  output->score = input.normalized_score();
  return true;
}

AssistiveWindowTypeMojo
EnumTraits<AssistiveWindowTypeMojo, AssistiveWindowType>::ToMojom(
    AssistiveWindowType type) {
  switch (type) {
    case AssistiveWindowType::kUndoWindow:
      return AssistiveWindowTypeMojo::kUndo;
    case AssistiveWindowType::kEmojiSuggestion:
      return AssistiveWindowTypeMojo::kEmojiSuggestion;
    case AssistiveWindowType::kPersonalInfoSuggestion:
      return AssistiveWindowTypeMojo::kPersonalInfoSuggestion;
    case AssistiveWindowType::kGrammarSuggestion:
      return AssistiveWindowTypeMojo::kGrammarSuggestion;
    case AssistiveWindowType::kMultiWordSuggestion:
      return AssistiveWindowTypeMojo::kMultiWordSuggestion;
    case AssistiveWindowType::kLongpressDiacriticsSuggestion:
      return AssistiveWindowTypeMojo::kLongpressDiacriticsSuggestion;
    case AssistiveWindowType::kNone:
    default:
      return AssistiveWindowTypeMojo::kHidden;
  }
}

bool EnumTraits<AssistiveWindowTypeMojo, AssistiveWindowType>::FromMojom(
    AssistiveWindowTypeMojo input,
    AssistiveWindowType* output) {
  switch (input) {
    case AssistiveWindowTypeMojo::kHidden:
      *output = AssistiveWindowType::kNone;
      return true;
    case AssistiveWindowTypeMojo::kUndo:
      *output = AssistiveWindowType::kUndoWindow;
      return true;
    case AssistiveWindowTypeMojo::kEmojiSuggestion:
      *output = AssistiveWindowType::kEmojiSuggestion;
      return true;
    case AssistiveWindowTypeMojo::kPersonalInfoSuggestion:
      *output = AssistiveWindowType::kPersonalInfoSuggestion;
      return true;
    case AssistiveWindowTypeMojo::kGrammarSuggestion:
      *output = AssistiveWindowType::kGrammarSuggestion;
      return true;
    case AssistiveWindowTypeMojo::kMultiWordSuggestion:
      *output = AssistiveWindowType::kMultiWordSuggestion;
      return true;
    case AssistiveWindowTypeMojo::kLongpressDiacriticsSuggestion:
      *output = AssistiveWindowType::kLongpressDiacriticsSuggestion;
      return true;
  }
}

bool StructTraits<AssistiveWindowDataView, AssistiveWindow>::Read(
    AssistiveWindowDataView input,
    AssistiveWindow* output) {
  if (!input.ReadType(&output->type)) {
    return false;
  }
  if (!input.ReadCandidates(&output->candidates)) {
    return false;
  }
  return true;
}

AutocorrectSuggestionProviderMojo
EnumTraits<AutocorrectSuggestionProviderMojo, AutocorrectSuggestionProvider>::
    ToMojom(AutocorrectSuggestionProvider provider) {
  switch (provider) {
    case AutocorrectSuggestionProvider::kUsEnglishPrebundled:
      return AutocorrectSuggestionProviderMojo::kUsEnglishPrebundled;
    case AutocorrectSuggestionProvider::kUsEnglishDownloaded:
      return AutocorrectSuggestionProviderMojo::kUsEnglishDownloaded;
    case AutocorrectSuggestionProvider::kUsEnglish840:
      return AutocorrectSuggestionProviderMojo::kUsEnglish840;
    case AutocorrectSuggestionProvider::kUsEnglish840V2:
      return AutocorrectSuggestionProviderMojo::kUsEnglish840V2;
    default:
      return AutocorrectSuggestionProviderMojo::kUnknown;
  }
}

bool EnumTraits<AutocorrectSuggestionProviderMojo,
                AutocorrectSuggestionProvider>::
    FromMojom(AutocorrectSuggestionProviderMojo input,
              AutocorrectSuggestionProvider* output) {
  switch (input) {
    case AutocorrectSuggestionProviderMojo::kUsEnglishPrebundled:
      *output = AutocorrectSuggestionProvider::kUsEnglishPrebundled;
      return true;
    case AutocorrectSuggestionProviderMojo::kUsEnglishDownloaded:
      *output = AutocorrectSuggestionProvider::kUsEnglishDownloaded;
      return true;
    case AutocorrectSuggestionProviderMojo::kUsEnglish840:
      *output = AutocorrectSuggestionProvider::kUsEnglish840;
      return true;
    case AutocorrectSuggestionProviderMojo::kUsEnglish840V2:
      *output = AutocorrectSuggestionProvider::kUsEnglish840V2;
      return true;
    default:
      *output = AutocorrectSuggestionProvider::kUnknown;
      return true;
  }
}

bool StructTraits<
    ash::ime::mojom::BucketedHistogramDataView,
    base::Histogram*>::Read(ash::ime::mojom::BucketedHistogramDataView input,
                            base::Histogram** output) {
  std::string name;
  if (!input.ReadName(&name)) {
    return false;
  }
  if (!base::StartsWith(name, "Untrusted.")) {
    DLOG(ERROR) << "Invalid histogram name: " << name
                << ". BucketedHistogram name must begin with 'Untrusted.'.";
    return false;
  }

  const auto minimum =
      base::strict_cast<base::Histogram::Sample>(input.minimum());
  const auto maximum =
      base::strict_cast<base::Histogram::Sample>(input.maximum());
  const auto bucket_count = base::strict_cast<size_t>(input.bucket_count());

  base::Histogram* counter = GetHistogramStrict(name, input.bucket_type(),
                                                minimum, maximum, bucket_count);

  // `counter` will be nullptr if the parameters are invalid.
  if (counter == nullptr) {
    DLOG(ERROR) << "Invalid bucket parameters: bucket_type="
                << static_cast<int>(input.bucket_type())
                << ", minimum=" << minimum << ", maximum=" << maximum
                << ", bucket_count=" << bucket_count;
    return false;
  }

  // As a final defensive check, ensure that the constructed histogram has all
  // the expected parameters as passed in.
  if (!counter->HasConstructionArguments(minimum, maximum, bucket_count)) {
    DLOG(ERROR) << "Invalid histogram. Expected histogram with minimum="
                << minimum << ", maximum=" << maximum
                << ", bucket_count=" << bucket_count
                << ", but got minimum=" << counter->declared_min()
                << ", maximum=" << counter->declared_max()
                << ", bucket_count=" << counter->bucket_count();
    return false;
  }

  *output = counter;
  return true;
}

}  // namespace mojo