chromium/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler.h

// 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.

#ifndef CHROME_BROWSER_ACCESSIBILITY_MEDIA_APP_AX_MEDIA_APP_UNTRUSTED_HANDLER_H_
#define CHROME_BROWSER_ACCESSIBILITY_MEDIA_APP_AX_MEDIA_APP_UNTRUSTED_HANDLER_H_

#include <stdint.h>

#include <map>
#include <memory>
#include <optional>
#include <vector>

#include "ash/webui/media_app_ui/media_app_ui_untrusted.mojom.h"
#include "base/callback_list.h"
#include "base/containers/circular_deque.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "chrome/browser/accessibility/media_app/ax_media_app.h"
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/browser_context.h"
#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "ui/accessibility/ax_action_handler_base.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_mode.h"
#include "ui/accessibility/ax_mode_observer.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_node_id_forward.h"
#include "ui/accessibility/ax_serializable_tree.h"
#include "ui/accessibility/ax_tree_data.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/ax_tree_manager.h"
#include "ui/accessibility/ax_tree_serializer.h"
#include "ui/accessibility/ax_tree_source.h"
#include "ui/accessibility/ax_tree_update.h"
#include "ui/accessibility/platform/ax_platform.h"
#include "ui/gfx/native_widget_types.h"

class SkBitmap;

namespace content {

class RenderFrameHost;
class WebContents;

}  // namespace content

namespace screen_ai {
class OpticalCharacterRecognizer;
}

namespace ui {

struct AXActionData;
class AXNode;
class RectF;

}  // namespace ui

