chromium/services/accessibility/features/mojo/mojo_handle.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "services/accessibility/features/mojo/mojo_handle.h"

#include <memory>

#include "base/logging.h"
#include "base/types/fixed_array.h"
#include "gin/arguments.h"
#include "gin/array_buffer.h"
#include "gin/converter.h"
#include "gin/data_object_builder.h"
#include "gin/dictionary.h"
#include "gin/handle.h"
#include "gin/public/context_holder.h"
#include "gin/public/wrapper_info.h"
#include "mojo/public/c/system/types.h"
#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "services/accessibility/features/mojo/mojo_watch_callback.h"
#include "services/accessibility/features/mojo/mojo_watcher.h"
#include "services/accessibility/features/registered_wrappable.h"
#include "v8/include/v8-array-buffer.h"
#include "v8/include/v8-container.h"
#include "v8/include/v8-function.h"
#include "v8/include/v8-isolate.h"
#include "v8/include/v8-local-handle.h"

namespace ax {

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

// static
gin::Handle<MojoHandle> MojoHandle::Create(v8::Local<v8::Context> context,
                                           mojo::ScopedHandle handle) {
  return gin::CreateHandle(context->GetIsolate(),
                           new MojoHandle(context, std::move(handle)));
}

MojoHandle::~MojoHandle() = default;

gin::ObjectTemplateBuilder MojoHandle::GetObjectTemplateBuilder(
    v8::Isolate* isolate) {
  return gin::Wrappable<MojoHandle>::GetObjectTemplateBuilder(isolate)
      .SetMethod("watch", &MojoHandle::Watch)
      .SetMethod("close", &MojoHandle::Close)
      .SetMethod("readMessage", &MojoHandle::ReadMessage)
      .SetMethod("writeMessage", &MojoHandle::WriteMessage);
}

void MojoHandle::Watch(gin::Arguments* arguments) {
  v8::Isolate* isolate = arguments->isolate();
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::Context> context = arguments->GetHolderCreationContext();

  v8::LocalVector<v8::Value> args = arguments->GetAll();
  CHECK_EQ(args.size(), 2u);

  // See third_party/blink/renderer/core/mojo/mojo_handle_signals.idl,
  // which defines the first argument as:
  // dictionary MojoHandleSignals {
  //   boolean readable = false;
  //   boolean writable = false;
  //   boolean peerClosed = false;
  // };
  CHECK(args[0]->IsObject());
  gin::Dictionary signals(isolate, args[0].As<v8::Object>());
  bool readable, writable, peer_closed = false;
  signals.Get("readable", &readable);
  signals.Get("writable", &writable);
  signals.Get("peerClosed", &peer_closed);

  // Now need to extract the MojoWatchCallback from the second
  // argument, see third_party/blink/renderer/core/mojo/mojo_handle.idl.
  CHECK(args[1]->IsFunction());
  v8::Local<v8::Function> v8_callback = args[1].As<v8::Function>();
  auto context_holder = std::make_unique<gin::ContextHolder>(isolate);
  context_holder->SetContext(context);
  auto callback = std::make_unique<MojoWatchCallback>(std::move(context_holder),
                                                      v8_callback);

  // Then make a MojoWatcher with the signals and callback, and return it.
  // The MojoWatcher should start watching when it's created
  // and call the callback then.
  arguments->Return(MojoWatcher::Create(context, handle_.get(), readable,
                                        writable, peer_closed,
                                        std::move(callback)));
}

void MojoHandle::Close(gin::Arguments* arguments) {
  v8::Isolate* isolate = arguments->isolate();
  v8::HandleScope handle_scope(isolate);
  handle_.reset();
}

void MojoHandle::ReadMessage(gin::Arguments* arguments) {
  v8::Isolate* isolate = arguments->isolate();
  v8::HandleScope handle_scope(isolate);

  // The result should be formatted as per
  // third_party/blink/renderer/core/mojo/mojo_read_message_result.idl:
  // dictionary MojoReadMessageResult {
  //   required MojoResult result;
  //   ArrayBuffer buffer;
  //   sequence<MojoHandle> handles;
  // };
  gin::DataObjectBuilder result_dict(isolate);

  mojo::ScopedMessageHandle message;
  MojoResult result =
      mojo::ReadMessageNew(mojo::MessagePipeHandle(handle_.get().value()),
                           &message, MOJO_READ_MESSAGE_FLAG_NONE);
  if (result != MOJO_RESULT_OK) {
    result_dict.Set("result", result);
    arguments->Return(result_dict.Build());
    return;
  }

  result = MojoSerializeMessage(message->value(), nullptr);
  if (result != MOJO_RESULT_OK && result != MOJO_RESULT_FAILED_PRECONDITION) {
    result_dict.Set("result", MOJO_RESULT_ABORTED);
    arguments->Return(result_dict.Build());
    return;
  }

  uint32_t num_bytes = 0, num_handles = 0;
  void* bytes = nullptr;
  std::vector<::MojoHandle> raw_handles;
  result = MojoGetMessageData(message->value(), /*options=*/nullptr, &bytes,
                              &num_bytes, nullptr, &num_handles);
  if (result == MOJO_RESULT_RESOURCE_EXHAUSTED) {
    // We don't know how many handles are in a message at first. The first call
    // retrieves that in num_handles. The second call can then provide adequate
    // storage for the copies.
    raw_handles.resize(num_handles);
    result = MojoGetMessageData(message->value(), /*options=*/nullptr, &bytes,
                                &num_bytes, raw_handles.data(), &num_handles);
  }

  if (result != MOJO_RESULT_OK) {
    result_dict.Set("result", MOJO_RESULT_ABORTED);
    arguments->Return(result_dict.Build());
    return;
  }

  void* buffer =
      gin::ArrayBufferAllocator::SharedInstance()->Allocate(num_bytes);
  auto deleter = [](void* buffer, size_t length, void* data) {
    gin::ArrayBufferAllocator::SharedInstance()->Free(buffer, length);
  };
  std::unique_ptr<v8::BackingStore> backing_store =
      v8::ArrayBuffer::NewBackingStore(buffer, num_bytes, deleter, nullptr);

  v8::Local<v8::ArrayBuffer> array_buffer =
      v8::ArrayBuffer::New(isolate, std::move(backing_store));
  if (num_bytes) {
    CHECK(array_buffer->Data());
    memcpy(array_buffer->Data(), bytes, num_bytes);
  }
  result_dict.Set("buffer", array_buffer);

  std::vector<gin::Handle<MojoHandle>> handles(num_handles);
  for (uint32_t i = 0; i < num_handles; ++i) {
    handles[i] = MojoHandle::Create(
        arguments->GetHolderCreationContext(),
        mojo::MakeScopedHandle(mojo::Handle(raw_handles[i])));
  }
  result_dict.Set("handles", handles);

  result_dict.Set("result", result);
  arguments->Return(result_dict.Build());
}

void MojoHandle::WriteMessage(gin::Arguments* arguments) {
  v8::Isolate* isolate = arguments->isolate();
  v8::HandleScope handle_scope(isolate);

  // third_party/blink/renderer/core/mojo/mojo_handle.idl defines this
  // method as:
  // MojoResult writeMessage(BufferSource buffer, sequence<MojoHandle> handles);
  v8::LocalVector<v8::Value> args = arguments->GetAll();
  DCHECK_EQ(args.size(), 2u);
  DCHECK(args[0]->IsArrayBuffer() || args[0]->IsArrayBufferView());
  DCHECK(args[1]->IsArray());

  v8::Local<v8::Array> v8_handles = args[1].As<v8::Array>();

  std::vector<gin::Handle<MojoHandle>> handles;
  if (!gin::ConvertFromV8(isolate, v8_handles, &handles)) {
    arguments->Return(MOJO_RESULT_INVALID_ARGUMENT);
    return;
  }

  std::vector<mojo::ScopedHandle> scoped_handles;
  scoped_handles.reserve(handles.size());
  bool has_invalid_handles = false;
  for (auto& handle : handles) {
    if (!handle->handle_.is_valid()) {
      has_invalid_handles = true;
    } else {
      scoped_handles.emplace_back(std::move(handle->handle_));
    }
  }
  if (has_invalid_handles) {
    arguments->Return(MOJO_RESULT_INVALID_ARGUMENT);
    return;
  }

  base::span<const uint8_t> bytes;
  if (args[0]->IsArrayBuffer()) {
    v8::Local<v8::ArrayBuffer> array = args[0].As<v8::ArrayBuffer>();
    bytes = base::make_span(static_cast<const uint8_t*>(array->Data()),
                            array->ByteLength());
  } else {
    v8::Local<v8::ArrayBufferView> view = args[0].As<v8::ArrayBufferView>();
    base::FixedArray<uint8_t> bites(view->ByteLength());
    view->CopyContents(bites.data(), bites.size());
    bytes = base::span(bites);
  }

  auto message = mojo::Message(bytes, base::make_span(scoped_handles));
  DCHECK(!message.IsNull());
  MojoResult result = mojo::WriteMessageNew(
      mojo::MessagePipeHandle(handle_.get().value()), message.TakeMojoMessage(),
      MOJO_WRITE_MESSAGE_FLAG_NONE);
  arguments->Return(result);
}

mojo::ScopedHandle MojoHandle::TakeHandle() {
  return std::move(handle_);
}

MojoHandle::MojoHandle(v8::Local<v8::Context> context,
                       mojo::ScopedHandle handle)
    : RegisteredWrappable(context), handle_(std::move(handle)) {}

}  // namespace ax