Skip to content

Commit 724b7dd

Browse files
authored
Allow overriding the received (this) of a jsg::Function, update forEach impls (#1027)
1 parent 32bdc3e commit 724b7dd

File tree

9 files changed

+71
-88
lines changed

9 files changed

+71
-88
lines changed

src/workerd/api/form-data.c++

+13-19
Original file line numberDiff line numberDiff line change
@@ -413,15 +413,18 @@ jsg::Ref<FormData::ValueIterator> FormData::values(jsg::Lock&) {
413413

414414
void FormData::forEach(
415415
jsg::Lock& js,
416-
jsg::V8Ref<v8::Function> callback,
417-
jsg::Optional<jsg::Value> thisArg,
418-
const jsg::TypeHandler<EntryType>& handler) {
419-
auto localCallback = callback.getHandle(js);
420-
auto localThisArg = thisArg.map([&js](jsg::Value& v) { return v.getHandle(js); })
421-
.orDefault(js.v8Undefined());
422-
// JSG_THIS.tryGetHandle() is guaranteed safe because `forEach()` is only called
423-
// from JavaScript, which means a Headers JS wrapper object must already exist.
424-
auto localParams = KJ_ASSERT_NONNULL(JSG_THIS.tryGetHandle(js.v8Isolate));
416+
jsg::Function<void(EntryType, kj::StringPtr, jsg::Ref<FormData>)> callback,
417+
jsg::Optional<jsg::Value> thisArg) {
418+
// Here, if the thisArg is not passed, or is passed explicitly as a null or
419+
// undefined, then undefined is used as the thisArg.
420+
auto receiver = js.v8Undefined();
421+
KJ_IF_MAYBE(arg, thisArg) {
422+
auto handle = arg->getHandle(js);
423+
if (!handle->IsNullOrUndefined()) {
424+
receiver = handle;
425+
}
426+
}
427+
callback.setReceiver(js.v8Ref(receiver));
425428

426429
// On each iteration of the for loop, a JavaScript callback is invokved. If a new
427430
// item is appended to the URLSearchParams within that function, the loop must pick
@@ -430,16 +433,7 @@ void FormData::forEach(
430433
// are added to the search params unconditionally on each iteration.
431434
for (size_t i = 0; i < this->data.size(); i++) {
432435
auto& [key, value] = this->data[i];
433-
static constexpr auto ARG_COUNT = 3;
434-
435-
v8::Local<v8::Value> args[ARG_COUNT] = {
436-
handler.wrap(js, clone(value)),
437-
jsg::v8Str(js.v8Isolate, key),
438-
localParams,
439-
};
440-
// Call jsg::check() to propagate exceptions, but we don't expect any
441-
// particular return value.
442-
jsg::check(localCallback->Call(js.v8Context(), localThisArg, ARG_COUNT, args));
436+
callback(js, clone(value), key, JSG_THIS);
443437
}
444438
}
445439

src/workerd/api/form-data.h

+2-3
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,8 @@ class FormData: public jsg::Object {
9898

9999
void forEach(
100100
jsg::Lock& js,
101-
jsg::V8Ref<v8::Function> callback,
102-
jsg::Optional<jsg::Value> thisArg,
103-
const jsg::TypeHandler<EntryType>& handler);
101+
jsg::Function<void(EntryType, kj::StringPtr, jsg::Ref<FormData>)> callback,
102+
jsg::Optional<jsg::Value> thisArg);
104103

105104
JSG_RESOURCE_TYPE(FormData, CompatibilityFlags::Reader flags) {
106105
JSG_METHOD(append);

src/workerd/api/http.c++

+10-22
Original file line numberDiff line numberDiff line change
@@ -396,31 +396,19 @@ jsg::Ref<Headers::ValueIterator> Headers::values(jsg::Lock& js) {
396396

397397
void Headers::forEach(
398398
jsg::Lock& js,
399-
jsg::V8Ref<v8::Function> callback,
399+
jsg::Function<void(kj::StringPtr, kj::StringPtr, jsg::Ref<Headers>)> callback,
400400
jsg::Optional<jsg::Value> thisArg) {
401-
auto localCallback = callback.getHandle(js);
402-
auto localThisArg = thisArg.map([&](jsg::Value& v) { return v.getHandle(js); })
403-
.orDefault(js.v8Undefined());
404-
405-
auto isolate = js.v8Isolate;
406-
407-
// JSG_THIS.getHandle() is guaranteed safe because `forEach()` is only called
408-
// from JavaScript, which means a Headers JS wrapper object must already exist.
409-
auto localHeaders = KJ_ASSERT_NONNULL(JSG_THIS.tryGetHandle(isolate));
401+
auto receiver = js.v8Undefined();
402+
KJ_IF_MAYBE(arg, thisArg) {
403+
auto handle = arg->getHandle(js);
404+
if (!handle->IsNullOrUndefined()) {
405+
receiver = handle;
406+
}
407+
}
408+
callback.setReceiver(js.v8Ref(receiver));
410409

411-
auto context = js.v8Context(); // Needed later for Call().
412410
for (auto& entry: getDisplayedHeaders(js)) {
413-
static constexpr auto ARG_COUNT = 3;
414-
v8::Local<v8::Value> args[ARG_COUNT] = {
415-
jsg::v8Str(isolate, entry.value),
416-
// Intern the header name and not the value since the names are generally
417-
// stable.
418-
jsg::v8StrIntern(isolate, entry.key),
419-
localHeaders,
420-
};
421-
// Call jsg::check() to propagate exceptions, but we don't expect any
422-
// particular return value.
423-
jsg::check(localCallback->Call(context, localThisArg, ARG_COUNT, args));
411+
callback(js, entry.value, entry.key, JSG_THIS);
424412
}
425413
}
426414

src/workerd/api/http.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ class Headers: public jsg::Object {
101101
void append(jsg::ByteString name, jsg::ByteString value);
102102
void delete_(jsg::ByteString name);
103103
void forEach(jsg::Lock& js,
104-
jsg::V8Ref<v8::Function>,
105-
jsg::Optional<jsg::Value>);
104+
jsg::Function<void(kj::StringPtr, kj::StringPtr, jsg::Ref<Headers>)>,
105+
jsg::Optional<jsg::Value>);
106106

107107
JSG_ITERATOR(EntryIterator, entries,
108108
kj::Array<jsg::ByteString>,

src/workerd/api/url-standard.c++

+11-12
Original file line numberDiff line numberDiff line change
@@ -2079,25 +2079,24 @@ jsg::Ref<URLSearchParams::ValueIterator> URLSearchParams::values(jsg::Lock&) {
20792079

20802080
void URLSearchParams::forEach(
20812081
jsg::Lock& js,
2082-
jsg::V8Ref<v8::Function> callback,
2082+
jsg::Function<void(jsg::UsvStringPtr, jsg::UsvStringPtr, jsg::Ref<URLSearchParams>)> callback,
20832083
jsg::Optional<jsg::Value> thisArg) {
2084-
auto cb = callback.getHandle(js);
2085-
auto this_ = thisArg.map([&js](jsg::Value& v) { return v.getHandle(js); })
2086-
.orDefault(js.v8Undefined());
2087-
auto query = KJ_ASSERT_NONNULL(JSG_THIS.tryGetHandle(js.v8Isolate));
2088-
// On each iteration of the for loop, a JavaScript callback is invokved. If a new
2084+
auto receiver = js.v8Undefined();
2085+
KJ_IF_MAYBE(arg, thisArg) {
2086+
auto handle = arg->getHandle(js);
2087+
if (!handle->IsNullOrUndefined()) {
2088+
receiver = handle;
2089+
}
2090+
}
2091+
callback.setReceiver(js.v8Ref(receiver));
2092+
// On each iteration of the for loop, a JavaScript callback is invoked. If a new
20892093
// item is appended to the URLSearchParams within that function, the loop must pick
20902094
// it up. Using the classic for (;;) syntax here allows for that. However, this does
20912095
// mean that it's possible for a user to trigger an infinite loop here if new items
20922096
// are added to the search params unconditionally on each iteration.
20932097
for (size_t i = 0; i < list.size(); i++) {
20942098
auto& entry = list[i];
2095-
v8::Local<v8::Value> args[3] = {
2096-
jsg::v8Str(js.v8Isolate, entry.value),
2097-
jsg::v8Str(js.v8Isolate, entry.name),
2098-
query,
2099-
};
2100-
jsg::check(cb->Call(js.v8Context(), this_, 3, args));
2099+
callback(js, entry.value, entry.name, JSG_THIS);
21012100
}
21022101
}
21032102

src/workerd/api/url-standard.h

+3-4
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,9 @@ class URLSearchParams: public jsg::Object {
116116
IteratorState,
117117
valueIteratorNext)
118118

119-
void forEach(
120-
jsg::Lock& js,
121-
jsg::V8Ref<v8::Function> callback,
122-
jsg::Optional<jsg::Value> thisArg);
119+
void forEach(jsg::Lock&,
120+
jsg::Function<void(jsg::UsvStringPtr, jsg::UsvStringPtr, jsg::Ref<URLSearchParams>)>,
121+
jsg::Optional<jsg::Value>);
123122

124123
jsg::UsvString toString();
125124

src/workerd/api/url.c++

+12-18
Original file line numberDiff line numberDiff line change
@@ -587,31 +587,25 @@ void URLSearchParams::sort() {
587587

588588
void URLSearchParams::forEach(
589589
jsg::Lock& js,
590-
jsg::V8Ref<v8::Function> callback,
590+
jsg::Function<void(kj::StringPtr, kj::StringPtr, jsg::Ref<URLSearchParams>)> callback,
591591
jsg::Optional<jsg::Value> thisArg) {
592-
auto localCallback = callback.getHandle(js);
593-
auto localThisArg = thisArg.map([&js](jsg::Value& v) { return v.getHandle(js); })
594-
.orDefault(js.v8Undefined());
595-
// JSG_THIS.getHandle() is guaranteed safe because `forEach()` is only called
596-
// from JavaScript, which means a Headers JS wrapper object must already exist.
597-
auto localParams = KJ_ASSERT_NONNULL(JSG_THIS.tryGetHandle(js.v8Isolate));
598-
599-
// On each iteration of the for loop, a JavaScript callback is invokved. If a new
592+
auto receiver = js.v8Undefined();
593+
KJ_IF_MAYBE(arg, thisArg) {
594+
auto handle = arg->getHandle(js);
595+
if (!handle->IsNullOrUndefined()) {
596+
receiver = handle;
597+
}
598+
}
599+
callback.setReceiver(js.v8Ref(receiver));
600+
601+
// On each iteration of the for loop, a JavaScript callback is invoked. If a new
600602
// item is appended to the this->url->query within that function, the loop must pick
601603
// it up. Using the classic for (;;) syntax here allows for that. However, this does
602604
// mean that it's possible for a user to trigger an infinite loop here if new items
603605
// are added to the search params unconditionally on each iteration.
604606
for (size_t i = 0; i < this->url->query.size(); i++) {
605607
auto& [key, value] = this->url->query[i];
606-
static constexpr auto ARG_COUNT = 3;
607-
v8::Local<v8::Value> args[ARG_COUNT] = {
608-
jsg::v8Str(js.v8Isolate, value),
609-
jsg::v8Str(js.v8Isolate, key),
610-
localParams,
611-
};
612-
// Call jsg::check() to propagate exceptions, but we don't expect any
613-
// particular return value.
614-
jsg::check(localCallback->Call(js.v8Context(), localThisArg, ARG_COUNT, args));
608+
callback(js, value, key, JSG_THIS);
615609
}
616610
}
617611

src/workerd/api/url.h

+3-4
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,9 @@ class URLSearchParams: public jsg::Object {
161161
IteratorState,
162162
valueIteratorNext)
163163

164-
void forEach(
165-
jsg::Lock& js,
166-
jsg::V8Ref<v8::Function> callback,
167-
jsg::Optional<jsg::Value> thisArg);
164+
void forEach(jsg::Lock&,
165+
jsg::Function<void(kj::StringPtr, kj::StringPtr, jsg::Ref<URLSearchParams>)>,
166+
jsg::Optional<jsg::Value>);
168167

169168
kj::String toString();
170169

src/workerd/jsg/function.h

+15-4
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,18 @@ class Function<Ret(Args...)> {
122122

123123
typedef Ret Wrapper(
124124
jsg::Lock& js,
125-
v8::Local<v8::Object> receiver, // the `this` value in the function
125+
v8::Local<v8::Value> receiver, // the `this` value in the function
126126
v8::Local<v8::Function> fn,
127127
Args...);
128128
// When holding a JavaScript function, `Wrapper` is a C++ function that will handle converting
129129
// C++ arguments into JavaScript values and then call the JS function.
130130

131131
Function(Wrapper* wrapper, V8Ref<v8::Object> receiver, V8Ref<v8::Function> function)
132+
: Function(wrapper,
133+
receiver.cast<v8::Value>(Lock::from(v8::Isolate::GetCurrent())),
134+
kj::mv(function)) {}
135+
136+
Function(Wrapper* wrapper, Value receiver, V8Ref<v8::Function> function)
132137
: impl(JsImpl {
133138
.wrapper = kj::mv(wrapper),
134139
.receiver = kj::mv(receiver),
@@ -231,12 +236,18 @@ class Function<Ret(Args...)> {
231236
__builtin_unreachable();
232237
}
233238

239+
inline void setReceiver(Value receiver) {
240+
KJ_IF_MAYBE(i, impl.template tryGet<JsImpl>()) {
241+
i->receiver = kj::mv(receiver);
242+
}
243+
}
244+
234245
private:
235246
Function(Ref<NativeFunction>&& func) : impl(kj::mv(func)) {}
236247

237248
struct JsImpl {
238249
Wrapper* wrapper;
239-
V8Ref<v8::Object> receiver;
250+
Value receiver;
240251
V8Ref<v8::Function> handle;
241252
};
242253

@@ -335,7 +346,7 @@ class FunctionWrapper {
335346
auto isolate = context->GetIsolate();
336347

337348
auto wrapperFn = [](Lock& js,
338-
v8::Local<v8::Object> receiver,
349+
v8::Local<v8::Value> receiver,
339350
v8::Local<v8::Function> func,
340351
Args... args) -> Ret {
341352
auto isolate = js.v8Isolate;
@@ -372,7 +383,7 @@ class FunctionWrapper {
372383
auto isolate = context->GetIsolate();
373384

374385
auto wrapperFn = [](Lock& js,
375-
v8::Local<v8::Object> receiver,
386+
v8::Local<v8::Value> receiver,
376387
v8::Local<v8::Function> func,
377388
Args... args) -> Ret {
378389
auto isolate = js.v8Isolate;

0 commit comments

Comments
 (0)