+ +
+And
+diff --git a/build.zig b/build.zig index d7effb26..9f627169 100644 --- a/build.zig +++ b/build.zig @@ -112,6 +112,33 @@ pub fn build(b: *Build) !void { test_step.dependOn(&run_tests.step); } + { + // ZIGDOM + // browser + const exe = b.addExecutable(.{ + .name = "legacy_test", + .use_llvm = true, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main_legacy_test.zig"), + .target = target, + .optimize = optimize, + .sanitize_c = enable_csan, + .sanitize_thread = enable_tsan, + .imports = &.{ + .{.name = "lightpanda", .module = lightpanda_module}, + }, + }), + }); + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("legacy_test", "Run the app"); + run_step.dependOn(&run_cmd.step); + } + { // wpt const exe = b.addExecutable(.{ diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 5b6b510b..72ce1bef 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -615,6 +615,8 @@ pub fn mapZigInstanceToJs(self: *Context, js_obj_: ?v8.Object, value: anytype) ! } const isolate = self.isolate; + const JsApi = bridge.Struct(ptr.child).JsApi; + // Sometimes we're creating a new v8.Object, like when // we're returning a value from a function. In those cases // we have to get the object template, and we can get an object @@ -626,19 +628,26 @@ pub fn mapZigInstanceToJs(self: *Context, js_obj_: ?v8.Object, value: anytype) ! const template = self.templates[resolved.class_id]; break :blk template.getInstanceTemplate().initInstance(v8_context); }; - const JsApi = bridge.Struct(ptr.child).JsApi; - // The TAO contains the pointer to our Zig instance as - // well as any meta data we'll need to use it later. - // See the TaggedAnyOpaque struct for more details. - const tao = try arena.create(TaggedAnyOpaque); - tao.* = .{ - .value = resolved.ptr, - .prototype_chain = resolved.prototype_chain.ptr, - .prototype_len = @intCast(resolved.prototype_chain.len), - .subtype = if (@hasDecl(JsApi.Meta, "subtype")) JsApi.Meta.subype else .node, - }; - js_obj.setInternalField(0, v8.External.init(isolate, tao)); + if (!@hasDecl(JsApi.Meta, "empty_with_no_proto")) { + // The TAO contains the pointer to our Zig instance as + // well as any meta data we'll need to use it later. + // See the TaggedAnyOpaque struct for more details. + const tao = try arena.create(TaggedAnyOpaque); + tao.* = .{ + .value = resolved.ptr, + .prototype_chain = resolved.prototype_chain.ptr, + .prototype_len = @intCast(resolved.prototype_chain.len), + .subtype = if (@hasDecl(JsApi.Meta, "subtype")) JsApi.Meta.subype else .node, + }; + js_obj.setInternalField(0, v8.External.init(isolate, tao)); + } else { + // If the struct is empty, we don't need to do all + // the TOA stuff and setting the internal data. + // When we try to map this from JS->Zig, in + // typeTaggedAnyOpaque, we'll also know there that + // the type is empty and can create an empty instance. + } const js_persistent = PersistentObject.init(isolate, js_obj); gop.value_ptr.* = js_persistent; @@ -1504,6 +1513,15 @@ pub fn typeTaggedAnyOpaque(comptime R: type, js_obj: v8.Object) !R { } const T = ti.pointer.child; + const JsApi = bridge.Struct(T).JsApi; + + if (@hasDecl(JsApi.Meta, "empty_with_no_proto")) { + // Empty structs aren't stored as TOAs and there's no data + // stored in the JSObject's IntenrnalField. Why bother when + // we can just return an empty struct here? + return @constCast(@as(*const T, &.{})); + } + // if it isn't an empty struct, then the v8.Object should have an // InternalFieldCount > 0, since our toa pointer should be embedded // at index 0 of the internal field count. @@ -1511,7 +1529,7 @@ pub fn typeTaggedAnyOpaque(comptime R: type, js_obj: v8.Object) !R { return error.InvalidArgument; } - const type_name = @typeName(bridge.Struct(T).JsApi); + const type_name = @typeName(JsApi); if (@hasField(bridge.JsApiLookup, type_name) == false) { @compileError("unknown Zig type: " ++ @typeName(R)); } diff --git a/src/browser/js/Env.zig b/src/browser/js/Env.zig index 71bed313..d75afd4b 100644 --- a/src/browser/js/Env.zig +++ b/src/browser/js/Env.zig @@ -314,7 +314,9 @@ fn generateConstructor(comptime JsApi: type, isolate: v8.Isolate) v8.FunctionTem }; const template = v8.FunctionTemplate.initCallback(isolate, callback); - template.getInstanceTemplate().setInternalFieldCount(1); + if (!@hasDecl(JsApi.Meta, "empty_with_no_proto")) { + template.getInstanceTemplate().setInternalFieldCount(1); + } const class_name = v8.String.initUtf8(isolate, if (@hasDecl(JsApi.Meta, "name")) JsApi.Meta.name else @typeName(JsApi)); template.setClassName(class_name); return template; diff --git a/src/browser/tests/domparser.html b/src/browser/tests/domparser.html index 390f7bfe..66014388 100644 --- a/src/browser/tests/domparser.html +++ b/src/browser/tests/domparser.html @@ -1,13 +1,13 @@ - + - diff --git a/src/browser/tests/legacy/browser.html b/src/browser/tests/legacy/browser.html new file mode 100644 index 00000000..1f60488b --- /dev/null +++ b/src/browser/tests/legacy/browser.html @@ -0,0 +1,10 @@ + + + diff --git a/src/browser/tests/legacy/crypto.html b/src/browser/tests/legacy/crypto.html new file mode 100644 index 00000000..f1dc291a --- /dev/null +++ b/src/browser/tests/legacy/crypto.html @@ -0,0 +1,26 @@ + + + diff --git a/src/browser/tests/legacy/css.html b/src/browser/tests/legacy/css.html new file mode 100644 index 00000000..3f83e934 --- /dev/null +++ b/src/browser/tests/legacy/css.html @@ -0,0 +1,6 @@ + + + diff --git a/src/browser/tests/legacy/cssom/css_rule_list.html b/src/browser/tests/legacy/cssom/css_rule_list.html new file mode 100644 index 00000000..577781e4 --- /dev/null +++ b/src/browser/tests/legacy/cssom/css_rule_list.html @@ -0,0 +1,8 @@ + + + diff --git a/src/browser/tests/legacy/cssom/css_style_declaration.html b/src/browser/tests/legacy/cssom/css_style_declaration.html new file mode 100644 index 00000000..ee4d3cd9 --- /dev/null +++ b/src/browser/tests/legacy/cssom/css_style_declaration.html @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + diff --git a/src/browser/tests/legacy/cssom/css_stylesheet.html b/src/browser/tests/legacy/cssom/css_stylesheet.html new file mode 100644 index 00000000..223ee2cd --- /dev/null +++ b/src/browser/tests/legacy/cssom/css_stylesheet.html @@ -0,0 +1,16 @@ + + + diff --git a/src/browser/tests/legacy/dom/animation.html b/src/browser/tests/legacy/dom/animation.html new file mode 100644 index 00000000..27e562a0 --- /dev/null +++ b/src/browser/tests/legacy/dom/animation.html @@ -0,0 +1,15 @@ + + + + diff --git a/src/browser/tests/legacy/dom/attribute.html b/src/browser/tests/legacy/dom/attribute.html new file mode 100644 index 00000000..2e208861 --- /dev/null +++ b/src/browser/tests/legacy/dom/attribute.html @@ -0,0 +1,33 @@ + + + +OK + + diff --git a/src/browser/tests/legacy/dom/character_data.html b/src/browser/tests/legacy/dom/character_data.html new file mode 100644 index 00000000..ff74da90 --- /dev/null +++ b/src/browser/tests/legacy/dom/character_data.html @@ -0,0 +1,48 @@ + + + +OK + + diff --git a/src/browser/tests/legacy/dom/comment.html b/src/browser/tests/legacy/dom/comment.html new file mode 100644 index 00000000..2f87846c --- /dev/null +++ b/src/browser/tests/legacy/dom/comment.html @@ -0,0 +1,9 @@ + + + diff --git a/src/browser/tests/legacy/dom/document.html b/src/browser/tests/legacy/dom/document.html new file mode 100644 index 00000000..950daaab --- /dev/null +++ b/src/browser/tests/legacy/dom/document.html @@ -0,0 +1,190 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/browser/tests/legacy/dom/document_fragment.html b/src/browser/tests/legacy/dom/document_fragment.html new file mode 100644 index 00000000..ff02b3a4 --- /dev/null +++ b/src/browser/tests/legacy/dom/document_fragment.html @@ -0,0 +1,34 @@ + + + + + diff --git a/src/browser/tests/legacy/dom/document_type.html b/src/browser/tests/legacy/dom/document_type.html new file mode 100644 index 00000000..ff7cdbc8 --- /dev/null +++ b/src/browser/tests/legacy/dom/document_type.html @@ -0,0 +1,13 @@ + + + diff --git a/src/browser/tests/legacy/dom/dom_parser.html b/src/browser/tests/legacy/dom/dom_parser.html new file mode 100644 index 00000000..bf9bec8a --- /dev/null +++ b/src/browser/tests/legacy/dom/dom_parser.html @@ -0,0 +1,7 @@ + + + diff --git a/src/browser/tests/legacy/dom/element.html b/src/browser/tests/legacy/dom/element.html new file mode 100644 index 00000000..3255b7d2 --- /dev/null +++ b/src/browser/tests/legacy/dom/element.html @@ -0,0 +1,341 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +content
+And
And
And
+ I am the original reference node. +
+over 9000
+ + + diff --git a/src/browser/tests/legacy/encoding/decoder.html b/src/browser/tests/legacy/encoding/decoder.html new file mode 100644 index 00000000..8a93dc46 --- /dev/null +++ b/src/browser/tests/legacy/encoding/decoder.html @@ -0,0 +1,60 @@ + + + + + + + + + diff --git a/src/browser/tests/legacy/encoding/encoder.html b/src/browser/tests/legacy/encoding/encoder.html new file mode 100644 index 00000000..affcd575 --- /dev/null +++ b/src/browser/tests/legacy/encoding/encoder.html @@ -0,0 +1,14 @@ + + + diff --git a/src/browser/tests/legacy/events/composition.html b/src/browser/tests/legacy/events/composition.html new file mode 100644 index 00000000..b5a6a710 --- /dev/null +++ b/src/browser/tests/legacy/events/composition.html @@ -0,0 +1,36 @@ + + + + + + + + + diff --git a/src/browser/tests/legacy/events/custom.html b/src/browser/tests/legacy/events/custom.html new file mode 100644 index 00000000..cb6ddd2b --- /dev/null +++ b/src/browser/tests/legacy/events/custom.html @@ -0,0 +1,25 @@ + + + diff --git a/src/browser/tests/legacy/events/event.html b/src/browser/tests/legacy/events/event.html new file mode 100644 index 00000000..752d64ba --- /dev/null +++ b/src/browser/tests/legacy/events/event.html @@ -0,0 +1,139 @@ + + + +
default
default
xx otherdefault2
!!hello, world
+ + diff --git a/src/browser/tests/legacy/polyfill/webcomponents.html b/src/browser/tests/legacy/polyfill/webcomponents.html new file mode 100644 index 00000000..5854bc82 --- /dev/null +++ b/src/browser/tests/legacy/polyfill/webcomponents.html @@ -0,0 +1,23 @@ + + + + + + diff --git a/src/browser/tests/legacy/storage/local_storage.html b/src/browser/tests/legacy/storage/local_storage.html new file mode 100644 index 00000000..4ad0b14f --- /dev/null +++ b/src/browser/tests/legacy/storage/local_storage.html @@ -0,0 +1,29 @@ + + + + diff --git a/src/browser/tests/legacy/streams/readable_stream.html b/src/browser/tests/legacy/streams/readable_stream.html new file mode 100644 index 00000000..a8339cc5 --- /dev/null +++ b/src/browser/tests/legacy/streams/readable_stream.html @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + diff --git a/src/browser/tests/legacy/testing.js b/src/browser/tests/legacy/testing.js new file mode 100644 index 00000000..891d9cc2 --- /dev/null +++ b/src/browser/tests/legacy/testing.js @@ -0,0 +1,206 @@ +// Note: this code tries to make sure that we don't fail to execute a tags we have have had at least + // 1 assertion. This helps ensure that if a script tag fails to execute, + // we'll report an error, even if no assertions failed. + const scripts = document.getElementsByTagName('script'); + for (script of scripts) { + const id = script.id; + if (!id) { + continue; + } + + if (!testing._executed_scripts.has(id)) { + console.warn(`Failed to execute any expectations for `); + throw new Error('Failed'); + } + } + + if (testing._status != 'ok') { + throw new Error(testing._status); + } + } + + // Set expectations to happen at some point in the future. Necessary for + // testing callbacks which will only be executed after page.wait is called. + function eventually(fn) { + // capture the current state (script id, stack) so that, when we do run this + // we can display more meaningful details on failure. + testing._eventually.push([fn, { + script_id: document.currentScript.id, + stack: new Error().stack, + }]); + + _registerErrorCallback(); + } + + async function async(promise, cb) { + const script_id = document.currentScript ? document.currentScript.id : '.\n There should be a eval error printed above this.`, + ); + } + } + + function _equal(a, b) { + if (a === b) { + return true; + } + if (a === null || b === null) { + return false; + } + if (typeof a !== 'object' || typeof b !== 'object') { + return false; + } + + if (Object.keys(a).length != Object.keys(b).length) { + return false; + } + + for (property in a) { + if (b.hasOwnProperty(property) === false) { + return false; + } + if (_equal(a[property], b[property]) === false) { + return false; + } + } + + return true; + } + + window.testing = { + _status: 'empty', + _eventually: [], + _executed_scripts: new Set(), + _captured: null, + skip: skip, + async: async, + assertOk: assertOk, + eventually: eventually, + expectEqual: expectEqual, + expectError: expectError, + withError: withError, + }; + + // Helper, so you can do $(sel) in a test + window.$ = function(sel) { + return document.querySelector(sel); + } + + // Helper, so you can do $$(sel) in a test + window.$$ = function(sel) { + return document.querySelectorAll(sel); + } + + if (!console.lp) { + // make this work in the browser + console.lp = console.log; + } +})(); diff --git a/src/browser/tests/legacy/url/url.html b/src/browser/tests/legacy/url/url.html new file mode 100644 index 00000000..ef770e46 --- /dev/null +++ b/src/browser/tests/legacy/url/url.html @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + diff --git a/src/browser/tests/legacy/url/url_search_params.html b/src/browser/tests/legacy/url/url_search_params.html new file mode 100644 index 00000000..03f22bcd --- /dev/null +++ b/src/browser/tests/legacy/url/url_search_params.html @@ -0,0 +1,94 @@ + + + + + diff --git a/src/browser/tests/legacy/window/frames.html b/src/browser/tests/legacy/window/frames.html new file mode 100644 index 00000000..fc4b7abc --- /dev/null +++ b/src/browser/tests/legacy/window/frames.html @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/src/browser/tests/legacy/window/window.html b/src/browser/tests/legacy/window/window.html new file mode 100644 index 00000000..aac91171 --- /dev/null +++ b/src/browser/tests/legacy/window/window.html @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/browser/tests/legacy/xhr/form_data.html b/src/browser/tests/legacy/xhr/form_data.html new file mode 100644 index 00000000..94bf8a27 --- /dev/null +++ b/src/browser/tests/legacy/xhr/form_data.html @@ -0,0 +1,130 @@ + + + + + + + + diff --git a/src/browser/tests/legacy/xhr/progress_event.html b/src/browser/tests/legacy/xhr/progress_event.html new file mode 100644 index 00000000..4b7f5df4 --- /dev/null +++ b/src/browser/tests/legacy/xhr/progress_event.html @@ -0,0 +1,17 @@ + + + diff --git a/src/browser/tests/legacy/xhr/xhr.html b/src/browser/tests/legacy/xhr/xhr.html new file mode 100644 index 00000000..13ab6216 --- /dev/null +++ b/src/browser/tests/legacy/xhr/xhr.html @@ -0,0 +1,110 @@ + + + + + + + + + + + diff --git a/src/browser/tests/legacy/xmlserializer.html b/src/browser/tests/legacy/xmlserializer.html new file mode 100644 index 00000000..0d3d4628 --- /dev/null +++ b/src/browser/tests/legacy/xmlserializer.html @@ -0,0 +1,8 @@ + + +And
+ diff --git a/src/browser/webapi/DOMParser.zig b/src/browser/webapi/DOMParser.zig index 35831295..051f004b 100644 --- a/src/browser/webapi/DOMParser.zig +++ b/src/browser/webapi/DOMParser.zig @@ -24,8 +24,6 @@ const Document = @import("Document.zig"); const HTMLDocument = @import("HTMLDocument.zig"); const DOMParser = @This(); -// @ZIGDOM support empty structs -_: u8 = 0, pub fn init() DOMParser { return .{}; @@ -63,6 +61,7 @@ pub const JsApi = struct { pub const name = "DOMParser"; pub const prototype_chain = bridge.prototypeChain(); pub var class_id: bridge.ClassId = undefined; + pub const empty_with_no_proto = true; }; pub const constructor = bridge.constructor(DOMParser.init, .{}); diff --git a/src/browser/webapi/NodeFilter.zig b/src/browser/webapi/NodeFilter.zig index c9fab415..232355dc 100644 --- a/src/browser/webapi/NodeFilter.zig +++ b/src/browser/webapi/NodeFilter.zig @@ -85,6 +85,7 @@ pub const JsApi = struct { pub const name = "NodeFilter"; pub const prototype_chain = bridge.prototypeChain(); pub var class_id: bridge.ClassId = undefined; + pub const empty_with_no_proto = true; }; pub const FILTER_ACCEPT = bridge.property(NodeFilter.FILTER_ACCEPT); diff --git a/src/lightpanda.zig b/src/lightpanda.zig index 57d27793..9c15f722 100644 --- a/src/lightpanda.zig +++ b/src/lightpanda.zig @@ -19,8 +19,12 @@ const std = @import("std"); pub const App = @import("App.zig"); pub const Server = @import("Server.zig"); +pub const Page = @import("browser/Page.zig"); +pub const Browser = @import("browser/Browser.zig"); +pub const Session = @import("browser/Session.zig"); pub const log = @import("log.zig"); +pub const js = @import("browser/js/js.zig"); pub const dump = @import("browser/dump.zig"); pub const build_config = @import("build_config"); @@ -32,7 +36,6 @@ pub const FetchOpts = struct { writer: ?*std.Io.Writer = null, }; pub fn fetch(app: *App, url: [:0]const u8, opts: FetchOpts) !void { - const Browser = @import("browser/Browser.zig"); var browser = try Browser.init(app); defer browser.deinit(); diff --git a/src/main_legacy_test.zig b/src/main_legacy_test.zig new file mode 100644 index 00000000..4dc93e7e --- /dev/null +++ b/src/main_legacy_test.zig @@ -0,0 +1,238 @@ +const std = @import("std"); +const lp = @import("lightpanda"); + +const Allocator = std.mem.Allocator; + +// used in custom panic handler +var current_test: ?[]const u8 = null; + +pub fn main() !void { + var gpa: std.heap.DebugAllocator(.{}) = .init; + defer _ = gpa.deinit(); + + const allocator = gpa.allocator(); + + var http_server = try TestHTTPServer.init(); + defer http_server.deinit(); + + { + var wg: std.Thread.WaitGroup = .{}; + wg.startMany(1); + var thrd = try std.Thread.spawn(.{}, TestHTTPServer.run, .{ &http_server, &wg }); + thrd.detach(); + wg.wait(); + } + lp.log.opts.level = .warn; + + var app = try lp.App.init(allocator, .{ + .run_mode = .serve, + .tls_verify_host = false, + .user_agent = "User-Agent: Lightpanda/1.0 internal-tester", + }); + defer app.deinit(); + + var test_arena = std.heap.ArenaAllocator.init(allocator); + defer test_arena.deinit(); + + var browser = try lp.Browser.init(app); + defer browser.deinit(); + + const session = try browser.newSession(); + + var dir = try std.fs.cwd().openDir("src/browser/tests/legacy/", .{ .iterate = true, .no_follow = true }); + defer dir.close(); + var walker = try dir.walk(allocator); + defer walker.deinit(); + while (try walker.next()) |entry| { + _ = test_arena.reset(.retain_capacity); + if (entry.kind != .file) { + continue; + } + + if (!std.mem.endsWith(u8, entry.basename, ".html")) { + continue; + } + std.debug.print("\n===={s}====\n", .{entry.path}); + current_test = entry.path; + run(test_arena.allocator(), entry.path, session) catch |err| { + std.debug.print("Failure: {s} - {any}\n", .{ entry.path, err }); + }; + } +} + +pub fn run(allocator: Allocator, file: []const u8, session: *lp.Session) !void { + const url = try std.fmt.allocPrintSentinel(allocator, "http://localhost:9589/{s}", .{file}, 0); + + const page = try session.createPage(); + defer session.removePage(); + + const js_context = page.js; + var try_catch: lp.js.TryCatch = undefined; + try_catch.init(js_context); + defer try_catch.deinit(); + + try page.navigate(url, .{}); + session.fetchWait(2000); + + page._session.browser.runMicrotasks(); + page._session.browser.runMessageLoop(); + + js_context.eval("testing.assertOk()", "testing.assertOk()") catch |err| { + const msg = try_catch.err(allocator) catch @errorName(err) orelse "unknown"; + + std.debug.print("{s}: test failure\nError: {s}\n", .{ file, msg }); + return err; + }; +} + +const TestHTTPServer = struct { + shutdown: bool, + dir: std.fs.Dir, + listener: ?std.net.Server, + + pub fn init() !TestHTTPServer { + return .{ + .dir = try std.fs.cwd().openDir("src/browser/tests/legacy/", .{}), + .shutdown = true, + .listener = null, + }; + } + + pub fn deinit(self: *TestHTTPServer) void { + self.shutdown = true; + if (self.listener) |*listener| { + listener.deinit(); + } + self.dir.close(); + } + + pub fn run(self: *TestHTTPServer, wg: *std.Thread.WaitGroup) !void { + const address = try std.net.Address.parseIp("127.0.0.1", 9589); + + self.listener = try address.listen(.{ .reuse_address = true }); + var listener = &self.listener.?; + + wg.finish(); + + while (true) { + const conn = listener.accept() catch |err| { + if (self.shutdown) { + return; + } + return err; + }; + const thrd = try std.Thread.spawn(.{}, handleConnection, .{ self, conn }); + thrd.detach(); + } + } + + fn handleConnection(self: *TestHTTPServer, conn: std.net.Server.Connection) !void { + defer conn.stream.close(); + + var req_buf: [2048]u8 = undefined; + var conn_reader = conn.stream.reader(&req_buf); + var conn_writer = conn.stream.writer(&req_buf); + + var http_server = std.http.Server.init(conn_reader.interface(), &conn_writer.interface); + + while (true) { + var req = http_server.receiveHead() catch |err| switch (err) { + error.ReadFailed => continue, + error.HttpConnectionClosing => continue, + else => { + std.debug.print("Test HTTP Server error: {}\n", .{err}); + return err; + }, + }; + + self.handler(&req) catch |err| { + std.debug.print("test http error '{s}': {}\n", .{ req.head.target, err }); + try req.respond("server error", .{ .status = .internal_server_error }); + return; + }; + } + } + + fn handler(server: *TestHTTPServer, req: *std.http.Server.Request) !void { + const path = req.head.target; + + // strip out leading '/' to make the path relative + const file = try server.dir.openFile(path[1..], .{}); + defer file.close(); + + const stat = try file.stat(); + var send_buffer: [4096]u8 = undefined; + + var res = try req.respondStreaming(&send_buffer, .{ + .content_length = stat.size, + .respond_options = .{ + .extra_headers = &.{ + .{ .name = "content-type", .value = getContentType(path) }, + }, + }, + }); + + var read_buffer: [4096]u8 = undefined; + var reader = file.reader(&read_buffer); + _ = try res.writer.sendFileAll(&reader, .unlimited); + try res.writer.flush(); + try res.end(); + } + + pub fn sendFile(req: *std.http.Server.Request, file_path: []const u8) !void { + var file = std.fs.cwd().openFile(file_path, .{}) catch |err| switch (err) { + error.FileNotFound => return req.respond("server error", .{ .status = .not_found }), + else => return err, + }; + defer file.close(); + + const stat = try file.stat(); + var send_buffer: [4096]u8 = undefined; + + var res = try req.respondStreaming(&send_buffer, .{ + .content_length = stat.size, + .respond_options = .{ + .extra_headers = &.{ + .{ .name = "content-type", .value = getContentType(file_path) }, + }, + }, + }); + + var read_buffer: [4096]u8 = undefined; + var reader = file.reader(&read_buffer); + _ = try res.writer.sendFileAll(&reader, .unlimited); + try res.writer.flush(); + try res.end(); + } + + fn getContentType(file_path: []const u8) []const u8 { + if (std.mem.endsWith(u8, file_path, ".js")) { + return "application/json"; + } + + if (std.mem.endsWith(u8, file_path, ".html")) { + return "text/html"; + } + + if (std.mem.endsWith(u8, file_path, ".htm")) { + return "text/html"; + } + + if (std.mem.endsWith(u8, file_path, ".xml")) { + // some wpt tests do this + return "text/xml"; + } + + std.debug.print("TestHTTPServer asked to serve an unknown file type: {s}\n", .{file_path}); + return "text/html"; + } +}; + +pub const panic = std.debug.FullPanic(struct { + pub fn panicFn(msg: []const u8, first_trace_addr: ?usize) noreturn { + if (current_test) |ct| { + std.debug.print("===panic running: {s}===\n", .{ct}); + } + std.debug.defaultPanic(msg, first_trace_addr); + } +}.panicFn); diff --git a/tests/html/bug-html-parsing-4.html b/tests/html/bug-html-parsing-4.html deleted file mode 100644 index 391ac0c7..00000000 --- a/tests/html/bug-html-parsing-4.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - -