chromium/chromecast/renderer/native_bindings_helper.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 "chromecast/renderer/native_bindings_helper.h"

#include "base/logging.h"
#include "content/public/renderer/render_frame.h"
#include "gin/converter.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 {
const char kCastObjectName[] = "cast";
const char kCastPlatformObjectKey[] = "__platform__";
}  // namespace

v8::Local<v8::Object> GetOrCreateCastPlatformObject(
    v8::Isolate* isolate,
    v8::Local<v8::Object> global) {
  v8::Local<v8::Object> cast =
      EnsureObjectExists(isolate, global, kCastObjectName);
  return EnsureObjectExists(isolate, cast, kCastPlatformObjectKey);
}

v8::Local<v8::Object> EnsureObjectExists(v8::Isolate* isolate,
                                         v8::Local<v8::Object> parent,
                                         const std::string& key) {
  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  v8::MaybeLocal<v8::Value> child =
      parent->Get(context, gin::StringToV8(isolate, key));
  v8::Local<v8::Value> child_local;
  v8::Local<v8::Object> child_object;
  if (child.ToLocal(&child_local) && child_local->IsObject() &&
      child_local->ToObject(context).ToLocal(&child_object))
    return child_object;

  if (!child_local.IsEmpty() && !child_local->IsUndefined())
    LOG(WARNING) << "Overwriting non-empty non-object with key " << key;

  v8::Local<v8::Object> new_child_object = v8::Object::New(isolate);
  v8::Maybe<bool> result =
      parent->Set(context, gin::StringToSymbol(isolate, key), new_child_object);
  if (result.IsNothing() || !result.FromJust())
    LOG(ERROR) << "Failed to set new object with key " << key;

  return new_child_object;
}

CastBinding::CastBinding(content::RenderFrame* render_frame)
    : content::RenderFrameObserver(render_frame) {}

CastBinding::~CastBinding() {}

void CastBinding::DidClearWindowObject() {
  TryInstall();
}

void CastBinding::OnDestruct() {
  delete this;
}

void CastBinding::TryInstall() {
  blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
  if (!web_frame)
    return;

  v8::Isolate* isolate = web_frame->GetAgentGroupScheduler()->Isolate();
  if (!isolate)
    return;

  // The HandleScope must be created before MainWorldScriptContext is called.
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
  v8::MicrotasksScope microtasks(context,
                                 v8::MicrotasksScope::kDoNotRunMicrotasks);
  if (context.IsEmpty())
    return;

  v8::Context::Scope context_scope(context);
  v8::Local<v8::Object> global = context->Global();
  v8::Local<v8::Object> cast_platform =
      GetOrCreateCastPlatformObject(isolate, global);
  Install(cast_platform, isolate);
}

}  // namespace chromecast