chromium/fuchsia_web/runners/cast/api_bindings_client.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 "fuchsia_web/runners/cast/api_bindings_client.h"

#include <string_view>
#include <utility>

#include "base/fuchsia/fuchsia_logging.h"
#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "components/cast/message_port/fuchsia/message_port_fuchsia.h"

namespace {

uint64_t kBindingsIdStart = 0xFF0000;

}  // namespace

ApiBindingsClient::ApiBindingsClient(
    fidl::InterfaceHandle<chromium::cast::ApiBindings> bindings_service,
    base::OnceClosure on_initialization_complete)
    : bindings_service_(bindings_service.Bind()),
      on_initialization_complete_(std::move(on_initialization_complete)) {
  DCHECK(bindings_service_);
  DCHECK(on_initialization_complete_);

  bindings_service_->GetAll(
      fit::bind_member(this, &ApiBindingsClient::OnBindingsReceived));

  bindings_service_.set_error_handler([this](zx_status_t status) {
    ZX_LOG(ERROR, status) << "ApiBindings disconnected.";
    std::move(on_initialization_complete_).Run();
  });
}

ApiBindingsClient::~ApiBindingsClient() {
  if (connector_ && frame_) {
    connector_->RegisterPortHandler({});

    // Remove all injected scripts using their automatically enumerated IDs.
    for (uint64_t i = 0; i < bindings_->size(); ++i)
      frame_->RemoveBeforeLoadJavaScript(kBindingsIdStart + i);
  }
}

void ApiBindingsClient::AttachToFrame(
    fuchsia::web::Frame* frame,
    cast_api_bindings::NamedMessagePortConnector* connector,
    base::OnceClosure on_error_callback) {
  DCHECK(!frame_) << "AttachToFrame() was called twice.";
  DCHECK(frame);
  DCHECK(connector);

  if (!bindings_service_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&ApiBindingsClient::CallOnErrorCallback,
                                  weak_ptr_factory_.GetWeakPtr(),
                                  std::move(on_error_callback)));
    return;
  }

  DCHECK(bindings_)
      << "AttachToFrame() was called before bindings were received.";

  connector_ = connector;
  frame_ = frame;

  bindings_service_.set_error_handler([on_error_callback =
                                           std::move(on_error_callback)](
                                          zx_status_t status) mutable {
    ZX_LOG_IF(ERROR, status != ZX_OK, status) << "ApiBindings disconnected.";
    std::move(on_error_callback).Run();
  });

  connector_->RegisterPortHandler(base::BindRepeating(
      &ApiBindingsClient::OnPortConnected, base::Unretained(this)));

  // Enumerate and inject all scripts in |bindings|.
  uint64_t bindings_id = kBindingsIdStart;
  for (chromium::cast::ApiBinding& entry : *bindings_) {
    frame_->AddBeforeLoadJavaScript(
        bindings_id++, {"*"}, std::move(*entry.mutable_before_load_script()),
        [](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
          CHECK(result.is_response()) << "JavaScript injection error: "
                                      << static_cast<uint32_t>(result.err());
        });
  }
}

void ApiBindingsClient::DetachFromFrame(fuchsia::web::Frame* frame) {
  DCHECK_EQ(frame, frame_);
  frame_ = nullptr;
  bindings_service_.set_error_handler(nullptr);
}

bool ApiBindingsClient::HasBindings() const {
  return bindings_.has_value();
}

bool ApiBindingsClient::OnPortConnected(
    std::string_view port_name,
    std::unique_ptr<cast_api_bindings::MessagePort> port) {
  if (!bindings_service_)
    return false;

  bindings_service_->Connect(
      std::string(port_name),
      cast_api_bindings::MessagePortFuchsia::FromMessagePort(port.get())
          ->TakeClientHandle());
  return true;
}

void ApiBindingsClient::OnBindingsReceived(
    std::vector<chromium::cast::ApiBinding> bindings) {
  bindings_ = std::move(bindings);
  bindings_service_.set_error_handler(nullptr);
  std::move(on_initialization_complete_).Run();
}

void ApiBindingsClient::CallOnErrorCallback(
    base::OnceClosure on_error_callback) {
  std::move(on_error_callback).Run();
}