910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/renderer/api/messaging/gin_port.h"

#include <cstring>
#include <string_view>

#include "base/functional/bind.h"
#include "extensions/common/api/messaging/message.h"
#include "extensions/common/mojom/event_dispatcher.mojom.h"
#include "extensions/common/mojom/message_port.mojom.h"
#include "extensions/renderer/api/messaging/messaging_util.h"
#include "extensions/renderer/bindings/api_binding_util.h"
#include "extensions/renderer/bindings/api_event_handler.h"
#include "extensions/renderer/bindings/event_emitter.h"
#include "extensions/renderer/console.h"
#include "extensions/renderer/get_script_context.h"
#include "gin/arguments.h"
#include "gin/converter.h"
#include "gin/object_template_builder.h"
#include "v8/include/v8-context.h"
#include "v8/include/v8-cppgc.h"
#include "v8/include/v8-object.h"
#include "v8/include/v8-primitive.h"

namespace extensions {

namespace {

constexpr char kSenderKey[] = "sender";
constexpr char kOnMessageEvent[] = "onMessage";
constexpr char kOnDisconnectEvent[] = "onDisconnect";
constexpr char kContextInvalidatedError[] = "Extension context invalidated.";

}  // namespace

GinPort::GinPort(v8::Local<v8::Context> context,
                 const PortId& port_id,
                 const std::string& name,
                 const mojom::ChannelType channel_type,
                 APIEventHandler* event_handler,
                 Delegate* delegate)
    : port_id_(port_id),
      name_(name),
      channel_type_(channel_type),
      event_handler_(event_handler),
      delegate_(delegate),
      accessed_sender_(false) {
  context_invalidation_listener_.emplace(
      context, base::BindOnce(&GinPort::OnContextInvalidated,
                              weak_factory_.GetWeakPtr()));
}

GinPort::~GinPort() = default;

gin::ObjectTemplateBuilder GinPort::GetObjectTemplateBuilder(
    v8::Isolate* isolate) {
  return gin::Wrappable<GinPort>::GetObjectTemplateBuilder(isolate)
      .SetMethod("disconnect", &GinPort::DisconnectHandler)
      .SetMethod("postMessage", &GinPort::PostMessageHandler)
      .SetLazyDataProperty("name", &GinPort::GetName)
      .SetLazyDataProperty("onDisconnect", &GinPort::GetOnDisconnectEvent)
      .SetLazyDataProperty("onMessage", &GinPort::GetOnMessageEvent)
      .SetLazyDataProperty("sender", &GinPort::GetSender);
}

const gin::WrapperInfo* GinPort::wrapper_info() const {
  return &kWrapperInfo;
}

const char* GinPort::GetHumanReadableName() const {
  return "Port";
}

void GinPort::DispatchOnMessage(v8::Local<v8::Context> context,
                                const Message& message) {
  DCHECK_EQ(State::kActive, state_);

  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  v8::HandleScope handle_scope(isolate);
  v8::Context::Scope context_scope(context);

  // Parsing should be fail-safe for kNative channel type as native messaging
  // hosts can send malformed messages.
  std::string error;
  v8::Local<v8::Value> parsed_message = messaging_util::MessageToV8(
      context, message, channel_type_ == mojom::ChannelType::kNative, &error);

  v8::Local<v8::Object> self = GetWrapper(isolate).ToLocalChecked();
  v8::LocalVector<v8::Value> args(isolate, {parsed_message, self});

  if (error.empty()) {
    DispatchEvent(context, &args, kOnMessageEvent);
  } else {
    ScriptContext* script_context = GetScriptContextFromV8Context(context);
    console::AddMessage(script_context,
                        blink::mojom::ConsoleMessageLevel::kError, error);
  }
}

void GinPort::DispatchOnDisconnect(v8::Local<v8::Context> context) {
  DCHECK_EQ(State::kActive, state_);

  // Update |state_| before dispatching the onDisconnect event, so that we are
  // able to reject attempts to disconnect the port again or to send a message
  // from the event handler.
  state_ = State::kDisconnected;

  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  v8::HandleScope handle_scope(isolate);
  v8::Context::Scope context_scope(context);

  v8::Local<v8::Object> self = GetWrapper(isolate).ToLocalChecked();
  v8::LocalVector<v8::Value> args(isolate, {self});
  DispatchEvent(context, &args, kOnDisconnectEvent);

  InvalidateEvents(context);

  DCHECK_NE(state_, State::kActive);
}

void GinPort::SetSender(v8::Local<v8::Context> context,
                        v8::Local<v8::Value> sender) {
  DCHECK_EQ(State::kActive, state_);
  DCHECK(!accessed_sender_)
      << "|sender| can only be set before its first access.";

  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Object> wrapper = GetWrapper(isolate).ToLocalChecked();
  v8::Local<v8::Private> key =
      v8::Private::ForApi(isolate, gin::StringToSymbol(isolate, kSenderKey));
  v8::Maybe<bool> set_result = wrapper->SetPrivate(context, key, sender);
  DCHECK(set_result.IsJust() && set_result.FromJust());
}

void GinPort::DisconnectHandler(gin::Arguments* arguments) {
  if (state_ == State::kInvalidated) {
    ThrowError(arguments->isolate(), kContextInvalidatedError);
    return;
  }

  // NOTE: We don't currently throw an error for calling disconnect() multiple
  // times, but we could.
  if (state_ == State::kDisconnected) {
    return;
  }

  v8::Local<v8::Context> context = arguments->GetHolderCreationContext();
  InvalidateEvents(context);
  delegate_->ClosePort(context, port_id_);
  state_ = State::kDisconnected;
}

void GinPort::PostMessageHandler(gin::Arguments* arguments,
                                 v8::Local<v8::Value> v8_message) {
  v8::Isolate* isolate = arguments->isolate();
  v8::Local<v8::Context> context = arguments->GetHolderCreationContext();

  if (state_ == State::kInvalidated) {
    ThrowError(isolate, kContextInvalidatedError);
    return;
  }

  if (state_ == State::kDisconnected) {
    ThrowError(isolate, "Attempting to use a disconnected port object");
    return;
  }

  std::string error;
  std::unique_ptr<Message> message = messaging_util::MessageFromV8(
      context, v8_message, port_id_.serialization_format, &error);
  // NOTE(devlin): JS-based bindings just log to the console here and return,
  // rather than throwing an error. But it really seems like it should be an
  // error. Let's see how this goes.
  if (!message) {
    ThrowError(isolate, error);
    return;
  }

  delegate_->PostMessageToPort(context, port_id_, std::move(message));
}

std::string GinPort::GetName() {
  return name_;
}

v8::Local<v8::Value> GinPort::GetOnDisconnectEvent(gin::Arguments* arguments) {
  return GetEvent(arguments->GetHolderCreationContext(), kOnDisconnectEvent);
}

v8::Local<v8::Value> GinPort::GetOnMessageEvent(gin::Arguments* arguments) {
  return GetEvent(arguments->GetHolderCreationContext(), kOnMessageEvent);
}

v8::Local<v8::Value> GinPort::GetSender(gin::Arguments* arguments) {
  accessed_sender_ = true;
  v8::Isolate* isolate = arguments->isolate();
  v8::Local<v8::Object> wrapper = GetWrapper(isolate).ToLocalChecked();
  v8::Local<v8::Private> key =
      v8::Private::ForApi(isolate, gin::StringToSymbol(isolate, kSenderKey));
  v8::Local<v8::Value> sender;
  if (!wrapper->GetPrivate(arguments->GetHolderCreationContext(), key)
           .ToLocal(&sender)) {
    NOTREACHED();
  }

  return sender;
}

v8::Local<v8::Object> GinPort::GetEvent(v8::Local<v8::Context> context,
                                        std::string_view event_name) {
  DCHECK(event_name == kOnMessageEvent || event_name == kOnDisconnectEvent);
  v8::Isolate* isolate = v8::Isolate::GetCurrent();

  if (state_ == State::kInvalidated) {
    ThrowError(isolate, kContextInvalidatedError);
    return v8::Local<v8::Object>();
  }

  v8::Local<v8::Object> wrapper = GetWrapper(isolate).ToLocalChecked();
  v8::Local<v8::Private> key =
      v8::Private::ForApi(isolate, gin::StringToSymbol(isolate, event_name));
  v8::Local<v8::Value> event_val;
  if (!wrapper->GetPrivate(context, key).ToLocal(&event_val)) {
    NOTREACHED();
  }

  DCHECK(!event_val.IsEmpty());
  v8::Local<v8::Object> event_object;
  if (event_val->IsUndefined()) {
    event_object = event_handler_->CreateAnonymousEventInstance(context);
    v8::Maybe<bool> set_result =
        wrapper->SetPrivate(context, key, event_object);
    if (!set_result.IsJust() || !set_result.FromJust()) {
      NOTREACHED();
    }
  } else {
    event_object = event_val.As<v8::Object>();
  }
  return event_object;
}

void GinPort::DispatchEvent(v8::Local<v8::Context> context,
                            v8::LocalVector<v8::Value>* args,
                            std::string_view event_name) {
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  v8::Local<v8::Value> on_message = GetEvent(context, event_name);
  EventEmitter* emitter = nullptr;
  gin::Converter<EventEmitter*>::FromV8(isolate, on_message, &emitter);
  CHECK(emitter);
  emitter->Fire(context, args, /*filter=*/nullptr,
                /*on_dispatched_callback=*/v8::Local<v8::Function>(),
                /*listener_error_callback=*/v8::Local<v8::Function>());
}

void GinPort::OnContextInvalidated() {
  DCHECK_NE(state_, State::kInvalidated);
  state_ = State::kInvalidated;
  // Note: no need to InvalidateEvents() here, since the APIEventHandler will
  // invalidate them when the context is disposed.
}

void GinPort::InvalidateEvents(v8::Local<v8::Context> context) {
  // No need to invalidate the events if the context itself was already
  // invalidated; the APIEventHandler will have already cleaned up the
  // listeners.
  if (state_ == State::kInvalidated) {
    return;
  }

  // TODO(devlin): By calling GetEvent() here, we'll end up creating an event
  // if one didn't exist. It would be more efficient to only invalidate events
  // that the port has already created.
  event_handler_->InvalidateCustomEvent(context,
                                        GetEvent(context, kOnMessageEvent));
  event_handler_->InvalidateCustomEvent(context,
                                        GetEvent(context, kOnDisconnectEvent));
}

void GinPort::ThrowError(v8::Isolate* isolate, std::string_view error) {
  isolate->ThrowException(
      v8::Exception::Error(gin::StringToV8(isolate, error)));
}

}  // namespace extensions