// Copyright 2020 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_BACKEND_SERVICE_MANAGER_H_
#define CHROME_BROWSER_PRINTING_PRINT_BACKEND_SERVICE_MANAGER_H_

#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <variant>

#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/no_destructor.h"
#include "base/types/expected.h"
#include "base/types/strong_alias.h"
#include "base/unguessable_token.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/services/printing/public/mojom/print_backend_service.mojom.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "printing/buildflags/buildflags.h"

#if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
#include "ui/gfx/native_ui_types.h"
#endif

#if !BUILDFLAG(ENABLE_OOP_PRINTING)
#error "Out-of-process printing must be enabled."
#endif

namespace crash_keys {
class ScopedPrinterInfo;
}

namespace printing {

class PrintedPage;

class PrintBackendServiceManager {
 public:
  // A RemoteId is used to identify a particular PrintBackendService that
  // will be servicing queries and printing of a document.  This abstraction
  // allows for identifying the service desired to be used, without relying
  // upon a printer name (which sometimes is not available, such as when
  // doing general queries).
  using RemoteId = base::StrongAlias<class RemoteIdTag, uint32_t>;

  // A ClientId represents a printing action that is being performed by a
  // browser tab.  There can be different ClientIds depending upon the
  // action that is being performed:
  // - During Print Preview, the tab will have a ClientId for the related
  //   queries.  This ClientId might make use of multiple different RemoteIds,
  //   depending upon the destinations selected during the preview.
  // - If a user initiates system print, then a different ClientId is used to
  //   manage the actions of the system dialog.
  // - Once it is time to print a document, a different ClientId is used for
  //   managing the printing sequence.
  // For system print dialog and document printing, using the same RemoteId
  // between the two different clients is important to be able to maintain the
  // same device context in the PrintBackendService.
  using ClientId = base::StrongAlias<class ClientIdTag, uint32_t>;

  // A ContextId is an abstraction of a printing context which resides in the
  // PrintBackendService.  There can be multiple ContextIds associated with a
  // RemoteId, since a service could be supporting a system print dialog as
  // well as multiple documents being printed.  A ContextId is only ever
  // associated with a single RemoteId.
  // For system print dialogs, the ContextId used to get the settings will
  // be shared with another ClientId for printing the document, so that the
  // same device context settings are used at printing time.
  using ContextId = base::StrongAlias<class ContextIdTag, uint32_t>;

  // Contains set of client IDs.
  using ClientsSet = base::flat_set<ClientId>;

  // Mapping of the RemoteId
  using QueryWithUiClientsMap = base::flat_map<ClientId, RemoteId>;

  // Mapping of clients to each remote ID that is for printing.
  using PrintClientsMap = base::flat_map<RemoteId, ClientsSet>;

  // Amount of idle time to wait before resetting the connection to the service.
  static constexpr base::TimeDelta kNoClientsRegisteredResetOnIdleTimeout =
      base::Seconds(10);
  static constexpr base::TimeDelta kClientsRegisteredResetOnIdleTimeout =
      base::Seconds(120);

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

  // Launch a service that is intended to persist indefinitely and can be used
  // by all further clients.
  static void LaunchPersistentService();

  // Client registration routines.  These act as a signal of impending activity
  // enabling possible optimizations within the manager.  They return an ID
  // which the callers are to use with `UnregisterClient()` once they have
  // completed their printing activity.

  // Register as a client of PrintBackendServiceManager for print queries.
  ClientId RegisterQueryClient();

  // Register as a client of PrintBackendServiceManager for print queries which
  // require a system print dialog UI.  If a platform cannot support concurrent
  // queries of this type then this will return `std::nullopt` if another
  // client is already registered.
  std::optional<ClientId> RegisterQueryWithUiClient();

  // Register as a client of PrintBackendServiceManager for printing a document
  // to a specific printer.
  ClientId RegisterPrintDocumentClient(const std::string& printer_name);

