chromium/chrome/browser/ash/crosapi/search_controller_ash.cc

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

#include "chrome/browser/ash/crosapi/search_controller_ash.h"

#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "chromeos/crosapi/mojom/launcher_search.mojom-forward.h"
#include "chromeos/crosapi/mojom/launcher_search.mojom-shared.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"

namespace crosapi {

SearchControllerAsh::SearchControllerAsh(
    mojo::PendingRemote<mojom::SearchController> search_controller)
    : search_controller_(std::move(search_controller)) {
  search_controller_.set_disconnect_handler(base::BindOnce(
      &SearchControllerAsh::HandleDisconnect, weak_factory_.GetWeakPtr()));
}

SearchControllerAsh::~SearchControllerAsh() = default;

void SearchControllerAsh::Search(const std::u16string& query,
                                 SearchResultsReceivedCallback callback) {
  if (search_controller_.is_connected()) {
    search_controller_->Search(
        query, base::BindOnce(&SearchControllerAsh::BindPublisher,
                              weak_factory_.GetWeakPtr(), std::move(callback)));
  }
}

void SearchControllerAsh::OnSearchResultsReceived(
    mojom::SearchStatus status,
    std::optional<std::vector<mojom::SearchResultPtr>> results) {
  switch (status) {
    case mojom::SearchStatus::kError: {
      LOG(ERROR) << "Search failed.";
      publisher_receivers_.Remove(publisher_receivers_.current_receiver());
      return;
    }
    case mojom::SearchStatus::kDone: {
      const auto& callback = publisher_receivers_.current_context();
      if (results.has_value() && !callback.is_null()) {
        callback.Run(std::move(results.value()));
      }
      return;
    }
    case mojom::SearchStatus::kInProgress:
    case mojom::SearchStatus::kCancelled:
    case mojom::SearchStatus::kBackendUnavailable: {
      return;
    }
  }
}

bool SearchControllerAsh::IsConnected() const {
  return search_controller_.is_connected();
}

void SearchControllerAsh::AddDisconnectHandler(DisconnectCallback handler) {
  if (!IsConnected()) {
    std::move(handler).Run(weak_factory_.GetWeakPtr());
    // USE-AFTER-FREE SAFETY: As disconnect handlers may destroy `this`, we
    // cannot refer to `this` (including using members or methods) after this
    // point.
    return;
  }

  disconnect_callbacks_.push_back(std::move(handler));
}

void SearchControllerAsh::HandleDisconnect() {
  CHECK(!IsConnected());

  base::WeakPtr<SearchControllerAsh> local_weak_this =
      weak_factory_.GetWeakPtr();
  // Move the disconnect callbacks into a local variable to ensure that they do
  // not get destroyed if `this` is destroyed.
  std::vector<DisconnectCallback> local_disconnect_callbacks =
      std::move(disconnect_callbacks_);

  // USE-AFTER-FREE SAFETY: As disconnect handlers may destroy `this`, we cannot
  // refer to `this` (including using members or methods) after this point.
  //
  // Use verbose local variable names to ensure that members are not
  // accidentally used instead.
  for (DisconnectCallback& callback : local_disconnect_callbacks) {
    std::move(callback).Run(local_weak_this);
  }
}

void SearchControllerAsh::BindPublisher(
    SearchResultsReceivedCallback callback,
    mojo::PendingAssociatedReceiver<mojom::SearchResultsPublisher> publisher) {
  publisher_receivers_.Add(this, std::move(publisher), std::move(callback));
}

}  // namespace crosapi