From 02df0dc2879ed0ca4750e1311591fb4746741595 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Tue, 26 May 2026 13:41:03 +0800 Subject: [PATCH 01/13] Unify Data URLs Previously, the logic for data/blob URLs were spread out at every http_client caller. fetch/XHR/ScriptManager all had their own "if this is a data url, ..." logic. This causes two issues. 1 - Duplication, particularly as we try to cover more edge cases that need to be handled in all places. 2 - Correctness because data/blob URLs are still URLs and still need to be "fetched" (from memory). They should still fire with the same timing as any other URL. That means that for fetch/XHR, they should fire asynchronously (i.e. on the next tick). And for ScriptManager they should fire depending on the type of script (normal/defer/async). This PR relies on the infrastructure added to: https://github.com/lightpanda-io/browser/pull/2506 in order to fulfilled a synthetic response on the next tick. Frame.navigate is excluded from this refactor. For one, about:blank must be special-cased and run synchronously (one of the few places where this is strictly required) and even blob URLs are a bit different: the blob URL list is the parent frame, not self, and there's more we need to do (set origin). Potentially there _is_ some improvement here, but it's both less significant and less simple. --- src/browser/Frame.zig | 6 +- src/browser/HttpClient.zig | 93 ++++++++ src/browser/ScriptManager.zig | 73 +------ src/browser/data_url.zig | 202 ++++++++++++++++++ src/browser/js/Execution.zig | 7 - .../tests/element/html/script/data_url.html | 29 +++ src/browser/tests/frames/data_url_iframe.html | 23 ++ src/browser/tests/worker/worker.html | 22 ++ src/browser/webapi/Worker.zig | 11 - src/browser/webapi/WorkerGlobalScope.zig | 6 +- src/browser/webapi/net/Fetch.zig | 25 --- src/browser/webapi/net/XMLHttpRequest.zig | 36 ---- 12 files changed, 377 insertions(+), 156 deletions(-) create mode 100644 src/browser/data_url.zig create mode 100644 src/browser/tests/element/html/script/data_url.html create mode 100644 src/browser/tests/frames/data_url_iframe.html diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index 49d82d07..500e6b8e 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -294,6 +294,7 @@ pub fn init(self: *Frame, frame_id: u32, page: *Page, parent: ?*Frame) !void { ._event_manager = EventManager.init(arena, self), }; self._to_load = &self._to_load_1; + self._http_owner.blob_urls = &self._blob_urls; var screen: *Screen = undefined; var visual_viewport: *VisualViewport = undefined; @@ -507,11 +508,6 @@ pub fn isSameOrigin(self: *const Frame, url: [:0]const u8) bool { return std.mem.eql(u8, URL.getHost(url), URL.getHost(current_origin)); } -/// Look up a blob URL in this frame's registry. -pub fn lookupBlobUrl(self: *Frame, url: []const u8) ?*Blob { - return self._blob_urls.get(url); -} - pub fn navigate(self: *Frame, request_url: [:0]const u8, opts: NavigateOpts) !void { lp.assert(self._load_state == .waiting, "frame.renavigate", .{}); const session = self._session; diff --git a/src/browser/HttpClient.zig b/src/browser/HttpClient.zig index d73a37a4..ad5cd412 100644 --- a/src/browser/HttpClient.zig +++ b/src/browser/HttpClient.zig @@ -567,6 +567,21 @@ fn requestT(self: *Client, req: Request, owner: ?*Owner) !*Transfer { // via transfer.abort which fires error_callback and deinits. `.created` // means no commit happened — anything else is held by an owner that // will clean up. + + // Synthetic schemes never touch the network or the layer chain — they skip + // robots/cache/interception and deliver on the next tick + if (Synthetic.isSynthetic(req.url)) { + // The 2nd transfer is the callback context. We don't actually use it, + // we're just sticking transfer in there to have something. + self.runNextTick(transfer, transfer, .{ .run = Synthetic.run }) catch |err| { + if (transfer.state == .created) { + transfer.abort(err); + } + return err; + }; + return transfer; + } + self.entry_layer.request(transfer) catch |err| { if (transfer.state == .created) { transfer.abort(err); @@ -577,6 +592,80 @@ fn requestT(self: *Client, req: Request, owner: ?*Owner) !*Transfer { return transfer; } +// Non-network URL schemes whose response is synthesized in-process rather than +// fetched, think blob data URLs. +const Synthetic = struct { + const data_url = @import("data_url.zig"); + + fn isSynthetic(url: []const u8) bool { + return std.mem.startsWith(u8, url, "data:") or std.mem.startsWith(u8, url, "blob:"); + } + + fn run(transfer: *Transfer, _: *anyopaque) void { + defer transfer.deinit(); + + const fulfilled = build(transfer) catch |err| { + transfer.req.error_callback(transfer.req.ctx, err); + return; + }; + deliver(&transfer.req, &fulfilled) catch |err| { + transfer.req.error_callback(transfer.req.ctx, err); + }; + } + + fn build(transfer: *Transfer) !FulfilledResponse { + const arena = transfer.arena; + const url = transfer.req.url; + + var body: []const u8 = ""; + var content_type: []const u8 = ""; + + if (std.mem.startsWith(u8, url, "data:")) { + const parsed = try data_url.parse(arena, url); + content_type = parsed.content_type; + body = parsed.body; + } else { + // blob: — resolved against the owning frame's registry. + const owner = transfer.owner orelse return error.BlobNotFound; + const blob_urls = owner.blob_urls orelse return error.BlobNotFound; + const blob = blob_urls.get(url) orelse return error.BlobNotFound; + content_type = blob._mime; + body = blob._slice; + } + + // A blob with no type yields no Content-Type header. + const headers = if (content_type.len > 0) blk: { + const h = try arena.alloc(http.Header, 1); + h[0] = .{ .name = "content-type", .value = content_type }; + break :blk h; + } else &[_]http.Header{}; + + return .{ + .url = url, + .body = body, + .status = 200, + .headers = headers, + }; + } + + fn deliver(req: *Request, fulfilled: *const FulfilledResponse) !void { + const response = Response.fromFulfilled(req.ctx, fulfilled); + if (req.start_callback) |cb| { + try cb(response); + } + const proceed = try req.header_callback(response); + if (!proceed) { + return error.Abort; + } + if (fulfilled.body) |b| { + if (b.len > 0) { + try req.data_callback(response, b); + } + } + try req.done_callback(req.ctx); + } +}; + const SyncContext = struct { allocator: Allocator, completion: union(enum) { @@ -1909,7 +1998,11 @@ pub const Owner = struct { transfers: std.DoublyLinkedList = .{}, websockets: std.DoublyLinkedList = .{}, + // The owning Frame's / WorkerGlobalScope's blob: registry, + blob_urls: ?*const std.StringHashMapUnmanaged(*Blob) = null, + const WebSocket = @import("webapi/net/WebSocket.zig"); + const Blob = @import("webapi/Blob.zig"); pub fn addTransfer(self: *Owner, t: *Transfer) void { self.transfers.append(&t.owner_node); diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig index 742a38e2..0c2af244 100644 --- a/src/browser/ScriptManager.zig +++ b/src/browser/ScriptManager.zig @@ -141,12 +141,11 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e var remote_url: ?[:0]const u8 = null; const base_url = frame.base(); if (element.getAttributeSafe(comptime .wrap("src"))) |src| { - if (try parseDataURI(arena, src)) |data_uri| { - source = .{ .@"inline" = data_uri }; - } else { - remote_url = try URL.resolve(arena, base_url, src, .{ .encoding = frame.charset }); - source = .{ .remote = .{} }; - } + // data: and blob: srcs flow through the normal request path; HttpClient + // synthesizes the response. Execution mode (blocking vs async/defer) is + // attribute-driven, the same as any other src. + remote_url = try URL.resolve(arena, base_url, src, .{ .encoding = frame.charset }); + source = .{ .remote = .{} }; } else { var buf = std.Io.Writer.Allocating.init(arena); try element.asNode().getChildTextContent(&buf.writer); @@ -333,65 +332,3 @@ pub fn parseImportmap(self: *ScriptManager, script: *const Script) !void { pub fn staticScriptsDone(self: *ScriptManager) void { self.base.staticScriptsDone(); } - -// Parses data:[][;base64], -fn parseDataURI(allocator: Allocator, src: []const u8) !?[]const u8 { - if (!std.mem.startsWith(u8, src, "data:")) { - return null; - } - - const uri = src[5..]; - const data_starts = std.mem.indexOfScalar(u8, uri, ',') orelse return null; - const data = uri[data_starts + 1 ..]; - - const unescaped = try URL.unescape(allocator, data); - - const metadata = uri[0..data_starts]; - if (std.mem.endsWith(u8, metadata, ";base64") == false) { - return unescaped; - } - - // Forgiving base64 decode per WHATWG spec: - // https://infra.spec.whatwg.org/#forgiving-base64-decode - // Step 1: Remove all ASCII whitespace - var stripped = try std.ArrayList(u8).initCapacity(allocator, unescaped.len); - for (unescaped) |c| { - if (!std.ascii.isWhitespace(c)) { - stripped.appendAssumeCapacity(c); - } - } - const trimmed = std.mem.trimRight(u8, stripped.items, "="); - - // Length % 4 == 1 is invalid - if (trimmed.len % 4 == 1) { - return error.InvalidCharacterError; - } - - const decoded_size = std.base64.standard_no_pad.Decoder.calcSizeForSlice(trimmed) catch return error.InvalidCharacterError; - const buffer = try allocator.alloc(u8, decoded_size); - std.base64.standard_no_pad.Decoder.decode(buffer, trimmed) catch return error.InvalidCharacterError; - return buffer; -} - -const testing = @import("../testing.zig"); -test "DataURI: parse valid" { - try assertValidDataURI("data:text/javascript; charset=utf-8;base64,Zm9v", "foo"); - try assertValidDataURI("data:text/javascript; charset=utf-8;,foo", "foo"); - try assertValidDataURI("data:,foo", "foo"); -} - -test "DataURI: parse invalid" { - try assertInvalidDataURI("atad:,foo"); - try assertInvalidDataURI("data:foo"); - try assertInvalidDataURI("data:"); -} - -fn assertValidDataURI(uri: []const u8, expected: []const u8) !void { - defer testing.reset(); - const data_uri = try parseDataURI(testing.arena_allocator, uri) orelse return error.TestFailed; - try testing.expectEqual(expected, data_uri); -} - -fn assertInvalidDataURI(uri: []const u8) !void { - try testing.expectEqual(null, parseDataURI(undefined, uri)); -} diff --git a/src/browser/data_url.zig b/src/browser/data_url.zig new file mode 100644 index 00000000..4c39d1b1 --- /dev/null +++ b/src/browser/data_url.zig @@ -0,0 +1,202 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// 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 . + +// "data: URL processor" — https://fetch.spec.whatwg.org/#data-url-processor. +// The single home for data: parsing: the HttpClient synthetic-scheme path and +// ScriptManager ( + + + + + + + + + + + + + diff --git a/src/browser/tests/frames/data_url_iframe.html b/src/browser/tests/frames/data_url_iframe.html new file mode 100644 index 00000000..17cbe8bf --- /dev/null +++ b/src/browser/tests/frames/data_url_iframe.html @@ -0,0 +1,23 @@ + + + + diff --git a/src/browser/tests/worker/worker.html b/src/browser/tests/worker/worker.html index 8b15346a..2560de8f 100644 --- a/src/browser/tests/worker/worker.html +++ b/src/browser/tests/worker/worker.html @@ -360,3 +360,25 @@ }); } + + diff --git a/src/browser/webapi/Worker.zig b/src/browser/webapi/Worker.zig index 4c96a0e4..be698018 100644 --- a/src/browser/webapi/Worker.zig +++ b/src/browser/webapi/Worker.zig @@ -25,7 +25,6 @@ const URL = @import("../URL.zig"); const Frame = @import("../Frame.zig"); const HttpClient = @import("../HttpClient.zig"); -const Blob = @import("Blob.zig"); const EventTarget = @import("EventTarget.zig"); const MessageEvent = @import("event/MessageEvent.zig"); const ErrorEvent = @import("event/ErrorEvent.zig"); @@ -89,16 +88,6 @@ pub fn init(url: []const u8, frame: *Frame) !*Worker { return self; } - if (std.mem.startsWith(u8, url, "blob:")) { - errdefer frame.removeWorker(self); - const blob: *Blob = frame.lookupBlobUrl(url) orelse { - log.warn(.js, "invalid blob", .{ .target = "worker" }); - return error.BlobNotFound; - }; - try self.loadInitialScript(blob._slice); - return self; - } - const headers = try session.browser.http_client.newHeaders(); frame.makeRequest(.{ .ctx = self, diff --git a/src/browser/webapi/WorkerGlobalScope.zig b/src/browser/webapi/WorkerGlobalScope.zig index 7744eca0..ee36864d 100644 --- a/src/browser/webapi/WorkerGlobalScope.zig +++ b/src/browser/webapi/WorkerGlobalScope.zig @@ -144,6 +144,8 @@ pub fn init(worker: *Worker, url: [:0]const u8) !*WorkerGlobalScope { }); errdefer factory.destroy(self); + self._http_owner.blob_urls = &self._blob_urls; + self._script_manager = ScriptManagerBase.init( arena, &session.browser.http_client, @@ -231,10 +233,6 @@ pub fn isSameOrigin(self: *const WorkerGlobalScope, url: [:0]const u8) bool { return std.mem.eql(u8, URL.getHost(url), URL.getHost(current_origin)); } -pub fn lookupBlobUrl(self: *WorkerGlobalScope, url: []const u8) ?*Blob { - return self._blob_urls.get(url); -} - pub fn makeRequest(self: *WorkerGlobalScope, req: HttpClient.Request) !void { return self._session.browser.http_client.request(req, &self._http_owner); } diff --git a/src/browser/webapi/net/Fetch.zig b/src/browser/webapi/net/Fetch.zig index f5fd4562..a8f12ff5 100644 --- a/src/browser/webapi/net/Fetch.zig +++ b/src/browser/webapi/net/Fetch.zig @@ -24,7 +24,6 @@ const js = @import("../../js/js.zig"); const Page = @import("../../Page.zig"); const URL = @import("../../URL.zig"); -const Blob = @import("../Blob.zig"); const Request = @import("Request.zig"); const Response = @import("Response.zig"); const AbortSignal = @import("../AbortSignal.zig"); @@ -58,10 +57,6 @@ pub fn init(input: Input, options: ?InitOpts, exec: *const Execution) !js.Promis } } - if (std.mem.startsWith(u8, request._url, "blob:")) { - return handleBlobUrl(request._url, resolver, exec); - } - const response = try Response.init(null, .{ .status = 0 }, exec); errdefer response.deinit(exec.context.page); @@ -121,26 +116,6 @@ pub fn init(input: Input, options: ?InitOpts, exec: *const Execution) !js.Promis return resolver.promise(); } -fn handleBlobUrl(url: []const u8, resolver: js.PromiseResolver, exec: *const Execution) !js.Promise { - const blob: *Blob = exec.lookupBlobUrl(url) orelse { - resolver.rejectError("fetch blob error", .{ .type_error = "BlobNotFound" }); - return resolver.promise(); - }; - - const response = try Response.init(null, .{ .status = 200 }, exec); - 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, exec); - } - - const js_val = try exec.context.local.?.zigValueToJs(response, .{}); - resolver.resolve("fetch blob done", js_val); - return resolver.promise(); -} - fn httpStartCallback(response: HttpClient.Response) !void { const self: *Fetch = @ptrCast(@alignCast(response.ctx)); if (comptime IS_DEBUG) { diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig index 98027202..de6b001d 100644 --- a/src/browser/webapi/net/XMLHttpRequest.zig +++ b/src/browser/webapi/net/XMLHttpRequest.zig @@ -249,10 +249,6 @@ pub fn send(self: *XMLHttpRequest, body_: ?BodyInit, exec_: *const Execution) !v const exec = self._exec; - if (std.mem.startsWith(u8, self._url, "blob:")) { - return self.handleBlobUrl(exec); - } - const session = exec.context.page.session; const http_client = &session.browser.http_client; var headers = try http_client.newHeaders(); @@ -293,38 +289,6 @@ pub fn send(self: *XMLHttpRequest, body_: ?BodyInit, exec_: *const Execution) !v }; } -fn handleBlobUrl(self: *XMLHttpRequest, exec: *const Execution) !void { - const blob = exec.lookupBlobUrl(self._url) orelse { - self.handleError(error.BlobNotFound); - return; - }; - - self._response_status = 200; - self._response_url = self._url; - - try self._response_data.appendSlice(self._arena, blob._slice); - self._response_len = blob._slice.len; - - try self.stateChanged(.headers_received, exec); - try self._proto.dispatch(.load_start, .{ .loaded = 0, .total = self._response_len orelse 0 }, exec); - try self.stateChanged(.loading, exec); - try self._proto.dispatch(.progress, .{ - .total = self._response_len orelse 0, - .loaded = self._response_data.items.len, - }, exec); - try self.stateChanged(.done, exec); - - const loaded = self._response_data.items.len; - try self._proto.dispatch(.load, .{ - .total = loaded, - .loaded = loaded, - }, exec); - try self._proto.dispatch(.load_end, .{ - .total = loaded, - .loaded = loaded, - }, exec); -} - pub fn getReadyState(self: *const XMLHttpRequest) u32 { return @intFromEnum(self._ready_state); } From 479c29816a52c292b76f152125d76d7168288f3c Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 27 May 2026 08:55:04 +0800 Subject: [PATCH 02/13] Remove legacy test AFAIK, these aren't being used and I've personally not have reason to reference / look at them for months. I have no issues though if we want to keep them in. --- build.zig | 36 +-- src/browser/tests/legacy/browser.html | 10 - src/browser/tests/legacy/crypto.html | 26 -- src/browser/tests/legacy/css.html | 6 - .../legacy/cssom/css_style_declaration.html | 101 ------ .../tests/legacy/cssom/css_stylesheet.html | 16 - src/browser/tests/legacy/dom/animation.html | 15 - src/browser/tests/legacy/dom/attribute.html | 33 -- .../tests/legacy/dom/character_data.html | 48 --- src/browser/tests/legacy/dom/comment.html | 9 - src/browser/tests/legacy/dom/document.html | 185 ----------- .../tests/legacy/dom/document_fragment.html | 34 -- .../tests/legacy/dom/document_type.html | 13 - src/browser/tests/legacy/dom/dom_parser.html | 7 - src/browser/tests/legacy/dom/element.html | 304 ------------------ .../tests/legacy/dom/event_target.html | 116 ------- src/browser/tests/legacy/dom/exceptions.html | 39 --- .../tests/legacy/dom/html_collection.html | 67 ---- .../tests/legacy/dom/implementation.html | 14 - .../legacy/dom/intersection_observer.html | 163 ---------- .../tests/legacy/dom/named_node_map.html | 19 -- src/browser/tests/legacy/dom/node_filter.html | 219 ------------- src/browser/tests/legacy/dom/node_list.html | 19 -- src/browser/tests/legacy/dom/node_owner.html | 34 -- src/browser/tests/legacy/dom/performance.html | 16 - .../legacy/dom/performance_observer.html | 5 - .../legacy/dom/processing_instruction.html | 22 -- src/browser/tests/legacy/dom/range.html | 41 --- src/browser/tests/legacy/dom/text.html | 19 -- src/browser/tests/legacy/dom/token_list.html | 64 ---- .../tests/legacy/encoding/decoder.html | 60 ---- .../tests/legacy/encoding/encoder.html | 14 - .../tests/legacy/events/composition.html | 36 --- src/browser/tests/legacy/events/custom.html | 25 -- src/browser/tests/legacy/events/event.html | 139 -------- src/browser/tests/legacy/events/keyboard.html | 88 ----- src/browser/tests/legacy/events/mouse.html | 34 -- src/browser/tests/legacy/fetch/fetch.html | 34 -- src/browser/tests/legacy/fetch/headers.html | 103 ------ src/browser/tests/legacy/fetch/request.html | 23 -- src/browser/tests/legacy/fetch/response.html | 51 --- src/browser/tests/legacy/file/blob.html | 125 ------- src/browser/tests/legacy/file/file.html | 7 - .../tests/legacy/html/abort_controller.html | 41 --- src/browser/tests/legacy/html/canvas.html | 13 - src/browser/tests/legacy/html/dataset.html | 30 -- src/browser/tests/legacy/html/document.html | 70 ---- src/browser/tests/legacy/html/element.html | 53 --- .../tests/legacy/html/error_event.html | 25 -- .../tests/legacy/html/history/history.html | 37 --- .../tests/legacy/html/history/history2.html | 23 -- .../html/history/history_after_nav.skip.html | 6 - src/browser/tests/legacy/html/image.html | 32 -- src/browser/tests/legacy/html/input.html | 111 ------- src/browser/tests/legacy/html/link.html | 60 ---- src/browser/tests/legacy/html/location.html | 33 -- .../legacy/html/navigation/navigation.html | 18 -- .../navigation/navigation_after_nav.skip.html | 8 - .../navigation_currententrychange.html | 15 - src/browser/tests/legacy/html/navigator.html | 8 - src/browser/tests/legacy/html/screen.html | 21 -- .../legacy/html/script/dynamic_import.html | 32 -- .../tests/legacy/html/script/import.html | 15 - .../tests/legacy/html/script/import.js | 2 - .../tests/legacy/html/script/import2.js | 2 - .../tests/legacy/html/script/importmap.html | 24 -- .../legacy/html/script/inline_defer.html | 28 -- .../tests/legacy/html/script/inline_defer.js | 1 - .../tests/legacy/html/script/order.html | 35 -- src/browser/tests/legacy/html/script/order.js | 2 - .../tests/legacy/html/script/order_async.js | 3 - .../tests/legacy/html/script/order_defer.js | 2 - .../tests/legacy/html/script/script.html | 31 -- src/browser/tests/legacy/html/select.html | 80 ----- src/browser/tests/legacy/html/slot.html | 179 ----------- src/browser/tests/legacy/html/style.html | 6 - src/browser/tests/legacy/html/svg.html | 38 --- .../tests/legacy/storage/local_storage.html | 29 -- .../tests/legacy/streams/readable_stream.html | 134 -------- src/browser/tests/legacy/testing.js | 206 ------------ src/browser/tests/legacy/url/url.html | 109 ------- .../tests/legacy/url/url_search_params.html | 93 ------ src/browser/tests/legacy/window/frames.html | 12 - src/browser/tests/legacy/window/window.html | 117 ------- src/browser/tests/legacy/xhr/form_data.html | 133 -------- .../tests/legacy/xhr/progress_event.html | 17 - src/browser/tests/legacy/xhr/xhr.html | 122 ------- src/browser/tests/legacy/xmlserializer.html | 8 - src/main_legacy_test.zig | 281 ---------------- 89 files changed, 2 insertions(+), 4782 deletions(-) delete mode 100644 src/browser/tests/legacy/browser.html delete mode 100644 src/browser/tests/legacy/crypto.html delete mode 100644 src/browser/tests/legacy/css.html delete mode 100644 src/browser/tests/legacy/cssom/css_style_declaration.html delete mode 100644 src/browser/tests/legacy/cssom/css_stylesheet.html delete mode 100644 src/browser/tests/legacy/dom/animation.html delete mode 100644 src/browser/tests/legacy/dom/attribute.html delete mode 100644 src/browser/tests/legacy/dom/character_data.html delete mode 100644 src/browser/tests/legacy/dom/comment.html delete mode 100644 src/browser/tests/legacy/dom/document.html delete mode 100644 src/browser/tests/legacy/dom/document_fragment.html delete mode 100644 src/browser/tests/legacy/dom/document_type.html delete mode 100644 src/browser/tests/legacy/dom/dom_parser.html delete mode 100644 src/browser/tests/legacy/dom/element.html delete mode 100644 src/browser/tests/legacy/dom/event_target.html delete mode 100644 src/browser/tests/legacy/dom/exceptions.html delete mode 100644 src/browser/tests/legacy/dom/html_collection.html delete mode 100644 src/browser/tests/legacy/dom/implementation.html delete mode 100644 src/browser/tests/legacy/dom/intersection_observer.html delete mode 100644 src/browser/tests/legacy/dom/named_node_map.html delete mode 100644 src/browser/tests/legacy/dom/node_filter.html delete mode 100644 src/browser/tests/legacy/dom/node_list.html delete mode 100644 src/browser/tests/legacy/dom/node_owner.html delete mode 100644 src/browser/tests/legacy/dom/performance.html delete mode 100644 src/browser/tests/legacy/dom/performance_observer.html delete mode 100644 src/browser/tests/legacy/dom/processing_instruction.html delete mode 100644 src/browser/tests/legacy/dom/range.html delete mode 100644 src/browser/tests/legacy/dom/text.html delete mode 100644 src/browser/tests/legacy/dom/token_list.html delete mode 100644 src/browser/tests/legacy/encoding/decoder.html delete mode 100644 src/browser/tests/legacy/encoding/encoder.html delete mode 100644 src/browser/tests/legacy/events/composition.html delete mode 100644 src/browser/tests/legacy/events/custom.html delete mode 100644 src/browser/tests/legacy/events/event.html delete mode 100644 src/browser/tests/legacy/events/keyboard.html delete mode 100644 src/browser/tests/legacy/events/mouse.html delete mode 100644 src/browser/tests/legacy/fetch/fetch.html delete mode 100644 src/browser/tests/legacy/fetch/headers.html delete mode 100644 src/browser/tests/legacy/fetch/request.html delete mode 100644 src/browser/tests/legacy/fetch/response.html delete mode 100644 src/browser/tests/legacy/file/blob.html delete mode 100644 src/browser/tests/legacy/file/file.html delete mode 100644 src/browser/tests/legacy/html/abort_controller.html delete mode 100644 src/browser/tests/legacy/html/canvas.html delete mode 100644 src/browser/tests/legacy/html/dataset.html delete mode 100644 src/browser/tests/legacy/html/document.html delete mode 100644 src/browser/tests/legacy/html/element.html delete mode 100644 src/browser/tests/legacy/html/error_event.html delete mode 100644 src/browser/tests/legacy/html/history/history.html delete mode 100644 src/browser/tests/legacy/html/history/history2.html delete mode 100644 src/browser/tests/legacy/html/history/history_after_nav.skip.html delete mode 100644 src/browser/tests/legacy/html/image.html delete mode 100644 src/browser/tests/legacy/html/input.html delete mode 100644 src/browser/tests/legacy/html/link.html delete mode 100644 src/browser/tests/legacy/html/location.html delete mode 100644 src/browser/tests/legacy/html/navigation/navigation.html delete mode 100644 src/browser/tests/legacy/html/navigation/navigation_after_nav.skip.html delete mode 100644 src/browser/tests/legacy/html/navigation/navigation_currententrychange.html delete mode 100644 src/browser/tests/legacy/html/navigator.html delete mode 100644 src/browser/tests/legacy/html/screen.html delete mode 100644 src/browser/tests/legacy/html/script/dynamic_import.html delete mode 100644 src/browser/tests/legacy/html/script/import.html delete mode 100644 src/browser/tests/legacy/html/script/import.js delete mode 100644 src/browser/tests/legacy/html/script/import2.js delete mode 100644 src/browser/tests/legacy/html/script/importmap.html delete mode 100644 src/browser/tests/legacy/html/script/inline_defer.html delete mode 100644 src/browser/tests/legacy/html/script/inline_defer.js delete mode 100644 src/browser/tests/legacy/html/script/order.html delete mode 100644 src/browser/tests/legacy/html/script/order.js delete mode 100644 src/browser/tests/legacy/html/script/order_async.js delete mode 100644 src/browser/tests/legacy/html/script/order_defer.js delete mode 100644 src/browser/tests/legacy/html/script/script.html delete mode 100644 src/browser/tests/legacy/html/select.html delete mode 100644 src/browser/tests/legacy/html/slot.html delete mode 100644 src/browser/tests/legacy/html/style.html delete mode 100644 src/browser/tests/legacy/html/svg.html delete mode 100644 src/browser/tests/legacy/storage/local_storage.html delete mode 100644 src/browser/tests/legacy/streams/readable_stream.html delete mode 100644 src/browser/tests/legacy/testing.js delete mode 100644 src/browser/tests/legacy/url/url.html delete mode 100644 src/browser/tests/legacy/url/url_search_params.html delete mode 100644 src/browser/tests/legacy/window/frames.html delete mode 100644 src/browser/tests/legacy/window/window.html delete mode 100644 src/browser/tests/legacy/xhr/form_data.html delete mode 100644 src/browser/tests/legacy/xhr/progress_event.html delete mode 100644 src/browser/tests/legacy/xhr/xhr.html delete mode 100644 src/browser/tests/legacy/xmlserializer.html delete mode 100644 src/main_legacy_test.zig diff --git a/build.zig b/build.zig index 052fb752..428ca7ef 100644 --- a/build.zig +++ b/build.zig @@ -102,10 +102,10 @@ pub fn build(b: *Build) !void { }); check.dependOn(&check_lib.step); - // Extras (snapshot_creator, legacy_test) are off the default install to + // Extras (snapshot_creator) are off the default install to // avoid paying for three exe compiles on every edit. Build explicitly // with `zig build extras`. - const extras_step = b.step("extras", "Build snapshot_creator and legacy_test"); + const extras_step = b.step("extras", "Build snapshot_creator"); { // browser @@ -185,38 +185,6 @@ pub fn build(b: *Build) !void { const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_tests.step); } - - { - // 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 }, - }, - }), - }); - extras_step.dependOn(&b.addInstallArtifact(exe, .{}).step); - - const exe_check = b.addLibrary(.{ - .name = "legacy_test_check", - .root_module = exe.root_module, - }); - check.dependOn(&exe_check.step); - - 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); - } } fn linkV8( diff --git a/src/browser/tests/legacy/browser.html b/src/browser/tests/legacy/browser.html deleted file mode 100644 index 1f60488b..00000000 --- a/src/browser/tests/legacy/browser.html +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/src/browser/tests/legacy/crypto.html b/src/browser/tests/legacy/crypto.html deleted file mode 100644 index f1dc291a..00000000 --- a/src/browser/tests/legacy/crypto.html +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/src/browser/tests/legacy/css.html b/src/browser/tests/legacy/css.html deleted file mode 100644 index 3f83e934..00000000 --- a/src/browser/tests/legacy/css.html +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/src/browser/tests/legacy/cssom/css_style_declaration.html b/src/browser/tests/legacy/cssom/css_style_declaration.html deleted file mode 100644 index 3d4e961e..00000000 --- a/src/browser/tests/legacy/cssom/css_style_declaration.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/browser/tests/legacy/cssom/css_stylesheet.html b/src/browser/tests/legacy/cssom/css_stylesheet.html deleted file mode 100644 index 20ee1dfe..00000000 --- a/src/browser/tests/legacy/cssom/css_stylesheet.html +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/src/browser/tests/legacy/dom/animation.html b/src/browser/tests/legacy/dom/animation.html deleted file mode 100644 index e109ab0e..00000000 --- a/src/browser/tests/legacy/dom/animation.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/dom/attribute.html b/src/browser/tests/legacy/dom/attribute.html deleted file mode 100644 index 2e208861..00000000 --- a/src/browser/tests/legacy/dom/attribute.html +++ /dev/null @@ -1,33 +0,0 @@ - - - -OK - - diff --git a/src/browser/tests/legacy/dom/character_data.html b/src/browser/tests/legacy/dom/character_data.html deleted file mode 100644 index ff74da90..00000000 --- a/src/browser/tests/legacy/dom/character_data.html +++ /dev/null @@ -1,48 +0,0 @@ - - - -OK - - diff --git a/src/browser/tests/legacy/dom/comment.html b/src/browser/tests/legacy/dom/comment.html deleted file mode 100644 index 2f87846c..00000000 --- a/src/browser/tests/legacy/dom/comment.html +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/browser/tests/legacy/dom/document.html b/src/browser/tests/legacy/dom/document.html deleted file mode 100644 index 647ddf19..00000000 --- a/src/browser/tests/legacy/dom/document.html +++ /dev/null @@ -1,185 +0,0 @@ - - - -
- OK -