  // Register as a client of PrintBackendServiceManager for printing a document
  // to a specific printer.  Use the same `RemoteId` for this new printing
  // client as has been used by the indicated query with UI client.  This method
  // will DCHECK if the client ID provided is not for a query with UI client.
  // Call can return nullopt if the service has terminated by the time this call
  // is made and the remote used by client `id` no longer exists.
  std::optional<ClientId> RegisterPrintDocumentClientReusingClientRemote(
      ClientId id);

  // Notify the manager that this client is no longer needing print backend
  // services.  This signal might alter the manager's internal optimizations.
  void UnregisterClient(ClientId id);

  // Wrappers around mojom::PrintBackendService call.
  void EnumeratePrinters(
      mojom::PrintBackendService::EnumeratePrintersCallback callback);
  void FetchCapabilities(
      const std::string& printer_name,
      mojom::PrintBackendService::FetchCapabilitiesCallback callback);
  void GetDefaultPrinterName(
      mojom::PrintBackendService::GetDefaultPrinterNameCallback callback);
#if BUILDFLAG(IS_CHROMEOS)
  void GetPrinterSemanticCapsAndDefaults(
      const std::string& printer_name,
      mojom::PrintBackendService::GetPrinterSemanticCapsAndDefaultsCallback
          callback);
#endif
#if BUILDFLAG(IS_WIN)
  void GetPaperPrintableArea(
      const std::string& printer_name,
      const PrintSettings::RequestedMedia& media,
      mojom::PrintBackendService::GetPaperPrintableAreaCallback callback);
#endif
  ContextId EstablishPrintingContext(ClientId client_id,
                                     const std::string& printer_name
#if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
                                     ,
                                     gfx::NativeView parent_view
#endif
  );
  void UseDefaultSettings(
      ClientId client_id,
      ContextId context_id,
      mojom::PrintBackendService::UseDefaultSettingsCallback callback);
#if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
  void AskUserForSettings(
      ClientId client_id,
      ContextId context_id,
      int max_pages,
      bool has_selection,
      bool is_scripted,
      mojom::PrintBackendService::AskUserForSettingsCallback callback);
#endif
  // `UpdatePrintSettings()` can be used prior to initiating a system print
  // dialog or right before starting to print a document.  The first requires a
  // `client_id` of `kQueryWithUi` type, while the latter requires a the ID to
  // be of type `kPrintDocument`.
  // The destination printer is still unknown when initiating a system print
  // dialog, so `printer_name` will be empty in this case.  The destination
  // must be known when starting to print a document.  `UpdatePrintSettings()`
  // uses this insight to know what kind of client type is to be expected for
  // the provided `client_id`.  The function will CHECK if the `client_id`
  // is not registered for the expected type.
  void UpdatePrintSettings(
      ClientId client_id,
      const std::string& printer_name,
      ContextId context_id,
      base::DictValue job_settings,
      mojom::PrintBackendService::UpdatePrintSettingsCallback callback);
  // `StartPrinting()` initiates the printing of a document.  The optional
  // `settings` is used in the case where a system print dialog is invoked
  // from in the browser, and this provides those settings for printing.
  void StartPrinting(
      ClientId client_id,
      const std::string& printer_name,
      ContextId context_id,
      int document_cookie,
      const std::u16string& document_name,
#if !BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
      std::optional<PrintSettings> settings,
#endif
      mojom::PrintBackendService::StartPrintingCallback callback);
#if BUILDFLAG(IS_WIN)
  void RenderPrintedPage(
      ClientId client_id,
      const std::string& printer_name,
      int document_cookie,
      const PrintedPage& page,
      mojom::MetafileDataType page_data_type,
      base::ReadOnlySharedMemoryRegion serialized_page_data,
      mojom::PrintBackendService::RenderPrintedPageCallback callback);
#endif
  void RenderPrintedDocument(
      ClientId client_id,
      const std::string& printer_name,
      int document_cookie,
      uint32_t page_count,
      mojom::MetafileDataType data_type,
      base::ReadOnlySharedMemoryRegion serialized_data,
      mojom::PrintBackendService::RenderPrintedDocumentCallback callback);
  void DocumentDone(ClientId client_id,
                    const std::string& printer_name,
                    int document_cookie,
                    mojom::PrintBackendService::DocumentDoneCallback callback);
  void Cancel(ClientId client_id,
              const std::string& printer_name,
              int document_cookie,
              mojom::PrintBackendService::CancelCallback callback);

