Skip to content

Commit afd29fe

Browse files
authored
Merge pull request #651 from lightpanda-io/html_all_collection
Rework HTMLAllCollection
2 parents 04c990d + b08ffcc commit afd29fe

File tree

7 files changed

+78
-16
lines changed

7 files changed

+78
-16
lines changed

.github/actions/install/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ inputs:
1717
zig-v8:
1818
description: 'zig v8 version to install'
1919
required: false
20-
default: 'v0.1.22'
20+
default: 'v0.1.23'
2121
v8:
2222
description: 'v8 version to install'
2323
required: false

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ ARG ZIG=0.14.0
55
ARG ZIG_MINISIG=RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
66
ARG ARCH=x86_64
77
ARG V8=11.1.134
8-
ARG ZIG_V8=v0.1.22
8+
ARG ZIG_V8=v0.1.23
99

1010
RUN apt-get update -yq && \
1111
apt-get install -yq xz-utils \

build.zig.zon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
.hash = "tigerbeetle_io-0.0.0-ViLgxpyRBAB5BMfIcj3KMXfbJzwARs9uSl8aRy2OXULd",
1414
},
1515
.v8 = .{
16-
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/4809111f930293c6d5082971ad7ffc3d822b6f37.tar.gz",
17-
.hash = "v8-0.0.0-xddH632xAwAjF7ieh48tjbMpu7fVVGr3r3aLwmBbFvPk",
16+
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/6f1ee74a0e7002ea3568e337ab716c1e75c53769.tar.gz",
17+
.hash = "v8-0.0.0-xddH6z2yAwCOPUGmy1IgXysI1yWt8ftd2Z3D5zp0I9tV",
1818
},
1919
//.v8 = .{ .path = "../zig-v8-fork" },
2020
//.tigerbeetle_io = .{ .path = "../tigerbeetle-io" },

src/browser/dom/html_collection.zig

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -149,17 +149,43 @@ pub fn HTMLCollectionByName(
149149
};
150150
}
151151

