diff --git a/src/browser/js/Object.zig b/src/browser/js/Object.zig
index fbf036e4..13899782 100644
--- a/src/browser/js/Object.zig
+++ b/src/browser/js/Object.zig
@@ -152,7 +152,7 @@ pub fn getPropertyNames(self: Object) js.Array {
}
pub fn nameIterator(self: Object) !NameIterator {
- const handle = v8.v8__Object__GetPropertyNames(self.handle, self.local.handle) orelse {
+ const handle = v8.v8__Object__GetOwnPropertyNames(self.handle, self.local.handle) orelse {
// see getOwnPropertyNames above
return error.TypeError;
};
diff --git a/src/browser/js/Snapshot.zig b/src/browser/js/Snapshot.zig
index 5562a73a..7da66e79 100644
--- a/src/browser/js/Snapshot.zig
+++ b/src/browser/js/Snapshot.zig
@@ -670,11 +670,12 @@ fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *const v8.F
if (value.static) {
v8.v8__Template__SetAccessorProperty(@ptrCast(template), js_name, getter_callback, setter_callback, attribute);
} else {
+ const accessor_attr = if (own_properties) attribute else attribute | v8.DontEnum;
v8.v8__ObjectTemplate__SetAccessorProperty__Config(prototype, &.{
.key = js_name,
.getter = getter_callback,
.setter = setter_callback,
- .attribute = attribute,
+ .attribute = accessor_attr,
});
}
},
@@ -695,10 +696,8 @@ fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *const v8.F
if (value.static and !own_properties) {
v8.v8__Template__Set(@ptrCast(template), js_name, @ptrCast(function_template), v8.None);
} else {
- // For own_properties namespaces, static methods still belong
- // on the instance — `CSS` is exposed as an instance via
- // `window.CSS`, not as a constructor.
- v8.v8__Template__Set(@ptrCast(member_template), js_name, @ptrCast(function_template), v8.None);
+ const fn_attr: v8.PropertyAttribute = if (own_properties) v8.None else v8.DontEnum;
+ v8.v8__Template__Set(@ptrCast(member_template), js_name, @ptrCast(function_template), fn_attr);
}
},
bridge.Indexed => {
diff --git a/src/browser/tests/net/url_search_params.html b/src/browser/tests/net/url_search_params.html
index a4213655..08d28ec0 100644
--- a/src/browser/tests/net/url_search_params.html
+++ b/src/browser/tests/net/url_search_params.html
@@ -416,6 +416,99 @@
}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig
index 5b4135fc..d602fc1b 100644
--- a/src/browser/webapi/Window.zig
+++ b/src/browser/webapi/Window.zig
@@ -206,6 +206,10 @@ pub fn getSelection(self: *const Window) *Selection {
return &self._document._selection;
}
+pub fn getFrameElement(self: *const Window) ?*Element.Html.IFrame {
+ return self._frame.iframe;
+}
+
pub fn getLocation(self: *const Window) *Location {
return self._location;
}
@@ -898,6 +902,7 @@ pub const JsApi = struct {
pub const structuredClone = bridge.function(Window.structuredClone, .{});
pub const getComputedStyle = bridge.function(Window.getComputedStyle, .{});
pub const getSelection = bridge.function(Window.getSelection, .{});
+ pub const frameElement = bridge.accessor(Window.getFrameElement, null, .{});
pub const frames = bridge.accessor(Window.getWindow, null, .{});
pub const index = bridge.indexed(Window.getFrame, null, .{ .null_as_undefined = true });
diff --git a/src/browser/webapi/net/URLSearchParams.zig b/src/browser/webapi/net/URLSearchParams.zig
index 747a8114..f0dc14ec 100644
--- a/src/browser/webapi/net/URLSearchParams.zig
+++ b/src/browser/webapi/net/URLSearchParams.zig
@@ -53,6 +53,15 @@ pub fn init(opts_: ?InitOpts, exec: *const Execution) !*URLSearchParams {
break :blk try paramsFromArray(arena, js_val.toArray());
}
if (js_val.isObject()) {
+ // Per the URL spec, an iterable init (URLSearchParams,
+ // Map, ...) should be walked via its @@iterator. We
+ // don't have a generic iterable path yet; cover the
+ // common case of `new URLSearchParams(otherUSP)` so
+ // the prototype-method-leak doesn't just turn into a
+ // silent empty querystring.
+ if (js_val.toZig(*URLSearchParams)) |other| {
+ break :blk try KeyValueList.copy(arena, other._params);
+ } else |_| {}
// normalizer is null, so frame won't be used
break :blk try KeyValueList.fromJsObject(arena, js_val.toObject(), null, exec.buf);
}
diff --git a/src/cdp/CDP.zig b/src/cdp/CDP.zig
index fa73efd8..92a17aa7 100644
--- a/src/cdp/CDP.zig
+++ b/src/cdp/CDP.zig
@@ -379,6 +379,7 @@ pub fn disposeBrowserContext(self: *CDP, browser_context_id: []const u8) bool {
bc.deinit();
self.browser.closeSession();
self.browser_context = null;
+ _ = self.browser_context_arena.reset(.{ .retain_with_limit = 1024 * 16 });
return true;
}
diff --git a/src/network/layer/RobotsLayer.zig b/src/network/layer/RobotsLayer.zig
index 710884e9..da0d5312 100644
--- a/src/network/layer/RobotsLayer.zig
+++ b/src/network/layer/RobotsLayer.zig
@@ -96,6 +96,13 @@ fn fetchRobotsThenRequest(
errdefer std.debug.assert(self.pending.remove(robots_url));
entry.value_ptr.* = .empty;
+ try entry.value_ptr.append(self.allocator, transfer);
+ transfer.loop_owned = true;
+ errdefer {
+ entry.value_ptr.deinit(self.allocator);
+ transfer.loop_owned = false;
+ }
+
const robots_ctx = try transfer.arena.create(RobotsContext);
robots_ctx.* = .{
.layer = self,
@@ -135,14 +142,16 @@ fn fetchRobotsThenRequest(
.error_callback = RobotsContext.errorCallback,
.shutdown_callback = RobotsContext.shutdownCallback,
}, transfer.owner);
- }
+ } else {
+ // Already one in flight, just queue behind.
+ try entry.value_ptr.append(self.allocator, transfer);
- try entry.value_ptr.append(self.allocator, transfer);
- // Parked: RobotsLayer owns destruction via flushPending / flushPendingShutdown
- // until robots.txt resolves. Without this, Client.request's errdefer (or
- // any caller's cleanup) would deinit a transfer that's still on the
- // pending list, leaving flushPending with a dangling pointer.
- transfer.loop_owned = true;
+ // Parked: RobotsLayer owns destruction via flushPending / flushPendingShutdown
+ // until robots.txt resolves. Without this, Client.request's errdefer (or
+ // any caller's cleanup) would deinit a transfer that's still on the
+ // pending list, leaving flushPending with a dangling pointer.
+ transfer.loop_owned = true;
+ }
}
fn flushPending(self: *RobotsLayer, robots_url: [:0]const u8, allowed: bool) void {