chromium/fuchsia_web/runners/common/web_content_runner.cc

// Copyright 2018 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/common/web_content_runner.h"

#include <lib/fdio/directory.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/sys/cpp/service_directory.h>
#include <utility>

#include "base/command_line.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/fuchsia/file_utils.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/process_context.h"
#include "base/fuchsia/scoped_service_binding.h"
#include "base/fuchsia/startup_context.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "fuchsia_web/runners/common/web_component.h"
#include "url/gurl.h"

namespace {

bool IsChannelClosed(const zx::channel& channel) {
  zx_signals_t observed = 0u;
  zx_status_t status =
      channel.wait_one(ZX_ERR_PEER_CLOSED, zx::time(), &observed);
  return status == ZX_OK;
}

}  // namespace

WebContentRunner::WebInstanceConfig::WebInstanceConfig()
    : extra_args(base::CommandLine::NO_PROGRAM) {}
WebContentRunner::WebInstanceConfig::~WebInstanceConfig() = default;

WebContentRunner::WebInstanceConfig::WebInstanceConfig(WebInstanceConfig&&) =
    default;
WebContentRunner::WebInstanceConfig&
WebContentRunner::WebInstanceConfig::operator=(WebInstanceConfig&&) = default;

WebContentRunner::WebContentRunner(
    CreateWebInstanceAndContextCallback create_web_instance_callback,
    GetWebInstanceConfigCallback get_web_instance_config_callback)
    : create_web_instance_callback_(std::move(create_web_instance_callback)),
      get_web_instance_config_callback_(
          std::move(get_web_instance_config_callback)) {
  DCHECK(create_web_instance_callback_);
  DCHECK(get_web_instance_config_callback_);
}

WebContentRunner::WebContentRunner(
    CreateWebInstanceAndContextCallback create_web_instance_callback,
    WebInstanceConfig web_instance_config)
    : create_web_instance_callback_(std::move(create_web_instance_callback)) {
  CreateWebInstanceAndContext(std::move(web_instance_config));
}

WebContentRunner::~WebContentRunner() = default;

fidl::InterfaceRequestHandler<fuchsia::web::FrameHost>
WebContentRunner::GetFrameHostRequestHandler() {
  return [this](fidl::InterfaceRequest<fuchsia::web::FrameHost> request) {
    EnsureWebInstanceAndContext();
    fdio_service_connect_at(web_instance_services_.channel().get(),
                            fuchsia::web::FrameHost::Name_,
                            request.TakeChannel().release());
  };
}

void WebContentRunner::CreateFrameWithParams(
    fuchsia::web::CreateFrameParams params,
    fidl::InterfaceRequest<fuchsia::web::Frame> request) {
  EnsureWebInstanceAndContext();

  context_->CreateFrameWithParams(std::move(params), std::move(request));
}

void WebContentRunner::DestroyComponent(WebComponent* component) {
  components_.erase(components_.find(component));
  if (components_.empty() && on_empty_callback_)
    std::move(on_empty_callback_).Run();
}

void WebContentRunner::RegisterComponent(
    std::unique_ptr<WebComponent> component) {
  components_.insert(std::move(component));
}

void WebContentRunner::SetOnEmptyCallback(base::OnceClosure on_empty) {
  on_empty_callback_ = std::move(on_empty);
}

void WebContentRunner::DestroyWebContext() {
  DCHECK(get_web_instance_config_callback_);
  context_ = nullptr;
}

void WebContentRunner::CloseFrameWithTimeout(fuchsia::web::FramePtr frame,
                                             base::TimeDelta timeout) {
  // Signal `frame` to close within the desired `timeout`, and store it to
  // `closing_frames_` which will retain it until it closes.
  frame->Close(std::move(
      fuchsia::web::FrameCloseRequest().set_timeout(timeout.ToZxDuration())));
  closing_frames_.AddInterfacePtr(std::move(frame));
}

void WebContentRunner::EnsureWebInstanceAndContext() {
  // Synchronously check whether the web.Context channel has closed, to reduce
  // the chance of issuing CreateFrameWithParams() to an already-closed channel.
  // This avoids potentially flaking a test - see crbug.com/1173418.
  if (context_ && IsChannelClosed(context_.channel()))
    context_.Unbind();

  if (!context_) {
    DCHECK(get_web_instance_config_callback_);
    CreateWebInstanceAndContext(get_web_instance_config_callback_.Run());
  }
}

void WebContentRunner::CreateWebInstanceAndContext(WebInstanceConfig config) {
  create_web_instance_callback_.Run(std::move(config.params),
                                    web_instance_services_.NewRequest(),
                                    config.extra_args);
  zx_status_t result = fdio_service_connect_at(
      web_instance_services_.channel().get(), fuchsia::web::Context::Name_,
      context_.NewRequest().TakeChannel().release());
  ZX_LOG_IF(ERROR, result != ZX_OK, result)
      << "fdio_service_connect_at(web.Context)";
  context_.set_error_handler([](zx_status_t status) {
    ZX_LOG(ERROR, status) << "Connection to web.Context lost.";
  });
}