Skip to content

Commit

Permalink
feat: add a new contextBridge module (#20639)
Browse files Browse the repository at this point in the history
* feat: add a new contextBridge module (#20307)

* feat: add a new contextBridge module

* chore: fix docs linting

* feat: add support for function arguments being proxied

* chore: ensure that contextBridge can only be used when contextIsolation is enabled

* docs: getReverseBinding can be null

* docs: fix broken links in md file

* feat: add support for promises in function parameters

* fix: linting failure for explicit constructor

* Update atom_api_context_bridge.cc

* chore: update docs and API design as per feedback

* refactor: remove reverse bindings and handle GC'able functions across the bridge

* chore: only expose debugGC in testing builds

* fix: do not proxy promises as objects

* spec: add complete spec coverage for contextBridge

* spec: add tests for null/undefined and the anti-overwrite logic

* chore: fix linting

* spec: add complex nested back-and-forth function calling

* fix: expose contextBridge in sandboxed renderers

* refactor: improve security of default_app using the new contextBridge module

* s/bindAPIInMainWorld/exposeInMainWorld

* chore: sorry for this commit, its a big one, I fixed like everything and refactored a lot

* chore: remove PassedValueCache as it is unused now

Values transferred from context A to context B are now cachde in the RenderFramePersistenceStore

* chore: move to anonymous namespace

* refactor: remove PassValueToOtherContextWithCache

* chore: remove commented unused code blocks

* chore: remove .only

* chore: remote commented code

* refactor: extract RenderFramePersistenceStore

* spec: ensure it works with numbered keys

* fix: handle number keys correctly

* fix: sort out the linter

* spec: update default_app asar spec for removed file

* refactor: change signatures to return v8 objects directly rather than the mate dictionary handle

* refactor: use the v8 serializer to support cloneable buffers and other object types

* chore: fix linting

* fix: handle hash collisions with a linked list in the map

* fix: enforce a recursion limit on the context bridge

* chore: fix linting

* chore: remove TODO

* chore: adapt for PR feedback

* chore: remove .only

* chore: clean up docs and clean up the proxy map when objects are released

* chore: ensure we cache object values that are cloned through the V8 serializer

* docs: mark contextBridge as experimental (#20638)

* docs: mark contextBridge as experimental

This commit didn't make it to the original PR, quick addition here

* Update context-bridge.md

* chore: touch up the differences between master and 6-0-x

* chore: add v8 serializer converter, cherry picked from 2fad53e

* chore: support converting OnceCallback to V8 (#17941)

* chore: fixup tests

* chore: fix linting

* chore: add patch for mojo message constructor
  • Loading branch information
MarshallOfSound committed Oct 21, 2019
1 parent 3ca62d9 commit 268cd39
Show file tree
Hide file tree
Showing 29 changed files with 2,220 additions and 59 deletions.
2 changes: 1 addition & 1 deletion atom/browser/api/atom_api_web_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@
#include "atom/common/color_util.h"
#include "atom/common/mouse_util.h"
#include "atom/common/native_mate_converters/blink_converter.h"
#include "atom/common/native_mate_converters/callback.h"
#include "atom/common/native_mate_converters/content_converter.h"
#include "atom/common/native_mate_converters/file_path_converter.h"
#include "atom/common/native_mate_converters/gfx_converter.h"
#include "atom/common/native_mate_converters/gurl_converter.h"
#include "atom/common/native_mate_converters/image_converter.h"
#include "atom/common/native_mate_converters/net_converter.h"
#include "atom/common/native_mate_converters/network_converter.h"
#include "atom/common/native_mate_converters/once_callback.h"
#include "atom/common/native_mate_converters/string16_converter.h"
#include "atom/common/native_mate_converters/value_converter.h"
#include "atom/common/node_includes.h"
Expand Down
175 changes: 175 additions & 0 deletions atom/common/native_mate_converters/blink_converter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@

#include <algorithm>
#include <string>
#include <utility>
#include <vector>

#include "atom/common/keyboard_util.h"
#include "atom/common/native_mate_converters/value_converter.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "gin/converter.h"
#include "mojo/public/cpp/base/values_mojom_traits.h"
#include "mojo/public/mojom/base/values.mojom.h"
#include "native_mate/dictionary.h"
#include "third_party/blink/public/platform/web_input_event.h"
#include "third_party/blink/public/platform/web_mouse_event.h"
Expand Down Expand Up @@ -527,4 +531,175 @@ bool Converter<network::mojom::ReferrerPolicy>::FromV8(
return true;
}

namespace {
constexpr uint8_t kNewSerializationTag = 0;
constexpr uint8_t kOldSerializationTag = 1;

class V8Serializer : public v8::ValueSerializer::Delegate {
public:
explicit V8Serializer(v8::Isolate* isolate,
bool use_old_serialization = false)
: isolate_(isolate),
serializer_(isolate, this),
use_old_serialization_(use_old_serialization) {}
~V8Serializer() override = default;

bool Serialize(v8::Local<v8::Value> value, blink::CloneableMessage* out) {
serializer_.WriteHeader();
if (use_old_serialization_) {
WriteTag(kOldSerializationTag);
if (!WriteBaseValue(value)) {
isolate_->ThrowException(
mate::StringToV8(isolate_, "An object could not be cloned."));
return false;
}
} else {
WriteTag(kNewSerializationTag);
bool wrote_value;
v8::TryCatch try_catch(isolate_);
if (!serializer_.WriteValue(isolate_->GetCurrentContext(), value)
.To(&wrote_value)) {
try_catch.Reset();
if (!V8Serializer(isolate_, true).Serialize(value, out)) {
try_catch.ReThrow();
return false;
}
return true;
}
DCHECK(wrote_value);
}

std::pair<uint8_t*, size_t> buffer = serializer_.Release();
DCHECK_EQ(buffer.first, data_.data());
out->encoded_message = base::make_span(buffer.first, buffer.second);
out->owned_encoded_message = std::move(data_);

return true;
}

bool WriteBaseValue(v8::Local<v8::Value> object) {
base::Value value;
if (!ConvertFromV8(isolate_, object, &value)) {
return false;
}
mojo::Message message = mojo_base::mojom::Value::SerializeAsMessage(&value);

serializer_.WriteUint32(message.data_num_bytes());
serializer_.WriteRawBytes(message.data(), message.data_num_bytes());
return true;
}

void WriteTag(uint8_t tag) { serializer_.WriteRawBytes(&tag, 1); }

// v8::ValueSerializer::Delegate
void* ReallocateBufferMemory(void* old_buffer,
size_t size,
size_t* actual_size) override {
DCHECK_EQ(old_buffer, data_.data());
data_.resize(size);
*actual_size = data_.capacity();
return data_.data();
}

void FreeBufferMemory(void* buffer) override {
DCHECK_EQ(buffer, data_.data());
data_ = {};
}

void ThrowDataCloneError(v8::Local<v8::String> message) override {
isolate_->ThrowException(v8::Exception::Error(message));
}

private:
v8::Isolate* isolate_;
std::vector<uint8_t> data_;
v8::ValueSerializer serializer_;
bool use_old_serialization_;
};

class V8Deserializer : public v8::ValueDeserializer::Delegate {
public:
V8Deserializer(v8::Isolate* isolate, const blink::CloneableMessage& message)
: isolate_(isolate),
deserializer_(isolate,
message.encoded_message.data(),
message.encoded_message.size(),
this) {}

v8::Local<v8::Value> Deserialize() {
v8::EscapableHandleScope scope(isolate_);
auto context = isolate_->GetCurrentContext();
bool read_header;
if (!deserializer_.ReadHeader(context).To(&read_header))
return v8::Null(isolate_);
DCHECK(read_header);
uint8_t tag;
if (!ReadTag(&tag))
return v8::Null(isolate_);
switch (tag) {
case kNewSerializationTag: {
v8::Local<v8::Value> value;
if (!deserializer_.ReadValue(context).ToLocal(&value)) {
return v8::Null(isolate_);
}
return scope.Escape(value);
}
case kOldSerializationTag: {
v8::Local<v8::Value> value;
if (!ReadBaseValue(&value)) {
return v8::Null(isolate_);
}
return scope.Escape(value);
}
default:
NOTREACHED() << "Invalid tag: " << tag;
return v8::Null(isolate_);
}
}

bool ReadTag(uint8_t* tag) {
const void* tag_bytes;
if (!deserializer_.ReadRawBytes(1, &tag_bytes))
return false;
*tag = *reinterpret_cast<const uint8_t*>(tag_bytes);
return true;
}

bool ReadBaseValue(v8::Local<v8::Value>* value) {
uint32_t length;
const void* data;
if (!deserializer_.ReadUint32(&length) ||
!deserializer_.ReadRawBytes(length, &data)) {
return false;
}
mojo::Message message(
base::make_span(reinterpret_cast<const uint8_t*>(data), length), {});
base::Value out;
if (!mojo_base::mojom::Value::DeserializeFromMessage(std::move(message),
&out)) {
return false;
}
*value = ConvertToV8(isolate_, out);
return true;
}

private:
v8::Isolate* isolate_;
v8::ValueDeserializer deserializer_;
};

} // namespace

v8::Local<v8::Value> Converter<blink::CloneableMessage>::ToV8(
v8::Isolate* isolate,
const blink::CloneableMessage& in) {
return V8Deserializer(isolate, in).Deserialize();
}

bool Converter<blink::CloneableMessage>::FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
blink::CloneableMessage* out) {
return V8Serializer(isolate).Serialize(val, out);
}

} // namespace mate
10 changes: 10 additions & 0 deletions atom/common/native_mate_converters/blink_converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_

#include "native_mate/converter.h"
#include "third_party/blink/public/common/messaging/cloneable_message.h"
#include "third_party/blink/public/platform/web_cache.h"
#include "third_party/blink/public/platform/web_input_event.h"
#include "third_party/blink/public/web/web_context_menu_data.h"
Expand Down Expand Up @@ -131,6 +132,15 @@ struct Converter<network::mojom::ReferrerPolicy> {
network::mojom::ReferrerPolicy* out);
};

template <>
struct Converter<blink::CloneableMessage> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const blink::CloneableMessage& in);
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
blink::CloneableMessage* out);
};

v8::Local<v8::Value> EditFlagsToV8(v8::Isolate* isolate, int editFlags);
v8::Local<v8::Value> MediaFlagsToV8(v8::Isolate* isolate, int mediaFlags);

Expand Down
24 changes: 7 additions & 17 deletions atom/common/native_mate_converters/callback.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_CALLBACK_H_
#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_CALLBACK_H_

#include <utility>
#include <vector>

#include "atom/common/api/locker.h"
Expand Down Expand Up @@ -54,7 +55,8 @@ struct V8FunctionInvoker<v8::Local<v8::Value>(ArgTypes...)> {
v8::Local<v8::Function> holder = function.NewHandle(isolate);
v8::Local<v8::Context> context = holder->CreationContext();
v8::Context::Scope context_scope(context);
std::vector<v8::Local<v8::Value>> args{ConvertToV8(isolate, raw)...};
std::vector<v8::Local<v8::Value>> args{
ConvertToV8(isolate, std::forward<ArgTypes>(raw))...};
v8::MaybeLocal<v8::Value> ret = holder->Call(
context, holder, args.size(), args.empty() ? nullptr : &args.front());
if (ret.IsEmpty())
Expand All @@ -78,7 +80,8 @@ struct V8FunctionInvoker<void(ArgTypes...)> {
v8::Local<v8::Function> holder = function.NewHandle(isolate);
v8::Local<v8::Context> context = holder->CreationContext();
v8::Context::Scope context_scope(context);
std::vector<v8::Local<v8::Value>> args{ConvertToV8(isolate, raw)...};
std::vector<v8::Local<v8::Value>> args{
ConvertToV8(isolate, std::forward<ArgTypes>(raw))...};
holder
->Call(context, holder, args.size(),
args.empty() ? nullptr : &args.front())
Expand All @@ -101,7 +104,8 @@ struct V8FunctionInvoker<ReturnType(ArgTypes...)> {
v8::Local<v8::Function> holder = function.NewHandle(isolate);
v8::Local<v8::Context> context = holder->CreationContext();
v8::Context::Scope context_scope(context);
std::vector<v8::Local<v8::Value>> args{ConvertToV8(isolate, raw)...};
std::vector<v8::Local<v8::Value>> args{
ConvertToV8(isolate, std::forward<ArgTypes>(raw))...};
v8::Local<v8::Value> result;
auto maybe_result = holder->Call(context, holder, args.size(),
args.empty() ? nullptr : &args.front());
Expand Down Expand Up @@ -138,20 +142,6 @@ struct NativeFunctionInvoker<ReturnType(ArgTypes...)> {

} // namespace internal

template <typename Sig>
struct Converter<base::OnceCallback<Sig>> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
base::OnceCallback<Sig>* out) {
if (!val->IsFunction())
return false;

*out = base::BindOnce(&internal::V8FunctionInvoker<Sig>::Go, isolate,
internal::SafeV8Function(isolate, val));
return true;
}
};

template <typename Sig>
struct Converter<base::RepeatingCallback<Sig>> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
Expand Down
87 changes: 87 additions & 0 deletions atom/common/native_mate_converters/once_callback.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) 2019 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_ONCE_CALLBACK_H_
#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_ONCE_CALLBACK_H_

#include <utility>

#include "atom/common/native_mate_converters/callback.h"

namespace mate {

namespace internal {

// Manages the OnceCallback with ref-couting.
template <typename Sig>
class RefCountedOnceCallback
: public base::RefCounted<RefCountedOnceCallback<Sig>> {
public:
explicit RefCountedOnceCallback(base::OnceCallback<Sig> callback)
: callback_(std::move(callback)) {}

base::OnceCallback<Sig> GetCallback() { return std::move(callback_); }

private:
friend class base::RefCounted<RefCountedOnceCallback<Sig>>;
~RefCountedOnceCallback() = default;

base::OnceCallback<Sig> callback_;
};

// Invokes the OnceCallback.
template <typename Sig>
struct InvokeOnceCallback {};

template <typename... ArgTypes>
struct InvokeOnceCallback<void(ArgTypes...)> {
static void Go(
scoped_refptr<RefCountedOnceCallback<void(ArgTypes...)>> holder,
ArgTypes... args) {
base::OnceCallback<void(ArgTypes...)> callback = holder->GetCallback();
DCHECK(!callback.is_null());
std::move(callback).Run(std::move(args)...);
}
};

template <typename ReturnType, typename... ArgTypes>
struct InvokeOnceCallback<ReturnType(ArgTypes...)> {
static ReturnType Go(
scoped_refptr<RefCountedOnceCallback<ReturnType(ArgTypes...)>> holder,
ArgTypes... args) {
base::OnceCallback<void(ArgTypes...)> callback = holder->GetCallback();
DCHECK(!callback.is_null());
return std::move(callback).Run(std::move(args)...);
}
};

} // namespace internal

template <typename Sig>
struct Converter<base::OnceCallback<Sig>> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
base::OnceCallback<Sig> val) {
// Reuse the converter of base::RepeatingCallback by storing the callback
// with a RefCounted.
auto holder = base::MakeRefCounted<internal::RefCountedOnceCallback<Sig>>(
std::move(val));
return Converter<base::RepeatingCallback<Sig>>::ToV8(
isolate,
base::BindRepeating(&internal::InvokeOnceCallback<Sig>::Go, holder));
}

static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
base::OnceCallback<Sig>* out) {
if (!val->IsFunction())
return false;
*out = base::BindOnce(&internal::V8FunctionInvoker<Sig>::Go, isolate,
internal::SafeV8Function(isolate, val));
return true;
}
};

} // namespace mate

#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_ONCE_CALLBACK_H_
1 change: 1 addition & 0 deletions atom/common/node_bindings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
V(atom_common_screen) \
V(atom_common_shell) \
V(atom_common_v8_util) \
V(atom_renderer_context_bridge) \
V(atom_renderer_ipc) \
V(atom_renderer_web_frame)

Expand Down

0 comments on commit 268cd39

Please sign in to comment.