Skip to content

Commit 32bad5f

Browse files
committed
Element.matches, Element.hasAttributes and DOMStringMap (Element.dataset)
1 parent 5ec5647 commit 32bad5f

22 files changed

+467
-38
lines changed

src/browser/Page.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ _attribute_lookup: std.AutoHashMapUnmanaged(usize, *Element.Attribute),
6363
// the return of elements.attributes.
6464
_attribute_named_node_map_lookup: std.AutoHashMapUnmanaged(usize, *Element.Attribute.NamedNodeMap),
6565

66+
// element.dataset -> DOMStringMap
67+
_element_datasets: std.AutoHashMapUnmanaged(*Element, *Element.DOMStringMap),
68+
6669
_script_manager: ScriptManager,
6770

6871
_polyfill_loader: polyfill.Loader = .{},
@@ -152,6 +155,7 @@ fn reset(self: *Page, comptime initializing: bool) !void {
152155
self._load_state = .parsing;
153156
self._attribute_lookup = .empty;
154157
self._attribute_named_node_map_lookup = .empty;
158+
self._element_datasets = .empty;
155159
self._event_manager = EventManager.init(self);
156160

157161
self._script_manager = ScriptManager.init(self);

src/browser/ScriptManager.zig

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,6 @@ const Script = struct {
707707
.cacheable = cacheable,
708708
});
709709

710-
711710
// Handle importmap special case here: the content is a JSON containing
712711
// imports.
713712
if (self.kind == .importmap) {

src/browser/js/Caller.zig

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ pub fn _getIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, info:
157157
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
158158
@field(args, "1") = idx;
159159
const ret = @call(.auto, func, args);
160-
return self.handleIndexedReturn(T, F, ret, info, opts);
160+
return self.handleIndexedReturn(T, F, true, ret, info, opts);
161161
}
162162

163163
pub fn getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: v8.Name, info: v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
@@ -173,10 +173,49 @@ pub fn _getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: v8.N
173173
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
174174
@field(args, "1") = try self.nameToString(name);
175175
const ret = @call(.auto, func, args);
176-
return self.handleIndexedReturn(T, F, ret, info, opts);
176+
return self.handleIndexedReturn(T, F, true, ret, info, opts);
177177
}
178178

