Compare commits

...

1 Commits

Author SHA1 Message Date
Sam Attard
b8f6459e44 refactor: attach translator holder via v8::Function data slot 2026-04-10 04:15:35 +00:00
3 changed files with 44 additions and 66 deletions

View File

@@ -4,7 +4,7 @@
#include "shell/common/gin_helper/callback.h"
#include "gin/dictionary.h"
#include "gin/arguments.h"
#include "gin/persistent.h"
#include "v8/include/cppgc/allocation.h"
#include "v8/include/v8-cppgc.h"
@@ -51,42 +51,28 @@ struct TranslatorHolder {
delete data.GetParameter();
}
static gin::DeprecatedWrapperInfo kWrapperInfo;
v8::Global<v8::External> handle;
Translator translator;
bool one_time = false;
bool called = false;
};
gin::DeprecatedWrapperInfo TranslatorHolder::kWrapperInfo = {
gin::kEmbedderNativeGin};
void CallTranslator(const v8::FunctionCallbackInfo<v8::Value>& info) {
gin::Arguments args(info);
auto* holder =
static_cast<TranslatorHolder*>(info.Data().As<v8::External>()->Value(
v8::kExternalPointerTypeTagDefault));
void CallTranslator(v8::Local<v8::External> external,
v8::Local<v8::Object> state,
gin::Arguments* args) {
// Whether the callback should only be called once.
v8::Isolate* isolate = args->isolate();
auto context = isolate->GetCurrentContext();
bool one_time =
state->Has(context, gin::StringToSymbol(isolate, "oneTime")).ToChecked();
// Check if the callback has already been called.
if (one_time) {
auto called_symbol = gin::StringToSymbol(isolate, "called");
if (state->Has(context, called_symbol).ToChecked()) {
args->ThrowTypeError("One-time callback was called more than once");
return;
} else {
state->Set(context, called_symbol, v8::True(isolate)).ToChecked();
}
if (holder->one_time && holder->called) {
args.ThrowTypeError("One-time callback was called more than once");
return;
}
holder->called = true;
auto* holder = static_cast<TranslatorHolder*>(
external->Value(v8::kExternalPointerTypeTagDefault));
holder->translator.Run(args);
holder->translator.Run(&args);
// Free immediately for one-time callback.
if (one_time)
delete holder;
if (holder->one_time)
holder->translator.Reset();
}
} // namespace
@@ -113,41 +99,11 @@ v8::Local<v8::Function> SafeV8Function::NewHandle(v8::Isolate* isolate) const {
v8::Local<v8::Value> CreateFunctionFromTranslator(v8::Isolate* isolate,
const Translator& translator,
bool one_time) {
gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
auto* wrapper_info = &TranslatorHolder::kWrapperInfo;
v8::Local<v8::FunctionTemplate> constructor =
data->DeprecatedGetFunctionTemplate(wrapper_info);
// The FunctionTemplate is cached.
if (constructor.IsEmpty()) {
constructor =
CreateFunctionTemplate(isolate, base::BindRepeating(&CallTranslator));
data->DeprecatedSetFunctionTemplate(wrapper_info, constructor);
}
auto* holder = new TranslatorHolder(isolate);
holder->translator = translator;
auto state = gin::Dictionary::CreateEmpty(isolate);
if (one_time)
state.Set("oneTime", true);
holder->one_time = one_time;
auto context = isolate->GetCurrentContext();
return BindFunctionWith(
isolate, context, constructor->GetFunction(context).ToLocalChecked(),
holder->handle.Get(isolate), gin::ConvertToV8(isolate, state));
}
// func.bind(func, arg1).
// NB(zcbenz): Using C++11 version crashes VS.
v8::Local<v8::Value> BindFunctionWith(v8::Isolate* isolate,
v8::Local<v8::Context> context,
v8::Local<v8::Function> func,
v8::Local<v8::Value> arg1,
v8::Local<v8::Value> arg2) {
v8::MaybeLocal<v8::Value> bind =
func->Get(context, gin::StringToV8(isolate, "bind"));
CHECK(!bind.IsEmpty());
v8::Local<v8::Function> bind_func = bind.ToLocalChecked().As<v8::Function>();
v8::Local<v8::Value> converted[] = {func, arg1, arg2};
return bind_func->Call(context, func, std::size(converted), converted)
return v8::Function::New(context, CallTranslator, holder->handle.Get(isolate))
.ToLocalChecked();
}

View File

@@ -118,11 +118,6 @@ using Translator = base::RepeatingCallback<void(gin::Arguments* args)>;
v8::Local<v8::Value> CreateFunctionFromTranslator(v8::Isolate* isolate,
const Translator& translator,
bool one_time);
v8::Local<v8::Value> BindFunctionWith(v8::Isolate* isolate,
v8::Local<v8::Context> context,
v8::Local<v8::Function> func,
v8::Local<v8::Value> arg1,
v8::Local<v8::Value> arg2);
// Calls callback with Arguments.
template <typename Sig>

View File

@@ -372,6 +372,33 @@ describe('contextBridge', () => {
expect(result).to.equal(123);
});
it('should proxy promises correctly when Function.prototype has been overridden in the main world', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
getPromise: () => Promise.resolve('proxied-ok')
});
});
const result = await callWithBindings((root: any) => {
return new Promise(resolve => {
let observed = false;
const original = Function.prototype.bind;
// eslint-disable-next-line no-extend-native
Function.prototype.bind = new Proxy(original, {
apply (target, thisArg, args) {
observed = true;
return Reflect.apply(target, thisArg, args);
}
});
root.example.getPromise().then((v: string) => {
// eslint-disable-next-line no-extend-native
Function.prototype.bind = original;
resolve({ observed, value: v });
});
});
});
expect(result).to.deep.equal({ observed: false, value: 'proxied-ok' });
});
it('should proxy methods', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {