Skip to content

Commit 56aa96d

Browse files
author
Guy Bedford
authored
feat: Blob support for fetch API (#1070)
1 parent 5e2c682 commit 56aa96d

15 files changed

+88
-66
lines changed

integration-tests/js-compute/fixtures/app/tests.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1096,7 +1096,6 @@
10961096
"GET /request/clone/valid": {},
10971097
"GET /request/clone/invalid": {},
10981098
"GET /response/blob": {
1099-
"environments": ["<disabled for now>"],
11001099
"downstream_response": {
11011100
"status": 200,
11021101
"headers": { "content-type": "text/html" },

runtime/fastly/builtins/fetch/request-response.cpp

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#include "request-response.h"
22
#include "../../../StarlingMonkey/builtins/web/base64.h"
3-
// #include "../../../StarlingMonkey/builtins/web/blob.h"
3+
#include "../../../StarlingMonkey/builtins/web/blob.h"
44
#include "../../../StarlingMonkey/builtins/web/dom-exception.h"
55
#include "../../../StarlingMonkey/builtins/web/streams/native-stream-source.h"
66
#include "../../../StarlingMonkey/builtins/web/streams/transform-stream.h"
@@ -34,8 +34,8 @@
3434
#pragma clang diagnostic pop
3535

3636
using builtins::web::base64::valueToJSByteString;
37-
// using builtins::web::blob::Blob;
38-
// using builtins::web::blob::BlobReader;
37+
using builtins::web::blob::Blob;
38+
using builtins::web::blob::BlobReader;
3939
using builtins::web::dom_exception::DOMException;
4040

4141
// We use the StarlingMonkey Headers implementation, despite it supporting features that we do
@@ -360,9 +360,7 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self,
360360

361361
host_api::HostString host_type_str;
362362

363-
// Blob support disabled pending bug fix in test
364-
// /override-content-length/request/init/object-literal/true
365-
/*if (body_obj && Blob::is_instance(body_obj)) {
363+
if (Blob::is_instance(body_obj)) {
366364
auto native_stream = NativeStreamSource::create(cx, body_obj, JS::UndefinedHandleValue,
367365
Blob::stream_pull, Blob::stream_cancel);
368366
if (!native_stream) {
@@ -396,8 +394,7 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self,
396394
MOZ_ASSERT(host_type_str);
397395
content_type = host_type_str.ptr.get();
398396
}
399-
} else */
400-
if (body_obj && JS::IsReadableStream(body_obj)) {
397+
} else if (body_obj && JS::IsReadableStream(body_obj)) {
401398
if (RequestOrResponse::body_unusable(cx, body_obj)) {
402399
JS_ReportErrorNumberLatin1(cx, FastlyGetErrorMessage, nullptr,
403400
JSMSG_READABLE_STREAM_LOCKED_OR_DISTRUBED);
@@ -584,9 +581,7 @@ bool RequestOrResponse::parse_body(JSContext *cx, JS::HandleObject self, JS::Uni
584581
}
585582
static_cast<void>(buf.release());
586583
result.setObject(*array_buffer);
587-
}
588-
// TODO: Blob support disabled pending bug fix
589-
/* else if constexpr (result_type == RequestOrResponse::BodyReadResult::Blob) {
584+
} else if constexpr (result_type == RequestOrResponse::BodyReadResult::Blob) {
590585
JS::RootedString contentType(cx, JS_GetEmptyString(cx));
591586
JS::RootedObject blob(cx, Blob::create(cx, std::move(buf), len, contentType));
592587

@@ -595,8 +590,7 @@ bool RequestOrResponse::parse_body(JSContext *cx, JS::HandleObject self, JS::Uni
595590
}
596591

597592
result.setObject(*blob);
598-
} */
599-
else {
593+
} else {
600594
JS::RootedString text(cx, JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(buf.get(), len)));
601595
if (!text) {
602596
return RejectPromiseWithPendingError(cx, result_promise);
@@ -1692,8 +1686,7 @@ const JSPropertySpec Request::static_properties[] = {
16921686
const JSFunctionSpec Request::methods[] = {
16931687
JS_FN("arrayBuffer", Request::bodyAll<RequestOrResponse::BodyReadResult::ArrayBuffer>, 0,
16941688
JSPROP_ENUMERATE),
1695-
// JS_FN("blob", Request::bodyAll<RequestOrResponse::BodyReadResult::Blob>, 0,
1696-
// JSPROP_ENUMERATE),
1689+
JS_FN("blob", Request::bodyAll<RequestOrResponse::BodyReadResult::Blob>, 0, JSPROP_ENUMERATE),
16971690
JS_FN("json", Request::bodyAll<RequestOrResponse::BodyReadResult::JSON>, 0, JSPROP_ENUMERATE),
16981691
JS_FN("text", Request::bodyAll<RequestOrResponse::BodyReadResult::Text>, 0, JSPROP_ENUMERATE),
16991692
JS_FN("setCacheOverride", Request::setCacheOverride, 3, JSPROP_ENUMERATE),
@@ -1910,9 +1903,10 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H
19101903
JS::RootedValue cache_override(cx);
19111904
JS::RootedValue cache_key(cx);
19121905
JS::RootedValue fastly_val(cx);
1913-
JS::RootedValue manualFramingHeaders(cx);
1914-
bool hasmanualFramingHeaders;
1906+
bool hasManualFramingHeaders = false;
1907+
bool setManualFramingHeaders = false;
19151908
if (init_val.isObject()) {
1909+
JS::RootedValue manualFramingHeaders(cx);
19161910
JS::RootedObject init(cx, init_val.toObjectOrNull());
19171911
if (!JS_GetProperty(cx, init, "method", &method_val) ||
19181912
!JS_GetProperty(cx, init, "headers", &headers_val) ||
@@ -1921,10 +1915,11 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H
19211915
!JS_GetProperty(cx, init, "cacheOverride", &cache_override) ||
19221916
!JS_GetProperty(cx, init, "cacheKey", &cache_key) ||
19231917
!JS_GetProperty(cx, init, "fastly", &fastly_val) ||
1924-
!JS_HasOwnProperty(cx, init, "manualFramingHeaders", &hasmanualFramingHeaders) ||
1918+
!JS_HasOwnProperty(cx, init, "manualFramingHeaders", &hasManualFramingHeaders) ||
19251919
!JS_GetProperty(cx, init, "manualFramingHeaders", &manualFramingHeaders)) {
19261920
return nullptr;
19271921
}
1922+
setManualFramingHeaders = manualFramingHeaders.isBoolean() && manualFramingHeaders.toBoolean();
19281923
} else if (!init_val.isNullOrUndefined()) {
19291924
JS_ReportErrorLatin1(cx, "Request constructor: |init| parameter can't be converted to "
19301925
"a dictionary");
@@ -2229,18 +2224,17 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H
22292224
JS::BooleanValue(false));
22302225
}
22312226

2232-
if (!hasmanualFramingHeaders) {
2227+
if (!hasManualFramingHeaders) {
22332228
if (input_request) {
2234-
manualFramingHeaders.set(
2235-
JS::GetReservedSlot(input_request, static_cast<uint32_t>(Slots::ManualFramingHeaders)));
2236-
} else {
2237-
manualFramingHeaders.setBoolean(false);
2229+
auto val =
2230+
JS::GetReservedSlot(input_request, static_cast<uint32_t>(Slots::ManualFramingHeaders));
2231+
setManualFramingHeaders = val.isBoolean() && val.toBoolean();
22382232
}
22392233
}
22402234
JS::SetReservedSlot(request, static_cast<uint32_t>(Slots::ManualFramingHeaders),
2241-
JS::BooleanValue(JS::ToBoolean(manualFramingHeaders)));
2235+
JS::BooleanValue(setManualFramingHeaders));
22422236

2243-
if (JS::ToBoolean(manualFramingHeaders)) {
2237+
if (setManualFramingHeaders) {
22442238
auto res =
22452239
request_handle.set_framing_headers_mode(host_api::FramingHeadersMode::ManuallyFromHeaders);
22462240
if (auto *err = res.to_err()) {
@@ -2947,7 +2941,7 @@ const JSPropertySpec Response::static_properties[] = {
29472941
const JSFunctionSpec Response::methods[] = {
29482942
JS_FN("arrayBuffer", bodyAll<RequestOrResponse::BodyReadResult::ArrayBuffer>, 0,
29492943
JSPROP_ENUMERATE),
2950-
// JS_FN("blob", bodyAll<RequestOrResponse::BodyReadResult::Blob>, 0, JSPROP_ENUMERATE),
2944+
JS_FN("blob", bodyAll<RequestOrResponse::BodyReadResult::Blob>, 0, JSPROP_ENUMERATE),
29512945
JS_FN("json", bodyAll<RequestOrResponse::BodyReadResult::JSON>, 0, JSPROP_ENUMERATE),
29522946
JS_FN("text", bodyAll<RequestOrResponse::BodyReadResult::Text>, 0, JSPROP_ENUMERATE),
29532947
JS_FN("setManualFramingHeaders", Response::setManualFramingHeaders, 1, JSPROP_ENUMERATE),

tests/wpt-harness/expectations/fetch/api/request/request-consume-empty.any.js.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"status": "PASS"
44
},
55
"Consume request's body as blob": {
6-
"status": "FAIL"
6+
"status": "PASS"
77
},
88
"Consume request's body as arrayBuffer": {
99
"status": "PASS"
@@ -21,13 +21,13 @@
2121
"status": "FAIL"
2222
},
2323
"Consume empty blob request body as arrayBuffer": {
24-
"status": "FAIL"
24+
"status": "PASS"
2525
},
2626
"Consume empty text request body as arrayBuffer": {
2727
"status": "PASS"
2828
},
2929
"Consume empty blob request body as text": {
30-
"status": "FAIL"
30+
"status": "PASS"
3131
},
3232
"Consume empty text request body as text": {
3333
"status": "PASS"

tests/wpt-harness/expectations/fetch/api/request/request-consume.any.js.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"status": "PASS"
44
},
55
"Consume String request's body as blob": {
6-
"status": "FAIL"
6+
"status": "PASS"
77
},
88
"Consume String request's body as arrayBuffer": {
99
"status": "PASS"
@@ -18,7 +18,7 @@
1818
"status": "PASS"
1919
},
2020
"Consume ArrayBuffer request's body as blob": {
21-
"status": "FAIL"
21+
"status": "PASS"
2222
},
2323
"Consume ArrayBuffer request's body as arrayBuffer": {
2424
"status": "PASS"
@@ -33,7 +33,7 @@
3333
"status": "PASS"
3434
},
3535
"Consume Uint8Array request's body as blob": {
36-
"status": "FAIL"
36+
"status": "PASS"
3737
},
3838
"Consume Uint8Array request's body as arrayBuffer": {
3939
"status": "PASS"
@@ -48,7 +48,7 @@
4848
"status": "PASS"
4949
},
5050
"Consume Int8Array request's body as blob": {
51-
"status": "FAIL"
51+
"status": "PASS"
5252
},
5353
"Consume Int8Array request's body as arrayBuffer": {
5454
"status": "PASS"
@@ -63,7 +63,7 @@
6363
"status": "PASS"
6464
},
6565
"Consume Float32Array request's body as blob": {
66-
"status": "FAIL"
66+
"status": "PASS"
6767
},
6868
"Consume Float32Array request's body as arrayBuffer": {
6969
"status": "PASS"
@@ -78,7 +78,7 @@
7878
"status": "PASS"
7979
},
8080
"Consume DataView request's body as blob": {
81-
"status": "FAIL"
81+
"status": "PASS"
8282
},
8383
"Consume DataView request's body as arrayBuffer": {
8484
"status": "PASS"
@@ -93,22 +93,22 @@
9393
"status": "FAIL"
9494
},
9595
"Consume blob response's body as blob": {
96-
"status": "FAIL"
96+
"status": "PASS"
9797
},
9898
"Consume blob response's body as text": {
99-
"status": "FAIL"
99+
"status": "PASS"
100100
},
101101
"Consume blob response's body as json": {
102-
"status": "FAIL"
102+
"status": "PASS"
103103
},
104104
"Consume blob response's body as arrayBuffer": {
105-
"status": "FAIL"
105+
"status": "PASS"
106106
},
107107
"Consume blob response's body as bytes": {
108108
"status": "FAIL"
109109
},
110110
"Consume blob response's body as blob (empty blob as input)": {
111-
"status": "FAIL"
111+
"status": "PASS"
112112
},
113113
"Consume JSON from text: '\"null\"'": {
114114
"status": "PASS"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"Request's body: initial state": {
3+
"status": "FAIL"
4+
},
5+
"Request without body cannot be disturbed": {
6+
"status": "PASS"
7+
},
8+
"Check cloning a disturbed request": {
9+
"status": "FAIL"
10+
},
11+
"Check creating a new request from a disturbed request": {
12+
"status": "FAIL"
13+
},
14+
"Check creating a new request with a new body from a disturbed request": {
15+
"status": "FAIL"
16+
},
17+
"Input request used for creating new request became disturbed": {
18+
"status": "FAIL"
19+
},
20+
"Input request used for creating new request became disturbed even if body is not used": {
21+
"status": "FAIL"
22+
},
23+
"Check consuming a disturbed request": {
24+
"status": "FAIL"
25+
},
26+
"Request construction failure should not set \"bodyUsed\"": {
27+
"status": "FAIL"
28+
}
29+
}

tests/wpt-harness/expectations/fetch/api/request/request-headers.any.js.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@
177177
"status": "PASS"
178178
},
179179
"Testing empty Request Content-Type header": {
180-
"status": "FAIL"
180+
"status": "PASS"
181181
},
182182
"Test that Request.headers has the [SameObject] extended attribute": {
183183
"status": "PASS"

tests/wpt-harness/expectations/fetch/api/request/request-init-contenttype.any.js.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
"status": "PASS"
44
},
55
"Default Content-Type for Request with Blob body (no type set)": {
6-
"status": "FAIL"
6+
"status": "PASS"
77
},
88
"Default Content-Type for Request with Blob body (empty type)": {
9-
"status": "FAIL"
9+
"status": "PASS"
1010
},
1111
"Default Content-Type for Request with Blob body (set type)": {
12-
"status": "FAIL"
12+
"status": "PASS"
1313
},
1414
"Default Content-Type for Request with buffer source body": {
1515
"status": "PASS"

tests/wpt-harness/expectations/fetch/api/request/request-structure.any.js.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"status": "PASS"
77
},
88
"Request has blob method": {
9-
"status": "FAIL"
9+
"status": "PASS"
1010
},
1111
"Request has formData method": {
1212
"status": "FAIL"

tests/wpt-harness/expectations/fetch/api/response/response-consume-empty.any.js.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"status": "PASS"
44
},
55
"Consume response's body as blob": {
6-
"status": "FAIL"
6+
"status": "PASS"
77
},
88
"Consume response's body as arrayBuffer": {
99
"status": "PASS"
@@ -21,13 +21,13 @@
2121
"status": "FAIL"
2222
},
2323
"Consume empty blob response body as arrayBuffer": {
24-
"status": "FAIL"
24+
"status": "PASS"
2525
},
2626
"Consume empty text response body as arrayBuffer": {
2727
"status": "PASS"
2828
},
2929
"Consume empty blob response body as text": {
30-
"status": "FAIL"
30+
"status": "PASS"
3131
},
3232
"Consume empty text response body as text": {
3333
"status": "PASS"

tests/wpt-harness/expectations/fetch/api/response/response-error-from-stream.any.js.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"status": "PASS"
1010
},
1111
"ReadableStream start() Error propagates to Response.blob() Promise": {
12-
"status": "FAIL"
12+
"status": "PASS"
1313
},
1414
"ReadableStream start() Error propagates to Response.bytes() Promise": {
1515
"status": "FAIL"
@@ -27,7 +27,7 @@
2727
"status": "PASS"
2828
},
2929
"ReadableStream pull() Error propagates to Response.blob() Promise": {
30-
"status": "FAIL"
30+
"status": "PASS"
3131
},
3232
"ReadableStream pull() Error propagates to Response.bytes() Promise": {
3333
"status": "FAIL"

0 commit comments

Comments
 (0)