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

Adds event emitter to window.ethereum object #8718

Merged
merged 4 commits into from
May 11, 2021
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
11 changes: 11 additions & 0 deletions components/brave_wallet/common/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ source_set("common") {
"features.cc",
"features.h",
]
deps = [
"//base",
":common_constants",
]
}

source_set("common_constants") {
sources = [
"web3_provider_constants.cc",
"web3_provider_constants.h",
]
deps = [ ":mojom" ]
}

Expand Down
15 changes: 15 additions & 0 deletions components/brave_wallet/common/web3_provider_constants.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* Copyright (c) 2021 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "brave/components/brave_wallet/common/web3_provider_constants.h"

namespace brave_wallet {

const char kConnectEvent[] = "connect";
const char kDisconnectEvent[] = "disconnect";
const char kChainChangedEvent[] = "chainChanged";
const char kAccountsChangedEvent[] = "accountsChanged";

} // namespace brave_wallet
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_RENDERER_WEB3_PROVIDER_CONSTANTS_H_
#define BRAVE_COMPONENTS_BRAVE_WALLET_RENDERER_WEB3_PROVIDER_CONSTANTS_H_
#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_COMMON_WEB3_PROVIDER_CONSTANTS_H_
#define BRAVE_COMPONENTS_BRAVE_WALLET_COMMON_WEB3_PROVIDER_CONSTANTS_H_

namespace brave_wallet {

extern const char kConnectEvent[];
extern const char kDisconnectEvent[];
extern const char kChainChangedEvent[];
extern const char kAccountsChangedEvent[];

enum class ProviderErrors {
kUserRejectedRequest = 4001, // User rejected the request
kUnauthorized = 4100, // The requested account and/or method has not
Expand All @@ -21,4 +26,4 @@ enum class ProviderErrors {

} // namespace brave_wallet

#endif // BRAVE_COMPONENTS_BRAVE_WALLET_RENDERER_WEB3_PROVIDER_CONSTANTS_H_
#endif // BRAVE_COMPONENTS_BRAVE_WALLET_COMMON_WEB3_PROVIDER_CONSTANTS_H_
3 changes: 2 additions & 1 deletion components/brave_wallet/renderer/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ source_set("renderer") {
"brave_wallet_js_handler.h",
"brave_wallet_response_helpers.cc",
"brave_wallet_response_helpers.h",
"web3_provider_constants.h",
]

deps = [
"//base",
"//brave/components/brave_wallet/common:common_constants",
"//brave/components/brave_wallet/common:mojom",
"//brave/components/brave_wallet/resources:ethereum_provider_generated_resources",
"//content/public/renderer",
"//gin",
"//mojo/public/cpp/bindings",
Expand Down
1 change: 1 addition & 0 deletions components/brave_wallet/renderer/DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ include_rules = [
"+gin",
"+third_party/blink/public",
"+v8/include",
"+ui/base",
]
113 changes: 111 additions & 2 deletions components/brave_wallet/renderer/brave_wallet_js_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
#include "brave/components/brave_wallet/renderer/brave_wallet_js_handler.h"

#include <utility>
#include <vector>

#include "base/json/json_writer.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "brave/components/brave_wallet/common/web3_provider_constants.h"
#include "brave/components/brave_wallet/renderer/brave_wallet_response_helpers.h"
#include "brave/components/brave_wallet/renderer/web3_provider_constants.h"
#include "brave/components/brave_wallet/resources/grit/brave_wallet_script_generated.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/v8_value_converter.h"
#include "gin/arguments.h"
Expand All @@ -21,12 +24,75 @@
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_script_source.h"
#include "ui/base/resource/resource_bundle.h"

namespace {

static base::NoDestructor<std::string> g_provider_script("");

// Hardcode id to 1 as it is unused
const uint32_t kRequestId = 1;
const char kRequestJsonRPC[] = "2.0";

std::string LoadDataResource(const int id) {
auto& resource_bundle = ui::ResourceBundle::GetSharedInstance();
if (resource_bundle.IsGzipped(id)) {
return resource_bundle.LoadDataResourceString(id);
}

return resource_bundle.GetRawDataResource(id).as_string();
}

v8::MaybeLocal<v8::Value> GetProperty(v8::Local<v8::Context> context,
v8::Local<v8::Value> object,
const std::u16string& name) {
v8::Isolate* isolate = context->GetIsolate();
v8::Local<v8::String> name_str =
gin::ConvertToV8(isolate, name).As<v8::String>();
v8::Local<v8::Object> object_obj;
if (!object->ToObject(context).ToLocal(&object_obj)) {
return v8::MaybeLocal<v8::Value>();
}

return object_obj->Get(context, name_str);
}

void CallMethodOfObject(blink::WebLocalFrame* web_frame,
const std::u16string& object_name,
const std::u16string& method_name,
base::Value arguments) {
if (web_frame->IsProvisional())
return;
v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
v8::Context::Scope context_scope(context);
v8::Local<v8::Value> object;
v8::Local<v8::Value> method;
if (!GetProperty(context, context->Global(), object_name).ToLocal(&object) ||
!GetProperty(context, object, method_name).ToLocal(&method)) {
return;
}
std::vector<v8::Local<v8::Value>> args;
for (auto const& argument : arguments.GetList()) {
args.push_back(content::V8ValueConverter::Create()->ToV8Value(&argument,
context));
}

web_frame->ExecuteMethodAndReturnValue(
v8::Local<v8::Function>::Cast(method), object,
static_cast<int>(args.size()), args.data()).ToLocalChecked();
}

} // namespace

namespace brave_wallet {

BraveWalletJSHandler::BraveWalletJSHandler(content::RenderFrame* render_frame)
: render_frame_(render_frame) {}
: render_frame_(render_frame) {
if (g_provider_script->empty()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is fine for now, but I don't think we need to store this in a static var. LoadDataResource loads from memory so there's really no benefit to doing this

*g_provider_script =
LoadDataResource(IDR_BRAVE_WALLET_SCRIPT_BRAVE_WALLET_SCRIPT_BUNDLE_JS);
}
}

BraveWalletJSHandler::~BraveWalletJSHandler() = default;

Expand All @@ -49,6 +115,7 @@ void BraveWalletJSHandler::AddJavaScriptObjectToFrame(
v8::Context::Scope context_scope(context);

CreateEthereumObject(isolate, context);
InjectInitScript();
}

void BraveWalletJSHandler::CreateEthereumObject(
Expand Down Expand Up @@ -107,6 +174,9 @@ v8::Local<v8::Promise> BraveWalletJSHandler::Request(
if (!out || !out->is_dict() || !out->GetAsDictionary(&out_dict))
return v8::Local<v8::Promise>();

// Hardcode id to 1 as it is unused
ALLOW_UNUSED_LOCAL(out_dict->SetIntPath("id", kRequestId));
Copy link
Collaborator

Choose a reason for hiding this comment

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

weird that these were needed, I don't see them used in other places, but I guess it's ok because other places also ignore the result

ALLOW_UNUSED_LOCAL(out_dict->SetStringPath("jsonrpc", kRequestJsonRPC));
std::string formed_input;
if (!base::JSONWriter::Write(*out_dict, &formed_input))
return v8::Local<v8::Promise>();
Expand Down Expand Up @@ -165,4 +235,43 @@ void BraveWalletJSHandler::OnRequest(
}
}

void BraveWalletJSHandler::ExecuteScript(const std::string script) {
blink::WebLocalFrame* web_frame = render_frame_->GetWebFrame();
if (web_frame->IsProvisional())
return;

web_frame->ExecuteScript(blink::WebString::FromUTF8(script));
}

void BraveWalletJSHandler::InjectInitScript() {
ExecuteScript(*g_provider_script);
}

void BraveWalletJSHandler::FireEvent(const std::string& event,
const std::string& event_args) {
base::Value args = base::Value(base::Value::Type::LIST);
args.Append(event);
args.Append(event_args);
CallMethodOfObject(render_frame_->GetWebFrame(),
u"ethereum",
u"emit",
std::move(args));
}

void BraveWalletJSHandler::ConnectEvent(const std::string& chain_id) {
FireEvent(kConnectEvent, chain_id);
}

void BraveWalletJSHandler::DisconnectEvent(const std::string& message) {
FireEvent(kDisconnectEvent, message);
}

void BraveWalletJSHandler::ChainChangedEvent(const std::string& chain_id) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think we want methods for each specific event, we should just have a list of constants for them

Copy link
Member Author

Choose a reason for hiding this comment

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

I will do that in upcoming PRs. As some events will come from browser process via mojo, some will not like connect, on connect we will ask a browser process what chain id is. That place is going to be changed in some kind in the upcoming PRs. Just some things are unknown yet till we investigate how and when exactly we should fire events.

Copy link
Collaborator

Choose a reason for hiding this comment

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

can we also add better error handling in a follow-up PR so the called gets notified if there are any JS errors?

FireEvent(kChainChangedEvent, chain_id);
}

void BraveWalletJSHandler::AccountsChangedEvent(const std::string& accounts) {
FireEvent(kAccountsChangedEvent, accounts);
}

} // namespace brave_wallet
7 changes: 7 additions & 0 deletions components/brave_wallet/renderer/brave_wallet_js_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ class BraveWalletJSHandler {
~BraveWalletJSHandler();

void AddJavaScriptObjectToFrame(v8::Local<v8::Context> context);
void FireEvent(const std::string& event, const std::string& event_args);
void ConnectEvent(const std::string& chain_id);
void DisconnectEvent(const std::string& message);
void ChainChangedEvent(const std::string& chain_id);
void AccountsChangedEvent(const std::string& accounts);

private:
void BindFunctionsToObject(v8::Isolate* isolate,
Expand All @@ -37,6 +42,8 @@ class BraveWalletJSHandler {
void CreateEthereumObject(v8::Isolate* isolate,
v8::Local<v8::Context> context);
bool EnsureConnected();
void InjectInitScript();
void ExecuteScript(const std::string script);

// A function to be called from JS
v8::Local<v8::Promise> Request(v8::Isolate* isolate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <utility>

#include "base/json/json_reader.h"
#include "base/json/json_writer.h"

namespace brave_wallet {

Expand Down Expand Up @@ -53,4 +54,33 @@ std::unique_ptr<base::Value> FormProviderResponse(
return base::Value::ToUniquePtrValue(result->Clone());
}

std::string FormProviderErrorResponse(const std::string& controller_response) {
base::JSONReader::ValueWithError value_with_error =
base::JSONReader::ReadAndReturnValueWithError(
controller_response, base::JSONParserOptions::JSON_PARSE_RFC);
base::Optional<base::Value>& response = value_with_error.value;

if (response) {
const base::Value* error = response->FindKey("error");
if (error) {
std::string error_response;
if (!base::JSONWriter::Write(*error, &error_response))
return "";

return error_response;
}
}

ProviderErrors code = ProviderErrors::kUnsupportedMethod;
std::string message =
"Invalid response, could not parse JSON: " + controller_response;

std::string error_response;
if (!base::JSONWriter::Write(*FormProviderResponse(code, message),
&error_response))
return "";

return error_response;
}

} // namespace brave_wallet
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#include <string>

#include "base/values.h"
#include "brave/components/brave_wallet/renderer/web3_provider_constants.h"
#include "brave/components/brave_wallet/common/web3_provider_constants.h"

namespace brave_wallet {

Expand All @@ -19,6 +19,7 @@ std::unique_ptr<base::Value> FormProviderResponse(ProviderErrors code,
std::unique_ptr<base::Value> FormProviderResponse(
const std::string& controller_response,
bool* reject);
std::string FormProviderErrorResponse(const std::string& controller_response);

} // namespace brave_wallet

Expand Down
18 changes: 18 additions & 0 deletions components/brave_wallet/resources/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import("//brave/components/common/typescript.gni")
import("//tools/grit/grit_rule.gni")
import("//tools/grit/repack.gni")

transpile_web_ui("brave_wallet_script_resources") {
entry_points = [ [
"brave_wallet_script",
rebase_path("brave_wallet_provider.js"),
] ]

resource_name = "brave_wallet_script"
}

pack_web_resources("ethereum_provider_generated_resources") {
resource_name = "brave_wallet_script"
output_dir = "$root_gen_dir/brave/components/brave_wallet/resources"
deps = [ ":brave_wallet_script_resources" ]
}
16 changes: 16 additions & 0 deletions components/brave_wallet/resources/brave_wallet_provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) 2021 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// you can obtain one at http://mozilla.org/MPL/2.0/.

(function() {
if (!window.ethereum) {
return
}
var EventEmitter = require('events')
var BraveWeb3ProviderEventEmitter = new EventEmitter()
window.ethereum.on = BraveWeb3ProviderEventEmitter.on
window.ethereum.emit = BraveWeb3ProviderEventEmitter.emit
window.ethereum.removeListener =
BraveWeb3ProviderEventEmitter.removeListener
})()
13 changes: 8 additions & 5 deletions components/resources/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,14 @@ repack("resources") {
]
}

if (brave_wallet_enabled && !is_android) {
deps += [ "//brave/components/brave_wallet_ui:resources" ]
sources += [
"$root_gen_dir/brave/components/brave_wallet/resources/brave_wallet.pak",
]
if (brave_wallet_enabled) {
if (!is_android) {
deps += [ "//brave/components/brave_wallet_ui:resources" ]
sources += [ "$root_gen_dir/brave/components/brave_wallet/resources/brave_wallet.pak" ]
}

deps += [ "//brave/components/brave_wallet/resources:ethereum_provider_generated_resources" ]
sources += [ "$root_gen_dir/brave/components/brave_wallet/resources/brave_wallet_script_generated.pak" ]
Copy link
Collaborator

Choose a reason for hiding this comment

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

there's no reason to expose additional pak files here, we should repack everything inside brave_wallet into a single output. See https://github.com/brave/brave-core/pull/8703/files#diff-5303bb2b8878c4fce006fc3b8bb18f3d4e0f386dbea78d4c8c37a23323762389R55

}

output = "$root_gen_dir/components/brave_components_resources.pak"
Expand Down