Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrap window.ethereum._metamask in gin::Wrappable #18411

Merged
merged 1 commit into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 73 additions & 59 deletions components/brave_wallet/renderer/js_ethereum_provider.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,6 @@ constexpr char kIsUnlocked[] = "isUnlocked";

namespace brave_wallet {

void JSEthereumProvider::OnIsUnlocked(
v8::Global<v8::Context> global_context,
v8::Global<v8::Promise::Resolver> promise_resolver,
v8::Isolate* isolate,
bool locked) {
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Promise::Resolver> resolver = promise_resolver.Get(isolate);
v8::Local<v8::Context> context = global_context.Get(isolate);
v8::MicrotasksScope microtasks(isolate, context->GetMicrotaskQueue(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
base::Value result = base::Value(!locked);
v8::Local<v8::Value> local_result =
content::V8ValueConverter::Create()->ToV8Value(result, context);
std::ignore = resolver->Resolve(context, local_result);
}

void JSEthereumProvider::SendResponse(
base::Value id,
v8::Global<v8::Context> global_context,
Expand Down Expand Up @@ -235,26 +219,6 @@ void JSEthereumProvider::Install(bool allow_overwrite_window_ethereum_provider,
SetOwnPropertyWritable(context, provider_object,
gin::StringToV8(isolate, kIsMetaMask), true);

// Set non-writable _metamask obj with non-writable isUnlocked method.
v8::Local<v8::Value> metamask_value;
v8::Local<v8::Object> metamask_obj = v8::Object::New(isolate);
provider_object
->Set(context, gin::StringToSymbol(isolate, kMetaMask), metamask_obj)
.Check();
SetOwnPropertyWritable(context, provider_object,
gin::StringToV8(isolate, kMetaMask), false);

metamask_obj
->Set(context, gin::StringToSymbol(isolate, kIsUnlocked),
gin::CreateFunctionTemplate(
isolate, base::BindRepeating(&JSEthereumProvider::IsUnlocked,
base::Unretained(provider.get())))
->GetFunction(context)
.ToLocalChecked())
.Check();
SetOwnPropertyWritable(context, metamask_obj,
gin::StringToV8(isolate, kIsUnlocked), false);

ExecuteScript(web_frame,
LoadDataResource(
IDR_BRAVE_WALLET_SCRIPT_ETHEREUM_PROVIDER_SCRIPT_BUNDLE_JS),
Expand All @@ -269,6 +233,78 @@ bool JSEthereumProvider::GetIsMetaMask() {
return true;
}

gin::WrapperInfo JSEthereumProvider::MetaMask::kWrapperInfo = {
gin::kEmbedderNativeGin};

JSEthereumProvider::MetaMask::MetaMask(content::RenderFrame* render_frame)
: render_frame_(render_frame) {}
JSEthereumProvider::MetaMask::~MetaMask() = default;

gin::ObjectTemplateBuilder
JSEthereumProvider::MetaMask::GetObjectTemplateBuilder(v8::Isolate* isolate) {
return gin::Wrappable<MetaMask>::GetObjectTemplateBuilder(isolate).SetMethod(
kIsUnlocked, &JSEthereumProvider::MetaMask::IsUnlocked);
}

const char* JSEthereumProvider::MetaMask::GetTypeName() {
return kMetaMask;
}

v8::Local<v8::Promise> JSEthereumProvider::MetaMask::IsUnlocked(
v8::Isolate* isolate) {
if (!ethereum_provider_.is_bound()) {
render_frame_->GetBrowserInterfaceBroker()->GetInterface(
ethereum_provider_.BindNewPipeAndPassReceiver());
}

v8::MaybeLocal<v8::Promise::Resolver> resolver =
v8::Promise::Resolver::New(isolate->GetCurrentContext());
if (resolver.IsEmpty()) {
return v8::Local<v8::Promise>();
}

auto global_context(
v8::Global<v8::Context>(isolate, isolate->GetCurrentContext()));
auto promise_resolver(
v8::Global<v8::Promise::Resolver>(isolate, resolver.ToLocalChecked()));
auto context(v8::Global<v8::Context>(isolate, isolate->GetCurrentContext()));
ethereum_provider_->IsLocked(base::BindOnce(
&JSEthereumProvider::MetaMask::OnIsUnlocked, base::Unretained(this),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with owning mojo::Remote, we don't need weak_ptr because the previous bound callback won't be called when mojo disconnected

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to avoid using base::Unretained(this) here? It seems like this opens up the possibility of another issue occurring if this object is destroyed before the async operation is completed based on what's being described here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional, when dealing with mojo interface, using weak_ptr is unnecessary. Mojo pipe will invalidate all of the ongoing message when disconnected so OnIsUnlocked won't be called when the class who owns mojo::Remote<mojom::EthereumProvider> is gone.

std::move(global_context), std::move(promise_resolver), isolate));

return resolver.ToLocalChecked()->GetPromise();
}

void JSEthereumProvider::MetaMask::OnIsUnlocked(
v8::Global<v8::Context> global_context,
v8::Global<v8::Promise::Resolver> promise_resolver,
v8::Isolate* isolate,
bool locked) {
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Promise::Resolver> resolver = promise_resolver.Get(isolate);
v8::Local<v8::Context> context = global_context.Get(isolate);
v8::MicrotasksScope microtasks(isolate, context->GetMicrotaskQueue(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
base::Value result = base::Value(!locked);
v8::Local<v8::Value> local_result =
content::V8ValueConverter::Create()->ToV8Value(result, context);
std::ignore = resolver->Resolve(context, local_result);
}

v8::Local<v8::Value> JSEthereumProvider::GetMetaMask(v8::Isolate* isolate) {
// Set non-writable _metamask obj with non-writable isUnlocked method.
gin::Handle<MetaMask> metamask =
gin::CreateHandle(isolate, new MetaMask(render_frame()));
if (metamask.IsEmpty()) {
return v8::Undefined(isolate);
}
v8::Local<v8::Value> metamask_value = metamask.ToV8();
SetOwnPropertyWritable(isolate->GetCurrentContext(),
metamask_value.As<v8::Object>(),
gin::StringToV8(isolate, kIsUnlocked), false);
return metamask_value;
}

std::string JSEthereumProvider::GetChainId() {
return chain_id_;
}
Expand Down Expand Up @@ -305,6 +341,7 @@ gin::ObjectTemplateBuilder JSEthereumProvider::GetObjectTemplateBuilder(
return gin::Wrappable<JSEthereumProvider>::GetObjectTemplateBuilder(isolate)
.SetProperty(kIsBraveWallet, &JSEthereumProvider::GetIsBraveWallet)
.SetProperty(kIsMetaMask, &JSEthereumProvider::GetIsMetaMask)
.SetProperty(kMetaMask, &JSEthereumProvider::GetMetaMask)
.SetProperty("chainId", &JSEthereumProvider::GetChainId)
.SetProperty("networkVersion", &JSEthereumProvider::GetNetworkVersion)
.SetProperty("selectedAddress", &JSEthereumProvider::GetSelectedAddress)
Expand Down Expand Up @@ -530,29 +567,6 @@ v8::Local<v8::Promise> JSEthereumProvider::Enable(v8::Isolate* isolate) {
return resolver.ToLocalChecked()->GetPromise();
}

v8::Local<v8::Promise> JSEthereumProvider::IsUnlocked(v8::Isolate* isolate) {
if (!EnsureConnected()) {
return v8::Local<v8::Promise>();
}

v8::MaybeLocal<v8::Promise::Resolver> resolver =
v8::Promise::Resolver::New(isolate->GetCurrentContext());
if (resolver.IsEmpty()) {
return v8::Local<v8::Promise>();
}

auto global_context(
v8::Global<v8::Context>(isolate, isolate->GetCurrentContext()));
auto promise_resolver(
v8::Global<v8::Promise::Resolver>(isolate, resolver.ToLocalChecked()));
auto context(v8::Global<v8::Context>(isolate, isolate->GetCurrentContext()));
ethereum_provider_->IsLocked(base::BindOnce(
&JSEthereumProvider::OnIsUnlocked, weak_ptr_factory_.GetWeakPtr(),
std::move(global_context), std::move(promise_resolver), isolate));

return resolver.ToLocalChecked()->GetPromise();
}

void JSEthereumProvider::FireEvent(const std::string& event,
base::ValueView event_args) {
if (!render_frame()) {
Expand Down
30 changes: 25 additions & 5 deletions components/brave_wallet/renderer/js_ethereum_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,30 @@ class JSEthereumProvider final : public gin::Wrappable<JSEthereumProvider>,
explicit JSEthereumProvider(content::RenderFrame* render_frame);
~JSEthereumProvider() override;

class MetaMask final : public gin::Wrappable<MetaMask> {
public:
static gin::WrapperInfo kWrapperInfo;

explicit MetaMask(content::RenderFrame*);
~MetaMask() override;
MetaMask(const MetaMask&) = delete;
MetaMask& operator=(const MetaMask&) = delete;

// gin::WrappableBase
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate) override;
const char* GetTypeName() override;
v8::Local<v8::Promise> IsUnlocked(v8::Isolate* isolate);

private:
void OnIsUnlocked(v8::Global<v8::Context> global_context,
v8::Global<v8::Promise::Resolver> promise_resolver,
v8::Isolate* isolate,
bool locked);
raw_ptr<content::RenderFrame> render_frame_;
mojo::Remote<mojom::EthereumProvider> ethereum_provider_;
};

// content::RenderFrameObserver
void OnDestruct() override {}
void WillReleaseScriptContext(v8::Local<v8::Context>,
Expand All @@ -66,6 +90,7 @@ class JSEthereumProvider final : public gin::Wrappable<JSEthereumProvider>,

bool GetIsBraveWallet();
bool GetIsMetaMask();
v8::Local<v8::Value> GetMetaMask(v8::Isolate* isolate);
std::string GetChainId();
v8::Local<v8::Value> GetNetworkVersion(v8::Isolate* isolate);
v8::Local<v8::Value> GetSelectedAddress(v8::Isolate* isolate);
Expand All @@ -75,7 +100,6 @@ class JSEthereumProvider final : public gin::Wrappable<JSEthereumProvider>,
v8::Local<v8::Value> input);
bool IsConnected();
v8::Local<v8::Promise> Enable(v8::Isolate* isolate);
v8::Local<v8::Promise> IsUnlocked(v8::Isolate* isolate);
v8::Local<v8::Promise> SendMethod(gin::Arguments* args);
void SendAsync(gin::Arguments* args);

Expand All @@ -89,10 +113,6 @@ class JSEthereumProvider final : public gin::Wrappable<JSEthereumProvider>,
const std::string& first_allowed_account,
const bool update_bind_js_properties);

void OnIsUnlocked(v8::Global<v8::Context> global_context,
v8::Global<v8::Promise::Resolver> promise_resolver,
v8::Isolate* isolate,
bool locked);
void SendResponse(base::Value id,
v8::Global<v8::Context> global_context,
std::unique_ptr<v8::Global<v8::Function>> callback,
Expand Down