- -

-

And

-
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/browser/tests/legacy/dom/document_fragment.html b/src/browser/tests/legacy/dom/document_fragment.html deleted file mode 100644 index a4b163a4..00000000 --- a/src/browser/tests/legacy/dom/document_fragment.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - diff --git a/src/browser/tests/legacy/dom/document_type.html b/src/browser/tests/legacy/dom/document_type.html deleted file mode 100644 index ff7cdbc8..00000000 --- a/src/browser/tests/legacy/dom/document_type.html +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/src/browser/tests/legacy/dom/dom_parser.html b/src/browser/tests/legacy/dom/dom_parser.html deleted file mode 100644 index bf9bec8a..00000000 --- a/src/browser/tests/legacy/dom/dom_parser.html +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/src/browser/tests/legacy/dom/element.html b/src/browser/tests/legacy/dom/element.html deleted file mode 100644 index 6a7155c8..00000000 --- a/src/browser/tests/legacy/dom/element.html +++ /dev/null @@ -1,304 +0,0 @@ - - - -
- OK -

- -

-

And

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -

content

-
-
- - - - --> diff --git a/src/browser/tests/legacy/dom/event_target.html b/src/browser/tests/legacy/dom/event_target.html deleted file mode 100644 index 68fb8c6b..00000000 --- a/src/browser/tests/legacy/dom/event_target.html +++ /dev/null @@ -1,116 +0,0 @@ - - - -