  // Query if printer driver has been found to require elevated privilege in
  // order to have print queries/commands succeed.
  bool PrinterDriverFoundToRequireElevatedPrivilege(
      const std::string& printer_name) const;

  // Make note that `printer_name` has been detected as requiring elevated
  // privileges in order to operate.
  void SetPrinterDriverFoundToRequireElevatedPrivilege(
      const std::string& printer_name);

  // Overrides the print backend service for testing.  Caller retains ownership
  // of `remote`.  Can be reset by passing in nullptr.
  void SetServiceForTesting(mojo::Remote<mojom::PrintBackendService>* remote);

  // Overrides the print backend service for testing when an alternate service
  // is required for fallback processing after an access denied error.  Caller
  // retains ownership of `remote`.  Can be reset by passing in nullptr.
  void SetServiceForFallbackTesting(
      mojo::Remote<mojom::PrintBackendService>* remote);

  // There is to be at most one instance of this at a time.
  static PrintBackendServiceManager& GetInstance();

  // Test support to revert to a fresh instance.
  static void ResetForTesting();

 private:
  friend base::NoDestructor<PrintBackendServiceManager>;
  friend class SystemAccessProcessPrintBrowserTestBase;
  FRIEND_TEST_ALL_PREFIXES(PrintBackendServiceManagerTest,
                           IsIdleTimeoutUpdateNeededForRegisteredClient);
  FRIEND_TEST_ALL_PREFIXES(PrintBackendServiceManagerTest,
                           IsIdleTimeoutUpdateNeededForUnregisteredClient);

  enum class ClientType {
    // Print Preview scenario, where printer might not be known.  Only performs
    // queries, none of which would invoke a system dialog.
    kQuery,
    // System print scenario, where printer is not known. Only performs queries,
    // and can require a window-modal system dialog be displayed to satisfy
    // those queries.
    kQueryWithUi,
    // Printer is known, and printing of a document will be performed.  System
    // dialogs might be required to complete printing (e.g., if driver saves to
    // a file).
    kPrintDocument,
  };

  // Type that maps to the return of all settings calls.
  using PrintSettingsResult = base::expected<PrintSettings, mojom::ResultCode>;

  // Types to track saved callbacks associated with currently executing mojom
  // service calls.  These will be run either after a Mojom call finishes
  // executing or if the service should disconnect before the mojom service
  // calls complete.
  // These need to be able to be found as a group for a particular remote that
  // might become disconnected, and so a map-per-remote is used as a container.
  // Use of a map allows for an ID key to be used to easily find any individual
  // callback that can be discarded once a service call succeeds normally.

  // Key is a callback ID.
  template <class... T>
  using SavedCallbacks =
      base::flat_map<base::UnguessableToken, base::OnceCallback<void(T...)>>;

  // Key is the remote ID that enables finding the correct remote.  Note that
  // the remote ID does not necessarily mean the printer name.
  template <class... T>
  using RemoteSavedCallbacks = base::flat_map<RemoteId, SavedCallbacks<T...>>;

