chromium/chromecast/renderer/settings_ui_bindings.cc

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

#include "chromecast/renderer/settings_ui_bindings.h"

#include <array>
#include <tuple>

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "content/public/renderer/render_frame.h"
#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
#include "third_party/blink/public/platform/scheduler/web_agent_group_scheduler.h"
#include "third_party/blink/public/web/web_local_frame.h"

namespace chromecast {
namespace shell {

namespace {

const int64_t kDelayBetweenReconnectionInMillis = 100;

const char kSettingsUiName[] = "settings_ui";
const char kSetSideSwipeHandler[] = "setSideSwipeHandler";
const char kSetPlatformInfoHandler[] = "setPlatformInfoHandler";
const char kRequestVisible[] = "requestVisible";

}  // namespace

SettingsUiBindings::SettingsUiBindings(content::RenderFrame* frame)
    : CastBinding(frame), binding_(this), weak_factory_(this) {
  ReconnectMojo();
}

SettingsUiBindings::~SettingsUiBindings() {}

void SettingsUiBindings::HandleSideSwipe(
    chromecast::mojom::SideSwipeEvent event,
    chromecast::mojom::SideSwipeOrigin origin,
    const gfx::Point& touch_location) {
  if (side_swipe_handler_.IsEmpty()) {
    return;
  }

  blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
  v8::Isolate* isolate = web_frame->GetAgentGroupScheduler()->Isolate();
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
  v8::MicrotasksScope microtasks_scope(
      context, v8::MicrotasksScope::kDoNotRunMicrotasks);
  v8::Context::Scope context_scope(context);
  v8::Local<v8::Function> handler =
      v8::Local<v8::Function>::New(isolate, std::move(side_swipe_handler_));

  v8::Local<v8::Number> touch_event =
      v8::Integer::New(isolate, static_cast<int>(event));
  v8::Local<v8::Number> touch_origin =
      v8::Integer::New(isolate, static_cast<int>(origin));
  v8::Local<v8::Number> touch_x = v8::Integer::New(isolate, touch_location.x());
  v8::Local<v8::Number> touch_y = v8::Integer::New(isolate, touch_location.y());

  auto args = v8::to_array<v8::Local<v8::Value>>(
      {touch_event, touch_origin, touch_x, touch_y});

  v8::MaybeLocal<v8::Value> maybe_result =
      handler->Call(context, context->Global(), args.size(), args.data());

  side_swipe_handler_ = v8::UniquePersistent<v8::Function>(isolate, handler);

  v8::Local<v8::Value> result;
  std::ignore = maybe_result.ToLocal(&result);
}

void SettingsUiBindings::SendPlatformInfo(
    const std::string& platform_info_json) {
  if (platform_info_handler_.IsEmpty()) {
    pending_platform_info_json_ = platform_info_json;
    return;
  }

  blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
  v8::Isolate* isolate = web_frame->GetAgentGroupScheduler()->Isolate();
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
  v8::MicrotasksScope microtasks_scope(
      context, v8::MicrotasksScope::kDoNotRunMicrotasks);
  v8::Context::Scope context_scope(context);
  v8::Local<v8::Function> handler =
      v8::Local<v8::Function>::New(isolate, std::move(platform_info_handler_));

  v8::Local<v8::String> platform_info =
      v8::String::NewFromUtf8(isolate, platform_info_json.data(),
                              v8::NewStringType::kInternalized)
          .ToLocalChecked();

  auto args = v8::to_array<v8::Local<v8::Value>>({platform_info});

  v8::MaybeLocal<v8::Value> maybe_result =
      handler->Call(context, context->Global(), args.size(), args.data());

  platform_info_handler_ = v8::UniquePersistent<v8::Function>(isolate, handler);

  v8::Local<v8::Value> result;
  std::ignore = maybe_result.ToLocal(&result);
}

void SettingsUiBindings::Install(v8::Local<v8::Object> cast_platform,
                                 v8::Isolate* isolate) {
  v8::Local<v8::Object> settings_ui =
      EnsureObjectExists(isolate, cast_platform, kSettingsUiName);

  InstallBinding(isolate, settings_ui, kSetSideSwipeHandler,
                 &SettingsUiBindings::SetSideSwipeHandler,
                 base::Unretained(this));
  InstallBinding(isolate, settings_ui, kSetPlatformInfoHandler,
                 &SettingsUiBindings::SetPlatformInfoHandler,
                 base::Unretained(this));
  InstallBinding(isolate, settings_ui, kRequestVisible,
                 &SettingsUiBindings::RequestVisible, base::Unretained(this));
}

void SettingsUiBindings::SetSideSwipeHandler(
    v8::Local<v8::Function> side_swipe_handler) {
  v8::Isolate* isolate =
      render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
  side_swipe_handler_ =
      v8::UniquePersistent<v8::Function>(isolate, side_swipe_handler);
}

void SettingsUiBindings::SetPlatformInfoHandler(
    v8::Local<v8::Function> platform_info_handler) {
  v8::Isolate* isolate =
      render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
  platform_info_handler_ =
      v8::UniquePersistent<v8::Function>(isolate, platform_info_handler);
  if (!pending_platform_info_json_.empty()) {
    SendPlatformInfo(pending_platform_info_json_);
    pending_platform_info_json_.clear();
  }
}

void SettingsUiBindings::RequestVisible(bool visible) {
  if (settings_platform_ptr_) {
    settings_platform_ptr_->RequestVisible(visible);
  }
}

void SettingsUiBindings::ReconnectMojo() {
  if (binding_.is_bound())
    binding_.reset();
  if (settings_platform_ptr_.is_bound())
    settings_platform_ptr_.reset();

  render_frame()->GetBrowserInterfaceBroker().GetInterface(
      settings_platform_ptr_.BindNewPipeAndPassReceiver());
  settings_platform_ptr_.set_disconnect_handler(base::BindOnce(
      &SettingsUiBindings::OnMojoConnectionError, weak_factory_.GetWeakPtr()));
  settings_platform_ptr_->Connect(binding_.BindNewPipeAndPassRemote());
  mojo_reconnect_timer_.Stop();
}

void SettingsUiBindings::OnMojoConnectionError() {
  LOG(WARNING) << "Disconnected from settings UI MOJO. Will reconnect every "
               << kDelayBetweenReconnectionInMillis << " milliseconds.";
  mojo_reconnect_timer_.Start(
      FROM_HERE, base::Milliseconds(kDelayBetweenReconnectionInMillis), this,
      &SettingsUiBindings::ReconnectMojo);
}

}  // namespace shell
}  // namespace chromecast