- - diff --git a/src/browser/tests/legacy/dom/exceptions.html b/src/browser/tests/legacy/dom/exceptions.html deleted file mode 100644 index 654f68ca..00000000 --- a/src/browser/tests/legacy/dom/exceptions.html +++ /dev/null @@ -1,39 +0,0 @@ - - - -
- OK -
- - - - diff --git a/src/browser/tests/legacy/dom/html_collection.html b/src/browser/tests/legacy/dom/html_collection.html deleted file mode 100644 index 10e3dca2..00000000 --- a/src/browser/tests/legacy/dom/html_collection.html +++ /dev/null @@ -1,67 +0,0 @@ - - -
- OK -

- -

-

And

- -
- - - - - - - - - - diff --git a/src/browser/tests/legacy/dom/implementation.html b/src/browser/tests/legacy/dom/implementation.html deleted file mode 100644 index 6dcc0838..00000000 --- a/src/browser/tests/legacy/dom/implementation.html +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/src/browser/tests/legacy/dom/intersection_observer.html b/src/browser/tests/legacy/dom/intersection_observer.html deleted file mode 100644 index d569894f..00000000 --- a/src/browser/tests/legacy/dom/intersection_observer.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/browser/tests/legacy/dom/named_node_map.html b/src/browser/tests/legacy/dom/named_node_map.html deleted file mode 100644 index 7cdcf4b7..00000000 --- a/src/browser/tests/legacy/dom/named_node_map.html +++ /dev/null @@ -1,19 +0,0 @@ - -
- - - diff --git a/src/browser/tests/legacy/dom/node_filter.html b/src/browser/tests/legacy/dom/node_filter.html deleted file mode 100644 index d5ac95f4..00000000 --- a/src/browser/tests/legacy/dom/node_filter.html +++ /dev/null @@ -1,219 +0,0 @@ - - - - -
- -
- - - - Text content - - - -
- -
- - - - - - - - - - - - - - - - diff --git a/src/browser/tests/legacy/dom/node_list.html b/src/browser/tests/legacy/dom/node_list.html deleted file mode 100644 index 911b8aa8..00000000 --- a/src/browser/tests/legacy/dom/node_list.html +++ /dev/null @@ -1,19 +0,0 @@ - -
- OK -

