chromium/chrome/browser/ash/input_method/editor_system_actuator.cc

// Copyright 2023 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/ash/input_method/editor_system_actuator.h"

#include <memory>

#include "ash/public/cpp/new_window_delegate.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/ash/input_method/editor_feedback.h"
#include "chrome/browser/ash/input_method/editor_metrics_enums.h"
#include "chrome/browser/ash/input_method/editor_metrics_recorder.h"
#include "chrome/browser/ash/input_method/editor_text_insertion.h"
#include "chrome/browser/ash/input_method/url_utils.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/url_constants.h"

namespace ash::input_method {
namespace {

constexpr base::TimeDelta kAnnouncementDelay = base::Milliseconds(200);

constexpr std::string_view
    kDomainsRequiringParagraphConcatenationWhenInsertingText[] = {
        "notion",
        "medium",
};

bool IsUrlAllowed(const GURL& url) {
  return url.SchemeIs(url::kHttpsScheme) ||
         url.spec().starts_with("chrome://os-settings/osLanguages/input");
}

EditorTextInsertion::InsertionStrategy GetInsertionStrategy(const GURL& url) {
  for (std::string_view domain :
       kDomainsRequiringParagraphConcatenationWhenInsertingText) {
    if (IsSubDomain(url, domain)) {
      return EditorTextInsertion::InsertionStrategy::kInsertAsASingleParagraph;
    }
  }
  return EditorTextInsertion::InsertionStrategy::kInsertAsMultipleParagraphs;
}

}  // namespace

EditorSystemActuator::EditorSystemActuator(
    Profile* profile,
    mojo::PendingAssociatedReceiver<orca::mojom::SystemActuator> receiver,
    System* system)
    : profile_(profile),
      system_actuator_receiver_(this, std::move(receiver)),
      system_(system) {}

EditorSystemActuator::~EditorSystemActuator() = default;

void EditorSystemActuator::InsertText(const std::string& text) {
  EditorMetricsRecorder* logger = system_->GetMetricsRecorder();
  logger->LogEditorState(EditorStates::kTextInsertionRequested);
  // After making an announcement there needs to be a small delay to ensure any
  // other announcements triggered from a text insertion do not collide with the
  // original announcement.
  system_->Announce(
      l10n_util::GetStringUTF16(IDS_EDITOR_ANNOUNCEMENT_TEXT_FOR_INSERTION));
  announcement_delay_.Start(
      FROM_HERE, kAnnouncementDelay,
      base::BindOnce(&EditorSystemActuator::QueueTextInsertion,
                     weak_ptr_factory_.GetWeakPtr(), text));
}

void EditorSystemActuator::ApproveConsent() {
  system_->ProcessConsentAction(ConsentAction::kApprove);
  system_->HandleTrigger(/*preset_query_id=*/std::nullopt,
                         /*freeform_text=*/std::nullopt);
}

void EditorSystemActuator::DeclineConsent() {
  system_->ProcessConsentAction(ConsentAction::kDecline);
  system_->CloseUI();
}

void EditorSystemActuator::OpenUrlInNewWindow(const GURL& url) {
  if (!IsUrlAllowed(url)) {
    mojo::ReportBadMessage("Invalid URL scheme. Only HTTPS is allowed.");
    return;
  }
  ash::NewWindowDelegate::GetPrimary()->OpenUrl(
      url, ash::NewWindowDelegate::OpenUrlFrom::kUnspecified,
      ash::NewWindowDelegate::Disposition::kNewForegroundTab);
}

void EditorSystemActuator::ShowUI() {
  system_->ShowUI();
}

void EditorSystemActuator::CloseUI() {
  system_->GetMetricsRecorder()->LogEditorState(
      EditorStates::kClickCloseButton);
  system_->CloseUI();
}

void EditorSystemActuator::SubmitFeedback(const std::string& description) {
  SendEditorFeedback(profile_, description);
  system_->Announce(
      l10n_util::GetStringUTF16(IDS_EDITOR_ANNOUNCEMENT_TEXT_FOR_FEEDBACK));
}

void EditorSystemActuator::OnTrigger(
    orca::mojom::TriggerContextPtr trigger_context) {
  EditorMetricsRecorder* logger = system_->GetMetricsRecorder();
  logger->SetTone(ToEditorMetricTone(std::move(trigger_context)));
}

void EditorSystemActuator::EmitMetricEvent(
    orca::mojom::MetricEvent metric_event) {
  EditorMetricsRecorder* logger = system_->GetMetricsRecorder();
  std::optional<EditorStates> editor_state_metric =
      ToEditorStatesMetric(metric_event);

  if (editor_state_metric.has_value()) {
    logger->LogEditorState(editor_state_metric.value());
  }
}

void EditorSystemActuator::OnFocus(int context_id) {
  if (queued_text_insertion_ == nullptr) {
    return;
  }
  if (queued_text_insertion_->HasTimedOut()) {
    queued_text_insertion_ = nullptr;
    return;
  }
  if (queued_text_insertion_->Commit()) {
    EditorMetricsRecorder* logger = system_->GetMetricsRecorder();
    logger->LogEditorState(EditorStates::kInsert);
    logger->LogNumberOfCharactersInserted(
        queued_text_insertion_->GetTextLength());
    logger->LogNumberOfCharactersSelectedForInsert(
        system_->GetSelectedTextLength());
    queued_text_insertion_ = nullptr;
    return;
  }
}

void EditorSystemActuator::QueueTextInsertion(const std::string pending_text) {
  // The text cannot be immediately inserted as the target input is not focused
  // at this point, the WebUI is focused. After closing the WebUI focus will
  // return to the original text input.
  queued_text_insertion_ = std::make_unique<EditorTextInsertion>(
      std::move(pending_text), GetInsertionStrategy(current_url_));
  EditorMetricsRecorder* logger = system_->GetMetricsRecorder();
  logger->LogEditorState(EditorStates::kTextQueuedForInsertion);
  system_->CloseUI();
}

void EditorSystemActuator::OnInputContextUpdated(const GURL& url) {
  current_url_ = url;
}

}  // namespace ash::input_method