// Copyright 2013 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_PRINTING_PRINT_VIEW_MANAGER_BASE_H_
#define CHROME_BROWSER_PRINTING_PRINT_VIEW_MANAGER_BASE_H_

#include <memory>
#include <string>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "build/build_config.h"
#include "chrome/browser/printing/print_job.h"
#include "components/enterprise/buildflags/buildflags.h"
#include "components/prefs/pref_member.h"
#include "components/printing/browser/print_manager.h"
#include "components/printing/browser/print_to_pdf/pdf_print_job.h"
#include "components/printing/common/print.mojom-forward.h"
#include "components/services/print_compositor/public/mojom/print_compositor.mojom.h"
#include "printing/buildflags/buildflags.h"
#include "ui/accessibility/ax_tree_update_forward.h"

#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
// Causes circular dependencies with //chrome/browser/ui. The nogncheck can be
// removed when printer_handler.h is modularized.
#include "chrome/browser/ui/webui/print_preview/printer_handler.h"  // nogncheck crbug.com/40147906
#endif

#if BUILDFLAG(ENABLE_OOP_PRINTING)
#include <optional>

#include "chrome/browser/printing/print_backend_service_manager.h"
#endif

#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
#include "chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h"  // nogncheck crbug.com/40147906
#endif  // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)

namespace base {
class RefCountedMemory;
}

namespace printing {

class PrintQueriesQueue;
class PrinterQuery;

// Base class for managing the print commands for a WebContents.
class PrintViewManagerBase : public PrintManager, public PrintJob::Observer {
 public:
  // An observer interface implemented by classes which are interested
  // in `PrintViewManagerBase` events. Only used for testing.
  class TestObserver : public base::CheckedObserver {
   public:
    virtual void OnPrintNow(const content::RenderFrameHost* rfh) {}

    virtual void OnScriptedPrint() {}

    // This method is never called unless `ENABLE_PRINT_PREVIEW`.
    virtual void OnPrintPreviewDone() {}

    // This method is never called unless `ENABLE_OOP_PRINTING`.
    virtual void OnRegisterSystemPrintClient(bool succeeded) {}

    virtual void OnDidPrintDocument() {}

    virtual void OnRenderFrameDeleted() {}
  };

  PrintViewManagerBase(const PrintViewManagerBase&) = delete;
  PrintViewManagerBase& operator=(const PrintViewManagerBase&) = delete;

  ~PrintViewManagerBase() override;

  // Prints the current document immediately. Since the rendering is
  // asynchronous, the actual printing will not be completed on the return of
  // this function. Returns false if printing is impossible at the moment.
  virtual bool PrintNow(content::RenderFrameHost* rfh);
#if BUILDFLAG(IS_ANDROID)
  virtual bool PrintNow(content::RenderFrameHost* rfh,
                        bool print_selection_only);
#endif

  // Like PrintNow(), but for the node under the context menu, instead of the
  // entire frame.
  void PrintNodeUnderContextMenu(content::RenderFrameHost* rfh);

#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
  // Prints the document in `print_data` with settings specified in
  // `job_settings`. Runs `callback` with an error string on failure and with an
  // empty string if the print job is started successfully. `rfh` is the render
  // frame host for the preview initiator contents respectively.
  void PrintForPrintPreview(base::DictValue job_settings,
                            scoped_refptr<base::RefCountedMemory> print_data,
                            content::RenderFrameHost* rfh,
                            PrinterHandler::PrintCallback callback);
#endif

  // Prints the current document pages specified by `page_ranges` with
  // parameters, specified by `print_pages_params` into a PDF document,
  // returned with `callback`. If `page_ranges` is empty, the entire
  // document is printed.
  void PrintToPdf(content::RenderFrameHost* rfh,
                  const std::string& page_ranges,
                  mojom::PrintPagesParamsPtr print_pages_params,
                  print_to_pdf::PdfPrintJob::PrintToPdfCallback callback);

// Notifies the print view manager that the system dialog has been cancelled
// after being opened from Print Preview.
#if BUILDFLAG(IS_WIN) && BUILDFLAG(ENABLE_PRINT_PREVIEW)
  void SystemDialogCancelled();
#endif