  // Note: these follow the signature of the generated mojo callbacks after
  // typemapping, as seen in the generated print_backend_service.mojom.h.
  using RemoteSavedEnumeratePrintersCallbacks =
      RemoteSavedCallbacks<mojom::PrintBackendService::EnumeratePrintersResult>;
  using RemoteSavedFetchCapabilitiesCallbacks =
      RemoteSavedCallbacks<mojom::PrintBackendService::FetchCapabilitiesResult>;
  using RemoteSavedGetDefaultPrinterNameCallbacks = RemoteSavedCallbacks<
      mojom::PrintBackendService::GetDefaultPrinterNameResult>;
#if BUILDFLAG(IS_CHROMEOS)
  using RemoteSavedGetPrinterSemanticCapsAndDefaultsCallbacks =
      RemoteSavedCallbacks<
          mojom::PrintBackendService::GetPrinterSemanticCapsAndDefaultsResult>;
#endif
#if BUILDFLAG(IS_WIN)
  using RemoteSavedGetPaperPrintableAreaCallbacks =
      RemoteSavedCallbacks<const gfx::Rect&>;
#endif
  using RemoteSavedUseDefaultSettingsCallbacks =
      RemoteSavedCallbacks<PrintSettingsResult>;
#if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
  using RemoteSavedAskUserForSettingsCallbacks =
      RemoteSavedCallbacks<PrintSettingsResult>;
#endif
  using RemoteSavedUpdatePrintSettingsCallbacks =
      RemoteSavedCallbacks<PrintSettingsResult>;
  using RemoteSavedStartPrintingCallbacks =
      RemoteSavedCallbacks<mojom::ResultCode, int /*job_id*/>;
#if BUILDFLAG(IS_WIN)
  using RemoteSavedRenderPrintedPageCallbacks =
      RemoteSavedCallbacks<mojom::ResultCode>;
#endif
  using RemoteSavedRenderPrintedDocumentCallbacks =
      RemoteSavedCallbacks<mojom::ResultCode>;
  using RemoteSavedDocumentDoneCallbacks =
      RemoteSavedCallbacks<mojom::ResultCode>;
  using RemoteSavedCancelCallbacks = RemoteSavedCallbacks<>;

  // Bundle of the `PrintBackendService` and its sandboxed/unsandboxed host
  // remotes.
  template <class T>
  struct RemotesBundle {
    RemotesBundle() = default;
    ~RemotesBundle() = default;
    mojo::Remote<mojom::PrintBackendService> service;
    mojo::Remote<T> host;
  };

  template <class T>
  using RemotesBundleMap =
      base::flat_map<RemoteId, std::unique_ptr<RemotesBundle<T>>>;

  // PrintBackendServiceManager needs to be able to run a callback either after
  // a successful return from the service or after the remote was disconnected.
  // This structure is used to save the callback's context.
  struct CallbackContext {
    CallbackContext();
    CallbackContext(CallbackContext&& other) noexcept;
    ~CallbackContext();

    bool is_sandboxed;
    RemoteId remote_id;
    base::UnguessableToken saved_callback_id;
  };

  struct ServiceAndCallbackContext {
    ServiceAndCallbackContext(
        CallbackContext callback_context,
        const mojo::Remote<mojom::PrintBackendService>& backend_service);
    ServiceAndCallbackContext(ServiceAndCallbackContext&& other) = delete;
    ~ServiceAndCallbackContext();
    CallbackContext context;
    const raw_ref<const mojo::Remote<mojom::PrintBackendService>> service;
  };

  PrintBackendServiceManager();
  ~PrintBackendServiceManager();

  static std::string ClientTypeToString(ClientType client_type);

  static void LogCallToRemote(std::string_view name,
                              const CallbackContext& context);
  static void LogCallbackFromRemote(std::string_view name,
                                    const CallbackContext& context);

  void SetCrashKeys(const std::string& printer_name);

  // Determine the remote ID that is used for the specified `printer_name`.
  // Could generate a new RemoteId if one has not been previously created
  // for the indicated printer.
  RemoteId GetRemoteIdForPrinterName(const std::string& printer_name);

  // Determine the remote ID that is used for the specified `client_id` of a
  // query with UI client.  Will crash if no such client is found.
  RemoteId GetRemoteIdForQueryWithUiClientId(ClientId client_id) const;

  // Determine the remote ID that is used for the specified `client_id` of a
  // print document client.  Will crash if no such client is found.
  RemoteId GetRemoteIdForPrintDocumentClientId(ClientId client_id) const;

  // Common helper for registering clients.  The `destination` parameter can be
  // either a `std::string` for a printer name or a `RemoteId` which was
  // generated from a prior registration.  This method will DCHECK if the
  // `destination` is a `RemoteId` and the registration requires launching
  // another service instance.
  std::optional<ClientId> RegisterClient(
      ClientType client_type,
      std::variant<std::string, RemoteId> destination);

