fix: proxy bridge arguments only once

This commit is contained in:
Samuel Maddock
2025-01-13 16:25:14 -05:00
parent a5dc2bf113
commit c8aa24969e
3 changed files with 49 additions and 16 deletions

View File

@@ -149,13 +149,6 @@ v8::MaybeLocal<v8::Object> CreateProxyForAPI(
bool support_dynamic_properties,
int recursion_depth,
BridgeErrorTarget error_target);
v8::MaybeLocal<v8::Value> PassValueToOtherContext(
v8::Local<v8::Context> source_context,
v8::Local<v8::Context> destination_context,
v8::Local<v8::Value> value,
v8::Local<v8::Value> parent_value,
bool support_dynamic_properties,
BridgeErrorTarget error_target);
v8::MaybeLocal<v8::Value> PassValueToOtherContextInner(
v8::Local<v8::Context> source_context,
@@ -467,15 +460,20 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
v8::Local<v8::Value> value,
v8::Local<v8::Value> parent_value,
bool support_dynamic_properties,
BridgeErrorTarget error_target) {
BridgeErrorTarget error_target,
context_bridge::ObjectCache* existing_object_cache) {
TRACE_EVENT0("electron", "ContextBridge::PassValueToOtherContext");
context_bridge::ObjectCache object_cache;
context_bridge::ObjectCache local_object_cache;
context_bridge::ObjectCache* object_cache =
existing_object_cache ? existing_object_cache : &local_object_cache;
const blink::ExecutionContext* source_execution_context =
blink::ExecutionContext::From(source_context);
DCHECK(source_execution_context);
return PassValueToOtherContextInner(
source_context, source_execution_context, destination_context, value,
parent_value, &object_cache, support_dynamic_properties, 0, error_target);
parent_value, object_cache, support_dynamic_properties, 0, error_target);
}
void ProxyFunctionWrapper(const v8::FunctionCallbackInfo<v8::Value>& info) {
@@ -509,15 +507,18 @@ void ProxyFunctionWrapper(const v8::FunctionCallbackInfo<v8::Value>& info) {
{
v8::Context::Scope func_owning_context_scope(func_owning_context);
// Cache duplicate arguments as the same proxied value.
context_bridge::ObjectCache object_cache;
std::vector<v8::Local<v8::Value>> original_args;
std::vector<v8::Local<v8::Value>> proxied_args;
args.GetRemaining(&original_args);
for (auto value : original_args) {
auto arg = PassValueToOtherContext(calling_context, func_owning_context,
value, calling_context->Global(),
support_dynamic_properties,
BridgeErrorTarget::kSource);
auto arg = PassValueToOtherContext(
calling_context, func_owning_context, value,
calling_context->Global(), support_dynamic_properties,
BridgeErrorTarget::kSource, &object_cache);
if (arg.IsEmpty())
return;
proxied_args.push_back(arg.ToLocalChecked());
@@ -1015,6 +1016,9 @@ v8::Local<v8::Value> ExecuteInWorld(v8::Isolate* isolate,
bool support_dynamic_properties = false;
uint32_t args_length = args_array.IsEmpty() ? 0 : args_array->Length();
// Cache duplicate arguments as the same proxied value.
context_bridge::ObjectCache object_cache;
for (uint32_t i = 0; i < args_length; ++i) {
v8::Local<v8::Value> arg;
if (!args_array->Get(source_context, i).ToLocal(&arg)) {
@@ -1025,7 +1029,8 @@ v8::Local<v8::Value> ExecuteInWorld(v8::Isolate* isolate,
auto proxied_arg = PassValueToOtherContext(
source_context, target_context, arg, source_context->Global(),
support_dynamic_properties, BridgeErrorTarget::kSource);
support_dynamic_properties, BridgeErrorTarget::kSource,
&object_cache);
if (proxied_arg.IsEmpty()) {
gin_helper::ErrorThrower(isolate).ThrowError(
base::StringPrintf("Failed to proxy argument at index %d", i));

View File

@@ -43,7 +43,8 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
*/
v8::Local<v8::Value> parent_value,
bool support_dynamic_properties,
BridgeErrorTarget error_target);
BridgeErrorTarget error_target,
context_bridge::ObjectCache* existing_object_cache = nullptr);
} // namespace electron::api

View File

@@ -408,6 +408,17 @@ describe('contextBridge', () => {
expect(result).equal(true);
});
it('should proxy function arguments only once', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', (a: any, b: any) => a === b);
});
const result = await callWithBindings(async (root: any) => {
const obj = { foo: 1 };
return root.example(obj, obj);
});
expect(result).to.be.true();
});
it('should properly handle errors thrown in proxied functions', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', () => { throw new Error('oh no'); });
@@ -1366,6 +1377,22 @@ describe('contextBridge', () => {
await donePromise;
});
it('proxies arguments only once', async () => {
await makeBindingWindow(() => {
const obj = {};
// @ts-ignore
globalThis.result = contextBridge.executeInMainWorld({
func: (a, b) => a === b,
args: [obj, obj]
});
});
const result = await callWithBindings(() => {
// @ts-ignore
return globalThis.result;
}, 999);
expect(result).to.be.true();
});
it('safely clones returned objects', async () => {
await makeBindingWindow(() => {
const obj = contextBridge.executeInMainWorld({