  std::u16string RenderSourceName();

  content::RenderFrameHost* GetPrintingRFHForTesting() const {
    return printing_rfh_;
  }

  // mojom::PrintManagerHost:
  void DidGetPrintedPagesCount(int32_t cookie, uint32_t number_pages) override;
  void DidPrintDocument(mojom::DidPrintDocumentParamsPtr params,
                        DidPrintDocumentCallback callback) override;
  void GetDefaultPrintSettings(
      GetDefaultPrintSettingsCallback callback) override;
  void IsPrintingEnabled(IsPrintingEnabledCallback callback) override;
  void ScriptedPrint(mojom::ScriptedPrintParamsPtr params,
                     ScriptedPrintCallback callback) override;
  void PrintingFailed(int32_t cookie,
                      mojom::PrintFailureReason reason) override;

  // Adds and removes observers for `PrintViewManagerBase` events. The order in
  // which notifications are sent to observers is undefined. Observers must be
  // sure to remove the observer before they go away.
  void AddTestObserver(TestObserver& observer);
  void RemoveTestObserver(TestObserver& observer);

 protected:
#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
  using PrintDocumentCallback =
      base::OnceCallback<void(scoped_refptr<base::RefCountedMemory> print_data,
                              const gfx::Size& page_size,
                              const gfx::Rect& content_area,
                              const gfx::Point& offsets)>;
#endif

  explicit PrintViewManagerBase(content::WebContents* web_contents);

  // Helper method for checking whether the WebContents is crashed.
  bool IsCrashed();

  void SetPrintingRFH(content::RenderFrameHost* rfh);

  // Helper method to do some common operations and checks when starting to
  // printing.
  bool StartPrintCommon(content::RenderFrameHost* rfh,
                        bool print_selection_only);

#if BUILDFLAG(ENABLE_OOP_PRINTING)
  // Register with the `PrintBackendServiceManager` as a client for queries
  // which will require a UI (the system print dialog).  Some platforms have
  // limitations on having multiple clients of this type; this function returns
  // `false` if such a registration fails because of this restriction.  In
  // that case no further attempts to make the queries should be made.
  bool RegisterSystemPrintClient();

  // Unregister with the `PrintBackendServiceManager` if a client for queries
  // which require a UI.  This function can be called even if there is no
  // current registration.
  void UnregisterSystemPrintClient();

  const std::optional<PrintBackendServiceManager::ClientId>&
  query_with_ui_client_id() const {
    return query_with_ui_client_id_;
  }
#endif  // BUILDFLAG(ENABLE_OOP_PRINTING)

  // content::WebContentsObserver implementation.
  void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;

  // Creates a new print job.
  virtual scoped_refptr<PrintJob> CreatePrintJob(
      PrintJobManager* print_job_manager);

  // Sets up a new empty print job with no settings loaded. If there is
  // currently a print job, safely disconnect from it. Returns false if it is
  // impossible to safely disconnect from the current print job or it is
  // impossible to create a new print job.
  virtual bool SetupNewPrintJob(std::unique_ptr<PrinterQuery> query);

  // Makes sure the current print_job_ has all its data before continuing, and
  // disconnect from it.
  // WARNING: `this` may not be alive after DisconnectFromCurrentPrintJob()
  // returns.
  void DisconnectFromCurrentPrintJob();

  bool GetPrintingEnabledBooleanPref() const;

  PrintQueriesQueue* queue() { return queue_.get(); }

  // PrintJob::Observer overrides:
  void OnDocDone(int job_id, PrintedDocument* document) override;
  void OnJobDone() override;
  void OnCanceling() override;
  void OnFailed() override;

  base::ObserverList<TestObserver>& GetTestObservers() {
    return test_observers_;
  }

  // Prints the document by posting on the IO thread. This should only be called
  // by `ScriptedPrint()` and `CompleteScriptedPrintAfterContentAnalysis()`.
  // This method is virtual for testing purposes.
  virtual void CompleteScriptedPrint(content::RenderFrameHost* rfh,
                                     mojom::ScriptedPrintParamsPtr params,
                                     ScriptedPrintCallback callback);

#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
  // Helper method bound to `content_analysis_before_printing_document_` when
  // content analysis should happen right before the document is to be printed.
  // This method is virtual for testing purposes.
  virtual void ContentAnalysisBeforePrintingDocument(
      enterprise_connectors::ContentAnalysisDelegate::Data scanning_data,
      scoped_refptr<base::RefCountedMemory> print_data,
      const gfx::Size& page_size,
      const gfx::Rect& content_area,
      const gfx::Point& offsets);

  // Helper method to set `analyzing_content_` in child classes.
  void set_analyzing_content(bool analyzing);

  void set_content_analysis_before_printing_document(
      PrintDocumentCallback callback);
#endif  // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)

  // Manages the low-level talk to the printer.
  scoped_refptr<PrintJob> print_job_;

 private:
  friend class TestPrintViewManager;

  // content::WebContentsObserver implementation.
  void RenderFrameHostStateChanged(
      content::RenderFrameHost* render_frame_host,
      content::RenderFrameHost::LifecycleState /*old_state*/,
      content::RenderFrameHost::LifecycleState new_state) override;

  // Cancels the print job.
  void NavigationStopped() override;

  // Implementation without callbacks.
  bool OnComposePdfDoneImpl(int document_cookie,
                            const gfx::Size& page_size,
                            const gfx::Rect& content_area,
                            const gfx::Point& physical_offsets,
                            mojom::PrintCompositor::Status status,
                            base::ReadOnlySharedMemoryRegion region);

  // IPC message handlers for service.
  void OnComposeDocumentDone(int document_cookie,
                             const gfx::Size& page_size,
                             const gfx::Rect& content_area,
                             const gfx::Point& physical_offsets,
                             DidPrintDocumentCallback callback,
                             mojom::PrintCompositor::Status status,
                             base::ReadOnlySharedMemoryRegion region);

  // Helper for mojom::PrintManagerHost handling.
  void OnDidPrintDocument(PrintManager::DidPrintDocumentCallback callback,
                          bool succeeded);

#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
  // Helpers for PrintForPrintPreview();
  void OnPrintSettingsDone(scoped_refptr<base::RefCountedMemory> print_data,
                           uint32_t page_count,
#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
                           bool show_system_dialog,
#endif
                           PrinterHandler::PrintCallback callback,
                           std::unique_ptr<PrinterQuery> printer_query);

  void StartLocalPrintJob(scoped_refptr<base::RefCountedMemory> print_data,
                          uint32_t page_count,
#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
                          bool show_system_dialog,
#endif
                          int cookie,
                          PrinterHandler::PrintCallback callback);
#endif  // BUILDFLAG(ENABLE_PRINT_PREVIEW)

  // Runs `callback` with `params` to reply to GetDefaultPrintSettings().
  void GetDefaultPrintSettingsReply(GetDefaultPrintSettingsCallback callback,
                                    mojom::PrintParamsPtr params);

  // Runs `callback` with `params` to reply to ScriptedPrint().
  void ScriptedPrintReply(ScriptedPrintCallback callback,
                          int process_id,
                          mojom::PrintPagesParamsPtr params);

  // Requests the RenderView to render all the missing pages for the print job.
  // No-op if no print job is pending. Returns true if at least one page has
  // been requested to the renderer.
  // WARNING: `this` may not be alive after RenderAllMissingPagesNow() returns.
  bool RenderAllMissingPagesNow();

