// 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 "pdf/post_message_receiver.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/values.h"
#include "gin/function_template.h"
#include "gin/handle.h"
#include "gin/interceptor.h"
#include "gin/object_template_builder.h"
#include "gin/public/wrapper_info.h"
#include "gin/wrappable.h"
#include "pdf/v8_value_converter.h"
#include "v8/include/v8.h"

namespace chrome_pdf {

namespace {

constexpr char kPropertyName[] = "postMessage";

}  // namespace

// static
gin::WrapperInfo PostMessageReceiver::kWrapperInfo = {gin::kEmbedderNativeGin};

// static
v8::Local<v8::Object> PostMessageReceiver::Create(
    v8::Isolate* isolate,
    base::WeakPtr<V8ValueConverter> v8_value_converter,
    base::WeakPtr<Client> client,
    scoped_refptr<base::SequencedTaskRunner> client_task_runner) {
  return gin::CreateHandle(
             isolate, new PostMessageReceiver(
                          isolate, std::move(v8_value_converter),
                          std::move(client), std::move(client_task_runner)))
      .ToV8()
      .As<v8::Object>();
}

PostMessageReceiver::~PostMessageReceiver() = default;

PostMessageReceiver::PostMessageReceiver(
    v8::Isolate* isolate,
    base::WeakPtr<V8ValueConverter> v8_value_converter,
    base::WeakPtr<Client> client,
    scoped_refptr<base::SequencedTaskRunner> client_task_runner)
    : gin::NamedPropertyInterceptor(isolate, this),
      v8_value_converter_(std::move(v8_value_converter)),
      isolate_(isolate),
      client_(std::move(client)),
      client_task_runner_(std::move(client_task_runner)) {}

gin::ObjectTemplateBuilder PostMessageReceiver::GetObjectTemplateBuilder(
    v8::Isolate* isolate) {
  // `gin::ObjectTemplateBuilder::SetMethod()` can't be used here because it
  // would create a function template which expects the first parameter to a
  // member function pointer to be the JavaScript `this` object corresponding
  // to this scriptable object exposed through Blink. However, the actual
  // receiving object for a plugin is an HTMLEmbedElement and Blink internally
  // forwards the parameters to this scriptable object.
  //
  // Also, passing a callback would cause Gin to ignore the target. Because Gin
  // creates the object template of a type only once per isolate, the member
  // method of the first `PostMessageReceiver` instance would get effectively
  // treated like a static method for all other instances.
  //
  // An interceptor allows for the creation of a function template per instance.
  return gin::Wrappable<PostMessageReceiver>::GetObjectTemplateBuilder(isolate)
      .AddNamedPropertyInterceptor();
}

const char* PostMessageReceiver::GetTypeName() {
  return "ChromePdfPostMessageReceiver";
}

v8::Local<v8::Value> PostMessageReceiver::GetNamedProperty(
    v8::Isolate* isolate,
    const std::string& property) {
  DCHECK_EQ(isolate_, isolate);

  if (property != kPropertyName)
    return v8::Local<v8::Value>();

  return GetFunctionTemplate()
      ->GetFunction(isolate->GetCurrentContext())
      .ToLocalChecked();
}

std::vector<std::string> PostMessageReceiver::EnumerateNamedProperties(
    v8::Isolate* isolate) {
  DCHECK_EQ(isolate_, isolate);
  return {kPropertyName};
}

v8::Local<v8::FunctionTemplate> PostMessageReceiver::GetFunctionTemplate() {
  if (function_template_.IsEmpty()) {
    function_template_.Reset(
        isolate_,
        gin::CreateFunctionTemplate(
            isolate_, base::BindRepeating(&PostMessageReceiver::PostMessage,
                                          weak_factory_.GetWeakPtr())));
  }

  return function_template_.Get(isolate_);
}

void PostMessageReceiver::PostMessage(v8::Local<v8::Value> message) {
  if (!client_ || !v8_value_converter_)
    return;

  std::unique_ptr<base::Value> converted_message =
      v8_value_converter_->FromV8Value(message, isolate_->GetCurrentContext());
  // The PDF Viewer UI should not be sending messages that cannot be converted.
  DCHECK(converted_message);
  DCHECK(converted_message->is_dict());

  client_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&Client::OnMessage, client_,
                                std::move(*converted_message).TakeDict()));
}

}  // namespace chrome_pdf