diff --git a/.github/workflows/wpt.yml b/.github/workflows/wpt.yml index 03313ea8..306f1457 100644 --- a/.github/workflows/wpt.yml +++ b/.github/workflows/wpt.yml @@ -5,6 +5,7 @@ env: AWS_SECRET_ACCESS_KEY: ${{ secrets.LPD_PERF_AWS_SECRET_ACCESS_KEY }} AWS_BUCKET: ${{ vars.LPD_PERF_AWS_BUCKET }} AWS_REGION: ${{ vars.LPD_PERF_AWS_REGION }} + AWS_CF_DISTRIBUTION: ${{ vars.AWS_CF_DISTRIBUTION }} LIGHTPANDA_DISABLE_TELEMETRY: true on: @@ -73,7 +74,7 @@ jobs: # use a self host runner. runs-on: lpd-bench-hetzner - timeout-minutes: 120 + timeout-minutes: 180 steps: - uses: actions/checkout@v6 @@ -107,7 +108,7 @@ jobs: run: | ./wpt serve 2> /dev/null & echo $! > WPT.pid sleep 10s - ./wptrunner -lpd-path ./lightpanda -json -concurrency 3 > wpt.json + ./wptrunner -lpd-path ./lightpanda -json -concurrency 6 > wpt.json kill `cat WPT.pid` - name: write commit diff --git a/src/App.zig b/src/App.zig index bb797ec5..2d930fd6 100644 --- a/src/App.zig +++ b/src/App.zig @@ -47,10 +47,17 @@ pub fn init(allocator: Allocator, config: *const Config) !*App { const app = try allocator.create(App); errdefer allocator.destroy(app); - app.config = config; - app.allocator = allocator; - - app.robots = RobotStore.init(allocator); + app.* = .{ + .config = config, + .allocator = allocator, + .robots = RobotStore.init(allocator), + .http = undefined, + .platform = undefined, + .snapshot = undefined, + .app_dir_path = undefined, + .telemetry = undefined, + .arena_pool = undefined, + }; app.http = try Http.init(allocator, &app.robots, config); errdefer app.http.deinit(); diff --git a/src/browser/Page.zig b/src/browser/Page.zig index aa40ab0b..dedb2a6e 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -69,6 +69,7 @@ const ArenaPool = App.ArenaPool; const timestamp = @import("../datetime.zig").timestamp; const milliTimestamp = @import("../datetime.zig").milliTimestamp; +const IFrame = Element.Html.IFrame; const WebApiURL = @import("webapi/URL.zig"); const GlobalEventHandlersLookup = @import("webapi/global_event_handlers.zig").Lookup; @@ -223,7 +224,7 @@ _arena_pool_leak_track: (if (IS_DEBUG) std.AutoHashMapUnmanaged(usize, struct { parent: ?*Page, window: *Window, document: *Document, -iframe: ?*Element.Html.IFrame = null, +iframe: ?*IFrame = null, frames: std.ArrayList(*Page) = .{}, frames_sorted: bool = true, @@ -323,9 +324,9 @@ pub fn init(self: *Page, frame_id: u32, session: *Session, parent: ?*Page) !void } } -pub fn deinit(self: *Page) void { +pub fn deinit(self: *Page, abort_http: bool) void { for (self.frames.items) |frame| { - frame.deinit(); + frame.deinit(abort_http); } if (comptime IS_DEBUG) { @@ -346,10 +347,16 @@ pub fn deinit(self: *Page) void { session.browser.env.destroyContext(self.js); self._script_manager.shutdown = true; + if (self.parent == null) { - // only the root frame needs to abort this. It's more efficient this way session.browser.http_client.abort(); + } else if (abort_http) { + // a small optimization, it's faster to abort _everything_ on the root + // page, so we prefer that. But if it's just the frame that's going + // away (a frame navigation) then we'll abort the frame-related requests + session.browser.http_client.abortFrame(self._frame_id); } + self._script_manager.deinit(); if (comptime IS_DEBUG) { @@ -357,6 +364,9 @@ pub fn deinit(self: *Page) void { while (it.next()) |value_ptr| { if (value_ptr.count > 0) { log.err(.bug, "ArenaPool Leak", .{ .owner = value_ptr.owner, .type = self._type, .url = self.url }); + if (comptime builtin.is_test) { + @panic("ArenaPool Leak"); + } } } } @@ -429,6 +439,9 @@ pub fn releaseArena(self: *Page, allocator: Allocator) void { const found = self._arena_pool_leak_track.getPtr(@intFromPtr(allocator.ptr)).?; if (found.count != 1) { log.err(.bug, "ArenaPool Double Free", .{ .owner = found.owner, .count = found.count, .type = self._type, .url = self.url }); + if (comptime builtin.is_test) { + @panic("ArenaPool Double Free"); + } return; } found.count = 0; @@ -459,16 +472,15 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi // if the url is about:blank, we load an empty HTML document in the // page and dispatch the events. if (std.mem.eql(u8, "about:blank", request_url)) { + self.url = "about:blank"; // Assume we parsed the document. // It's important to force a reset during the following navigation. self._parse_state = .complete; - { - const parse_arena = try self.getArena(.{ .debug = "about:blank parse" }); - defer self.releaseArena(parse_arena); - var parser = Parser.init(parse_arena, self.document.asNode(), self); - parser.parse("
"); - } + self.document.injectBlank(self) catch |err| { + log.err(.browser, "inject blank", .{ .err = err }); + return error.InjectBlankFailed; + }; self.documentIsComplete(); session.notification.dispatch(.page_navigate, &.{ @@ -559,57 +571,89 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi }; } -// We cannot navigate immediately as navigating will delete the DOM tree, -// which holds this event's node. -// As such we schedule the function to be called as soon as possible. -pub fn scheduleNavigation(self: *Page, request_url: []const u8, opts: NavigateOpts, priority: NavigationPriority) !void { - if (self.canScheduleNavigation(priority) == false) { +// Navigation can happen in many places, such as executing a + + + + + + + + diff --git a/src/browser/tests/frames/frames.html b/src/browser/tests/frames/frames.html index f1519323..4e614de9 100644 --- a/src/browser/tests/frames/frames.html +++ b/src/browser/tests/frames/frames.html @@ -7,45 +7,59 @@ } - + + + + - - + + + + diff --git a/src/browser/tests/frames/support/after_link.html b/src/browser/tests/frames/support/after_link.html new file mode 100644 index 00000000..0cd5fc99 --- /dev/null +++ b/src/browser/tests/frames/support/after_link.html @@ -0,0 +1,2 @@ + +It was clicked! diff --git a/src/browser/tests/frames/support/with_link.html b/src/browser/tests/frames/support/with_link.html new file mode 100644 index 00000000..bc31b190 --- /dev/null +++ b/src/browser/tests/frames/support/with_link.html @@ -0,0 +1,2 @@ + +a link diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index 10af05fc..74a260e8 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -40,6 +40,8 @@ const Selection = @import("Selection.zig"); pub const XMLDocument = @import("XMLDocument.zig"); pub const HTMLDocument = @import("HTMLDocument.zig"); +const IS_DEBUG = @import("builtin").mode == .Debug; + const Document = @This(); _type: Type, @@ -937,6 +939,32 @@ fn validateElementName(name: []const u8) !void { } } +// When a page or frame's URL is about:blank, or as soon as a frame is +// programmatically created, it has this default "blank" content +pub fn injectBlank(self: *Document, page: *Page) error{InjectBlankError}!void { + self._injectBlank(page) catch |err| { + // we wrap _injectBlank like this so that injectBlank can only return an + // InjectBlankError. injectBlank is used in when nodes are inserted + // as since it inserts node itself, Zig can't infer the error set. + log.err(.browser, "inject blank", .{ .err = err }); + return error.InjectBlankError; + }; +} + +fn _injectBlank(self: *Document, page: *Page) !void { + if (comptime IS_DEBUG) { + // should only be called on an empty document + std.debug.assert(self.asNode()._children == null); + } + + const html = try page.createElementNS(.html, "html", null); + const head = try page.createElementNS(.html, "head", null); + const body = try page.createElementNS(.html, "body", null); + try page.appendNode(html, head, .{}); + try page.appendNode(html, body, .{}); + try page.appendNode(self.asNode(), html, .{}); +} + const ReadyState = enum { loading, interactive, diff --git a/src/browser/webapi/DocumentFragment.zig b/src/browser/webapi/DocumentFragment.zig index f804b08e..004ee916 100644 --- a/src/browser/webapi/DocumentFragment.zig +++ b/src/browser/webapi/DocumentFragment.zig @@ -195,8 +195,9 @@ pub fn cloneFragment(self: *DocumentFragment, deep: bool, page: *Page) !*Node { var child_it = node.childrenIterator(); while (child_it.next()) |child| { - const cloned_child = try child.cloneNode(true, page); - try page.appendNode(fragment_node, cloned_child, .{ .child_already_connected = self_is_connected }); + if (try child.cloneNodeForAppending(true, page)) |cloned_child| { + try page.appendNode(fragment_node, cloned_child, .{ .child_already_connected = self_is_connected }); + } } } diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index b4fedc9f..9da4df71 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -1328,20 +1328,12 @@ pub fn clone(self: *Element, deep: bool, page: *Page) !*Node { if (deep) { var child_it = self.asNode().childrenIterator(); while (child_it.next()) |child| { - const cloned_child = try child.cloneNode(true, page); - if (cloned_child._parent != null) { - // This is almost always false, the only case where a cloned - // node would already have a parent is with a custom element - // that has a constructor (which is called during cloning) which - // inserts it somewhere. In that case, whatever parent was set - // in the constructor should not be changed. - continue; + if (try child.cloneNodeForAppending(true, page)) |cloned_child| { + // We pass `true` to `child_already_connected` as a hacky optimization + // We _know_ this child isn't connected (Because the parent isn't connected) + // setting this to `true` skips all connection checks. + try page.appendNode(node, cloned_child, .{ .child_already_connected = true }); } - - // We pass `true` to `child_already_connected` as a hacky optimization - // We _know_ this child isn't connected (Because the parent isn't connected) - // setting this to `true` skips all connection checks. - try page.appendNode(node, cloned_child, .{ .child_already_connected = true }); } } diff --git a/src/browser/webapi/HTMLDocument.zig b/src/browser/webapi/HTMLDocument.zig index 9efde318..15ba610b 100644 --- a/src/browser/webapi/HTMLDocument.zig +++ b/src/browser/webapi/HTMLDocument.zig @@ -180,8 +180,8 @@ pub fn getLocation(self: *const HTMLDocument) ?*@import("Location.zig") { return self._proto._location; } -pub fn setLocation(_: *const HTMLDocument, url: [:0]const u8, page: *Page) !void { - return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .script); +pub fn setLocation(self: *HTMLDocument, url: [:0]const u8, page: *Page) !void { + return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = self._proto._page }); } pub fn getAll(self: *HTMLDocument, page: *Page) !*collections.HTMLAllCollection { diff --git a/src/browser/webapi/Location.zig b/src/browser/webapi/Location.zig index dbda1e51..9055abbb 100644 --- a/src/browser/webapi/Location.zig +++ b/src/browser/webapi/Location.zig @@ -83,19 +83,19 @@ pub fn setHash(_: *const Location, hash: []const u8, page: *Page) !void { return page.scheduleNavigation(normalized_hash, .{ .reason = .script, .kind = .{ .replace = null }, - }, .script); + }, .{ .script = page }); } pub fn assign(_: *const Location, url: [:0]const u8, page: *Page) !void { - return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .script); + return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = page }); } pub fn replace(_: *const Location, url: [:0]const u8, page: *Page) !void { - return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .replace = null } }, .script); + return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .replace = null } }, .{ .script = page }); } pub fn reload(_: *const Location, page: *Page) !void { - return page.scheduleNavigation(page.url, .{ .reason = .script, .kind = .reload }, .script); + return page.scheduleNavigation(page.url, .{ .reason = .script, .kind = .reload }, .{ .script = page }); } pub fn toString(self: *const Location, page: *const Page) ![:0]const u8 { diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index 4b351b6f..15541491 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -724,6 +724,9 @@ const CloneError = error{ TooManyContexts, LinkLoadError, StyleLoadError, + TypeError, + CompilationError, + JsException, }; pub fn cloneNode(self: *Node, deep_: ?bool, page: *Page) CloneError!*Node { const deep = deep_ orelse false; @@ -751,6 +754,29 @@ pub fn cloneNode(self: *Node, deep_: ?bool, page: *Page) CloneError!*Node { } } +/// Clone a node for the purpose of appending to a parent. +/// Returns null if the cloned node was already attached somewhere by a custom element +/// constructor, indicating that the constructor's decision should be respected. +/// +/// This helper is used when iterating over children to clone them. The typical pattern is: +/// while (child_it.next()) |child| { +/// if (try child.cloneNodeForAppending(true, page)) |cloned| { +/// try page.appendNode(parent, cloned, opts); +/// } +/// } +/// +/// The only case where a cloned node would already have a parent is when a custom element +/// constructor (which runs during cloning per the HTML spec) explicitly attaches the element +/// somewhere. In that case, we respect the constructor's decision and return null to signal +/// that the cloned node should not be appended to our intended parent. +pub fn cloneNodeForAppending(self: *Node, deep: bool, page: *Page) CloneError!?*Node { + const cloned = try self.cloneNode(deep, page); + if (cloned._parent != null) { + return null; + } + return cloned; +} + pub fn compareDocumentPosition(self: *Node, other: *Node) u16 { const DISCONNECTED: u16 = 0x01; const PRECEDING: u16 = 0x02; diff --git a/src/browser/webapi/Range.zig b/src/browser/webapi/Range.zig index 840fa227..21a3ce12 100644 --- a/src/browser/webapi/Range.zig +++ b/src/browser/webapi/Range.zig @@ -446,8 +446,9 @@ pub fn cloneContents(self: *const Range, page: *Page) !*DocumentFragment { var offset = self._proto._start_offset; while (offset < self._proto._end_offset) : (offset += 1) { if (self._proto._start_container.getChildAt(offset)) |child| { - const cloned = try child.cloneNode(true, page); - _ = try fragment.asNode().appendChild(cloned, page); + if (try child.cloneNodeForAppending(true, page)) |cloned| { + _ = try fragment.asNode().appendChild(cloned, page); + } } } } @@ -468,9 +469,11 @@ pub fn cloneContents(self: *const Range, page: *Page) !*DocumentFragment { if (self._proto._start_container.parentNode() == self._proto._end_container.parentNode()) { var current = self._proto._start_container.nextSibling(); while (current != null and current != self._proto._end_container) { - const cloned = try current.?.cloneNode(true, page); - _ = try fragment.asNode().appendChild(cloned, page); - current = current.?.nextSibling(); + const next = current.?.nextSibling(); + if (try current.?.cloneNodeForAppending(true, page)) |cloned| { + _ = try fragment.asNode().appendChild(cloned, page); + } + current = next; } } diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 91f55b60..2c3e3cd0 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -160,8 +160,8 @@ pub fn getSelection(self: *const Window) *Selection { return &self._document._selection; } -pub fn setLocation(_: *const Window, url: [:0]const u8, page: *Page) !void { - return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .script); +pub fn setLocation(self: *Window, url: [:0]const u8, page: *Page) !void { + return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = self._page }); } pub fn getHistory(_: *Window, page: *Page) *History { diff --git a/src/browser/webapi/element/html/IFrame.zig b/src/browser/webapi/element/html/IFrame.zig index e9cffc6e..d912dd41 100644 --- a/src/browser/webapi/element/html/IFrame.zig +++ b/src/browser/webapi/element/html/IFrame.zig @@ -30,7 +30,7 @@ const IFrame = @This(); _proto: *HtmlElement, _src: []const u8 = "", _executed: bool = false, -_content_window: ?*Window = null, +_window: ?*Window = null, pub fn asElement(self: *IFrame) *Element { return self._proto._proto; @@ -40,11 +40,11 @@ pub fn asNode(self: *IFrame) *Node { } pub fn getContentWindow(self: *const IFrame) ?*Window { - return self._content_window; + return self._window; } pub fn getContentDocument(self: *const IFrame) ?*Document { - const window = self._content_window orelse return null; + const window = self._window orelse return null; return window._document; } diff --git a/src/browser/webapi/encoding/TextDecoderStream.zig b/src/browser/webapi/encoding/TextDecoderStream.zig index b0b7e45c..a5b2dc9a 100644 --- a/src/browser/webapi/encoding/TextDecoderStream.zig +++ b/src/browser/webapi/encoding/TextDecoderStream.zig @@ -72,14 +72,6 @@ pub fn init(label_: ?[]const u8, opts_: ?InitOpts, page: *Page) !TextDecoderStre }; } -pub fn acquireRef(self: *TextDecoderStream) void { - self._transform.acquireRef(); -} - -pub fn deinit(self: *TextDecoderStream, shutdown: bool, page: *Page) void { - self._transform.deinit(shutdown, page); -} - fn decodeTransform(controller: *TransformStream.DefaultController, chunk: js.Value, ignoreBOM: bool) !void { // chunk should be a Uint8Array; decode it as UTF-8 string const typed_array = try chunk.toZig(js.TypedArray(u8)); @@ -119,8 +111,6 @@ pub const JsApi = struct { pub const name = "TextDecoderStream"; pub const prototype_chain = bridge.prototypeChain(); pub var class_id: bridge.ClassId = undefined; - pub const weak = true; - pub const finalizer = bridge.finalizer(TextDecoderStream.deinit); }; pub const constructor = bridge.constructor(TextDecoderStream.init, .{}); diff --git a/src/browser/webapi/encoding/TextEncoderStream.zig b/src/browser/webapi/encoding/TextEncoderStream.zig index a7ae5e2e..b2526637 100644 --- a/src/browser/webapi/encoding/TextEncoderStream.zig +++ b/src/browser/webapi/encoding/TextEncoderStream.zig @@ -34,14 +34,6 @@ pub fn init(page: *Page) !TextEncoderStream { }; } -pub fn acquireRef(self: *TextEncoderStream) void { - self._transform.acquireRef(); -} - -pub fn deinit(self: *TextEncoderStream, shutdown: bool, page: *Page) void { - self._transform.deinit(shutdown, page); -} - fn encodeTransform(controller: *TransformStream.DefaultController, chunk: js.Value) !void { // chunk should be a JS string; encode it as UTF-8 bytes (Uint8Array) const str = chunk.isString() orelse return error.InvalidChunk; @@ -64,8 +56,6 @@ pub const JsApi = struct { pub const name = "TextEncoderStream"; pub const prototype_chain = bridge.prototypeChain(); pub var class_id: bridge.ClassId = undefined; - pub const weak = true; - pub const finalizer = bridge.finalizer(TextEncoderStream.deinit); }; pub const constructor = bridge.constructor(TextEncoderStream.init, .{}); diff --git a/src/browser/webapi/navigation/Navigation.zig b/src/browser/webapi/navigation/Navigation.zig index e81ff46e..447f5e00 100644 --- a/src/browser/webapi/navigation/Navigation.zig +++ b/src/browser/webapi/navigation/Navigation.zig @@ -308,7 +308,7 @@ pub fn navigateInner( _ = try self.pushEntry(url, .{ .source = .navigation, .value = state }, page, true); } else { - try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .script); + try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .{ .script = page }); } }, .replace => |state| { @@ -321,7 +321,7 @@ pub fn navigateInner( _ = try self.replaceEntry(url, .{ .source = .navigation, .value = state }, page, true); } else { - try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .script); + try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .{ .script = page }); } }, .traverse => |index| { @@ -334,11 +334,11 @@ pub fn navigateInner( // todo: Fire navigate event finished.resolve("navigation traverse", {}); } else { - try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .script); + try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .{ .script = page }); } }, .reload => { - try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .script); + try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .{ .script = page }); }, } diff --git a/src/browser/webapi/streams/ReadableStream.zig b/src/browser/webapi/streams/ReadableStream.zig index 7d99a0c1..e4e5d0f9 100644 --- a/src/browser/webapi/streams/ReadableStream.zig +++ b/src/browser/webapi/streams/ReadableStream.zig @@ -52,8 +52,6 @@ _pull_fn: ?js.Function.Global = null, _pulling: bool = false, _pull_again: bool = false, _cancel: ?Cancel = null, -_arena: std.mem.Allocator, -_rc: usize = 0, const UnderlyingSource = struct { start: ?js.Function = null, @@ -70,18 +68,13 @@ const QueueingStrategy = struct { pub fn init(src_: ?UnderlyingSource, strategy_: ?QueueingStrategy, page: *Page) !*ReadableStream { const strategy: QueueingStrategy = strategy_ orelse .{}; - const arena = try page.getArena(.{ .debug = "ReadableStream" }); - errdefer page.releaseArena(arena); - - const self = try arena.create(ReadableStream); - self.* = .{ + const self = try page._factory.create(ReadableStream{ ._page = page, ._state = .readable, - ._arena = arena, ._reader = null, ._controller = undefined, ._stored_error = null, - }; + }); self._controller = try ReadableStreamDefaultController.init(self, strategy.highWaterMark, page); @@ -115,23 +108,6 @@ pub fn initWithData(data: []const u8, page: *Page) !*ReadableStream { return stream; } -pub fn deinit(self: *ReadableStream, _: bool, page: *Page) void { - const rc = self._rc; - if (comptime IS_DEBUG) { - std.debug.assert(rc != 0); - } - - if (rc == 1) { - page.releaseArena(self._arena); - } else { - self._rc = rc - 1; - } -} - -pub fn acquireRef(self: *ReadableStream) void { - self._rc += 1; -} - pub fn getReader(self: *ReadableStream, page: *Page) !*ReadableStreamDefaultReader { if (self.getLocked()) { return error.ReaderLocked; @@ -144,12 +120,6 @@ pub fn getReader(self: *ReadableStream, page: *Page) !*ReadableStreamDefaultRead pub fn releaseReader(self: *ReadableStream) void { self._reader = null; - - const rc = self._rc; - if (comptime IS_DEBUG) { - std.debug.assert(rc != 0); - } - self._rc = rc - 1; } pub fn getAsyncIterator(self: *ReadableStream, page: *Page) !*AsyncIterator { @@ -397,8 +367,6 @@ pub const JsApi = struct { pub const name = "ReadableStream"; pub const prototype_chain = bridge.prototypeChain(); pub var class_id: bridge.ClassId = undefined; - pub const weak = true; - pub const finalizer = bridge.finalizer(ReadableStream.deinit); }; pub const constructor = bridge.constructor(ReadableStream.init, .{}); @@ -422,14 +390,6 @@ pub const AsyncIterator = struct { }); } - pub fn acquireRef(self: *AsyncIterator) void { - self._stream.acquireRef(); - } - - pub fn deinit(self: *AsyncIterator, shutdown: bool, page: *Page) void { - self._stream.deinit(shutdown, page); - } - pub fn next(self: *AsyncIterator, page: *Page) !js.Promise { return self._reader.read(page); } @@ -446,8 +406,6 @@ pub const AsyncIterator = struct { pub const name = "ReadableStreamAsyncIterator"; pub const prototype_chain = bridge.prototypeChain(); pub var class_id: bridge.ClassId = undefined; - pub const weak = true; - pub const finalizer = bridge.finalizer(AsyncIterator.deinit); }; pub const next = bridge.function(ReadableStream.AsyncIterator.next, .{}); diff --git a/src/browser/webapi/streams/ReadableStreamDefaultController.zig b/src/browser/webapi/streams/ReadableStreamDefaultController.zig index cd6c6352..18228475 100644 --- a/src/browser/webapi/streams/ReadableStreamDefaultController.zig +++ b/src/browser/webapi/streams/ReadableStreamDefaultController.zig @@ -27,8 +27,6 @@ const ReadableStreamDefaultReader = @import("ReadableStreamDefaultReader.zig"); const IS_DEBUG = @import("builtin").mode == .Debug; -/// ReadableStreamDefaultController uses ReadableStream's arena to make -/// allocation. Indeed, the controller is owned by its ReadableStream. const ReadableStreamDefaultController = @This(); pub const Chunk = union(enum) { @@ -48,6 +46,7 @@ pub const Chunk = union(enum) { _page: *Page, _stream: *ReadableStream, +_arena: std.mem.Allocator, _queue: std.ArrayList(Chunk), _pending_reads: std.ArrayList(js.PromiseResolver.Global), _high_water_mark: u32, @@ -57,22 +56,15 @@ pub fn init(stream: *ReadableStream, high_water_mark: u32, page: *Page) !*Readab ._page = page, ._queue = .empty, ._stream = stream, + ._arena = page.arena, ._pending_reads = .empty, ._high_water_mark = high_water_mark, }); } -pub fn acquireRef(self: *ReadableStreamDefaultController) void { - self._stream.acquireRef(); -} - -pub fn deinit(self: *ReadableStreamDefaultController, shutdown: bool, page: *Page) void { - self._stream.deinit(shutdown, page); -} - pub fn addPendingRead(self: *ReadableStreamDefaultController, page: *Page) !js.Promise { const resolver = page.js.local.?.createPromiseResolver(); - try self._pending_reads.append(self._stream._arena, try resolver.persist()); + try self._pending_reads.append(self._arena, try resolver.persist()); return resolver.promise(); } @@ -82,8 +74,8 @@ pub fn enqueue(self: *ReadableStreamDefaultController, chunk: Chunk) !void { } if (self._pending_reads.items.len == 0) { - const chunk_copy = try chunk.dupe(self._stream._arena); - return self._queue.append(self._stream._arena, chunk_copy); + const chunk_copy = try chunk.dupe(self._page.arena); + return self._queue.append(self._arena, chunk_copy); } // I know, this is ouch! But we expect to have very few (if any) @@ -117,7 +109,7 @@ pub fn enqueueValue(self: *ReadableStreamDefaultController, value: js.Value) !vo if (self._pending_reads.items.len == 0) { const persisted = try value.persist(); - try self._queue.append(self._stream._arena, .{ .js_value = persisted }); + try self._queue.append(self._arena, .{ .js_value = persisted }); return; } @@ -178,7 +170,7 @@ pub fn doError(self: *ReadableStreamDefaultController, err: []const u8) !void { } self._stream._state = .errored; - self._stream._stored_error = try self._stream._arena.dupe(u8, err); + self._stream._stored_error = try self._page.arena.dupe(u8, err); // Reject all pending reads for (self._pending_reads.items) |resolver| { @@ -218,8 +210,6 @@ pub const JsApi = struct { pub const name = "ReadableStreamDefaultController"; pub const prototype_chain = bridge.prototypeChain(); pub var class_id: bridge.ClassId = undefined; - pub const weak = true; - pub const finalizer = bridge.finalizer(ReadableStreamDefaultController.deinit); }; pub const enqueue = bridge.function(ReadableStreamDefaultController.enqueueValue, .{}); diff --git a/src/browser/webapi/streams/ReadableStreamDefaultReader.zig b/src/browser/webapi/streams/ReadableStreamDefaultReader.zig index e8c639ce..2d3c5bbe 100644 --- a/src/browser/webapi/streams/ReadableStreamDefaultReader.zig +++ b/src/browser/webapi/streams/ReadableStreamDefaultReader.zig @@ -19,8 +19,6 @@ const std = @import("std"); const js = @import("../../js/js.zig"); -const IS_DEBUG = @import("builtin").mode == .Debug; - const Page = @import("../../Page.zig"); const ReadableStream = @import("ReadableStream.zig"); const ReadableStreamDefaultController = @import("ReadableStreamDefaultController.zig"); @@ -37,21 +35,6 @@ pub fn init(stream: *ReadableStream, page: *Page) !*ReadableStreamDefaultReader }); } -pub fn acquireRef(self: *ReadableStreamDefaultReader) void { - const stream = self._stream orelse { - if (comptime IS_DEBUG) { - std.debug.assert(false); - } - return; - }; - stream.acquireRef(); -} - -pub fn deinit(self: *ReadableStreamDefaultReader, shutdown: bool, page: *Page) void { - const stream = self._stream orelse return; - stream.deinit(shutdown, page); -} - pub const ReadResult = struct { done: bool, value: Chunk, @@ -127,8 +110,6 @@ pub const JsApi = struct { pub const name = "ReadableStreamDefaultReader"; pub const prototype_chain = bridge.prototypeChain(); pub var class_id: bridge.ClassId = undefined; - pub const weak = true; - pub const finalizer = bridge.finalizer(ReadableStreamDefaultReader.deinit); }; pub const read = bridge.function(ReadableStreamDefaultReader.read, .{}); diff --git a/src/browser/webapi/streams/TransformStream.zig b/src/browser/webapi/streams/TransformStream.zig index bfed7330..e1f42029 100644 --- a/src/browser/webapi/streams/TransformStream.zig +++ b/src/browser/webapi/streams/TransformStream.zig @@ -85,14 +85,6 @@ pub fn initWithZigTransform(zig_transform: ZigTransformFn, page: *Page) !*Transf return self; } -pub fn acquireRef(self: *TransformStream) void { - self._readable.acquireRef(); -} - -pub fn deinit(self: *TransformStream, shutdown: bool, page: *Page) void { - self._readable.deinit(shutdown, page); -} - pub fn transformWrite(self: *TransformStream, chunk: js.Value, page: *Page) !void { if (self._controller._zig_transform_fn) |zig_fn| { // Zig-level transform (used by TextEncoderStream etc.) @@ -138,8 +130,6 @@ pub const JsApi = struct { pub const name = "TransformStream"; pub const prototype_chain = bridge.prototypeChain(); pub var class_id: bridge.ClassId = undefined; - pub const weak = true; - pub const finalizer = bridge.finalizer(TransformStream.deinit); }; pub const constructor = bridge.constructor(TransformStream.init, .{}); @@ -175,14 +165,6 @@ pub const TransformStreamDefaultController = struct { }); } - pub fn acquireRef(self: *TransformStreamDefaultController) void { - self._stream.acquireRef(); - } - - pub fn deinit(self: *TransformStreamDefaultController, shutdown: bool, page: *Page) void { - self._stream.deinit(shutdown, page); - } - pub fn enqueue(self: *TransformStreamDefaultController, chunk: ReadableStreamDefaultController.Chunk) !void { try self._stream._readable._controller.enqueue(chunk); } @@ -207,8 +189,6 @@ pub const TransformStreamDefaultController = struct { pub const name = "TransformStreamDefaultController"; pub const prototype_chain = bridge.prototypeChain(); pub var class_id: bridge.ClassId = undefined; - pub const weak = true; - pub const finalizer = bridge.finalizer(TransformStreamDefaultController.deinit); }; pub const enqueue = bridge.function(TransformStreamDefaultController.enqueueValue, .{});