chromium/components/services/app_service/public/cpp/instance_registry.h

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

#ifndef COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_INSTANCE_REGISTRY_H_
#define COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_INSTANCE_REGISTRY_H_

#include <list>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>

#include "ash/public/cpp/shelf_types.h"
#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "components/services/app_service/public/cpp/instance.h"
#include "components/services/app_service/public/cpp/instance_update.h"
#include "ui/aura/window.h"

class InstanceRegistryTest;

namespace apps {

// The parameters to create or update the instance for the `app_id` and
// `window`, when calling InstanceRegistry::CreateOrUpdateInstance.
struct InstanceParams {
  InstanceParams(const std::string& app_id, aura::Window* window);

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

  ~InstanceParams();

  const std::string app_id;
  raw_ptr<aura::Window> window;
  std::optional<std::string> launch_id;
  std::optional<std::pair<InstanceState, base::Time>> state;
  std::optional<content::BrowserContext*> browser_context;
};

// An in-memory store of all the Instances (i.e. running apps) seen by
// AppServiceProxy. Can be queried synchronously for information about the
// currently running instances, and can be observed to receive updates about
// changes to Instance state.
//
// InstanceRegistry receives a stream of `app::Instance` delta updates from App
// Service, and stores the "sum" of these updates. When a new `apps::Instance`
// is received, observers are notified about the update, and then the delta is
// "added" to the stored state.
//
// This class is not thread-safe.
class InstanceRegistry {
 public:
  class Observer : public base::CheckedObserver {
   public:
    // Called whenever the InstanceRegistry receives an update for any
    // instance. `update` exposes the latest field values and whether they have
    // changed in this update (as per the docs on `apps::InstanceUpdate`). The
    // `update` argument shouldn't be accessed after OnAppUpdate returns.
    virtual void OnInstanceUpdate(const InstanceUpdate& update) = 0;

    // Called when the InstanceRegistry object (the thing that this observer
    // observes) will be destroyed. In response, the observer, |this|, should
    // call "instance_registry->RemoveObserver(this)", whether directly or
    // indirectly (e.g. via base::ScopedObservation::Reset).
    virtual void OnInstanceRegistryWillBeDestroyed(InstanceRegistry* cache) = 0;

   protected:
    ~Observer() override;
  };

  InstanceRegistry();
  ~InstanceRegistry();

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

  // Prefer using a base::ScopedObservation to safely manage the observation,
  // instead of calling these methods directly.
  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

  using InstancePtr = std::unique_ptr<Instance>;
  using InstanceIds = std::set<base::UnguessableToken>;

  // Creates a new instance for the `app_id` and `window` with a new instance id
  // if there is no exist instance. Otherwise, reuse the existing instance id
  // with `param` to update the instance.
  //
  // This function calls OnInstance to add the new instance or update the
  // existing instance.
  //
  // Note: For Lacros windows having multiple tabs/nstances, this interface
  // should not be called, since `window` might have multiple instances.
  void CreateOrUpdateInstance(InstanceParams&& param);

  // Notification and merging might be delayed until after OnInstance returns.
  // For example, suppose that the initial set of states is (a0, b0, c0) for
  // three app_id's ("a", "b", "c"). Now suppose OnInstance is called with an
  // update (b1), and when notified of b1, an observer calls OnInstance
  // again with (b2). The b1 delta should be processed before the b2 delta,
  // as it was sent first, and both b1 and b2 will be updated to the observer
  // following the sequence. This means that processing b2 (scheduled by the
  // second OnInstance call) should wait until the first OnInstance call has
  // finished processing b1, and then b2, which means that processing b2 is
  // delayed until after the second OnInstance call returns.
  //
  // The caller presumably calls OnInstance(std::move(delta)).
  void OnInstance(InstancePtr delta);

  // Returns instances for the |app_id|.
  std::set<raw_ptr<const Instance, SetExperimental>> GetInstances(
      const std::string& app_id);

  // Returns one state for the `window`.
  //
  // Note: This interface is used for the standalone window, or the ash Chrome
  // browser tab window, which has one instance only. For Lacros windows which
  // might have multiple instances for tabs, this interface should not be
  // called, since `window` might have multiple instances, and the InstanceState
  // returned in these cases will be arbitrary.
  InstanceState GetState(const aura::Window* window) const;

  // Returns the shelf id for the `window`.
  //
  // Note: This interface is used for the standalone window, or the ash Chrome
  // browser tab window, which has one instance only. For Lacros windows which
  // might have multiple instances for tabs, this interface should not be
  // called, since `window` might have multiple instances, and the ShelfID
  // returned in these cases will be arbitrary.
  ash::ShelfID GetShelfId(const aura::Window* window) const;

  // Return true if there is an instance for the `window`.
  bool Exists(const aura::Window* window) const;

  // Return true if there is any instance in the InstanceRegistry for |app_id|.
  bool ContainsAppId(const std::string& app_id) const;