  // Get the total number of clients registered.
  size_t GetClientsRegisteredCount() const;

#if BUILDFLAG(IS_WIN)
  // Query if printer driver has known reasons for requiring elevated
  // privileges in order to operate.  In these cases relying upon fallback
  // after an access-denied error is not preferable.  Any such reasons are
  // platform specific.
  bool PrinterDriverKnownToRequireElevatedPrivilege(
      const std::string& printer_name,
      ClientType client_type) const;
#endif

  // Determines if a service should be sandboxed when launched.
  bool ShouldServiceBeSandboxed(const std::string& printer_name,
                                ClientType client_type) const;

  // Acquires a remote handle to the Print Backend Service instance, launching a
  // process to host the service if necessary.
  const mojo::Remote<mojom::PrintBackendService>&
  GetService(const RemoteId& remote_id, ClientType client_type, bool sandboxed);

  // Helper to `GetService` for a particular remotes bundle type.
  template <class T>
  mojo::Remote<mojom::PrintBackendService>& GetServiceFromBundle(
      const RemoteId& remote_id,
      ClientType client_type,
      bool sandboxed,
      RemotesBundleMap<T>& bundle_map);

  // Get the idle timeout value to user for a particular client type.
  constexpr base::TimeDelta GetClientTypeIdleTimeout(
      ClientType client_type) const;

  // Whether any clients are queries with UI to `remote_id`.
  bool HasQueryWithUiClientForRemoteId(const RemoteId& remote_id) const;

  // Whether any clients are printing documents to `remote_id`.
  bool HasPrintDocumentClientForRemoteId(const RemoteId& remote_id) const;

  // Get the number of clients printing documents to `remote_id`.
  size_t GetPrintDocumentClientsCountForRemoteId(
      const RemoteId& remote_id) const;

  // Determine if idle timeout should be modified based upon there having been
  // a new client registered for `registered_client_type`.
  std::optional<base::TimeDelta> DetermineIdleTimeoutUpdateOnRegisteredClient(
      ClientType registered_client_type,
      const RemoteId& remote_id) const;

  // Determine if idle timeout should be modified after a client of type
  // `unregistered_client_type` has been unregistered.
  std::optional<base::TimeDelta> DetermineIdleTimeoutUpdateOnUnregisteredClient(
      ClientType unregistered_client_type,
      const RemoteId& remote_id) const;

  // Helper functions to adjust service idle timeout duration.
  void SetServiceIdleHandler(
      mojo::Remote<printing::mojom::PrintBackendService>& service,
      bool sandboxed,
      const RemoteId& remote_id,
      base::TimeDelta timeout);
  void UpdateServiceIdleTimeoutByRemoteId(const RemoteId& remote_id,
                                          base::TimeDelta timeout);

  // Callback when predetermined idle timeout occurs indicating no in-flight
  // messages for a short period of time.  `sandboxed` is used to distinguish
  // which mapping of remotes the timeout applies to.
  void OnIdleTimeout(bool sandboxed, const RemoteId& remote_id);

  // Callback when service has disconnected (e.g., process crashes).
  // `sandboxed` is used to distinguish which mapping of remotes the
  // disconnection applies to.
  void OnRemoteDisconnected(bool sandboxed, const RemoteId& remote_id);