- -

-

And

- -
- - - diff --git a/src/browser/tests/legacy/dom/node_owner.html b/src/browser/tests/legacy/dom/node_owner.html deleted file mode 100644 index 0aec74c5..00000000 --- a/src/browser/tests/legacy/dom/node_owner.html +++ /dev/null @@ -1,34 +0,0 @@ - -
-

- I am the original reference node. -

-
- - - diff --git a/src/browser/tests/legacy/dom/performance.html b/src/browser/tests/legacy/dom/performance.html deleted file mode 100644 index 0fbfe6fd..00000000 --- a/src/browser/tests/legacy/dom/performance.html +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/src/browser/tests/legacy/dom/performance_observer.html b/src/browser/tests/legacy/dom/performance_observer.html deleted file mode 100644 index 303fc15f..00000000 --- a/src/browser/tests/legacy/dom/performance_observer.html +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/src/browser/tests/legacy/dom/processing_instruction.html b/src/browser/tests/legacy/dom/processing_instruction.html deleted file mode 100644 index 67bc8fc4..00000000 --- a/src/browser/tests/legacy/dom/processing_instruction.html +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/src/browser/tests/legacy/dom/range.html b/src/browser/tests/legacy/dom/range.html deleted file mode 100644 index a60862ca..00000000 --- a/src/browser/tests/legacy/dom/range.html +++ /dev/null @@ -1,41 +0,0 @@ - - - -