152-
pub fn HTMLCollectionAll(
153-
root: ?*parser.Node,
154-
include_root: bool,
155-
) !HTMLCollection {
156-
return HTMLCollection{
157-
.root = root,
158-
.walker = .{ .walkerDepthFirst = .{} },
159-
.matcher = .{ .matchTrue = .{} },
160-
.include_root = include_root,
152+
// HTMLAllCollection is a special type: instances of it are falsy. It's the only
153+
// object in the WebAPI that behaves like this - in fact, it's even a special
154+
// case in the JavaScript spec.
155+
// This is important, because a lot of browser detection rely on this behavior
156+
// to determine what browser is running.
157+
158+
// It's also possible to use an instance like a function:
159+
// document.all(3)
160+
// document.all('some_id')
161+
pub const HTMLAllCollection = struct {
162+
pub const prototype = *HTMLCollection;
163+
164+
proto: HTMLCollection,
165+
166+
pub const mark_as_undetectable = true;
167+
168+
pub fn init(root: ?*parser.Node) HTMLAllCollection {
169+
return .{ .proto = .{
170+
.root = root,
171+
.walker = .{ .walkerDepthFirst = .{} },
172+
.matcher = .{ .matchTrue = .{} },
173+
.include_root = true,
174+
} };
175+
}
176+
177+
const CAllAsFunctionArg = union(enum) {
178+
index: u32,
179+
id: []const u8,
161180
};
162-
}
181+
182+
pub fn jsCallAsFunction(self: *HTMLAllCollection, arg: CAllAsFunctionArg) !?Union {
183+
return switch (arg) {
184+
.index => |i| self.proto._item(i),
185+
.id => |id| self.proto._namedItem(id),
186+
};
187+
}
188+
};
163189

164190
pub fn HTMLCollectionChildren(
165191
root: ?*parser.Node,

src/browser/dom/node.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const Document = @import("document.zig").Document;
3333
const DocumentType = @import("document_type.zig").DocumentType;
3434
const DocumentFragment = @import("document_fragment.zig").DocumentFragment;
3535
const HTMLCollection = @import("html_collection.zig").HTMLCollection;
36+
const HTMLAllCollection = @import("html_collection.zig").HTMLAllCollection;
3637
const HTMLCollectionIterator = @import("html_collection.zig").HTMLCollectionIterator;
3738
const Walker = @import("walker.zig").WalkerDepthFirst;
3839

@@ -50,6 +51,7 @@ pub const Interfaces = .{
5051
DocumentType,
5152
DocumentFragment,
5253
HTMLCollection,
54+
HTMLAllCollection,
5355
HTMLCollectionIterator,
5456
HTML.Interfaces,
5557
};

src/browser/html/document.zig

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ pub const HTMLDocument = struct {
151151
return try collection.HTMLCollectionByAnchors(parser.documentHTMLToNode(self), false);
152152
}
153153

154-
pub fn get_all(self: *parser.DocumentHTML) !collection.HTMLCollection {
155-
return try collection.HTMLCollectionAll(parser.documentHTMLToNode(self), true);
154+
pub fn get_all(self: *parser.DocumentHTML) collection.HTMLAllCollection {
155+
return collection.HTMLAllCollection.init(parser.documentHTMLToNode(self));
156156
}
157157

158158
pub fn get_currentScript(self: *parser.DocumentHTML) !?*parser.Script {
@@ -260,4 +260,11 @@ test "Browser.HTML.Document" {
260260
.{ "document.cookie = 'favorite_food=tripe; SameSite=None; Secure'", "favorite_food=tripe; SameSite=None; Secure" },
261261
.{ "document.cookie", "name=Oeschger; favorite_food=tripe" },
262262
}, .{});
263+
264+
try runner.testCases(&.{
265+
.{ "!document.all", "true" },
266+
.{ "!!document.all", "false" },
267+
.{ "document.all(5)", "[object HTMLParagraphElement]" },
268+
.{ "document.all('content')", "[object HTMLDivElement]" },
269+
}, .{});
263270
}

src/runtime/js.zig

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,6 +1221,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
12211221

12221222
generateIndexer(Struct, template_proto);
12231223
generateNamedIndexer(Struct, template_proto);
1224+
generateUndetectable(Struct, template.getInstanceTemplate());
12241225
}
12251226

12261227
// Even if a struct doesn't have a `constructor` function, we still
@@ -1420,6 +1421,32 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
14201421
template_proto.setNamedProperty(configuration, null);
14211422
}
14221423

1424+
fn generateUndetectable(comptime Struct: type, template: v8.ObjectTemplate) void {
1425+
const has_js_call_as_function = @hasDecl(Struct, "jsCallAsFunction");
1426+
1427+
if (has_js_call_as_function) {
1428+
template.setCallAsFunctionHandler(struct {
1429+
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
1430+
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
1431+
var caller = Caller(Self, State).init(info);
1432+
defer caller.deinit();
1433+
1434+
const named_function = comptime NamedFunction.init(Struct, "jsCallAsFunction");
1435+
caller.method(Struct, named_function, info) catch |err| {
1436+
caller.handleError(Struct, named_function, err, info);
1437+
};
1438+
}
1439+
}.callback);
1440+
}
1441+
1442+
if (@hasDecl(Struct, "mark_as_undetectable") and Struct.mark_as_undetectable) {
1443+
if (!has_js_call_as_function) {
1444+
@compileError(@typeName(Struct) ++ ": mark_as_undetectable required jsCallAsFunction to be defined. This is a hard-coded requirement in V8, because mark_as_undetectable only exists for HTMLAllCollection which is also callable.");
1445+
}
1446+
template.markAsUndetectable();
1447+
}
1448+
}
1449+
14231450
// Turns a Zig value into a JS one.
14241451
fn zigValueToJs(
14251452
templates: []v8.FunctionTemplate,

0 commit comments

Comments
 (0)