  // Calls f, a void-returning function whose arguments are (const
  // apps::InstanceUpdate&), on each window in the instance_registry.
  //
  // f's argument is an apps::InstanceUpdate instead of an Instance* so that
  // callers can more easily share code with Observer::OnInstanceUpdate (which
  // also takes an apps::InstanceUpdate), and an apps::InstanceUpdate also has a
  // StateIsNull method.
  //
  // The apps::InstanceUpdate argument to f shouldn't be accessed after f
  // returns.
  //
  // f must be synchronous, and if it asynchronously calls ForEachInstance
  // again, it's not guaranteed to see a consistent state.
  template <typename FunctionType>
  void ForEachInstance(FunctionType f) const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);

    for (const auto& s_iter : states_) {
      const apps::Instance* state = s_iter.second.get();
      f(apps::InstanceUpdate(state, nullptr));
    }
  }

  // Calls f, a void-returning function whose arguments are (const
  // apps::InstanceUpdate&), on the instance in the instance_registry with the
  // given instance id. It will return true (and call f) if there is such an
  // instance, otherwise it will return false (and not call f). The
  // InstanceUpdate argument to f has the same semantics as for ForEachInstance,
  // above.
  //
  // f must be synchronous, and if it asynchronously calls ForOneInstance again,
  // it's not guaranteed to see a consistent state.
  template <typename FunctionType>
  bool ForOneInstance(const base::UnguessableToken& instance_id,
                      FunctionType f) const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);

    auto s_iter = states_.find(instance_id);
    apps::Instance* state =
        (s_iter != states_.end()) ? s_iter->second.get() : nullptr;
    if (state) {
      f(apps::InstanceUpdate(state, nullptr));
      return true;
    }
    return false;
  }

  // Calls f, a void-returning function whose arguments are (const
  // apps::InstanceUpdate&), on instances in the instance_registry with the
  // given window. It will return true (and call f) if there is such an
  // instance, otherwise it will return false (and not call f). The
  // InstanceUpdate argument to f has the same semantics as for ForEachInstance,
  // above.
  //
  // f must be synchronous, and if it asynchronously calls ForOneInstance again,
  // it's not guaranteed to see a consistent state.
  template <typename FunctionType>
  bool ForInstancesWithWindow(const aura::Window* window,
                              FunctionType f) const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);

    InstanceIds instance_ids;
    auto it = window_to_instance_ids_.find(window);
    if (it != window_to_instance_ids_.end()) {
      instance_ids = it->second;
    }

    // There could be some instances in `deltas_pending_`.
    for (const auto& delta : deltas_pending_) {
      if (delta->Window() == window) {
        instance_ids.insert(delta->InstanceId());
      }
    }

    // `old_state_` might be an instance to be deleted. But in OnInstanceUpdate,
    // the caller might still need that instance to know which instance will be
    // deleted.
    if (old_state_ && old_state_.get()) {
      instance_ids.insert(old_state_->InstanceId());
    }

    if (instance_ids.empty()) {
      return false;
    }

    for (const auto& instance_id : instance_ids) {
      auto s_iter = states_.find(instance_id);
      apps::Instance* state =
          (s_iter != states_.end()) ? s_iter->second.get() : nullptr;
      if (state) {
        f(apps::InstanceUpdate(state, nullptr));
      }
    }
    return true;
  }

 private:
  friend class InstanceRegistryTest;

  void DoOnInstance(InstancePtr deltas);

  void MaybeRemoveInstance(const Instance* delta);

  void MaybeRemoveInstanceId(const base::UnguessableToken& instance_id,
                             aura::Window* window);

  base::ObserverList<Observer> observers_;

  // OnInstance calls DoOnInstance zero or more times. If we're nested,
  // in_progress is true, so that there's multiple OnInstance call to this
  // InstanceRegistry in the call stack, the deeper OnInstances call simply adds
  // work to deltas_pending_ and returns without calling DoOnInstance. If we're
  // not nested, in_progress is false, OnInstance calls DoOnInstance one or
  // more times; "more times" happens if DoOnInstance notifying observers leads
  // to more OnInstance calls that enqueue deltas_pending_ work.
  //
  // Nested OnInstance calls are expected to be rare (but still dealt with
  // sensibly). In the typical case, OnInstance should call DoOnInstance
  // exactly once, and deltas_pending_ will stay empty.
  bool in_progress_ = false;

  // Maps from the instance id to the latest state: the "sum" of all previous
  // deltas.
  std::map<const base::UnguessableToken, InstancePtr> states_;

  std::list<InstancePtr> deltas_pending_;

  // Maps from window to a set of instance id.
  std::map<const aura::Window*, InstanceIds> window_to_instance_ids_;

  // Maps from instance id to window, to check whether the window is changed for
  // the instance id. When a tab is pulled to a new Lacros window, the window
  // might be changed, and the instance id should be removed from
  // `window_to_instance_ids_`. `states_` can't be used to check window, because
  // some instances might be in `deltas_pending_`.
  std::map<const base::UnguessableToken, aura::Window*> instance_id_to_window_;

  // Maps from app id to instances.
  std::map<const std::string,
           std::set<raw_ptr<const Instance, SetExperimental>>>
      app_id_to_instances_;

  std::unique_ptr<Instance> old_state_;

  SEQUENCE_CHECKER(my_sequence_checker_);
};

}  // namespace apps

#endif  // COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_INSTANCE_REGISTRY_H_