179-
fn handleIndexedReturn(self: *Caller, comptime T: type, comptime F: type, ret: anytype, info: v8.PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
179+
pub fn setNamedIndex(self: *Caller, comptime T: type, func: anytype, name: v8.Name, js_value: v8.Value, info: v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
180+
return self._setNamedIndex(T, func, name, js_value, info, opts) catch |err| {
181+
self.handleError(T, @TypeOf(func), err, info, opts);
182+
return v8.Intercepted.No;
183+
};
184+
}
185+
186+
pub fn _setNamedIndex(self: *Caller, comptime T: type, func: anytype, name: v8.Name, js_value: v8.Value, info: v8.PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
187+
const F = @TypeOf(func);
188+
var args: ParameterTypes(F) = undefined;
189+
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
190+
@field(args, "1") = try self.nameToString(name);
191+
@field(args, "2") = try self.context.jsValueToZig(@TypeOf(@field(args, "2")), js_value);
192+
if (@typeInfo(F).@"fn".params.len == 4) {
193+
@field(args, "3") = self.context.page;
194+
}
195+
const ret = @call(.auto, func, args);
196+
return self.handleIndexedReturn(T, F, false, ret, info, opts);
197+
}
198+
199+
pub fn deleteNamedIndex(self: *Caller, comptime T: type, func: anytype, name: v8.Name, info: v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
200+
return self._deleteNamedIndex(T, func, name, info, opts) catch |err| {
201+
self.handleError(T, @TypeOf(func), err, info, opts);
202+
return v8.Intercepted.No;
203+
};
204+
}
205+
206+
pub fn _deleteNamedIndex(self: *Caller, comptime T: type, func: anytype, name: v8.Name, info: v8.PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
207+
const F = @TypeOf(func);
208+
var args: ParameterTypes(F) = undefined;
209+
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
210+
@field(args, "1") = try self.nameToString(name);
211+
if (@typeInfo(F).@"fn".params.len == 3) {
212+
@field(args, "2") = self.context.page;
213+
}
214+
const ret = @call(.auto, func, args);
215+
return self.handleIndexedReturn(T, F, false, ret, info, opts);
216+
}
217+
218+
fn handleIndexedReturn(self: *Caller, comptime T: type, comptime F: type, comptime getter: bool, ret: anytype, info: v8.PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
180219
// need to unwrap this error immediately for when opts.null_as_undefined == true
181220
// and we need to compare it to null;
182221
const non_error_ret = switch (@typeInfo(@TypeOf(ret))) {
@@ -197,7 +236,9 @@ fn handleIndexedReturn(self: *Caller, comptime T: type, comptime F: type, ret: a
197236
else => ret,
198237
};
199238

200-
info.getReturnValue().set(try self.context.zigValueToJs(non_error_ret, opts));
239+
if (comptime getter) {
240+
info.getReturnValue().set(try self.context.zigValueToJs(non_error_ret, opts));
241+
}
201242
return v8.Intercepted.Yes;
202243
}
203244

src/browser/js/Env.zig

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -253,13 +253,12 @@ pub fn attachClass(comptime JsApi: type, isolate: v8.Isolate, template: v8.Funct
253253
};
254254
template_proto.setIndexedProperty(configuration, null);
255255
},
256-
bridge.NamedIndexed => {
257-
const configuration = v8.NamedPropertyHandlerConfiguration{
258-
.getter = value.getter,
259-
.flags = v8.PropertyHandlerFlags.OnlyInterceptStrings | v8.PropertyHandlerFlags.NonMasking,
260-
};
261-
template_proto.setNamedProperty(configuration, null);
262-
},
256+
bridge.NamedIndexed => template.getInstanceTemplate().setNamedProperty(.{
257+
.getter = value.getter,
258+
.setter = value.setter,
259+
.deleter = value.deleter,
260+
.flags = v8.PropertyHandlerFlags.OnlyInterceptStrings | v8.PropertyHandlerFlags.NonMasking,
261+
}, null),
263262
bridge.Iterator => {
264263
// Same as a function, but with a specific name
265264
const function_template = v8.FunctionTemplate.initCallback(isolate, value.func);
@@ -326,7 +325,6 @@ fn generateConstructor(comptime JsApi: type, isolate: v8.Isolate) v8.FunctionTem
326325

327326
// if (has_js_call_as_function) {
328327

329-
330328
// if (@hasDecl(Struct, "htmldda") and Struct.htmldda) {
331329
// if (!has_js_call_as_function) {
332330
// @compileError(@typeName(Struct) ++ ": htmldda 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.");

src/browser/js/bridge.zig

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ pub fn Builder(comptime T: type) type {
4545
return Indexed.init(T, getter_func, opts);
4646
}
4747

48-
pub fn namedIndexed(comptime getter_func: anytype, comptime opts: NamedIndexed.Opts) NamedIndexed {
49-
return NamedIndexed.init(T, getter_func, opts);
48+
pub fn namedIndexed(comptime getter_func: anytype, setter_func: anytype, deleter_func: anytype, comptime opts: NamedIndexed.Opts) NamedIndexed {
49+
return NamedIndexed.init(T, getter_func, setter_func, deleter_func, opts);
5050
}
5151

5252
pub fn iterator(comptime func: anytype, comptime opts: Iterator.Opts) Iterator {
@@ -221,14 +221,16 @@ pub const Indexed = struct {
221221

222222
pub const NamedIndexed = struct {
223223
getter: *const fn (c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8,
224+
setter: ?*const fn (c_name: ?*const v8.C_Name, c_value: ?*const v8.C_Value, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 = null,
225+
deleter: ?*const fn (c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 = null,
224226

225227
const Opts = struct {
226228
as_typed_array: bool = false,
227229
null_as_undefined: bool = false,
228230
};
229231

230-
fn init(comptime T: type, comptime getter: anytype, comptime opts: Opts) NamedIndexed {
231-
return .{ .getter = struct {
232+
fn init(comptime T: type, comptime getter: anytype, setter: anytype, deleter: anytype, comptime opts: Opts) NamedIndexed {
233+
const getter_fn = struct {
232234
fn wrap(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
233235
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
234236
var caller = Caller.init(info);
@@ -238,7 +240,39 @@ pub const NamedIndexed = struct {
238240
.null_as_undefined = opts.null_as_undefined,
239241
});
240242
}
241-
}.wrap };
243+
}.wrap;
244+
245+
const setter_fn = if (@typeInfo(@TypeOf(setter)) == .null) null else struct {
246+
fn wrap(c_name: ?*const v8.C_Name, c_value: ?*const v8.C_Value, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
247+
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
248+
var caller = Caller.init(info);
249+
defer caller.deinit();
250+
251+
return caller.setNamedIndex(T, setter, .{ .handle = c_name.? }, .{ .handle = c_value.? }, info, .{
252+
.as_typed_array = opts.as_typed_array,
253+
.null_as_undefined = opts.null_as_undefined,
254+
});
255+
}
256+
}.wrap;
257+
258+
const deleter_fn = if (@typeInfo(@TypeOf(deleter)) == .null) null else struct {
259+
fn wrap(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
260+
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
261+
var caller = Caller.init(info);
262+
defer caller.deinit();
263+
264+
return caller.deleteNamedIndex(T, deleter, .{ .handle = c_name.? }, info, .{
265+
.as_typed_array = opts.as_typed_array,
266+
.null_as_undefined = opts.null_as_undefined,
267+
});
268+
}
269+
}.wrap;
270+
271+
return .{
272+
.getter = getter_fn,
273+
.setter = setter_fn,
274+
.deleter = deleter_fn,
275+
};
242276
}
243277
};
244278

@@ -269,7 +303,6 @@ pub const Iterator = struct {
269303
}
270304
};
271305

272-
273306
pub const Callable = struct {
274307
func: *const fn (?*const v8.C_FunctionCallbackInfo) callconv(.c) void,
275308

@@ -278,16 +311,16 @@ pub const Callable = struct {
278311
};
279312

280313
fn init(comptime T: type, comptime func: anytype, comptime opts: Opts) Callable {
281-
return .{.func = struct {
314+
return .{ .func = struct {
282315
fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
283316
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
284317
var caller = Caller.init(info);
285318
defer caller.deinit();
286319
caller.method(T, func, info, .{
287320
.null_as_undefined = opts.null_as_undefined,
288321
});
289-
}}.wrap
290-
};
322+
}
323+
}.wrap };
291324
}
292325
};
293326

@@ -457,6 +490,7 @@ pub const JsApis = flattenTypes(&.{
457490
@import("../webapi/DOMNodeIterator.zig"),
458491
@import("../webapi/NodeFilter.zig"),
459492
@import("../webapi/Element.zig"),
493+
@import("../webapi/element/DOMStringMap.zig"),
460494
@import("../webapi/element/Attribute.zig"),
461495
@import("../webapi/element/Html.zig"),
462496
@import("../webapi/element/html/IFrame.zig"),

src/browser/tests/element/attributes.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,22 @@
8383

8484
assertAttributes([{name: 'id', value: 'attr1'}, {name: 'class', value: 'sHow'}]);
8585
</script>
86+
87+
<script id=hasAttribute>
88+
{
89+
const el1 = $('#attr1');
90+
91+
testing.expectEqual(true, el1.hasAttribute('id'));
92+
testing.expectEqual(true, el1.hasAttribute('ID'));
93+
testing.expectEqual(true, el1.hasAttribute('class'));
94+
testing.expectEqual(true, el1.hasAttribute('CLASS'));
95+
testing.expectEqual(false, el1.hasAttribute('other'));
96+
testing.expectEqual(false, el1.hasAttribute('nope'));
97+
98+
el1.setAttribute('data-test', 'value');
99+
testing.expectEqual(true, el1.hasAttribute('data-test'));
100+
101+
el1.removeAttribute('data-test');
102+
testing.expectEqual(false, el1.hasAttribute('data-test'));
103+
}
104+
</script>

0 commit comments

Comments
 (0)