Merge pull request #2193 from lightpanda-io/worker_apis

Enable more WebAPIs for Workers
This commit is contained in:
Karl Seguin
2026-04-20 15:30:54 +08:00
committed by GitHub
15 changed files with 253 additions and 106 deletions

View File

@@ -3613,7 +3613,7 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form
// The submitter can be an input box (if enter was entered on the box)
// I don't think this is technically correct, but FormData handles it ok
const form_data = try FormData.init(form, submitter_, self);
const form_data = try FormData.init(form, submitter_, &self.js.execution);
const arena = try self._session.getArena(.medium, "submitForm");
errdefer self._session.releaseArena(arena);

View File

@@ -302,6 +302,7 @@ fn _createContext(self: *Env, global: anytype, params: ContextParams) !*Context
context.execution = .{
.url = &global.url,
.buf = &global.buf,
.charset = &global.charset,
.context = context,
.arena = global.arena,
.call_arena = params.call_arena,

View File

@@ -45,3 +45,11 @@ _scheduler: *Scheduler,
// Pointer to the url field (Page or WorkerGlobalScope) - allows access to current url even after navigation
url: *[:0]const u8,
// Pointer to the charset field of the global (Page or WorkerGlobalScope).
charset: *[]const u8,
// Returns the current base URL of the global scope.
pub fn base(self: *const Execution) [:0]const u8 {
return self.context.global.base();
}

View File

@@ -919,15 +919,21 @@ pub const WorkerJsApis = flattenTypes(&.{
@import("../webapi/File.zig"),
@import("../webapi/Console.zig"),
@import("../webapi/Crypto.zig"),
@import("../webapi/net/FormData.zig"),
@import("../webapi/net/Headers.zig"),
@import("../webapi/net/Request.zig"),
@import("../webapi/net/Response.zig"),
// @import("../webapi/URL.zig"),
// @import("../webapi/Blob.zig"),
// @import("../webapi/net/FormData.zig"),
// @import("../webapi/Performance.zig"),
// @import("../webapi/net/Response.zig"),
// @import("../webapi/net/Request.zig"),
// @import("../webapi/net/Headers.zig"),
// @import("../webapi/AbortSignal.zig"),
// @import("../webapi/AbortController.zig"),
// @import("../webapi/streams/ReadableStream.zig"),
// @import("../webapi/streams/ReadableStreamDefaultReader.zig"),
// @import("../webapi/streams/ReadableStreamDefaultController.zig"),
// @import("../webapi/streams/WritableStream.zig"),
// @import("../webapi/streams/WritableStreamDefaultWriter.zig"),
// @import("../webapi/streams/WritableStreamDefaultController.zig"),
});
// Master list of ALL JS APIs across all contexts.

View File

@@ -0,0 +1,55 @@
// Exercises WebAPI classes available in WorkerGlobalScope.
// Replies with either { ok: true, results: {...} } or { ok: false, err }.
(async function() {
try {
// Headers
const headers = new Headers();
headers.set('X-Test', 'hello');
headers.append('X-Test', 'world');
// FormData (no form - pure data container)
const fd = new FormData();
fd.set('name', 'first');
fd.append('name', 'second');
// Request
const request = new Request('https://example.com/path', {
method: 'POST',
headers: { 'x-custom': 'header' },
body: 'request body',
});
const request_body_text = await request.text();
// Response
const response = new Response('response body', {
status: 201,
statusText: 'Created',
headers: { 'content-type': 'text/plain' },
});
const response_body_text = await response.text();
const response_clone_text = await response.clone().text();
postMessage({
ok: true,
results: {
headers_get: headers.get('x-test'),
headers_has: headers.has('x-test'),
headers_has_missing: headers.has('x-missing'),
formdata_get: fd.get('name'),
formdata_getall: fd.getAll('name'),
request_url: request.url,
request_method: request.method,
request_headers_custom: request.headers.get('x-custom'),
request_body_text,
response_status: response.status,
response_status_text: response.statusText,
response_ok: response.ok,
response_headers_content_type: response.headers.get('content-type'),
response_body_text,
response_clone_text,
},
});
} catch (e) {
postMessage({ ok: false, err: String(e), stack: e.stack });
}
})();

View File

@@ -172,6 +172,47 @@
}
</script>
<script id="worker_webapi_net" type=module>
{
const state = await testing.async();
const worker = new Worker('./api-worker.js');
worker.onmessage = function(event) {
state.resolve(event.data);
};
await state.done((data) => {
testing.expectTrue(data.ok, 'worker api error: ' + data.err);
const r = data.results;
// Headers
testing.expectEqual('hello, world', r.headers_get);
testing.expectEqual(true, r.headers_has);
testing.expectEqual(false, r.headers_has_missing);
// FormData
testing.expectEqual('first', r.formdata_get);
testing.expectEqual(2, r.formdata_getall.length);
testing.expectEqual('first', r.formdata_getall[0]);
testing.expectEqual('second', r.formdata_getall[1]);
// Request
testing.expectEqual('https://example.com/path', r.request_url);
testing.expectEqual('POST', r.request_method);
testing.expectEqual('header', r.request_headers_custom);
testing.expectEqual('request body', r.request_body_text);
// Response
testing.expectEqual(201, r.response_status);
testing.expectEqual('Created', r.response_status_text);
testing.expectEqual(true, r.response_ok);
testing.expectEqual('text/plain', r.response_headers_content_type);
testing.expectEqual('response body', r.response_body_text);
testing.expectEqual('response body', r.response_clone_text);
});
}
</script>
<script id="worker_structured_clone_nested" type=module>
{
const state = await testing.async();

View File

@@ -94,9 +94,9 @@ pub fn init(parts_: ?[]const js.Value, opts_: ?InitOptions, page: *Page) !*Blob
}
/// Creates a new Blob from raw byte slices (for internal Zig use).
pub fn initFromBytes(data: []const u8, content_type: []const u8, validate_mime: bool, page: *Page) !*Blob {
const arena = try page.getArena(.large, "Blob");
errdefer page.releaseArena(arena);
pub fn initFromBytes(data: []const u8, content_type: []const u8, validate_mime: bool, session: *Session) !*Blob {
const arena = try session.getArena(.large, "Blob");
errdefer session.releaseArena(arena);
const mime = try validateMimeType(arena, content_type, validate_mime);
@@ -291,7 +291,7 @@ pub fn slice(
start_: ?i32,
end_: ?i32,
content_type_: ?[]const u8,
page: *Page,
session: *Session,
) !*Blob {
const data = self._slice;
@@ -312,7 +312,7 @@ pub fn slice(
break :blk @min(data.len, @max(start, @as(u31, @intCast(requested_end))));
};
return Blob.initFromBytes(data[start..end], content_type_ orelse "", false, page);
return Blob.initFromBytes(data[start..end], content_type_ orelse "", false, session);
}
/// Returns the size of the Blob in bytes.

View File

@@ -53,6 +53,8 @@ arena: Allocator,
call_arena: Allocator,
url: [:0]const u8,
buf: [1024]u8 = undefined, // same size as page.buf
// Document charset (matches Page.charset). Workers default to UTF-8.
charset: []const u8 = "UTF-8",
js: *JS.Context,
// Reference back to the Worker object (for postMessage to page)

View File

@@ -47,7 +47,7 @@ pub const Input = Request.Input;
pub const InitOpts = Request.InitOpts;
pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
const request = try Request.init(input, options, page);
const request = try Request.init(input, options, &page.js.execution);
const resolver = page.js.local.?.createPromiseResolver();
if (request._signal) |signal| {
@@ -61,7 +61,7 @@ pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
return handleBlobUrl(request._url, resolver, page);
}
const response = try Response.init(null, .{ .status = 0 }, page);
const response = try Response.init(null, .{ .status = 0 }, &page.js.execution);
errdefer response.deinit(page._session);
const fetch = try response._arena.create(Fetch);
@@ -120,13 +120,13 @@ fn handleBlobUrl(url: []const u8, resolver: js.PromiseResolver, page: *Page) !js
return resolver.promise();
};
const response = try Response.init(null, .{ .status = 200 }, page);
const response = try Response.init(null, .{ .status = 200 }, &page.js.execution);
response._body = .{ .bytes = try response._arena.dupe(u8, blob._slice) };
response._url = try response._arena.dupeZ(u8, url);
response._type = .basic;
if (blob._mime.len > 0) {
try response._headers.append("Content-Type", blob._mime, page);
try response._headers.append("Content-Type", blob._mime, &page.js.execution);
}
const js_val = try page.js.local.?.zigValueToJs(response, .{});
@@ -191,7 +191,7 @@ fn httpHeaderDoneCallback(response: HttpClient.Response) !bool {
var it = response.headerIterator();
while (it.next()) |hdr| {
try res._headers.append(hdr.name, hdr.value, self._page);
try res._headers.append(hdr.name, hdr.value, &self._page.js.execution);
}
return true;

View File

@@ -26,6 +26,7 @@ const Element = @import("../Element.zig");
const KeyValueList = @import("../KeyValueList.zig");
const log = lp.log;
const Execution = js.Execution;
const Allocator = std.mem.Allocator;
const FormData = @This();
@@ -33,21 +34,30 @@ const FormData = @This();
_arena: Allocator,
_list: KeyValueList,
pub fn init(form: ?*Form, submitter: ?*Element, page: *Page) !*FormData {
const form_data = try page._factory.create(FormData{
._arena = page.arena,
pub fn init(form_: ?*Form, submitter: ?*Element, exec: *const Execution) !*FormData {
const form = form_ orelse {
return try exec._factory.create(FormData{
._arena = exec.arena,
._list = KeyValueList.init(),
});
};
const page = switch (exec.context.global) {
.page => |p| p,
.worker => lp.assert(false, "FormData worker form", .{}),
};
const form_data = try exec._factory.create(FormData{
._arena = exec.arena,
._list = try collectForm(page.arena, form, submitter, page),
});
// Dispatch `formdata` event if form provided.
if (form) |_form| {
const form_data_event = try (@import("../event/FormDataEvent.zig")).initTrusted(
comptime .wrap("formdata"),
.{ .bubbles = true, .cancelable = false, .formData = form_data },
page,
);
try page._event_manager.dispatch(_form.asNode().asEventTarget(), form_data_event.asEvent());
}
const form_data_event = try (@import("../event/FormDataEvent.zig")).initTrusted(
comptime .wrap("formdata"),
.{ .bubbles = true, .cancelable = false, .formData = form_data },
page,
);
try page._event_manager.dispatch(form.asNode().asEventTarget(), form_data_event.asEvent());
return form_data;
}
@@ -56,8 +66,8 @@ pub fn get(self: *const FormData, name: []const u8) ?[]const u8 {
return self._list.get(name);
}
pub fn getAll(self: *const FormData, name: []const u8, page: *Page) ![]const []const u8 {
return self._list.getAll(page.call_arena, name);
pub fn getAll(self: *const FormData, name: []const u8, exec: *const Execution) ![]const []const u8 {
return self._list.getAll(exec.call_arena, name);
}
pub fn has(self: *const FormData, name: []const u8) bool {

View File

@@ -3,10 +3,10 @@ const lp = @import("lightpanda");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const KeyValueList = @import("../KeyValueList.zig");
const log = lp.log;
const Execution = js.Execution;
const Allocator = std.mem.Allocator;
const Headers = @This();
@@ -19,31 +19,31 @@ pub const InitOpts = union(enum) {
js_obj: js.Object,
};
pub fn init(opts_: ?InitOpts, page: *Page) !*Headers {
pub fn init(opts_: ?InitOpts, exec: *const Execution) !*Headers {
const list = if (opts_) |opts| switch (opts) {
.obj => |obj| try KeyValueList.copy(page.arena, obj._list),
.js_obj => |js_obj| try KeyValueList.fromJsObject(page.arena, js_obj, normalizeHeaderName, &page.buf),
.strings => |kvs| try KeyValueList.fromArray(page.arena, kvs, normalizeHeaderName, &page.buf),
.obj => |obj| try KeyValueList.copy(exec.arena, obj._list),
.js_obj => |js_obj| try KeyValueList.fromJsObject(exec.arena, js_obj, normalizeHeaderName, exec.buf),
.strings => |kvs| try KeyValueList.fromArray(exec.arena, kvs, normalizeHeaderName, exec.buf),
} else KeyValueList.init();
return page._factory.create(Headers{
return exec._factory.create(Headers{
._list = list,
});
}
pub fn append(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void {
const normalized_name = normalizeHeaderName(name, &page.buf);
try self._list.append(page.arena, normalized_name, value);
pub fn append(self: *Headers, name: []const u8, value: []const u8, exec: *const Execution) !void {
const normalized_name = normalizeHeaderName(name, exec.buf);
try self._list.append(exec.arena, normalized_name, value);
}
pub fn delete(self: *Headers, name: []const u8, page: *Page) void {
const normalized_name = normalizeHeaderName(name, &page.buf);
pub fn delete(self: *Headers, name: []const u8, exec: *const Execution) void {
const normalized_name = normalizeHeaderName(name, exec.buf);
self._list.delete(normalized_name, null);
}
pub fn get(self: *const Headers, name: []const u8, page: *Page) !?[]const u8 {
const normalized_name = normalizeHeaderName(name, &page.buf);
const all_values = try self._list.getAll(page.call_arena, normalized_name);
pub fn get(self: *const Headers, name: []const u8, exec: *const Execution) !?[]const u8 {
const normalized_name = normalizeHeaderName(name, exec.buf);
const all_values = try self._list.getAll(exec.call_arena, normalized_name);
if (all_values.len == 0) {
return null;
@@ -51,17 +51,17 @@ pub fn get(self: *const Headers, name: []const u8, page: *Page) !?[]const u8 {
if (all_values.len == 1) {
return all_values[0];
}
return try std.mem.join(page.call_arena, ", ", all_values);
return try std.mem.join(exec.call_arena, ", ", all_values);
}
pub fn has(self: *const Headers, name: []const u8, page: *Page) bool {
const normalized_name = normalizeHeaderName(name, &page.buf);
pub fn has(self: *const Headers, name: []const u8, exec: *const Execution) bool {
const normalized_name = normalizeHeaderName(name, exec.buf);
return self._list.has(normalized_name);
}
pub fn set(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void {
const normalized_name = normalizeHeaderName(name, &page.buf);
try self._list.set(page.arena, normalized_name, value);
pub fn set(self: *Headers, name: []const u8, value: []const u8, exec: *const Execution) !void {
const normalized_name = normalizeHeaderName(name, exec.buf);
try self._list.set(exec.arena, normalized_name, value);
}
pub fn keys(self: *Headers, exec: *const js.Execution) !*KeyValueList.KeyIterator {

View File

@@ -26,6 +26,7 @@ const Page = @import("../../Page.zig");
const Headers = @import("Headers.zig");
const Blob = @import("../Blob.zig");
const AbortSignal = @import("../AbortSignal.zig");
const Execution = js.Execution;
const Allocator = std.mem.Allocator;
const Request = @This();
@@ -70,16 +71,16 @@ const Cache = enum {
pub const js_enum_from_string = true;
};
pub fn init(input: Input, opts_: ?InitOpts, page: *Page) !*Request {
const arena = page.arena;
pub fn init(input: Input, opts_: ?InitOpts, exec: *const Execution) !*Request {
const arena = exec.arena;
const url = switch (input) {
.url => |u| try URL.resolve(arena, page.base(), u, .{ .always_dupe = true, .encoding = page.charset }),
.url => |u| try URL.resolve(arena, exec.base(), u, .{ .always_dupe = true, .encoding = exec.charset.* }),
.request => |r| try arena.dupeZ(u8, r._url),
};
const opts = opts_ orelse InitOpts{};
const method = if (opts.method) |m|
try parseMethod(m, page)
try parseMethod(m, exec)
else switch (input) {
.url => .GET,
.request => |r| r._method,
@@ -87,7 +88,7 @@ pub fn init(input: Input, opts_: ?InitOpts, page: *Page) !*Request {
const headers = if (opts.headers) |headers_init| switch (headers_init) {
.obj => |h| h,
else => try Headers.init(headers_init, page),
else => try Headers.init(headers_init, exec),
} else switch (input) {
.url => null,
.request => |r| r._headers,
@@ -107,7 +108,7 @@ pub fn init(input: Input, opts_: ?InitOpts, page: *Page) !*Request {
.request => |r| r._signal,
};
return page._factory.create(Request{
return exec._factory.create(Request{
._url = url,
._arena = arena,
._method = method,
@@ -119,12 +120,12 @@ pub fn init(input: Input, opts_: ?InitOpts, page: *Page) !*Request {
});
}
fn parseMethod(method: []const u8, page: *Page) !http.Method {
fn parseMethod(method: []const u8, exec: *const Execution) !http.Method {
if (method.len > "propfind".len) {
return error.InvalidMethod;
}
const lower = std.ascii.lowerString(&page.buf, method);
const lower = std.ascii.lowerString(exec.buf, method);
const method_lookup = std.StaticStringMap(http.Method).initComptime(.{
.{ "get", .GET },
@@ -159,50 +160,50 @@ pub fn getSignal(self: *const Request) ?*AbortSignal {
return self._signal;
}
pub fn getHeaders(self: *Request, page: *Page) !*Headers {
pub fn getHeaders(self: *Request, exec: *const Execution) !*Headers {
if (self._headers) |headers| {
return headers;
}
const headers = try Headers.init(null, page);
const headers = try Headers.init(null, exec);
self._headers = headers;
return headers;
}
pub fn blob(self: *Request, page: *Page) !js.Promise {
pub fn blob(self: *Request, exec: *const Execution) !js.Promise {
const body = self._body orelse "";
const headers = try self.getHeaders(page);
const content_type = try headers.get("content-type", page) orelse "";
const headers = try self.getHeaders(exec);
const content_type = try headers.get("content-type", exec) orelse "";
const b = try Blob.initFromBytes(body, content_type, true, page);
const b = try Blob.initFromBytes(body, content_type, true, exec.context.session);
return page.js.local.?.resolvePromise(b);
return exec.context.local.?.resolvePromise(b);
}
pub fn text(self: *const Request, page: *Page) !js.Promise {
pub fn text(self: *const Request, exec: *const Execution) !js.Promise {
const body = self._body orelse "";
return page.js.local.?.resolvePromise(body);
return exec.context.local.?.resolvePromise(body);
}
pub fn json(self: *const Request, page: *Page) !js.Promise {
pub fn json(self: *const Request, exec: *const Execution) !js.Promise {
const body = self._body orelse "";
const local = page.js.local.?;
const local = exec.context.local.?;
const value = local.parseJSON(body) catch {
return local.rejectPromise(.{ .syntax_error = "failed to parse" });
};
return local.resolvePromise(try value.persist());
}
pub fn arrayBuffer(self: *const Request, page: *Page) !js.Promise {
return page.js.local.?.resolvePromise(js.ArrayBuffer{ .values = self._body orelse "" });
pub fn arrayBuffer(self: *const Request, exec: *const Execution) !js.Promise {
return exec.context.local.?.resolvePromise(js.ArrayBuffer{ .values = self._body orelse "" });
}
pub fn bytes(self: *const Request, page: *Page) !js.Promise {
return page.js.local.?.resolvePromise(js.TypedArray(u8){ .values = self._body orelse "" });
pub fn bytes(self: *const Request, exec: *const Execution) !js.Promise {
return exec.context.local.?.resolvePromise(js.TypedArray(u8){ .values = self._body orelse "" });
}
pub fn clone(self: *const Request, page: *Page) !*Request {
return page._factory.create(Request{
pub fn clone(self: *const Request, exec: *const Execution) !*Request {
return exec._factory.create(Request{
._url = self._url,
._arena = self._arena,
._method = self._method,

View File

@@ -18,15 +18,18 @@
const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig");
const HttpClient = @import("../../HttpClient.zig");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Headers = @import("Headers.zig");
const ReadableStream = @import("../streams/ReadableStream.zig");
const Blob = @import("../Blob.zig");
const HttpClient = @import("../../HttpClient.zig");
const Blob = @import("../Blob.zig");
const ReadableStream = @import("../streams/ReadableStream.zig");
const Headers = @import("Headers.zig");
const Execution = js.Execution;
const Allocator = std.mem.Allocator;
const Response = @This();
@@ -69,9 +72,10 @@ pub const BodyInit = union(enum) {
js_val: js.Value,
};
pub fn init(body_: ?BodyInit, opts_: ?InitOpts, page: *Page) !*Response {
const arena = try page.getArena(.large, "Response");
errdefer page.releaseArena(arena);
pub fn init(body_: ?BodyInit, opts_: ?InitOpts, exec: *const Execution) !*Response {
const session = exec.context.session;
const arena = try session.getArena(.large, "Response");
errdefer session.releaseArena(arena);
const opts = opts_ orelse InitOpts{};
const status_text = if (opts.statusText) |st| try arena.dupe(u8, st) else "";
@@ -101,7 +105,7 @@ pub fn init(body_: ?BodyInit, opts_: ?InitOpts, page: *Page) !*Response {
._body = body,
._type = .basic,
._is_redirected = false,
._headers = try Headers.init(opts.headers, page),
._headers = try Headers.init(opts.headers, exec),
};
return self;
}
@@ -146,11 +150,18 @@ pub fn getType(self: *const Response) []const u8 {
return @tagName(self._type);
}
pub fn getBody(self: *Response, page: *Page) !?*ReadableStream {
pub fn getBody(self: *Response, exec: *const Execution) !?*ReadableStream {
return switch (self._body) {
.empty => null,
.stream => |stream| stream,
.bytes => |body| {
// ReadableStream creation currently requires a Page. Workers
// cannot produce stream bodies yet, so falling back to the page
// here is safe.
const page = switch (exec.context.global) {
.page => |p| p,
.worker => unreachable,
};
if (body.len == 0) {
const stream = try ReadableStream.init(null, null, page);
try stream._controller.close();
@@ -165,17 +176,18 @@ pub fn isOK(self: *const Response) bool {
return self._status >= 200 and self._status <= 299;
}
pub fn getText(self: *const Response, page: *Page) !js.Promise {
pub fn getText(self: *const Response, exec: *const Execution) !js.Promise {
const local = exec.context.local.?;
const body = switch (self._body) {
.bytes => |b| b,
.empty => "",
.stream => return page.js.local.?.rejectPromise(.{ .type_error = "Cannot read text from stream body" }),
.stream => return local.rejectPromise(.{ .type_error = "Cannot read text from stream body" }),
};
return page.js.local.?.resolvePromise(body);
return local.resolvePromise(body);
}
pub fn getJson(self: *Response, page: *Page) !js.Promise {
const local = page.js.local.?;
pub fn getJson(self: *Response, exec: *const Execution) !js.Promise {
const local = exec.context.local.?;
const body = switch (self._body) {
.bytes => |b| b,
.empty => "",
@@ -187,11 +199,21 @@ pub fn getJson(self: *Response, page: *Page) !js.Promise {
return local.resolvePromise(try value.persist());
}
pub fn arrayBuffer(self: *Response, page: *Page) !js.Promise {
pub fn arrayBuffer(self: *Response, exec: *const Execution) !js.Promise {
const local = exec.context.local.?;
return switch (self._body) {
.bytes => |body| page.js.local.?.resolvePromise(js.ArrayBuffer{ .values = body }),
.empty => page.js.local.?.resolvePromise(js.ArrayBuffer{ .values = "" }),
.stream => |stream| StreamConsumer.start(stream, page),
.bytes => |body| local.resolvePromise(js.ArrayBuffer{ .values = body }),
.empty => local.resolvePromise(js.ArrayBuffer{ .values = "" }),
.stream => |stream| blk: {
// StreamConsumer currently requires a Page. Workers cannot
// produce stream bodies yet, so falling back to the page here
// is safe.
const page = switch (exec.context.global) {
.page => |p| p,
.worker => unreachable,
};
break :blk StreamConsumer.start(stream, page);
},
};
}
@@ -308,20 +330,20 @@ const StreamConsumer = struct {
}
};
pub fn blob(self: *const Response, page: *Page) !js.Promise {
const local = page.js.local.?;
pub fn blob(self: *const Response, exec: *const Execution) !js.Promise {
const local = exec.context.local.?;
const body = switch (self._body) {
.bytes => |b| b,
.empty => "",
.stream => return local.rejectPromise(.{ .type_error = "Cannot read blob from stream body" }),
};
const content_type = try self._headers.get("content-type", page) orelse "";
const b = try Blob.initFromBytes(body, content_type, true, page);
const content_type = try self._headers.get("content-type", exec) orelse "";
const b = try Blob.initFromBytes(body, content_type, true, exec.context.session);
return local.resolvePromise(b);
}
pub fn bytes(self: *const Response, page: *Page) !js.Promise {
const local = page.js.local.?;
pub fn bytes(self: *const Response, exec: *const Execution) !js.Promise {
const local = exec.context.local.?;
const body = switch (self._body) {
.bytes => |b| b,
.empty => "",
@@ -330,14 +352,15 @@ pub fn bytes(self: *const Response, page: *Page) !js.Promise {
return local.resolvePromise(js.TypedArray(u8){ .values = body });
}
pub fn clone(self: *const Response, page: *Page) !*Response {
pub fn clone(self: *const Response, exec: *const Execution) !*Response {
const session = exec.context.session;
const body_len = switch (self._body) {
.bytes => |b| b.len,
.empty => 0,
.stream => 0,
};
const arena = try page.getArena(body_len + self._url.len + 256, "Response.clone");
errdefer page.releaseArena(arena);
const arena = try session.getArena(body_len + self._url.len + 256, "Response.clone");
errdefer session.releaseArena(arena);
const body: Body = switch (self._body) {
.bytes => |b| .{ .bytes = try arena.dupe(u8, b) },
@@ -356,7 +379,7 @@ pub fn clone(self: *const Response, page: *Page) !*Response {
._body = body,
._type = self._type,
._is_redirected = self._is_redirected,
._headers = try Headers.init(.{ .obj = self._headers }, page),
._headers = try Headers.init(.{ .obj = self._headers }, exec),
._http_response = null,
};
return cloned;

View File

@@ -466,7 +466,7 @@ fn dispatchMessageEvent(self: *WebSocket, data: []const u8, frame_type: http.WsF
switch (self._binary_type) {
.arraybuffer => .{ .arraybuffer = .{ .values = data } },
.blob => blk: {
const blob = try Blob.initFromBytes(data, "", false, page);
const blob = try Blob.initFromBytes(data, "", false, page._session);
blob.acquireRef();
break :blk .{ .blob = blob };
},

View File

@@ -95,7 +95,7 @@ pub fn init(page: *Page) !*XMLHttpRequest {
._page = page,
._arena = arena,
._proto = undefined,
._request_headers = try Headers.init(null, page),
._request_headers = try Headers.init(null, &page.js.execution),
});
return self;
}
@@ -218,7 +218,7 @@ pub fn setRequestHeader(self: *XMLHttpRequest, name: []const u8, value: []const
if (self._ready_state != .opened) {
return error.InvalidStateError;
}
return self._request_headers.append(name, value, page);
return self._request_headers.append(name, value, &page.js.execution);
}
pub fn send(self: *XMLHttpRequest, body_: ?[]const u8) !void {