  // Helper function to choose correct saved callbacks mapping.
  RemoteSavedEnumeratePrintersCallbacks&
  GetRemoteSavedEnumeratePrintersCallbacks(bool sandboxed);
  RemoteSavedFetchCapabilitiesCallbacks&
  GetRemoteSavedFetchCapabilitiesCallbacks(bool sandboxed);
  RemoteSavedGetDefaultPrinterNameCallbacks&
  GetRemoteSavedGetDefaultPrinterNameCallbacks(bool sandboxed);
#if BUILDFLAG(IS_CHROMEOS)
  RemoteSavedGetPrinterSemanticCapsAndDefaultsCallbacks&
  GetRemoteSavedGetPrinterSemanticCapsAndDefaultsCallbacks(bool sandboxed);
#endif
#if BUILDFLAG(IS_WIN)
  RemoteSavedGetPaperPrintableAreaCallbacks&
  GetRemoteSavedGetPaperPrintableAreaCallbacks(bool sandboxed);
#endif
  RemoteSavedUseDefaultSettingsCallbacks&
  GetRemoteSavedUseDefaultSettingsCallbacks(bool sandboxed);
#if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
  RemoteSavedAskUserForSettingsCallbacks&
  GetRemoteSavedAskUserForSettingsCallbacks(bool sandboxed);
#endif
  RemoteSavedUpdatePrintSettingsCallbacks&
  GetRemoteSavedUpdatePrintSettingsCallbacks(bool sandboxed);
  RemoteSavedStartPrintingCallbacks& GetRemoteSavedStartPrintingCallbacks(
      bool sandboxed);
#if BUILDFLAG(IS_WIN)
  RemoteSavedRenderPrintedPageCallbacks&
  GetRemoteSavedRenderPrintedPageCallbacks(bool sandboxed);
#endif
  RemoteSavedRenderPrintedDocumentCallbacks&
  GetRemoteSavedRenderPrintedDocumentCallbacks(bool sandboxed);
  RemoteSavedDocumentDoneCallbacks& GetRemoteSavedDocumentDoneCallbacks(
      bool sandboxed);
  RemoteSavedCancelCallbacks& GetRemoteSavedCancelCallbacks(bool sandboxed);

  // Helper function to get the service and initialize a `context` for a given
  // `printer_name`.  This is used for calls supporting Print Preview, where
  // the client type is `kQuery`.
  ServiceAndCallbackContext GetServiceAndCallbackContextForQuery(
      const std::string& printer_name);

  // Helper function to get the service and initialize a `context` for a given
  // query with UI `client_id`.  Use `printer_name` for extra sandbox behavior
  // handling.  This is used for calls supporting system print dialogs and
  // printing of a document.
  ServiceAndCallbackContext GetServiceAndCallbackContextForQueryWithUiClient(
      ClientId client_id,
      const std::string& printer_name);

  // Helper function to get the service and initialize a `context` for a given
  // print document `client_id`.  Use `printer_name` for extra sandbox behavior
  // handling.  This is used for calls supporting system print dialogs and
  // printing of a document.
  ServiceAndCallbackContext GetServiceAndCallbackContextForPrintDocumentClient(
      ClientId client_id,
      const std::string& printer_name);

  // Helper functions to save outstanding callbacks.
  template <class... T, class... X>
  void SaveCallback(RemoteSavedCallbacks<T...>& saved_callbacks,
                    const RemoteId& remote_id,
                    const base::UnguessableToken& saved_callback_id,
                    base::OnceCallback<void(X...)> callback);

  // Helper functions for local callback wrappers for mojom calls.
  template <class... T, class... X>
  void ServiceCallbackDone(RemoteSavedCallbacks<T...>& saved_callbacks,
                           const RemoteId& remote_id,
                           const base::UnguessableToken& saved_callback_id,
                           X... data);

  // Local callback wrappers for mojom calls.
  void OnDidEnumeratePrinters(
      const CallbackContext& context,
      mojom::PrintBackendService::EnumeratePrintersResult printer_list);
  void OnDidFetchCapabilities(
      const CallbackContext& context,
      mojom::PrintBackendService::FetchCapabilitiesResult
          printer_caps_and_info);
  void OnDidGetDefaultPrinterName(
      const CallbackContext& context,
      mojom::PrintBackendService::GetDefaultPrinterNameResult printer_name);
#if BUILDFLAG(IS_CHROMEOS)
  void OnDidGetPrinterSemanticCapsAndDefaults(
      const CallbackContext& context,
      mojom::PrintBackendService::GetPrinterSemanticCapsAndDefaultsResult
          printer_caps);
#endif
#if BUILDFLAG(IS_WIN)
  void OnDidGetPaperPrintableArea(const CallbackContext& context,
                                  const gfx::Rect& printable_area_um);
#endif
  void OnDidUseDefaultSettings(const CallbackContext& context,
                               PrintSettingsResult settings);
#if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
  void OnDidAskUserForSettings(const CallbackContext& context,
                               PrintSettingsResult settings);
#endif
  void OnDidUpdatePrintSettings(const CallbackContext& context,
                                PrintSettingsResult printer_caps);
  void OnDidStartPrinting(const CallbackContext& context,
                          mojom::ResultCode result,
                          int job_id);
#if BUILDFLAG(IS_WIN)
  void OnDidRenderPrintedPage(const CallbackContext& context,
                              mojom::ResultCode result);
#endif
  void OnDidRenderPrintedDocument(const CallbackContext& context,
                                  mojom::ResultCode result);
  void OnDidDocumentDone(const CallbackContext& context,
                         mojom::ResultCode result);
  void OnDidCancel(const CallbackContext& context);

