This commit is contained in:
Jarred Sumner
2025-10-02 15:34:15 -07:00
parent d9a7dab269
commit a9c930d797
9 changed files with 173 additions and 81 deletions

View File

@@ -197,7 +197,7 @@ pub const HTMLRewriter = struct {
if (kind != .other) {
{
const body_value = try jsc.WebCore.Body.extract(global, response_value);
const body_value = try jsc.WebCore.Body.extract(global, response_value, null);
const resp = bun.new(Response, Response{
.init = .{
.status_code = 200,

View File

@@ -44,7 +44,7 @@ pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, b
}
}
return Response.constructor(globalThis, callframe);
return Response.constructor(globalThis, callframe, .zero);
}
pub export fn BakeResponseClass__constructRedirect(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) callconv(jsc.conv) jsc.JSValue {

View File

@@ -16,9 +16,9 @@ pub fn use(this: *Body) Blob {
return this.value.use();
}
pub fn clone(this: *Body, globalThis: *JSGlobalObject) bun.JSError!Body {
pub fn clone(this: *Body, owner: jsc.WebCore.ReadableStream.Ref.Owner, globalThis: *JSGlobalObject, readable_stream_tee: ?*[2]jsc.JSValue) bun.JSError!Body {
return Body{
.value = try this.value.clone(globalThis),
.value = try this.value.clone(owner, globalThis, readable_stream_tee),
};
}
@@ -504,6 +504,10 @@ pub const Value = union(Tag) {
}
pub fn fromJS(globalThis: *JSGlobalObject, value: JSValue) bun.JSError!Value {
return fromJSWithReadableStreamValue(globalThis, value, null);
}
pub fn fromJSWithReadableStreamValue(globalThis: *JSGlobalObject, value: JSValue, readable_stream_value: ?*JSValue) bun.JSError!Value {
value.ensureStillAlive();
if (value.isEmptyOrUndefinedOrNull()) {
@@ -598,6 +602,11 @@ pub const Value = union(Tag) {
else => {},
}
if (readable_stream_value) |readable_stream_ptr| {
readable_stream_ptr.* = readable.value;
return .{ .Locked = .{ .global = globalThis, .readable = .empty } };
}
return Body.Value.fromReadableStreamWithoutLockCheck(readable, globalThis);
}
@@ -941,23 +950,32 @@ pub const Value = union(Tag) {
}
}
pub fn tee(this: *Value, globalThis: *jsc.JSGlobalObject) bun.JSError!Value {
pub fn tee(this: *Value, owner: jsc.WebCore.ReadableStream.Ref.Owner, globalThis: *jsc.JSGlobalObject, readable_stream_tee: ?*[2]jsc.JSValue) bun.JSError!Value {
var locked = &this.Locked;
if (locked.readable.isDisturbed(.strong, globalThis)) {
return Value{ .Used = {} };
if (locked.readable.isDisturbed(owner, globalThis)) {
return .Used;
}
if (try locked.readable.tee(.{ .strong = {} }, globalThis)) |readable| {
return Value{
if (try locked.readable.tee(owner, globalThis, readable_stream_tee)) |result| {
if (readable_stream_tee != null) {
return .{
.Locked = .{
.global = globalThis,
},
};
}
return .{
.Locked = .{
.readable = .{ .strong = jsc.WebCore.ReadableStream.Strong.init(readable.@"0", globalThis) },
.readable = .{ .strong = .init(result.@"1", globalThis) },
.global = globalThis,
},
};
}
if (locked.promise != null or locked.action != .none or locked.readable.has(.strong, globalThis)) {
return Value{ .Used = {} };
if (locked.promise != null or locked.action != .none or locked.readable.has(owner, globalThis)) {
return .Used;
}
var drain_result: jsc.WebCore.DrainResult = .{
@@ -970,8 +988,8 @@ pub const Value = union(Tag) {
}
if (drain_result == .empty or drain_result == .aborted) {
this.* = .{ .Null = {} };
return Value{ .Null = {} };
this.* = .Null;
return .Null;
}
var reader = jsc.WebCore.ByteStream.Source.new(.{
@@ -994,28 +1012,35 @@ pub const Value = union(Tag) {
.ptr = .{ .Bytes = &reader.context },
.value = stream_value,
};
locked.readable = .{ .strong = jsc.WebCore.ReadableStream.Strong.init(stream, globalThis) };
locked.readable.set(.strong, stream, globalThis);
if (locked.onReadableStreamAvailable) |onReadableStreamAvailable| {
onReadableStreamAvailable(locked.task.?, globalThis, locked.readable.get(.{ .strong = {} }, globalThis).?);
onReadableStreamAvailable(locked.task.?, globalThis, locked.readable.get(owner, globalThis).?);
}
const first, const second = (try locked.readable.tee(.strong, globalThis)) orelse return Value{ .Used = {} };
locked.readable = .{ .strong = .init(first, globalThis) };
const tee_result = (try locked.readable.tee(owner, globalThis, readable_stream_tee)) orelse return Value{ .Used = {} };
if (readable_stream_tee != null) {
return .{
.Locked = .{
.global = globalThis,
},
};
}
return Value{
.Locked = .{
.readable = .{ .strong = .init(second, globalThis) },
.readable = .{ .strong = .init(tee_result.@"1", globalThis) },
.global = globalThis,
},
};
}
pub fn clone(this: *Value, globalThis: *jsc.JSGlobalObject) bun.JSError!Value {
this.toBlobIfPossible(.empty);
pub fn clone(this: *Value, owner: jsc.WebCore.ReadableStream.Ref.Owner, globalThis: *jsc.JSGlobalObject, readable_stream_tee: ?*[2]jsc.JSValue) bun.JSError!Value {
this.toBlobIfPossible(owner);
if (this.* == .Locked) {
return this.tee(globalThis);
return try this.tee(owner, globalThis, readable_stream_tee);
}
if (this.* == .InternalBlob) {
@@ -1054,10 +1079,11 @@ pub const Value = union(Tag) {
pub fn extract(
globalThis: *JSGlobalObject,
value: JSValue,
readable_stream_value: ?*JSValue,
) bun.JSError!Body {
var body = Body{ .value = Value{ .Null = {} } };
body.value = try Value.fromJS(globalThis, value);
body.value = try Value.fromJSWithReadableStreamValue(globalThis, value, readable_stream_value);
if (body.value == .Blob) {
assert(!body.value.Blob.isHeapAllocated()); // owned by Body
}
@@ -1328,7 +1354,7 @@ pub const ValueBufferer = struct {
js_sink: ?*ArrayBufferSink.JSSink = null,
byte_stream: ?*jsc.WebCore.ByteStream = null,
// readable stream strong ref to keep byte stream alive
readable_stream_ref: jsc.WebCore.ReadableStream.Ref = .{ .empty = {} },
readable_stream_ref: jsc.WebCore.ReadableStream.Strong = .{},
stream_buffer: bun.MutableString,
allocator: std.mem.Allocator,
global: *JSGlobalObject,
@@ -1564,14 +1590,15 @@ pub const ValueBufferer = struct {
assert(value.* == .Locked);
const locked = &value.Locked;
if (locked.readable.get(.{ .empty = {} }, sink.global)) |stream| {
// keep the stream alive until we're done with it
sink.readable_stream_ref = locked.readable;
value.* = .{ .Used = {} };
if (stream.isLocked(sink.global)) {
return error.StreamAlreadyUsed;
}
// keep the stream alive until we're done with it
sink.readable_stream_ref = .init(stream, sink.global);
value.deinit();
value.* = .{ .Used = {} };
switch (stream.ptr) {
.Invalid => {
return error.InvalidStream;

View File

@@ -29,7 +29,7 @@ pub const Ref = union(Type) {
.Response => {
if (owner == .Response) {
if (owner.Response.as(jsc.WebCore.Response)) |_| {
if (jsc.WebCore.Response.js.gc.body.get(owner.Response)) |body_value| {
if (Response.js.gc.body.get(owner.Response)) |body_value| {
return ReadableStream.fromJS(body_value, global) catch null;
}
}
@@ -38,7 +38,7 @@ pub const Ref = union(Type) {
.Request => {
if (owner == .Request) {
if (owner.Request.as(jsc.WebCore.Request)) |_| {
if (jsc.WebCore.Request.js.gc.body.get(owner.Request)) |body_value| {
if (Request.js.gc.body.get(owner.Request)) |body_value| {
return ReadableStream.fromJS(body_value, global) catch null;
}
}
@@ -55,9 +55,50 @@ pub const Ref = union(Type) {
return stream.isDisturbed(global);
}
pub fn tee(this: *const Ref, owner: Owner, global: *jsc.JSGlobalObject) bun.JSError!?struct { ReadableStream, ReadableStream } {
pub fn setValue(this: *Ref, owner: Owner, stream_jsvalue: jsc.JSValue, global: *jsc.JSGlobalObject) void {
switch (owner) {
.Response => |jsvalue| {
if (jsvalue != .zero) {
Response.js.gc.body.set(jsvalue, global, stream_jsvalue);
this.deinit();
this.* = .Response;
} else {
this.deinit();
this.* = .empty;
}
},
.Request => |jsvalue| {
if (jsvalue != .zero) {
Request.js.gc.body.set(jsvalue, global, stream_jsvalue);
this.deinit();
this.* = .Request;
} else {
this.deinit();
this.* = .empty;
}
},
.strong => {
this.deinit();
this.* = .{ .strong = .init(stream_jsvalue, global) };
},
.empty => {},
}
}
pub fn set(this: *Ref, owner: Owner, stream: ReadableStream, global: *jsc.JSGlobalObject) void {
this.setValue(owner, stream.value, global);
}
pub fn tee(this: *Ref, owner: Owner, global: *jsc.JSGlobalObject, readable_stream_value: ?*[2]jsc.JSValue) bun.JSError!?struct { ReadableStream, ReadableStream } {
const stream = get(this, owner, global) orelse return null;
return stream.tee(global) catch null;
const result = try stream.tee(global) orelse return null;
if (readable_stream_value) |value| {
value.* = .{ result.@"0".value, result.@"1".value };
} else {
this.set(owner, result.@"0", global);
}
return result;
}
pub fn has(this: *const Ref, owner: Owner, global: *jsc.JSGlobalObject) bool {
@@ -77,16 +118,16 @@ pub const Ref = union(Type) {
pub fn init(owner: Owner, global: *jsc.JSGlobalObject) Ref {
switch (owner) {
.Response => {
return .{ .Response = {} };
return .Response;
},
.Request => {
return .{ .Request = {} };
return .Request;
},
.strong => {
return .{ .strong = .init(owner.strong, global) };
},
.empty => {
return .{ .empty = {} };
return .empty;
},
}
}
@@ -100,7 +141,7 @@ pub const Ref = union(Type) {
.Request => {},
.empty => {},
}
this.* = .{ .empty = {} };
this.* = .empty;
}
pub fn abort(this: *Ref, owner: Owner, global: *jsc.JSGlobalObject) bool {
@@ -974,3 +1015,5 @@ const Blob = webcore.Blob;
const streams = webcore.streams;
const std = @import("std");
const Request = jsc.WebCore.Request;
const Response = jsc.WebCore.Response;

View File

@@ -175,7 +175,8 @@ pub export fn Bun__JSRequest__calculateEstimatedByteSize(this: *Request) void {
pub fn toJS(this: *Request, globalObject: *JSGlobalObject) JSValue {
this.calculateEstimatedByteSize();
return js.toJSUnchecked(globalObject, this);
const value = js.toJSUnchecked(globalObject, this);
return value;
}
extern "C" fn Bun__JSRequest__createForBake(globalObject: *jsc.JSGlobalObject, requestPtr: *Request) callconv(jsc.conv) jsc.JSValue;
@@ -522,7 +523,7 @@ const Fields = enum {
url,
};
pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSValue) bun.JSError!Request {
pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSValue, readable_stream_tee: ?*[2]jsc.JSValue) bun.JSError!Request {
var success = false;
const vm = globalThis.bunVM();
const body = try vm.initRequestBodyValue(.{ .Null = {} });
@@ -582,7 +583,7 @@ pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSV
if (value_type == .DOMWrapper) {
if (value.asDirect(Request)) |request| {
if (values_to_try.len == 1) {
try request.cloneInto(&req, bun.default_allocator, globalThis, fields.contains(.url));
try request.cloneInto(&req, bun.default_allocator, globalThis, fields.contains(.url), value, readable_stream_tee);
success = true;
return req;
}
@@ -610,7 +611,7 @@ pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSV
switch (request.body.value) {
.Null, .Empty, .Used => {},
else => {
req.body.value = try request.body.value.clone(globalThis);
req.body.value = try request.body.value.clone(.{ .Request = value }, globalThis, readable_stream_tee);
fields.insert(.body);
},
}
@@ -641,7 +642,11 @@ pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSV
switch (response.body.value) {
.Null, .Empty, .Used => {},
else => {
req.body.value = try response.body.value.clone(globalThis);
req.body.value = try response.body.value.clone(.empty, globalThis, readable_stream_tee);
if (readable_stream_tee) |tee_value| {
Response.js.gc.body.set(value, globalThis, tee_value[1]);
}
fields.insert(.body);
},
}
@@ -654,7 +659,7 @@ pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSV
if (!fields.contains(.body)) {
if (try value.fastGet(globalThis, .body)) |body_| {
fields.insert(.body);
req.body.value = try Body.Value.fromJS(globalThis, body_);
req.body.value = try Body.Value.fromJSWithReadableStreamValue(globalThis, body_, if (readable_stream_tee) |tee| &tee[1] else null);
}
if (globalThis.hasException()) return error.JSError;
@@ -775,12 +780,23 @@ pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSV
return req;
}
pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!*Request {
pub fn constructor(
globalThis: *jsc.JSGlobalObject,
callframe: *jsc.CallFrame,
thisValue: JSValue,
) bun.JSError!*Request {
const arguments_ = callframe.arguments_old(2);
const arguments = arguments_.ptr[0..arguments_.len];
var readable_stream_tee: [2]JSValue = .{ .zero, .zero };
const request = try constructInto(globalThis, arguments);
return Request.new(request);
const request = try constructInto(globalThis, arguments, &readable_stream_tee);
const result = Request.new(request);
if (readable_stream_tee[1] != .zero and result.body.value == .Locked) {
js.gc.body.set(thisValue, globalThis, readable_stream_tee[1]);
}
return result;
}
pub fn getBodyValue(
@@ -795,22 +811,16 @@ pub fn doClone(
callframe: *jsc.CallFrame,
) bun.JSError!jsc.JSValue {
const this_value = callframe.this();
const cloned = try this.clone(bun.default_allocator, globalThis);
var readable_stream_tee: [2]JSValue = .{ .zero, .zero };
const cloned = try this.clone(bun.default_allocator, globalThis, this_value, &readable_stream_tee);
const js_wrapper = cloned.toJS(globalThis);
if (js_wrapper != .zero) {
if (cloned.body.value == .Locked) {
if (cloned.body.value.Locked.readable.get(.{ .Request = js_wrapper }, globalThis)) |readable| {
// If we are teed, then we need to update the cached .body
// value to point to the new readable stream
// We must do this on both the original and cloned request
// but especially the original request since it will have a stale .body value now.
js.bodySetCached(js_wrapper, globalThis, readable.value);
const this_js = this.toJS(globalThis);
if (this.body.value.Locked.readable.get(.{ .Request = this_js }, globalThis)) |other_readable| {
js.bodySetCached(this_value, globalThis, other_readable.value);
}
}
if (this.body.value == .Locked and readable_stream_tee[0] != .zero) {
js.gc.body.set(js_wrapper, globalThis, readable_stream_tee[0]);
}
if (cloned.body.value == .Locked and readable_stream_tee[1] != .zero) {
js.gc.body.set(js_wrapper, globalThis, readable_stream_tee[1]);
}
}
@@ -928,11 +938,13 @@ pub fn cloneInto(
allocator: std.mem.Allocator,
globalThis: *JSGlobalObject,
preserve_url: bool,
this_value: jsc.JSValue,
readable_stream_tee: ?*[2]jsc.JSValue,
) bun.JSError!void {
_ = allocator;
this.ensureURL() catch {};
const vm = globalThis.bunVM();
var body_ = try this.body.value.clone(globalThis);
var body_ = try this.body.value.clone(.{ .Request = this_value }, globalThis, readable_stream_tee);
errdefer body_.deinit();
const body = try vm.initRequestBodyValue(body_);
const url = if (preserve_url) req.url else this.url.dupeRef();
@@ -953,10 +965,10 @@ pub fn cloneInto(
}
}
pub fn clone(this: *Request, allocator: std.mem.Allocator, globalThis: *JSGlobalObject) bun.JSError!*Request {
pub fn clone(this: *Request, allocator: std.mem.Allocator, globalThis: *JSGlobalObject, this_value: jsc.JSValue, readable_stream_tee: ?*[2]jsc.JSValue) bun.JSError!*Request {
const req = Request.new(undefined);
errdefer bun.destroy(req);
try this.cloneInto(req, allocator, globalThis, false);
try this.cloneInto(req, allocator, globalThis, false, this_value, readable_stream_tee);
return req;
}

View File

@@ -298,8 +298,10 @@ pub fn makeMaybePooled(globalObject: *jsc.JSGlobalObject, ptr: *Response) JSValu
pub fn cloneValue(
this: *Response,
globalThis: *JSGlobalObject,
this_value: jsc.JSValue,
readable_stream_tee: ?*[2]jsc.JSValue,
) bun.JSError!Response {
var body = try this.body.clone(globalThis);
var body = try this.body.clone(.{ .Response = this_value }, globalThis, readable_stream_tee);
errdefer body.deinit(bun.default_allocator);
var init = try this.init.clone(globalThis);
errdefer init.deinit(bun.default_allocator);
@@ -311,8 +313,8 @@ pub fn cloneValue(
};
}
pub fn clone(this: *Response, globalThis: *JSGlobalObject) bun.JSError!*Response {
return bun.new(Response, try this.cloneValue(globalThis));
pub fn clone(this: *Response, globalThis: *JSGlobalObject, this_value: jsc.JSValue, readable_stream_tee: ?*[2]jsc.JSValue) bun.JSError!*Response {
return bun.new(Response, try this.cloneValue(globalThis, this_value, readable_stream_tee));
}
pub fn getStatus(
@@ -538,7 +540,7 @@ pub fn constructError(
return response.toJS(globalThis);
}
pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!*Response {
pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, thisValue: JSValue) bun.JSError!*Response {
var arguments = callframe.argumentsAsArray(2);
if (!arguments[0].isUndefinedOrNull() and arguments[0].isObject()) {
@@ -566,10 +568,11 @@ pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) b
return s3.throwSignError(sign_err, globalThis);
};
defer result.deinit();
response.init.headers = try response.getOrCreateHeaders(globalThis);
const headers = try response.getOrCreateHeaders(globalThis);
errdefer headers.deref();
try headers.put(.Location, result.url, globalThis);
response.redirected = true;
var headers_ref = response.init.headers.?;
try headers_ref.put(.Location, result.url, globalThis);
response.init.headers = headers;
return bun.new(Response, response);
}
}
@@ -595,13 +598,15 @@ pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) b
return error.JSError;
}
var readable_stream_value: JSValue = .zero;
var body: Body = brk: {
if (arguments[0].isUndefinedOrNull()) {
break :brk Body{
.value = Body.Value{ .Null = {} },
};
}
break :brk try Body.extract(globalThis, arguments[0]);
break :brk try Body.extract(globalThis, arguments[0], &readable_stream_value);
};
errdefer body.deinit(bun.default_allocator);
@@ -609,21 +614,23 @@ pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) b
return error.JSError;
}
if (body.value == .Blob and
init.headers != null and
body.value.Blob.content_type.len > 0 and
!init.headers.?.fastHas(.ContentType))
{
try init.headers.?.put(.ContentType, body.value.Blob.content_type, globalThis);
}
var response = bun.new(Response, Response{
.body = body,
.init = init,
});
if (response.body.value == .Blob and
response.init.headers != null and
response.body.value.Blob.content_type.len > 0 and
!response.init.headers.?.fastHas(.ContentType))
{
try response.init.headers.?.put(.ContentType, response.body.value.Blob.content_type, globalThis);
}
response.calculateEstimatedByteSize();
if (thisValue != .zero and readable_stream_value != .zero) {
response.body.value.Locked.readable.set(.{ .Response = thisValue }, response.body.value.Locked.global, readable_stream_value);
}
return response;
}

View File

@@ -4,6 +4,7 @@ export default [
define({
name: "Request",
construct: true,
constructNeedsThis: true,
finalize: true,
final: false,
klass: {},
@@ -68,6 +69,7 @@ export default [
define({
name: "Response",
construct: true,
constructNeedsThis: true,
finalize: true,
final: false,
JSType: "0b11101110",

View File

@@ -1710,7 +1710,7 @@ size_t ${name}::memoryCost(void* ptr) {
size_t ${name}::estimatedSize(JSC::JSCell* cell, JSC::VM& vm) {
auto* thisObject = jsCast<${name}*>(cell);
auto* wrapped = thisObject->wrapped();
return Base::estimatedSize(cell, vm) + ${name}::memoryCost(wrapped);
return Base::estimatedSize(cell, vm) + (wrapped ? ${name}::memoryCost(wrapped) : 0);
}
void ${name}::destroy(JSCell* cell)

View File

@@ -424,7 +424,8 @@ size_t ${controller}::memoryCost(void* sinkPtr) {
}
size_t ${controller}::estimatedSize(JSCell* cell, JSC::VM& vm) {
return Base::estimatedSize(cell, vm) + ${controller}::memoryCost(jsCast<${controller}*>(cell)->wrapped());
auto* wrapped = jsCast<${controller}*>(cell)->wrapped();
return Base::estimatedSize(cell, vm) + (wrapped ? ${controller}::memoryCost(wrapped) : 0);
}
JSC_DECLARE_HOST_FUNCTION(${controller}__close);