mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
Implement XMLHttpRequest.upload
Return an XMLHttpRequestUpload (inheriting XMLHttpRequestEventTarget) from the lazily-cached `upload` attribute. Fixes htmx login flows that called `xhr.upload.addEventListener(...)`.
This commit is contained in:
@@ -949,6 +949,7 @@ pub const PageJsApis = flattenTypes(&.{
|
||||
@import("../webapi/net/URLSearchParams.zig"),
|
||||
@import("../webapi/net/XMLHttpRequest.zig"),
|
||||
@import("../webapi/net/XMLHttpRequestEventTarget.zig"),
|
||||
@import("../webapi/net/XMLHttpRequestUpload.zig"),
|
||||
@import("../webapi/net/WebSocket.zig"),
|
||||
@import("../webapi/event/CloseEvent.zig"),
|
||||
@import("../webapi/streams/ReadableStream.zig"),
|
||||
@@ -1036,6 +1037,7 @@ pub const WorkerJsApis = flattenTypes(&.{
|
||||
@import("../webapi/canvas/OffscreenCanvasRenderingContext2D.zig"),
|
||||
@import("../webapi/net/XMLHttpRequest.zig"),
|
||||
@import("../webapi/net/XMLHttpRequestEventTarget.zig"),
|
||||
@import("../webapi/net/XMLHttpRequestUpload.zig"),
|
||||
@import("../webapi/net/WebSocket.zig"),
|
||||
@import("../webapi/FileReader.zig"),
|
||||
@import("../webapi/ImageData.zig"),
|
||||
|
||||
@@ -329,3 +329,22 @@
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr_upload type=module>
|
||||
{
|
||||
// upload is an XMLHttpRequestUpload (an XMLHttpRequestEventTarget) and
|
||||
// exposes addEventListener; htmx relies on this not throwing.
|
||||
const req = new XMLHttpRequest();
|
||||
testing.expectEqual('XMLHttpRequestUpload', req.upload.constructor.name);
|
||||
testing.expectEqual(true, req.upload instanceof XMLHttpRequestEventTarget);
|
||||
testing.expectEqual(true, req.upload instanceof EventTarget);
|
||||
testing.expectEqual('function', typeof req.upload.addEventListener);
|
||||
|
||||
// The same instance is returned on every access so listeners stick.
|
||||
testing.expectEqual(req.upload, req.upload);
|
||||
|
||||
let registered = false;
|
||||
req.upload.addEventListener('progress', () => { registered = true; });
|
||||
testing.expectEqual(false, registered);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -34,6 +34,7 @@ const EventTarget = @import("../EventTarget.zig");
|
||||
const Headers = @import("Headers.zig");
|
||||
const BodyInit = @import("body_init.zig").BodyInit;
|
||||
const XMLHttpRequestEventTarget = @import("XMLHttpRequestEventTarget.zig");
|
||||
const XMLHttpRequestUpload = @import("XMLHttpRequestUpload.zig");
|
||||
|
||||
const log = lp.log;
|
||||
const Execution = js.Execution;
|
||||
@@ -44,6 +45,7 @@ const XMLHttpRequest = @This();
|
||||
_rc: lp.RC(u8) = .{},
|
||||
_exec: *const Execution,
|
||||
_proto: *XMLHttpRequestEventTarget,
|
||||
_upload: ?*XMLHttpRequestUpload = null,
|
||||
_arena: Allocator,
|
||||
_http_response: ?HttpClient.Response = null,
|
||||
_active_request: bool = false,
|
||||
@@ -112,29 +114,9 @@ pub fn deinit(self: *XMLHttpRequest, page: *Page) void {
|
||||
func.release();
|
||||
}
|
||||
|
||||
{
|
||||
const proto = self._proto;
|
||||
if (proto._on_abort) |func| {
|
||||
func.release();
|
||||
}
|
||||
if (proto._on_error) |func| {
|
||||
func.release();
|
||||
}
|
||||
if (proto._on_load) |func| {
|
||||
func.release();
|
||||
}
|
||||
if (proto._on_load_end) |func| {
|
||||
func.release();
|
||||
}
|
||||
if (proto._on_load_start) |func| {
|
||||
func.release();
|
||||
}
|
||||
if (proto._on_progress) |func| {
|
||||
func.release();
|
||||
}
|
||||
if (proto._on_timeout) |func| {
|
||||
func.release();
|
||||
}
|
||||
self._proto.releaseListeners();
|
||||
if (self._upload) |upload| {
|
||||
upload._proto.releaseListeners();
|
||||
}
|
||||
|
||||
page.releaseArena(self._arena);
|
||||
@@ -289,6 +271,18 @@ pub fn send(self: *XMLHttpRequest, body_: ?BodyInit, exec_: *const Execution) !v
|
||||
};
|
||||
}
|
||||
|
||||
// https://xhr.spec.whatwg.org/#the-upload-attribute
|
||||
// The XMLHttpRequestUpload object is created lazily and cached: scripts expect
|
||||
// the same instance on every access so their event listeners stick.
|
||||
pub fn getUpload(self: *XMLHttpRequest) !*XMLHttpRequestUpload {
|
||||
if (self._upload) |upload| {
|
||||
return upload;
|
||||
}
|
||||
const upload = try self._exec._factory.xhrEventTarget(self._arena, XMLHttpRequestUpload{ ._proto = undefined });
|
||||
self._upload = upload;
|
||||
return upload;
|
||||
}
|
||||
|
||||
pub fn getReadyState(self: *const XMLHttpRequest) u32 {
|
||||
return @intFromEnum(self._ready_state);
|
||||
}
|
||||
@@ -611,6 +605,7 @@ pub const JsApi = struct {
|
||||
pub const DONE = bridge.property(@intFromEnum(XMLHttpRequest.ReadyState.done), .{ .template = true });
|
||||
|
||||
pub const onreadystatechange = bridge.accessor(XMLHttpRequest.getOnReadyStateChange, XMLHttpRequest.setOnReadyStateChange, .{});
|
||||
pub const upload = bridge.accessor(XMLHttpRequest.getUpload, null, .{});
|
||||
pub const timeout = bridge.accessor(XMLHttpRequest.getTimeout, XMLHttpRequest.setTimeout, .{});
|
||||
pub const withCredentials = bridge.accessor(XMLHttpRequest.getWithCredentials, XMLHttpRequest.setWithCredentials, .{ .dom_exception = true });
|
||||
pub const open = bridge.function(XMLHttpRequest.open, .{});
|
||||
|
||||
@@ -37,13 +37,24 @@ _on_timeout: ?js.Function.Temp = null,
|
||||
|
||||
pub const Type = union(enum) {
|
||||
request: *@import("XMLHttpRequest.zig"),
|
||||
// TODO: xml_http_request_upload
|
||||
upload: *@import("XMLHttpRequestUpload.zig"),
|
||||
};
|
||||
|
||||
pub fn asEventTarget(self: *XMLHttpRequestEventTarget) *EventTarget {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
pub fn releaseListeners(self: *XMLHttpRequestEventTarget) void {
|
||||
inline for (.{
|
||||
"_on_abort", "_on_error", "_on_load", "_on_load_end",
|
||||
"_on_load_start", "_on_progress", "_on_timeout",
|
||||
}) |field| {
|
||||
if (@field(self, field)) |func| {
|
||||
func.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchType, progress_: ?Progress, exec: *const Execution) !void {
|
||||
const field, const typ = comptime blk: {
|
||||
break :blk switch (event_type) {
|
||||
|
||||
46
src/browser/webapi/net/XMLHttpRequestUpload.zig
Normal file
46
src/browser/webapi/net/XMLHttpRequestUpload.zig
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
|
||||
const EventTarget = @import("../EventTarget.zig");
|
||||
const XMLHttpRequestEventTarget = @import("XMLHttpRequestEventTarget.zig");
|
||||
|
||||
// https://xhr.spec.whatwg.org/#xmlhttprequestupload
|
||||
//
|
||||
// Returned by XMLHttpRequest.upload. It only inherits from
|
||||
// XMLHttpRequestEventTarget; it has no members of its own. We don't yet emit
|
||||
// upload progress events, but the object still needs to exist so scripts (e.g.
|
||||
// htmx) can call addEventListener on it without throwing.
|
||||
const XMLHttpRequestUpload = @This();
|
||||
|
||||
_proto: *XMLHttpRequestEventTarget,
|
||||
|
||||
pub fn asEventTarget(self: *XMLHttpRequestUpload) *EventTarget {
|
||||
return self._proto.asEventTarget();
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(XMLHttpRequestUpload);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "XMLHttpRequestUpload";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user