|
1 | 1 | #include "builtins/request-response.h"
|
2 |
| - |
3 | 2 | #include "builtins/cache-override.h"
|
4 | 3 | #include "builtins/client-info.h"
|
5 | 4 | #include "builtins/fastly.h"
|
6 | 5 | #include "builtins/fetch-event.h"
|
7 | 6 | #include "builtins/kv-store.h"
|
8 | 7 | #include "builtins/native-stream-source.h"
|
| 8 | +#include "builtins/shared/dom-exception.h" |
9 | 9 | #include "builtins/shared/url.h"
|
10 | 10 | #include "builtins/transform-stream.h"
|
11 | 11 | #include "core/encode.h"
|
|
29 | 29 | namespace builtins {
|
30 | 30 |
|
31 | 31 | namespace {
|
| 32 | +bool error_stream_controller_with_pending_exception(JSContext *cx, JS::HandleObject controller) { |
| 33 | + JS::RootedValue exn(cx); |
| 34 | + if (!JS_GetPendingException(cx, &exn)) |
| 35 | + return false; |
| 36 | + JS_ClearPendingException(cx); |
| 37 | + |
| 38 | + JS::RootedValueArray<1> args(cx); |
| 39 | + args[0].set(exn); |
| 40 | + JS::RootedValue r(cx); |
| 41 | + return JS::Call(cx, controller, "error", args, &r); |
| 42 | +} |
32 | 43 | constexpr size_t HANDLE_READ_CHUNK_SIZE = 8192;
|
33 | 44 |
|
| 45 | +bool process_body_read(JSContext *cx, int32_t handle, JS::HandleObject context, |
| 46 | + JS::HandleObject promise) { |
| 47 | + MOZ_ASSERT(context); |
| 48 | + JS::RootedObject streamSource(cx, context); |
| 49 | + MOZ_ASSERT(builtins::NativeStreamSource::is_instance(streamSource)); |
| 50 | + host_api::HttpBody body(handle); |
| 51 | + JS::RootedObject owner(cx, builtins::NativeStreamSource::owner(streamSource)); |
| 52 | + JS::RootedObject controller(cx, builtins::NativeStreamSource::controller(streamSource)); |
| 53 | + |
| 54 | + auto read_res = body.read(HANDLE_READ_CHUNK_SIZE); |
| 55 | + if (auto *err = read_res.to_err()) { |
| 56 | + HANDLE_ERROR(cx, *err); |
| 57 | + return error_stream_controller_with_pending_exception(cx, controller); |
| 58 | + } |
| 59 | + |
| 60 | + auto &chunk = read_res.unwrap(); |
| 61 | + if (chunk.len == 0) { |
| 62 | + JS::RootedValue r(cx); |
| 63 | + return JS::Call(cx, controller, "close", JS::HandleValueArray::empty(), &r); |
| 64 | + } |
| 65 | + |
| 66 | + // We don't release control of chunk's data until after we've checked that the array buffer |
| 67 | + // allocation has been successful, as that ensures that the return path frees chunk automatically |
| 68 | + // when necessary. |
| 69 | + JS::RootedObject buffer( |
| 70 | + cx, JS::NewArrayBufferWithContents(cx, chunk.len, chunk.ptr.get(), |
| 71 | + JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory)); |
| 72 | + if (!buffer) { |
| 73 | + return error_stream_controller_with_pending_exception(cx, controller); |
| 74 | + } |
| 75 | + |
| 76 | + // At this point `buffer` has taken full ownership of the chunk's data. |
| 77 | + std::ignore = chunk.ptr.release(); |
| 78 | + |
| 79 | + JS::RootedObject byte_array(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 0, chunk.len)); |
| 80 | + if (!byte_array) { |
| 81 | + return false; |
| 82 | + } |
| 83 | + |
| 84 | + JS::RootedValueArray<1> enqueue_args(cx); |
| 85 | + enqueue_args[0].setObject(*byte_array); |
| 86 | + JS::RootedValue r(cx); |
| 87 | + if (!JS::Call(cx, controller, "enqueue", enqueue_args, &r)) { |
| 88 | + return error_stream_controller_with_pending_exception(cx, controller); |
| 89 | + } |
| 90 | + |
| 91 | + return true; |
| 92 | +} |
| 93 | + |
34 | 94 | // https://fetch.spec.whatwg.org/#concept-method-normalize
|
35 | 95 | // Returns `true` if the method name was normalized, `false` otherwise.
|
36 | 96 | bool normalize_http_method(char *method) {
|
@@ -120,6 +180,39 @@ bool enqueue_internal_method(JSContext *cx, JS::HandleObject receiver,
|
120 | 180 |
|
121 | 181 | } // namespace
|
122 | 182 |
|
| 183 | +bool RequestOrResponse::process_pending_request(JSContext *cx, int32_t handle, |
| 184 | + JS::HandleObject context, |
| 185 | + JS::HandleObject promise) { |
| 186 | + MOZ_ASSERT(builtins::Request::is_instance(context)); |
| 187 | + host_api::HttpPendingReq pending(handle); |
| 188 | + auto res = pending.wait(); |
| 189 | + if (auto *err = res.to_err()) { |
| 190 | + std::string message = std::move(err->message()).value_or("when attempting to fetch resource."); |
| 191 | + builtins::DOMException::raise(cx, message, "NetworkError"); |
| 192 | + return RejectPromiseWithPendingError(cx, promise); |
| 193 | + } |
| 194 | + |
| 195 | + auto [response_handle, body] = res.unwrap(); |
| 196 | + JS::RootedObject response_instance(cx, JS_NewObjectWithGivenProto(cx, &builtins::Response::class_, |
| 197 | + builtins::Response::proto_obj)); |
| 198 | + if (!response_instance) { |
| 199 | + return false; |
| 200 | + } |
| 201 | + |
| 202 | + bool is_upstream = true; |
| 203 | + bool is_grip_upgrade = false; |
| 204 | + JS::RootedObject response(cx, |
| 205 | + builtins::Response::create(cx, response_instance, response_handle, body, |
| 206 | + is_upstream, is_grip_upgrade, nullptr)); |
| 207 | + if (!response) { |
| 208 | + return false; |
| 209 | + } |
| 210 | + |
| 211 | + builtins::RequestOrResponse::set_url(response, builtins::RequestOrResponse::url(context)); |
| 212 | + JS::RootedValue response_val(cx, JS::ObjectValue(*response)); |
| 213 | + return JS::ResolvePromise(cx, promise, response_val); |
| 214 | +} |
| 215 | + |
123 | 216 | bool RequestOrResponse::is_instance(JSObject *obj) {
|
124 | 217 | return Request::is_instance(obj) || Response::is_instance(obj) || KVStoreEntry::is_instance(obj);
|
125 | 218 | }
|
@@ -814,8 +907,15 @@ bool RequestOrResponse::body_source_pull_algorithm(JSContext *cx, JS::CallArgs a
|
814 | 907 | // (This deadlock happens in automated tests, but admittedly might not happen
|
815 | 908 | // in real usage.)
|
816 | 909 |
|
817 |
| - if (!core::EventLoop::queue_async_task(source)) |
| 910 | + JS::RootedObject self(cx, &args.thisv().toObject()); |
| 911 | + JS::RootedObject owner(cx, builtins::NativeStreamSource::owner(self)); |
| 912 | + auto task = core::AsyncTask::create( |
| 913 | + cx, builtins::RequestOrResponse::body_handle(owner).async_handle().handle, source, nullptr, |
| 914 | + process_body_read); |
| 915 | + |
| 916 | + if (!core::EventLoop::queue_async_task(task)) { |
818 | 917 | return false;
|
| 918 | + } |
819 | 919 |
|
820 | 920 | args.rval().setUndefined();
|
821 | 921 | return true;
|
@@ -862,7 +962,12 @@ bool RequestOrResponse::body_reader_then_handler(JSContext *cx, JS::HandleObject
|
862 | 962 | }
|
863 | 963 |
|
864 | 964 | if (Request::is_instance(body_owner)) {
|
865 |
| - if (!core::EventLoop::queue_async_task(body_owner)) { |
| 965 | + JS::RootedObject promise(cx, Request::response_promise(body_owner)); |
| 966 | + auto task = core::AsyncTask::create( |
| 967 | + cx, builtins::Request::pending_handle(body_owner).async_handle().handle, body_owner, |
| 968 | + promise, RequestOrResponse::process_pending_request); |
| 969 | + |
| 970 | + if (!core::EventLoop::queue_async_task(task)) { |
866 | 971 | return false;
|
867 | 972 | }
|
868 | 973 | }
|
|
0 commit comments