over 9000

- - - - - - - - diff --git a/src/browser/tests/legacy/dom/text.html b/src/browser/tests/legacy/dom/text.html deleted file mode 100644 index d7ceba08..00000000 --- a/src/browser/tests/legacy/dom/text.html +++ /dev/null @@ -1,19 +0,0 @@ - -OK - - - diff --git a/src/browser/tests/legacy/dom/token_list.html b/src/browser/tests/legacy/dom/token_list.html deleted file mode 100644 index b04d5658..00000000 --- a/src/browser/tests/legacy/dom/token_list.html +++ /dev/null @@ -1,64 +0,0 @@ - -

- - - diff --git a/src/browser/tests/legacy/encoding/decoder.html b/src/browser/tests/legacy/encoding/decoder.html deleted file mode 100644 index 8a93dc46..00000000 --- a/src/browser/tests/legacy/encoding/decoder.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - diff --git a/src/browser/tests/legacy/encoding/encoder.html b/src/browser/tests/legacy/encoding/encoder.html deleted file mode 100644 index affcd575..00000000 --- a/src/browser/tests/legacy/encoding/encoder.html +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/src/browser/tests/legacy/events/composition.html b/src/browser/tests/legacy/events/composition.html deleted file mode 100644 index b5a6a710..00000000 --- a/src/browser/tests/legacy/events/composition.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - diff --git a/src/browser/tests/legacy/events/custom.html b/src/browser/tests/legacy/events/custom.html deleted file mode 100644 index cb6ddd2b..00000000 --- a/src/browser/tests/legacy/events/custom.html +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/src/browser/tests/legacy/events/event.html b/src/browser/tests/legacy/events/event.html deleted file mode 100644 index d6bbe80f..00000000 --- a/src/browser/tests/legacy/events/event.html +++ /dev/null @@ -1,139 +0,0 @@ - - - -

-

-
- - - - - - - - - - - - - - - - diff --git a/src/browser/tests/legacy/events/keyboard.html b/src/browser/tests/legacy/events/keyboard.html deleted file mode 100644 index e76de57f..00000000 --- a/src/browser/tests/legacy/events/keyboard.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - diff --git a/src/browser/tests/legacy/events/mouse.html b/src/browser/tests/legacy/events/mouse.html deleted file mode 100644 index 230bf188..00000000 --- a/src/browser/tests/legacy/events/mouse.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - diff --git a/src/browser/tests/legacy/fetch/fetch.html b/src/browser/tests/legacy/fetch/fetch.html deleted file mode 100644 index d6649860..00000000 --- a/src/browser/tests/legacy/fetch/fetch.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/fetch/headers.html b/src/browser/tests/legacy/fetch/headers.html deleted file mode 100644 index 0626ad3d..00000000 --- a/src/browser/tests/legacy/fetch/headers.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - diff --git a/src/browser/tests/legacy/fetch/request.html b/src/browser/tests/legacy/fetch/request.html deleted file mode 100644 index f3118948..00000000 --- a/src/browser/tests/legacy/fetch/request.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/fetch/response.html b/src/browser/tests/legacy/fetch/response.html deleted file mode 100644 index ef49a260..00000000 --- a/src/browser/tests/legacy/fetch/response.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - diff --git a/src/browser/tests/legacy/file/blob.html b/src/browser/tests/legacy/file/blob.html deleted file mode 100644 index 343fd32b..00000000 --- a/src/browser/tests/legacy/file/blob.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - diff --git a/src/browser/tests/legacy/file/file.html b/src/browser/tests/legacy/file/file.html deleted file mode 100644 index 05f23ad7..00000000 --- a/src/browser/tests/legacy/file/file.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/html/abort_controller.html b/src/browser/tests/legacy/html/abort_controller.html deleted file mode 100644 index 5f9f0ee0..00000000 --- a/src/browser/tests/legacy/html/abort_controller.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - diff --git a/src/browser/tests/legacy/html/canvas.html b/src/browser/tests/legacy/html/canvas.html deleted file mode 100644 index ed1980eb..00000000 --- a/src/browser/tests/legacy/html/canvas.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/src/browser/tests/legacy/html/dataset.html b/src/browser/tests/legacy/html/dataset.html deleted file mode 100644 index 8eff6927..00000000 --- a/src/browser/tests/legacy/html/dataset.html +++ /dev/null @@ -1,30 +0,0 @@ - - -
- - - - diff --git a/src/browser/tests/legacy/html/document.html b/src/browser/tests/legacy/html/document.html deleted file mode 100644 index 01e5d76a..00000000 --- a/src/browser/tests/legacy/html/document.html +++ /dev/null @@ -1,70 +0,0 @@ - - - -
- - - - - - - diff --git a/src/browser/tests/legacy/html/element.html b/src/browser/tests/legacy/html/element.html deleted file mode 100644 index d1701ae3..00000000 --- a/src/browser/tests/legacy/html/element.html +++ /dev/null @@ -1,53 +0,0 @@ - - -
abcc
- - - - - - - - - - - diff --git a/src/browser/tests/legacy/html/error_event.html b/src/browser/tests/legacy/html/error_event.html deleted file mode 100644 index be2c56a4..00000000 --- a/src/browser/tests/legacy/html/error_event.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/html/history/history.html b/src/browser/tests/legacy/html/history/history.html deleted file mode 100644 index 3f6d0708..00000000 --- a/src/browser/tests/legacy/html/history/history.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/html/history/history2.html b/src/browser/tests/legacy/html/history/history2.html deleted file mode 100644 index 89fc73b4..00000000 --- a/src/browser/tests/legacy/html/history/history2.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/html/history/history_after_nav.skip.html b/src/browser/tests/legacy/html/history/history_after_nav.skip.html deleted file mode 100644 index 5180b14a..00000000 --- a/src/browser/tests/legacy/html/history/history_after_nav.skip.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/html/image.html b/src/browser/tests/legacy/html/image.html deleted file mode 100644 index 053b2cfa..00000000 --- a/src/browser/tests/legacy/html/image.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/html/input.html b/src/browser/tests/legacy/html/input.html deleted file mode 100644 index 0232ddbf..00000000 --- a/src/browser/tests/legacy/html/input.html +++ /dev/null @@ -1,111 +0,0 @@ - - - -
-

- -

-
- - - - - - - - diff --git a/src/browser/tests/legacy/html/link.html b/src/browser/tests/legacy/html/link.html deleted file mode 100644 index 95869052..00000000 --- a/src/browser/tests/legacy/html/link.html +++ /dev/null @@ -1,60 +0,0 @@ - - -OK - - diff --git a/src/browser/tests/legacy/html/location.html b/src/browser/tests/legacy/html/location.html deleted file mode 100644 index ad49b411..00000000 --- a/src/browser/tests/legacy/html/location.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - diff --git a/src/browser/tests/legacy/html/navigation/navigation.html b/src/browser/tests/legacy/html/navigation/navigation.html deleted file mode 100644 index f30b9e8e..00000000 --- a/src/browser/tests/legacy/html/navigation/navigation.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/html/navigation/navigation_after_nav.skip.html b/src/browser/tests/legacy/html/navigation/navigation_after_nav.skip.html deleted file mode 100644 index e55691bb..00000000 --- a/src/browser/tests/legacy/html/navigation/navigation_after_nav.skip.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/html/navigation/navigation_currententrychange.html b/src/browser/tests/legacy/html/navigation/navigation_currententrychange.html deleted file mode 100644 index c84bcbad..00000000 --- a/src/browser/tests/legacy/html/navigation/navigation_currententrychange.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/html/navigator.html b/src/browser/tests/legacy/html/navigator.html deleted file mode 100644 index fb2b3ffe..00000000 --- a/src/browser/tests/legacy/html/navigator.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/html/screen.html b/src/browser/tests/legacy/html/screen.html deleted file mode 100644 index 5239ba43..00000000 --- a/src/browser/tests/legacy/html/screen.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - diff --git a/src/browser/tests/legacy/html/script/dynamic_import.html b/src/browser/tests/legacy/html/script/dynamic_import.html deleted file mode 100644 index ddaa19a2..00000000 --- a/src/browser/tests/legacy/html/script/dynamic_import.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - diff --git a/src/browser/tests/legacy/html/script/import.html b/src/browser/tests/legacy/html/script/import.html deleted file mode 100644 index 7a4037af..00000000 --- a/src/browser/tests/legacy/html/script/import.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - diff --git a/src/browser/tests/legacy/html/script/import.js b/src/browser/tests/legacy/html/script/import.js deleted file mode 100644 index fb140c03..00000000 --- a/src/browser/tests/legacy/html/script/import.js +++ /dev/null @@ -1,2 +0,0 @@ -let greeting = 'hello'; -export {greeting as 'greeting'}; diff --git a/src/browser/tests/legacy/html/script/import2.js b/src/browser/tests/legacy/html/script/import2.js deleted file mode 100644 index 328b8943..00000000 --- a/src/browser/tests/legacy/html/script/import2.js +++ /dev/null @@ -1,2 +0,0 @@ -let greeting = 'world'; -export {greeting as 'greeting'}; diff --git a/src/browser/tests/legacy/html/script/importmap.html b/src/browser/tests/legacy/html/script/importmap.html deleted file mode 100644 index 973d5080..00000000 --- a/src/browser/tests/legacy/html/script/importmap.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - diff --git a/src/browser/tests/legacy/html/script/inline_defer.html b/src/browser/tests/legacy/html/script/inline_defer.html deleted file mode 100644 index 11350032..00000000 --- a/src/browser/tests/legacy/html/script/inline_defer.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - diff --git a/src/browser/tests/legacy/html/script/inline_defer.js b/src/browser/tests/legacy/html/script/inline_defer.js deleted file mode 100644 index 1e0ee1a4..00000000 --- a/src/browser/tests/legacy/html/script/inline_defer.js +++ /dev/null @@ -1 +0,0 @@ -dyn1_loaded += 1; diff --git a/src/browser/tests/legacy/html/script/order.html b/src/browser/tests/legacy/html/script/order.html deleted file mode 100644 index 7efbbef3..00000000 --- a/src/browser/tests/legacy/html/script/order.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/browser/tests/legacy/html/script/order.js b/src/browser/tests/legacy/html/script/order.js deleted file mode 100644 index 31e602fc..00000000 --- a/src/browser/tests/legacy/html/script/order.js +++ /dev/null @@ -1,2 +0,0 @@ -list += 'a'; -testing.expectEqual('a', list); diff --git a/src/browser/tests/legacy/html/script/order_async.js b/src/browser/tests/legacy/html/script/order_async.js deleted file mode 100644 index 97c9adac..00000000 --- a/src/browser/tests/legacy/html/script/order_async.js +++ /dev/null @@ -1,3 +0,0 @@ -list += 'f'; -testing.expectEqual('abcdef', list); - diff --git a/src/browser/tests/legacy/html/script/order_defer.js b/src/browser/tests/legacy/html/script/order_defer.js deleted file mode 100644 index 3911b644..00000000 --- a/src/browser/tests/legacy/html/script/order_defer.js +++ /dev/null @@ -1,2 +0,0 @@ -list += 'e'; -testing.expectEqual('abcde', list); diff --git a/src/browser/tests/legacy/html/script/script.html b/src/browser/tests/legacy/html/script/script.html deleted file mode 100644 index d5910a62..00000000 --- a/src/browser/tests/legacy/html/script/script.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/browser/tests/legacy/html/select.html b/src/browser/tests/legacy/html/select.html deleted file mode 100644 index f18dfdab..00000000 --- a/src/browser/tests/legacy/html/select.html +++ /dev/null @@ -1,80 +0,0 @@ - - - -
- -
- - - diff --git a/src/browser/tests/legacy/html/slot.html b/src/browser/tests/legacy/html/slot.html deleted file mode 100644 index 36e3bc1f..00000000 --- a/src/browser/tests/legacy/html/slot.html +++ /dev/null @@ -1,179 +0,0 @@ - - - - - - -default -

default

-

default

xx other
-More

default2

!!
- - - - - - - -
hello
- - -
hello
- - - - - -
hello
- diff --git a/src/browser/tests/legacy/html/style.html b/src/browser/tests/legacy/html/style.html deleted file mode 100644 index e92cd3ca..00000000 --- a/src/browser/tests/legacy/html/style.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/html/svg.html b/src/browser/tests/legacy/html/svg.html deleted file mode 100644 index 36854649..00000000 --- a/src/browser/tests/legacy/html/svg.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - OVER 9000!! - - - - - OVER 9000!!! - - - diff --git a/src/browser/tests/legacy/storage/local_storage.html b/src/browser/tests/legacy/storage/local_storage.html deleted file mode 100644 index c78f3889..00000000 --- a/src/browser/tests/legacy/storage/local_storage.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - diff --git a/src/browser/tests/legacy/streams/readable_stream.html b/src/browser/tests/legacy/streams/readable_stream.html deleted file mode 100644 index a8339cc5..00000000 --- a/src/browser/tests/legacy/streams/readable_stream.html +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/browser/tests/legacy/testing.js b/src/browser/tests/legacy/testing.js deleted file mode 100644 index 8e732f70..00000000 --- a/src/browser/tests/legacy/testing.js +++ /dev/null @@ -1,206 +0,0 @@ -// 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 frame.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 deleted file mode 100644 index 72ca45f0..00000000 --- a/src/browser/tests/legacy/url/url.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/browser/tests/legacy/url/url_search_params.html b/src/browser/tests/legacy/url/url_search_params.html deleted file mode 100644 index 738253d2..00000000 --- a/src/browser/tests/legacy/url/url_search_params.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - diff --git a/src/browser/tests/legacy/window/frames.html b/src/browser/tests/legacy/window/frames.html deleted file mode 100644 index 669fbfde..00000000 --- a/src/browser/tests/legacy/window/frames.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/src/browser/tests/legacy/window/window.html b/src/browser/tests/legacy/window/window.html deleted file mode 100644 index dee57062..00000000 --- a/src/browser/tests/legacy/window/window.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/browser/tests/legacy/xhr/form_data.html b/src/browser/tests/legacy/xhr/form_data.html deleted file mode 100644 index cda34c06..00000000 --- a/src/browser/tests/legacy/xhr/form_data.html +++ /dev/null @@ -1,133 +0,0 @@ - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - diff --git a/src/browser/tests/legacy/xhr/progress_event.html b/src/browser/tests/legacy/xhr/progress_event.html deleted file mode 100644 index 4b7f5df4..00000000 --- a/src/browser/tests/legacy/xhr/progress_event.html +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/src/browser/tests/legacy/xhr/xhr.html b/src/browser/tests/legacy/xhr/xhr.html deleted file mode 100644 index c7c557bd..00000000 --- a/src/browser/tests/legacy/xhr/xhr.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - - - - - -

And

- diff --git a/src/main_legacy_test.zig b/src/main_legacy_test.zig deleted file mode 100644 index bd77b5fa..00000000 --- a/src/main_legacy_test.zig +++ /dev/null @@ -1,281 +0,0 @@ -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 args = try std.process.argsWithAllocator(allocator); - defer args.deinit(); - _ = args.next(); // executable name - - var filter: ?[]const u8 = null; - if (args.next()) |n| { - filter = n; - } - - 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; - const config = try lp.Config.init(allocator, "legacy-test", .{ .serve = .{ - .insecure_disable_tls_host_verification = true, - .user_agent_suffix = "internal-tester", - } }); - defer config.deinit(allocator); - - var app = try lp.App.init(allocator, &config); - defer app.deinit(); - - var test_arena = std.heap.ArenaAllocator.init(allocator); - defer test_arena.deinit(); - - var browser: lp.Browser = undefined; - try browser.init(app, .{}, null); - defer browser.deinit(); - - const notification = try lp.Notification.init(allocator); - defer notification.deinit(); - - const session = try browser.newSession(notification); - defer session.deinit(); - - 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; - } - - if (std.mem.endsWith(u8, entry.basename, ".skip.html")) { - continue; - } - - if (filter) |f| { - if (std.mem.indexOf(u8, entry.path, f) == null) { - 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 frame = try session.createPage(); - defer session.removePage(); - - var ls: lp.js.Local.Scope = undefined; - frame.js.localScope(&ls); - defer ls.deinit(); - - var try_catch: lp.js.TryCatch = undefined; - try_catch.init(&ls.local); - defer try_catch.deinit(); - - try frame.navigate(url, .{}); - var runner = try session.runner(.{}); - try runner.wait(false, .{ .ms = 2000 }); - - ls.local.eval("testing.assertOk()", "testing.assertOk()") catch |err| { - const caught = try_catch.caughtOrError(allocator, err); - std.debug.print("{s}: test failure\nError: {f}\n", .{ file, caught }); - 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; - - if (std.mem.eql(u8, path, "/xhr")) { - return req.respond("1234567890" ** 10, .{ - .extra_headers = &.{ - .{ .name = "Content-Type", .value = "text/html; charset=utf-8" }, - }, - }); - } - - if (std.mem.eql(u8, path, "/xhr/json")) { - return req.respond("{\"over\":\"9000!!!\"}", .{ - .extra_headers = &.{ - .{ .name = "Content-Type", .value = "application/json" }, - }, - }); - } - - // 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); From 7cb7d01d979db3a2400f29e1fe98e3126ad69cc6 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 27 May 2026 10:40:52 +0800 Subject: [PATCH 03/13] Add StaticRange This doesn't solve anything, but I was looking at /input-events/ WPT tests and many are stuck with due to this type not existing. I don't expect this to fix any of those tests, but it does move them along a bit further (the /input-events test are all based on editing capabilities that we don't have). We already had a // todo StaticRange in AbstractRange, and since it's very simple, why not. --- src/browser/Factory.zig | 8 +- src/browser/js/bridge.zig | 1 + src/browser/tests/staticrange.html | 136 +++++++++++++++++++++++++++ src/browser/webapi/AbstractRange.zig | 12 ++- src/browser/webapi/Range.zig | 5 + src/browser/webapi/StaticRange.zig | 85 +++++++++++++++++ 6 files changed, 242 insertions(+), 5 deletions(-) create mode 100644 src/browser/tests/staticrange.html create mode 100644 src/browser/webapi/StaticRange.zig diff --git a/src/browser/Factory.zig b/src/browser/Factory.zig index 9288cc4f..248ededa 100644 --- a/src/browser/Factory.zig +++ b/src/browser/Factory.zig @@ -282,7 +282,13 @@ pub fn abstractRange(_: *const Factory, arena: Allocator, child: anytype, frame: ._type = unionInit(AbstractRange.Type, chain.get(1)), }; chain.setLeaf(1, child); - frame._live_ranges.append(&abstract_range._range_link); + + if (abstract_range._type != .static_range) { + // StaticRanges are not live, so they don't get added to the frame's + // live_ranges list. + frame._live_ranges.append(&abstract_range._range_link); + } + return chain.get(1); } diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index e971edd1..96c5cd65 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -827,6 +827,7 @@ pub const PageJsApis = flattenTypes(&.{ @import("../webapi/XMLSerializer.zig"), @import("../webapi/AbstractRange.zig"), @import("../webapi/Range.zig"), + @import("../webapi/StaticRange.zig"), @import("../webapi/NodeFilter.zig"), @import("../webapi/Element.zig"), @import("../webapi/element/DOMStringMap.zig"), diff --git a/src/browser/tests/staticrange.html b/src/browser/tests/staticrange.html new file mode 100644 index 00000000..c11952ef --- /dev/null +++ b/src/browser/tests/staticrange.html @@ -0,0 +1,136 @@ + + + +
abcdefghi
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/browser/webapi/AbstractRange.zig b/src/browser/webapi/AbstractRange.zig index 2a530d9b..732acc7e 100644 --- a/src/browser/webapi/AbstractRange.zig +++ b/src/browser/webapi/AbstractRange.zig @@ -24,6 +24,7 @@ const Page = @import("../Page.zig"); const Node = @import("Node.zig"); const Range = @import("Range.zig"); +const StaticRange = @import("StaticRange.zig"); const Allocator = std.mem.Allocator; @@ -48,8 +49,11 @@ pub fn acquireRef(self: *AbstractRange) void { } pub fn deinit(self: *AbstractRange, page: *Page) void { - if (page.findFrameByLoaderId(self._frame_loader_id)) |frame| { - frame._live_ranges.remove(&self._range_link); + // StaticRanges are never registered in the live-range list + if (self._type != .static_range) { + if (page.findFrameByLoaderId(self._frame_loader_id)) |frame| { + frame._live_ranges.remove(&self._range_link); + } } page.releaseArena(self._arena); } @@ -60,7 +64,7 @@ pub fn releaseRef(self: *AbstractRange, page: *Page) void { pub const Type = union(enum) { range: *Range, - // TODO: static_range: *StaticRange, + static_range: *StaticRange, }; pub fn as(self: *AbstractRange, comptime T: type) *T { @@ -70,6 +74,7 @@ pub fn as(self: *AbstractRange, comptime T: type) *T { pub fn is(self: *AbstractRange, comptime T: type) ?*T { switch (self._type) { .range => |r| return if (T == Range) r else null, + .static_range => |sr| return if (T == StaticRange) sr else null, } } @@ -339,5 +344,4 @@ pub const JsApi = struct { pub const endContainer = bridge.accessor(AbstractRange.getEndContainer, null, .{}); pub const endOffset = bridge.accessor(AbstractRange.getEndOffset, null, .{}); pub const collapsed = bridge.accessor(AbstractRange.getCollapsed, null, .{}); - pub const commonAncestorContainer = bridge.accessor(AbstractRange.getCommonAncestorContainer, null, .{}); }; diff --git a/src/browser/webapi/Range.zig b/src/browser/webapi/Range.zig index 2a367093..e7c76873 100644 --- a/src/browser/webapi/Range.zig +++ b/src/browser/webapi/Range.zig @@ -43,6 +43,10 @@ pub fn asAbstractRange(self: *Range) *AbstractRange { return self._proto; } +pub fn getCommonAncestorContainer(self: *const Range) *Node { + return self._proto.getCommonAncestorContainer(); +} + pub fn setStart(self: *Range, node: *Node, offset: u32) !void { if (node._type == .document_type) { return error.InvalidNodeType; @@ -703,6 +707,7 @@ pub const JsApi = struct { pub const END_TO_START = bridge.property(3, .{ .template = true }); pub const constructor = bridge.constructor(Range.init, .{}); + pub const commonAncestorContainer = bridge.accessor(Range.getCommonAncestorContainer, null, .{}); pub const setStart = bridge.function(Range.setStart, .{ .dom_exception = true }); pub const setEnd = bridge.function(Range.setEnd, .{ .dom_exception = true }); pub const setStartBefore = bridge.function(Range.setStartBefore, .{ .dom_exception = true }); diff --git a/src/browser/webapi/StaticRange.zig b/src/browser/webapi/StaticRange.zig new file mode 100644 index 00000000..a9e5997b --- /dev/null +++ b/src/browser/webapi/StaticRange.zig @@ -0,0 +1,85 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +const js = @import("../js/js.zig"); +const Frame = @import("../Frame.zig"); + +const Node = @import("Node.zig"); +const AbstractRange = @import("AbstractRange.zig"); + +const StaticRange = @This(); + +// The boundary points and `collapsed` accessor live on the shared AbstractRange +// prototype. Unlike Range, a StaticRange is *static*: the factory keeps it out +// of the frame's live-range list, so DOM mutations never move its boundaries. +_proto: *AbstractRange, + +// https://dom.spec.whatwg.org/#dictdef-staticrangeinit +// All members are required. The fields are non-optional with no default, so the +// argument decoder rejects a missing or null member with a TypeError. +pub const StaticRangeInit = struct { + startContainer: *Node, + startOffset: u32, + endContainer: *Node, + endOffset: u32, +}; + +pub fn init(opts: StaticRangeInit, frame: *Frame) !*StaticRange { + // https://dom.spec.whatwg.org/#dom-staticrange-staticrange + // Throw InvalidNodeTypeError if either container is a DocumentType or Attr. + // Note: offsets are stored verbatim — StaticRange does no length validation. + if (isInvalidContainer(opts.startContainer) or isInvalidContainer(opts.endContainer)) { + return error.InvalidNodeType; + } + + const arena = try frame.getArena(.medium, "StaticRange"); + errdefer frame.releaseArena(arena); + + const static_range = try frame._factory.abstractRange(arena, StaticRange{ ._proto = undefined }, frame); + const proto = static_range._proto; + proto._start_container = opts.startContainer; + proto._start_offset = opts.startOffset; + proto._end_container = opts.endContainer; + proto._end_offset = opts.endOffset; + return static_range; +} + +fn isInvalidContainer(node: *Node) bool { + return node._type == .document_type or node._type == .attribute; +} + +pub fn asAbstractRange(self: *StaticRange) *AbstractRange { + return self._proto; +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(StaticRange); + + pub const Meta = struct { + pub const name = "StaticRange"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; + + pub const constructor = bridge.constructor(StaticRange.init, .{ .dom_exception = true }); +}; + +const testing = @import("../../testing.zig"); +test "WebApi: StaticRange" { + try testing.htmlRunner("staticrange.html", .{}); +} From 014b8e12ebb5b49764894674b635da3d4f71692d Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 27 May 2026 13:20:14 +0800 Subject: [PATCH 04/13] Add shadow dom piercing to markdown dump The markdown renderer currently ignores shadow-dom. It doesn't "pierce" it, so it'll render the light-dom (which is the template / fallback), which won't be right. This largely mimics the existing logic in dump. --- src/browser/markdown.zig | 106 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 3 deletions(-) diff --git a/src/browser/markdown.zig b/src/browser/markdown.zig index 52f792dc..898608c8 100644 --- a/src/browser/markdown.zig +++ b/src/browser/markdown.zig @@ -20,9 +20,12 @@ const std = @import("std"); const Frame = @import("Frame.zig"); const URL = @import("URL.zig"); -const TreeWalker = @import("webapi/TreeWalker.zig"); -const Element = @import("webapi/Element.zig"); + const Node = @import("webapi/Node.zig"); +const Element = @import("webapi/Element.zig"); +const TreeWalker = @import("webapi/TreeWalker.zig"); +const Slot = @import("webapi/element/html/Slot.zig"); + const isAllWhitespace = @import("../string.zig").isAllWhitespace; pub const Opts = struct {}; @@ -133,6 +136,10 @@ const Context = struct { writer: *std.Io.Writer, frame: *Frame, + // When there's a slot-attribute, we skip rendering, unless this flag has + // bet set to true. + force_slot: bool = false, + fn ensureNewline(self: *Context) !void { if (!self.state.last_char_was_newline) { try self.writer.writeByte('\n'); @@ -170,11 +177,37 @@ const Context = struct { } } + // Render a 's assigned light-DOM nodes, or its own children as + // fallback. Same as dump's dumpSlotContent. + fn renderSlotContent(self: *Context, slot: *Slot) !void { + const assigned = slot.assignedNodes(null, self.frame) catch return; + if (assigned.len == 0) { + return self.renderChildren(slot.asNode()); + } + for (assigned) |node| { + // ensures that we don't skip this element when rending it. + self.force_slot = true; + try self.render(node); + } + self.force_slot = false; + } + fn renderElement(self: *Context, el: *Element) !void { + const force_slot = self.force_slot; + self.force_slot = false; + const tag = el.getTag(); if (!isVisibleElement(el)) return; + if (!force_slot) { + if (el.getAttributeSafe(comptime .wrap("slot")) != null) { + // This element has a slot attribute, and we aren't forcing slot + // rendering (i.e. this is the light-DOM), skip it. + return; + } + } + // Ensure block elements start on a new line if (tag.isBlock() and !self.state.in_table) { try self.ensureNewline(); @@ -342,10 +375,21 @@ const Context = struct { } return; }, + .slot => return self.renderSlotContent(el.as(Slot)), else => {}, } - try self.renderChildren(el.asNode()); + // Composed tree: a shadow host renders its shadow tree in place of its + // light-DOM children (light DOM is visible only through ). Applies + // to open and closed roots alike. markdown is always a rendered-content + // path (cf. dump.zig's default .rendered mode), so we always pierce; the + // early-return tags above can never be valid shadow hosts, so only this + // generic path needs the check. + if (self.frame._element_shadow_roots.get(el)) |shadow| { + try self.renderChildren(shadow.asNode()); + } else { + try self.renderChildren(el.asNode()); + } switch (tag) { .pre => { @@ -714,3 +758,59 @@ test "browser.markdown: anchor fallback label" { \\ , "[](http://localhost/no-label)\n"); } + +// Builds a shadow host
, populates its light DOM with `light` and its +// (open) shadow tree with `shadow`, then dumps the host. Declarative shadow DOM +// parsing isn't implemented, so the shadow tree is attached imperatively. +fn testMarkdownShadow(light: []const u8, shadow: []const u8, expected: []const u8) !void { + const testing = @import("../testing.zig"); + const frame = try testing.test_session.createPage(); + defer testing.test_session.removePage(); + frame.url = "http://localhost/"; + + const doc = frame.window._document; + + const host = try doc.createElement("div", null, frame); + if (light.len > 0) { + try frame.parseHtmlAsChildren(host.asNode(), light); + } + + const sr = try host.attachShadow("open", frame); + try frame.parseHtmlAsChildren(sr.asNode(), shadow); + + var aw: std.Io.Writer.Allocating = .init(testing.allocator); + defer aw.deinit(); + try dump(host.asNode(), .{}, &aw.writer, frame); + + try testing.expectString(expected, aw.written()); +} + +test "browser.markdown: shadow content is pierced" { + try testMarkdownShadow("", "Shadow content", "Shadow content\n"); +} + +test "browser.markdown: slot projects assigned light DOM" { + // The slotted renders at the slot's position in the shadow tree + // (inside the

), and not a second time at its light-DOM position. + try testMarkdownShadow( + \\Slotted + , + \\

+ , "\n## Slotted\n"); +} + +test "browser.markdown: unassigned light DOM is omitted" { + // Light DOM is visible only through a ; with no matching slot the + // light

must not appear — only the shadow tree's content does. + try testMarkdownShadow( + \\

orphan

+ , + \\
only shadow
+ , "only shadow\n"); +} + +test "browser.markdown: slot fallback content when nothing assigned" { + try testMarkdownShadow("", + \\Default text + , "Default text\n"); +} From c04c6b3827dc50b703b8b3c499c45ee58509987d Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 27 May 2026 18:13:01 +0800 Subject: [PATCH 05/13] Use Node's own frame when executing on nodeIsReady When nodeIsReady is called from a dynamic (JS) insertion, execute any code on the node's frame, not the frame that did the insertion. The code is a bit uglier than it has to be, because getting the node's frame isn't free, and we only execute code for a few types (scripts, links, ...) so we only get the owning frame when we're sure it's needed (thus the duplication) --- src/browser/Frame.zig | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index 6561f2cd..e739e8d7 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -3254,7 +3254,9 @@ pub fn _insertNodeRelative(self: *Frame, comptime from_parser: bool, parent: *No if (should_notify) { if (comptime from_parser == false) { // When the parser adds the node, nodeIsReady is only called when the - // nodeComplete() callback is executed. + // nodeComplete() callback is executed. nodeIsReady resolves the + // node's owning frame itself (only for the few node types that have + // ready work), so pass the incumbent `self`. try self.nodeIsReady(false, child); // Check if text was added to a script that hasn't started yet. @@ -3605,6 +3607,15 @@ fn nodeIsReady(self: *Frame, comptime from_parser: bool, node: *Node) !void { // we don't execute scripts added via innerHTML = ', loading an