  // Helper functions to run outstanding callbacks when a remote has become
  // disconnected.
  template <class... T>
  void RunSavedCallbacks(RemoteSavedCallbacks<T...>& saved_callbacks,
                         const RemoteId& remote_id,
                         typename std::remove_reference<T>::type... result);
  template <class... T>
  void RunSavedResultCallbacks(
      RemoteSavedCallbacks<T...>& saved_callbacks,
      const RemoteId& remote_id,
      typename std::remove_reference<T>::type... result);

  // Test support for client ID management.
  static void SetClientsForTesting(
      const ClientsSet& query_clients,
      const QueryWithUiClientsMap& query_with_ui_clients,
      const PrintClientsMap& print_document_clients);

  // Bundles of remotes for the Print Backend Service and their corresponding
  // wrapping hosts, to manage these sets until they disconnect.  The sandboxed
  // and unsandboxed services are kept separate.
  RemotesBundleMap<mojom::SandboxedPrintBackendHost> sandboxed_remotes_bundles_;
  RemotesBundleMap<mojom::UnsandboxedPrintBackendHost>
      unsandboxed_remotes_bundles_;

  // Members tracking active clients to aid retention of a service process.

  // Set of IDs for clients actively engaged in printing queries.  This could
  // include any tab which has triggered Print Preview.
  ClientsSet query_clients_;

  // Set of IDs for clients actively engaged in a printing query which requires
  // the use of a UI.  Such a UI corresponds to a modal system dialog.  For
  // Linux there can be multiple of these, but for other platforms there can be
  // at most one such client.  Track the `RemoteId` which is associated with
  // each such client.
  QueryWithUiClientsMap query_with_ui_clients_;

  // Map of remote ID to the set of clients printing documents to it.
  PrintClientsMap print_document_clients_;

  // Simple counter for incrementing ClientId.  All ClientId objects are used
  // only within the browser process, so no need for this to be a more
  // complicated token.
  uint32_t last_client_id_ = 0;

  // Simple counter for incrementing ContextId.  ContextId objects are passed
  // as parameters to the service but are never provided back, so no need for
  // this to be a more complicated token.
  uint32_t last_context_id_ = 0;

  // Track the saved callbacks for each remote.
  RemoteSavedEnumeratePrintersCallbacks
      sandboxed_saved_enumerate_printers_callbacks_;
  RemoteSavedEnumeratePrintersCallbacks
      unsandboxed_saved_enumerate_printers_callbacks_;
  RemoteSavedFetchCapabilitiesCallbacks
      sandboxed_saved_fetch_capabilities_callbacks_;
  RemoteSavedFetchCapabilitiesCallbacks
      unsandboxed_saved_fetch_capabilities_callbacks_;
  RemoteSavedGetDefaultPrinterNameCallbacks
      sandboxed_saved_get_default_printer_name_callbacks_;
  RemoteSavedGetDefaultPrinterNameCallbacks
      unsandboxed_saved_get_default_printer_name_callbacks_;
#if BUILDFLAG(IS_CHROMEOS)
  RemoteSavedGetPrinterSemanticCapsAndDefaultsCallbacks
      sandboxed_saved_get_printer_semantic_caps_and_defaults_callbacks_;
  RemoteSavedGetPrinterSemanticCapsAndDefaultsCallbacks
      unsandboxed_saved_get_printer_semantic_caps_and_defaults_callbacks_;
#endif
#if BUILDFLAG(IS_WIN)
  RemoteSavedGetPaperPrintableAreaCallbacks
      sandboxed_saved_get_paper_printable_area_callbacks_;
  RemoteSavedGetPaperPrintableAreaCallbacks
      unsandboxed_saved_get_paper_printable_area_callbacks_;
#endif
  RemoteSavedUseDefaultSettingsCallbacks
      sandboxed_saved_use_default_settings_callbacks_;
  RemoteSavedUseDefaultSettingsCallbacks
      unsandboxed_saved_use_default_settings_callbacks_;
#if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
  RemoteSavedAskUserForSettingsCallbacks
      sandboxed_saved_ask_user_for_settings_callbacks_;
  RemoteSavedAskUserForSettingsCallbacks
      unsandboxed_saved_ask_user_for_settings_callbacks_;
#endif
  RemoteSavedUpdatePrintSettingsCallbacks
      sandboxed_saved_update_print_settings_callbacks_;
  RemoteSavedUpdatePrintSettingsCallbacks
      unsandboxed_saved_update_print_settings_callbacks_;
  RemoteSavedStartPrintingCallbacks sandboxed_saved_start_printing_callbacks_;
  RemoteSavedStartPrintingCallbacks unsandboxed_saved_start_printing_callbacks_;
#if BUILDFLAG(IS_WIN)
  RemoteSavedRenderPrintedPageCallbacks
      sandboxed_saved_render_printed_page_callbacks_;
  RemoteSavedRenderPrintedPageCallbacks
      unsandboxed_saved_render_printed_page_callbacks_;
#endif
  RemoteSavedRenderPrintedDocumentCallbacks
      sandboxed_saved_render_printed_document_callbacks_;
  RemoteSavedRenderPrintedDocumentCallbacks
      unsandboxed_saved_render_printed_document_callbacks_;
  RemoteSavedDocumentDoneCallbacks sandboxed_saved_document_done_callbacks_;
  RemoteSavedDocumentDoneCallbacks unsandboxed_saved_document_done_callbacks_;
  RemoteSavedCancelCallbacks sandboxed_saved_cancel_callbacks_;
  RemoteSavedCancelCallbacks unsandboxed_saved_cancel_callbacks_;

  // Set of printer drivers which require elevated permissions to operate.
  // It is expected that most print drivers will succeed with the preconfigured
  // sandbox permissions.  Should any drivers be discovered to require more than
  // that (and thus fail with access denied errors) then we need to fallback to
  // performing the operation with modified restrictions.
  base::flat_set<std::string> drivers_requiring_elevated_privilege_;

#if BUILDFLAG(IS_WIN)
  // Support for process model where there can be multiple PrintBackendService
  // instances.  This is necessary because Windows printer drivers are not
  // thread safe.  Map key is a printer name.
  base::flat_map<std::string, RemoteId> remote_id_map_;
#endif

  // Used as base for generating `RemoteId` values.  Only used internally
  // within browser process management code, so a simple incrementating
  // sequence is sufficient.
  uint32_t remote_id_sequence_ = 0;

  // Set when launched services are intended to persist indefinitely, rather
  // than being disconnected after a finite idle timeout expires.
  bool persistent_service_ = false;

  // Crash key is kept at class level so that we can obtain printer driver
  // information for a prior call should the process be terminated due to Mojo
  // message response validation.
  std::unique_ptr<crash_keys::ScopedPrinterInfo> crash_keys_;

  // Override of service to use for testing.
  raw_ptr<mojo::Remote<mojom::PrintBackendService>>
      sandboxed_service_remote_for_test_ = nullptr;
  raw_ptr<mojo::Remote<mojom::PrintBackendService>>
      unsandboxed_service_remote_for_test_ = nullptr;
};

}  // namespace printing

#endif  // CHROME_BROWSER_PRINTING_PRINT_BACKEND_SERVICE_MANAGER_H_
