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

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

#include "components/services/app_service/public/cpp/instance_registry.h"

#include <memory>
#include <utility>

#include "base/containers/contains.h"
#include "base/memory/raw_ptr.h"
#include "base/unguessable_token.h"
#include "components/services/app_service/public/cpp/instance.h"
#include "components/services/app_service/public/cpp/instance_update.h"

namespace apps {

InstanceParams::InstanceParams(const std::string& app_id, aura::Window* window)
    : app_id(app_id), window(window) {}

InstanceParams::~InstanceParams() = default;

InstanceRegistry::Observer::~Observer() {
  CHECK(!IsInObserverList());
}

InstanceRegistry::InstanceRegistry() = default;

InstanceRegistry::~InstanceRegistry() {
  for (auto& obs : observers_) {
    obs.OnInstanceRegistryWillBeDestroyed(this);
  }
  DCHECK(observers_.empty());
}

void InstanceRegistry::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void InstanceRegistry::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

void InstanceRegistry::CreateOrUpdateInstance(InstanceParams&& params) {
  base::UnguessableToken instance_id;
  auto it = window_to_instance_ids_.find(params.window);
  if (it == window_to_instance_ids_.end()) {
    instance_id = base::UnguessableToken::Create();
  } else {
    instance_id = *it->second.begin();
  }

  auto instance =
      std::make_unique<Instance>(params.app_id, instance_id, params.window);

  if (params.launch_id.has_value()) {
    instance->SetLaunchId(params.launch_id.value());
  }

  if (params.state.has_value()) {
    instance->UpdateState(params.state.value().first,
                          params.state.value().second);
  }

  if (params.browser_context.has_value()) {
    instance->SetBrowserContext(params.browser_context.value());
  }
  OnInstance(std::move(instance));
}

void InstanceRegistry::OnInstance(InstancePtr delta) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);

  if (!delta || !delta->InstanceId()) {
    return;
  }

  // If the instance state is not kDestroyed, adds to
  // `window_to_instance_ids_`, otherwise removes the instance key from
  // `window_to_instance_ids_`.
  if (static_cast<InstanceState>(delta->State() & InstanceState::kDestroyed) ==
      InstanceState::kUnknown) {
    auto it = instance_id_to_window_.find(delta->InstanceId());
    // If `window` is changed, remove the instance id from
    // `window_to_instance_ids_`.
    if (it != instance_id_to_window_.end() && it->second != delta->Window()) {
      MaybeRemoveInstanceId(/*instance_id=*/it->first, /*window=*/it->second);
    }
    window_to_instance_ids_[delta->Window()].insert(delta->InstanceId());
    instance_id_to_window_[delta->InstanceId()] = delta->Window();
  } else {
    MaybeRemoveInstanceId(delta->InstanceId(), delta->Window());
    instance_id_to_window_.erase(delta->InstanceId());
  }

  if (in_progress_) {
    deltas_pending_.push_back(std::move(delta));
    return;
  }
  DoOnInstance(std::move(delta));
  while (!deltas_pending_.empty()) {
    InstancePtr instance = std::move(*deltas_pending_.begin());
    deltas_pending_.pop_front();
    DoOnInstance(std::move(instance));
  }
}

std::set<raw_ptr<const Instance, SetExperimental>>
InstanceRegistry::GetInstances(const std::string& app_id) {
  auto it = app_id_to_instances_.find(app_id);
  if (it == app_id_to_instances_.end()) {
    return std::set<raw_ptr<const Instance, SetExperimental>>();
  }
  return it->second;
}

InstanceState InstanceRegistry::GetState(const aura::Window* window) const {
  InstanceState state = InstanceState::kUnknown;
  ForInstancesWithWindow(window, [&state](const apps::InstanceUpdate& update) {
    state = update.State();
  });
  return state;
}

ash::ShelfID InstanceRegistry::GetShelfId(const aura::Window* window) const {
  ash::ShelfID shelf_id;
  ForInstancesWithWindow(
      window, [&shelf_id](const apps::InstanceUpdate& update) {
        shelf_id = ash::ShelfID(update.AppId(), update.LaunchId());
      });
  return shelf_id;
}

bool InstanceRegistry::Exists(const aura::Window* window) const {
  bool found = false;
  ForInstancesWithWindow(
      window, [&](const apps::InstanceUpdate& update) { found = true; });
  return found;
}

bool InstanceRegistry::ContainsAppId(const std::string& app_id) const {
  return base::Contains(app_id_to_instances_, app_id);
}

void InstanceRegistry::DoOnInstance(InstancePtr delta) {
  in_progress_ = true;

  auto s_iter = states_.find(delta->InstanceId());
  Instance* state = (s_iter != states_.end()) ? s_iter->second.get() : nullptr;
  if (InstanceUpdate::Equals(state, delta.get())) {
    in_progress_ = false;
    return;
  }

  Instance* new_delta = delta.get();
  if (state) {
    old_state_ = state->Clone();
    InstanceUpdate::Merge(state, new_delta);
  } else {
    old_state_.reset();

    // `new_delta` is still valid, though `delta` is moved, because `new_delta`
    // is the pointer to the content of `delta`.
    states_.insert(std::make_pair(new_delta->InstanceId(), std::move(delta)));
    app_id_to_instances_[new_delta->AppId()].insert(new_delta);
  }

  for (auto& obs : observers_) {
    obs.OnInstanceUpdate(InstanceUpdate(old_state_.get(), new_delta));
  }

  if (static_cast<InstanceState>(new_delta->State() &
                                 InstanceState::kDestroyed) !=
      InstanceState::kUnknown) {
    MaybeRemoveInstance(new_delta);
  }

  old_state_.reset();
  in_progress_ = false;
}

void InstanceRegistry::MaybeRemoveInstance(const Instance* delta) {
  DCHECK(delta);

  auto it = states_.find(delta->InstanceId());
  if (it == states_.end()) {
    return;
  }

  const auto& app_id = delta->AppId();
  app_id_to_instances_[app_id].erase(it->second.get());
  if (app_id_to_instances_[app_id].empty()) {
    app_id_to_instances_.erase(app_id);
  }

  states_.erase(it);
}

void InstanceRegistry::MaybeRemoveInstanceId(
    const base::UnguessableToken& instance_id,
    aura::Window* window) {
  window_to_instance_ids_[window].erase(instance_id);
  if (window_to_instance_ids_[window].empty()) {
    window_to_instance_ids_.erase(window);
  }
}

}  // namespace apps