Skip to content

Commit f1ff789

Browse files
karlseguinmookums
authored andcommitted
implement custom elements - i think/hope
1 parent 1f45d5b commit f1ff789

File tree

4 files changed

+81
-73
lines changed

4 files changed

+81
-73
lines changed

src/browser/dom/document.zig

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -121,27 +121,28 @@ pub const Document = struct {
121121
return try Element.toInterface(e);
122122
}
123123

124-
pub fn _createElement(self: *parser.Document, tag_name: []const u8, page: *Page) !ElementUnion {
125-
const e = try parser.documentCreateElement(self, tag_name);
126-
127-
const custom_elements = &page.window.custom_elements;
128-
if (custom_elements._get(tag_name, page)) |construct| {
129-
var result: Env.Function.Result = undefined;
130-
131-
_ = construct.newInstance(e, &result) catch |err| {
132-
log.fatal(.user_script, "newInstance error", .{
133-
.err = result.exception,
134-
.stack = result.stack,
135-
.tag_name = tag_name,
136-
.source = "createElement",
137-
});
138-
return err;
139-
};
140-
141-
return try Element.toInterface(e);
142-
}
124+
const CreateElementResult = union(enum) {
125+
element: ElementUnion,
126+
custom: Env.JsObject,
127+
};
128+
129+
pub fn _createElement(self: *parser.Document, tag_name: []const u8, page: *Page) !CreateElementResult {
130+
const custom_element = page.window.custom_elements._get(tag_name) orelse {
131+
const e = try parser.documentCreateElement(self, tag_name);
132+
return .{.element = try Element.toInterface(e)};
133+
};
143134

144-
return try Element.toInterface(e);
135+
var result: Env.Function.Result = undefined;
136+
const js_obj = custom_element.newInstance(&result) catch |err| {
137+
log.fatal(.user_script, "newInstance error", .{
138+
.err = result.exception,
139+
.stack = result.stack,
140+
.tag_name = tag_name,
141+
.source = "createElement",
142+
});
143+
return err;
144+
};
145+
return .{.custom = js_obj};
145146
}
146147

147148
pub fn _createElementNS(self: *parser.Document, ns: []const u8, tag_name: []const u8) !ElementUnion {

src/browser/html/elements.zig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const urlStitch = @import("../../url.zig").URL.stitch;
2727
const URL = @import("../url/url.zig").URL;
2828
const Node = @import("../dom/node.zig").Node;
2929
const Element = @import("../dom/element.zig").Element;
30+
const ElementUnion = @import("../dom/element.zig").Union;
3031

3132
const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSStyleDeclaration;
3233

@@ -113,6 +114,16 @@ pub const HTMLElement = struct {
113114
pub const prototype = *Element;
114115
pub const subtype = .node;
115116

117+
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
118+
const constructor_name = try js_this.constructorName(page.call_arena);
119+
const tag_name = page.window.custom_elements.names.get(constructor_name) orelse {
120+
return error.IllegalContructor;
121+
};
122+
123+
const el = try parser.documentCreateElement(@ptrCast(page.window.document), tag_name);
124+
return el;
125+
}
126+
116127
pub fn get_style(e: *parser.ElementHTML, page: *Page) !*CSSStyleDeclaration {
117128
const state = try page.getOrCreateNodeState(@ptrCast(e));
118129
return &state.style;
@@ -614,6 +625,7 @@ pub const HTMLImageElement = struct {
614625
pub const Factory = struct {
615626
pub const js_name = "Image";
616627
pub const subtype = .node;
628+
617629
pub const js_legacy_factory = true;
618630
pub const prototype = *HTMLImageElement;
619631

src/browser/webcomponents/custom_element_registry.zig

Lines changed: 25 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -26,57 +26,33 @@ const Page = @import("../page.zig").Page;
2626
const Element = @import("../dom/element.zig").Element;
2727

2828
pub const CustomElementRegistry = struct {
29-
map: std.StringHashMapUnmanaged(v8.FunctionTemplate) = .empty,
30-
constructors: std.StringHashMapUnmanaged(v8.Persistent(v8.Function)) = .empty,
29+
// JS FunctionName -> Definition Name, so that, given a function, we can
30+
// create the element with the right tag
31+
names: std.StringHashMapUnmanaged([]const u8) = .empty,
3132

32-
pub fn _define(self: *CustomElementRegistry, name: []const u8, el: Env.Function, page: *Page) !void {
33-
log.info(.browser, "Registering WebComponent", .{ .component = name });
33+
// tag_name -> Function
34+
lookup: std.StringHashMapUnmanaged(Env.Function) = .empty,
3435

35-
const context = page.main_context;
36-
const duped_name = try page.arena.dupe(u8, name);
36+
pub fn _define(self: *CustomElementRegistry, tag_name: []const u8, fun: Env.Function, page: *Page) !void {
37+
log.info(.browser, "define custom element", .{ .name = tag_name });
3738

38-
const template = v8.FunctionTemplate.initCallback(context.isolate, struct {
39-
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
40-
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
41-
const this = info.getThis();
39+
const arena = page.arena;
4240

43-
const isolate = info.getIsolate();
44-
const ctx = isolate.getCurrentContext();
41+
const gop = try self.lookup.getOrPut(arena, tag_name);
42+
if (gop.found_existing) {
43+
return error.DuplicateCustomElement;
44+
}
45+
errdefer _ = self.lookup.remove(tag_name);
4546

46-
const registry_key = v8.String.initUtf8(isolate, "__lightpanda_constructor");
47-
const original_function = this.getValue(ctx, registry_key.toName()) catch unreachable;
48-
if (original_function.isFunction()) {
49-
const f = original_function.castTo(Env.Function);
50-
f.call(void, .{}) catch unreachable;
51-
}
52-
}
53-
}.callback);
47+
const owned_tag_name = try arena.dupe(u8, tag_name);
48+
gop.key_ptr.* = owned_tag_name;
49+
gop.value_ptr.* = fun;
5450

55-
const instance_template = template.getInstanceTemplate();
56-
instance_template.setInternalFieldCount(1);
57-
58-
const registry_key = v8.String.initUtf8(context.isolate, "__lightpanda_constructor");
59-
instance_template.set(registry_key.toName(), el.func, (1 << 1));
60-
61-
const class_name = v8.String.initUtf8(context.isolate, name);
62-
template.setClassName(class_name);
63-
64-
try self.map.put(page.arena, duped_name, template);
65-
66-
// const entry = try self.map.getOrPut(page.arena, try page.arena.dupe(u8, name));
67-
// if (entry.found_existing) return error.NotSupportedError;
68-
// entry.value_ptr.* = el;
51+
try self.names.putNoClobber(arena, try fun.getName(arena), owned_tag_name);
6952
}
7053

71-
pub fn _get(self: *CustomElementRegistry, name: []const u8, page: *Page) ?Env.Function {
72-
if (self.map.get(name)) |template| {
73-
const func = template.getFunction(page.main_context.v8_context);
74-
return Env.Function{
75-
.js_context = page.main_context,
76-
.func = v8.Persistent(v8.Function).init(page.main_context.isolate, func),
77-
.id = func.toObject().getIdentityHash(),
78-
};
79-
} else return null;
54+
pub fn _get(self: *CustomElementRegistry, name: []const u8) ?Env.Function {
55+
return self.lookup.get(name);
8056
}
8157
};
8258

@@ -92,8 +68,9 @@ test "Browser.CustomElementRegistry" {
9268

9369
// Define a simple custom element
9470
.{
95-
\\ class MyElement {
71+
\\ class MyElement extends HTMLElement {
9672
\\ constructor() {
73+
\\ super();
9774
\\ this.textContent = 'Hello World';
9875
\\ }
9976
\\ }
@@ -108,10 +85,10 @@ test "Browser.CustomElementRegistry" {
10885

10986
// Create element via document.createElement
11087
.{ "let el = document.createElement('my-element')", "undefined" },
111-
// .{ "el instanceof MyElement", "true" },
112-
// .{ "el instanceof HTMLElement", "true" },
113-
// .{ "el.tagName", "MY-ELEMENT" },
114-
// .{ "el.textContent", "Hello World" },
88+
.{ "el instanceof MyElement", "true" },
89+
.{ "el instanceof HTMLElement", "true" },
90+
.{ "el.tagName", "MY-ELEMENT" },
91+
.{ "el.textContent", "Hello World" },
11592

11693
// Create element via HTML parsing
11794
// .{ "document.body.innerHTML = '<my-element></my-element>'", "undefined" },

src/runtime/js.zig

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,11 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
12571257
exception: []const u8,
12581258
};
12591259

1260+
pub fn getName(self: *const Function, allocator: Allocator) ![]const u8 {
1261+
const name = self.func.castToFunction().getName();
1262+
return valueToString(allocator, name, self.js_context.isolate, self.js_context.v8_context);
1263+
}
1264+
12601265
pub fn withThis(self: *const Function, value: anytype) !Function {
12611266
const this_obj = if (@TypeOf(value) == JsObject)
12621267
value.js_obj
@@ -1271,7 +1276,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
12711276
};
12721277
}
12731278

1274-
pub fn newInstance(self: *const Function, instance: anytype, result: *Result) !PersistentObject {
1279+
pub fn newInstance(self: *const Function, result: *Result) !JsObject {
12751280
const context = self.js_context;
12761281

12771282
var try_catch: TryCatch = undefined;
@@ -1280,7 +1285,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
12801285

12811286
// This creates a new instance using this Function as a constructor.
12821287
// This returns a generic Object
1283-
const js_this = self.func.castToFunction().initInstance(context.v8_context, &.{}) orelse {
1288+
const js_obj = self.func.castToFunction().initInstance(context.v8_context, &.{}) orelse {
12841289
if (try_catch.hasCaught()) {
12851290
const allocator = context.call_arena;
12861291
result.stack = try_catch.stack(allocator) catch null;
@@ -1292,7 +1297,10 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
12921297
return error.JsConstructorFailed;
12931298
};
12941299

1295-
return try context._mapZigInstanceToJs(js_this, instance);
1300+
return .{
1301+
.js_context = context,
1302+
.js_obj = js_obj,
1303+
};
12961304
}
12971305

12981306
pub fn call(self: *const Function, comptime T: type, args: anytype) !T {
@@ -1474,6 +1482,11 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
14741482
.js_obj = array.castTo(v8.Object),
14751483
};
14761484
}
1485+
1486+
pub fn constructorName(self: JsObject, allocator: Allocator) ![]const u8 {
1487+
const str = try self.js_obj.getConstructorName();
1488+
return jsStringToZig(allocator, str, self.js_context.isolate);
1489+
}
14771490
};
14781491

14791492
// This only exists so that we know whether a function wants the opaque
@@ -1496,6 +1509,10 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
14961509
pub fn set(self: JsThis, key: []const u8, value: anytype, opts: JsObject.SetOpts) !void {
14971510
return self.obj.set(key, value, opts);
14981511
}
1512+
1513+
pub fn constructorName(self: JsThis, allocator: Allocator) ![]const u8 {
1514+
return try self.obj.constructorName(allocator);
1515+
}
14991516
};
15001517

15011518
pub const TryCatch = struct {
@@ -1808,7 +1825,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
18081825
// a constructor function, we'll return an error.
18091826
if (@hasDecl(Struct, "constructor") == false) {
18101827
const iso = caller.isolate;
1811-
const js_exception = iso.throwException(createException(iso, "illegal constructor"));
1828+
const js_exception = iso.throwException(createException(iso, "Illegal Constructor"));
18121829
info.getReturnValue().set(js_exception);
18131830
return;
18141831
}
@@ -2633,6 +2650,7 @@ fn Caller(comptime E: type, comptime State: type) type {
26332650
var js_err: ?v8.Value = switch (err) {
26342651
error.InvalidArgument => createTypeException(isolate, "invalid argument"),
26352652
error.OutOfMemory => createException(isolate, "out of memory"),
2653+
error.IllegalConstructor => createException(isolate, "Illegal Contructor"),
26362654
else => blk: {
26372655
const func = @field(Struct, named_function.name);
26382656
const return_type = @typeInfo(@TypeOf(func)).@"fn".return_type orelse {
@@ -2745,8 +2763,8 @@ fn Caller(comptime E: type, comptime State: type) type {
27452763
// a JS argument
27462764
if (comptime isJsThis(params[params.len - 1].type.?)) {
27472765
@field(args, std.fmt.comptimePrint("{d}", .{params.len - 1 + offset})) = .{ .obj = .{
2766+
.js_context = js_context,
27482767
.js_obj = info.getThis(),
2749-
.executor = self.executor,
27502768
} };
27512769

27522770
// AND the 2nd last parameter is state

0 commit comments

Comments
 (0)