namespace ash {

struct AXMediaAppPageMetadata : ash::media_app_ui::mojom::PageMetadata {
  // The page number of the page that this metadata describes. 1-indexed. Pages
  // with a page number of 0 are 'deleted'.
  uint32_t page_num;
};

class AXMediaAppUntrustedHandler
    : public media_app_ui::mojom::OcrUntrustedPageHandler,
#if BUILDFLAG(IS_CHROMEOS_LACROS)
      private ui::AXModeObserver,
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
      private ui::AXActionHandlerBase {
 public:
  using TreeSource =
      ui::AXTreeSource<const ui::AXNode*, ui::AXTreeData*, ui::AXNodeData>;
  using TreeSerializer = ui::AXTreeSerializer<const ui::AXNode*,
                                              std::vector<const ui::AXNode*>,
                                              ui::AXTreeUpdate*,
                                              ui::AXTreeData*,
                                              ui::AXNodeData>;

  enum class OcrStatus {
    kUninitialized,
    kInitializationFailed,
    kInProgressWithNoTextExtractedYet,
    kInProgressWithTextExtracted,
    kCompletedWithNoTextExtracted,
    kCompletedWithTextExtracted,
  };

  AXMediaAppUntrustedHandler(
      content::BrowserContext& context,
      gfx::NativeWindow native_window,
      mojo::PendingRemote<media_app_ui::mojom::OcrUntrustedPage> page);
  AXMediaAppUntrustedHandler(const AXMediaAppUntrustedHandler&) = delete;
  AXMediaAppUntrustedHandler& operator=(
      const AXMediaAppUntrustedHandler&) = delete;
  ~AXMediaAppUntrustedHandler() override;

  // Informs the MediaApp whether the PDF OCR feature is enabled, i.e. the user
  // has an accessibility service such as ChromeVox activated.
  void SetPdfOcrEnabledState();

  virtual bool IsOcrServiceEnabled() const;
  bool IsAccessibilityEnabled() const;

  void OnOCRServiceInitialized(bool successful);

#if BUILDFLAG(IS_CHROMEOS_ASH)
  void OnAshAccessibilityModeChanged(
      const ash::AccessibilityStatusEventDetails& details);
#else
  // ui::AXModeObserver:
  void OnAXModeAdded(ui::AXMode mode) override;
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

  // ui::AXActionHandlerBase:
  void PerformAction(const ui::AXActionData& action_data) override;

  // ash::media_app_ui::mojom::OcrUntrustedPageHandler:
  void PageMetadataUpdated(
      const std::vector<ash::media_app_ui::mojom::PageMetadataPtr>
          page_metadata) override;
  void PageContentsUpdated(const std::string& dirty_page_id) override;
  void ViewportUpdated(const ::gfx::RectF& viewport_box,
                       float scale_factor) override;

 protected:
  void PushDirtyPage(const std::string& dirty_page_id);
  std::string PopDirtyPage();
  virtual void OcrNextDirtyPageIfAny();

  size_t min_pages_per_batch_ = 2u;
  size_t pages_ocred_on_initial_load_ = 0u;
  // `AXMediaApp` should outlive this handler.
  raw_ptr<AXMediaApp> media_app_;
  bool has_landmark_node_ = true;
  bool has_postamble_page_ = true;
  ui::AXTreeManager document_;
  std::unique_ptr<TreeSource> document_source_;
  std::unique_ptr<TreeSerializer> document_serializer_;
  std::map<const std::string, AXMediaAppPageMetadata> page_metadata_;
  std::map<const std::string, std::unique_ptr<ui::AXTreeManager>> pages_;
  std::map<const std::string, std::unique_ptr<TreeSource>> page_sources_;
  std::map<const std::string, std::unique_ptr<TreeSerializer>>
      page_serializers_;
  std::unique_ptr<std::vector<ui::AXTreeUpdate>>
      pending_serialized_updates_for_testing_;
  scoped_refptr<screen_ai::OpticalCharacterRecognizer> ocr_;

 private:
  size_t ComputePagesPerBatch() const;
  std::vector<ui::AXNodeData> CreateStatusNodesWithLandmark() const;
  std::vector<ui::AXNodeData> CreatePostamblePage() const;
  void SendAXTreeToAccessibilityService(const ui::AXTreeManager& manager,
                                        TreeSerializer& serializer);
  void ShowOcrServiceFailedToInitializeMessage();
  void GenerateDocumentTree();
  void UpdateDocumentTree(ui::AXTreeUpdate& document_update);
  void UpdatePageLocation(const std::string& page_id,
                          const gfx::RectF& page_location);
  // A callback which is run after the Media App sends the bitmap of the page
  // that should be OCRed.
  void OnBitmapReceived(const std::string& dirty_page_id,
                        const SkBitmap& bitmap);
  void OnPageOcred(const std::string& dirty_page_id,
                   const ui::AXTreeUpdate& tree_update);
  content::WebContents* GetMediaAppWebContents() const;
  content::RenderFrameHost* GetMediaAppRenderFrameHost() const;
  void StitchDocumentTree();
  bool HasRendererTerminatedDueToBadPageId(const std::string& method_name,
                                           const std::string& page_id);
  std::unique_ptr<gfx::Transform> MakeTransformFromOffsetAndScale() const;

#if BUILDFLAG(IS_CHROMEOS_ASH)
  // Observes whether spoken feedback is enabled in Ash.
  base::CallbackListSubscription accessibility_status_subscription_;
#else
  // Observes the presence of any accessibility service in LaCrOS.
  base::ScopedObservation<ui::AXPlatform, ui::AXModeObserver>
      ax_mode_observation_{this};
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
  // This `BrowserContext` will always outlive the WebUI, so this is safe.
  raw_ref<content::BrowserContext> browser_context_;
  gfx::NativeWindow native_window_;
  mojo::Remote<media_app_ui::mojom::OcrUntrustedPage> media_app_page_;
  gfx::RectF viewport_box_;
  float scale_factor_ = 0.0f;
  base::circular_deque<std::string> dirty_page_ids_;
  OcrStatus ocr_status_ = OcrStatus::kUninitialized;
  ui::AXTreeID document_tree_id_ = ui::AXTreeID::CreateNewAXTreeID();
  SEQUENCE_CHECKER(sequence_checker_);
  std::optional<mojo::ReportBadMessageCallback> bad_message_callback_ =
      std::nullopt;
  // Records when the user starts reading content in MediaApp.
  base::TimeTicks start_reading_time_;
  // Records of most recent time when the user reads content in MediaApp.
  base::TimeTicks latest_reading_time_;
  // Records the greatest page number to which the user has navigated.
  size_t greatest_visited_page_number_ = 0;

  base::WeakPtrFactory<AXMediaAppUntrustedHandler> weak_ptr_factory_{this};
};

}  // namespace ash

#endif  // CHROME_BROWSER_ACCESSIBILITY_MEDIA_APP_AX_MEDIA_APP_UNTRUSTED_HANDLER_H_