  // Checks that synchronization is correct with `print_job_` based on `cookie`.
  bool PrintJobHasDocument(int cookie);

  // Starts printing the `document` in `print_job_` with the given `print_data`.
  // This method assumes PrintJobHasDocument() has been called, and `print_data`
  // contains valid data.
  void PrintDocument(scoped_refptr<base::RefCountedMemory> print_data,
                     const gfx::Size& page_size,
                     const gfx::Rect& content_area,
                     const gfx::Point& offsets);

  // Quits the current message loop if these conditions hold true: a document is
  // loaded and is complete and waiting_for_pages_to_be_rendered_ is true. This
  // function is called in DidPrintDocument(). The inner message loop was
  // created by RenderAllMissingPagesNow().
  void ShouldQuitFromInnerMessageLoop();

  // Terminates the print job. No-op if no print job has been created. If
  // `cancel` is true, cancel it instead of waiting for the job to finish. Will
  // call ReleasePrintJob().
  void TerminatePrintJob(bool cancel);

  // Releases print_job_. Correctly deregisters from notifications. No-op if
  // no print job has been created.
  void ReleasePrintJob();

  // Runs an inner message loop. It will set inside_inner_message_loop_ to true
  // while the blocking inner message loop is running. This is useful in cases
  // where the RenderView is about to be destroyed while a printing job isn't
  // finished.
  // WARNING: `this` may not be alive after RunInnerMessageLoop() returns.
  bool RunInnerMessageLoop();

  // In the case of Scripted Printing, where the renderer is controlling the
  // control flow, print_job_ is initialized whenever possible. No-op is
  // print_job_ is initialized.
  bool OpportunisticallyCreatePrintJob(int cookie);

  // Release the PrinterQuery associated with our `cookie_`.
  void ReleasePrinterQuery();

#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
  // Helper method called after a verdict has been obtained from scanning
  // to-be-printed content, right before the actual `print_job_` starts.
  // Printing will proceed only if `allowed` is set to true, otherwise the print
  // job will be cancelled.
  void CompletePrintDocumentAfterContentAnalysis(
      scoped_refptr<base::RefCountedMemory> print_data,
      const gfx::Size& page_size,
      const gfx::Rect& content_area,
      const gfx::Point& offsets,
      bool allowed);
#endif  // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)

  bool PrintNowImpl(content::RenderFrameHost* rfh, bool print_selection_only);

  // The current RFH that is printing with a system printing dialog.
  raw_ptr<content::RenderFrameHost> printing_rfh_ = nullptr;

  // Indication of success of the print job.
  bool printing_succeeded_ = false;

  // Indication that the job is getting canceled.
  bool canceling_job_ = false;

#if BUILDFLAG(IS_ANDROID)
  bool print_selection_only_ = false;
#endif

  // Set while running an inner message loop inside RenderAllMissingPagesNow().
  // This means we are _blocking_ until all the necessary pages have been
  // rendered or the print settings are being loaded.
  base::OnceClosure quit_inner_loop_;

  // Whether printing is enabled.
  BooleanPrefMember printing_enabled_;

#if BUILDFLAG(ENABLE_OOP_PRINTING)
  // Client ID with the print backend service manager for system print dialog.
  std::optional<PrintBackendServiceManager::ClientId> query_with_ui_client_id_;
#endif

#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
  // Indicates that the page/document is currently undergoing content analysis.
  bool analyzing_content_ = false;

  // Called by `PrintDocument` to insert content analysis logic before key
  // printing steps like `PrintJob::StartPrinting`.
  PrintDocumentCallback content_analysis_before_printing_document_;
#endif  // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)

  const scoped_refptr<PrintQueriesQueue> queue_;

  base::ObserverList<TestObserver> test_observers_;

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

}  // namespace printing

#endif  // CHROME_BROWSER_PRINTING_PRINT_VIEW_MANAGER_BASE_H_
