mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-10-09 12:51:45 -04:00
webapi: give every indexer implementation a query handler
If an explicit query handler isn't provided, we bridge creates a generic one that wraps the getter.
This commit is contained in:
6 files changed
+182
-15
No files matched your search
@@ -275,10 +275,11 @@ fn createSnapshotContext(
|
||||
.data = null,
|
||||
.flags = v8.kOnlyInterceptStrings | v8.kNonMasking,
|
||||
});
|
||||
const window_index = @import("../webapi/Window.zig").JsApi.index;
|
||||
v8.v8__ObjectTemplate__SetIndexedHandler(global_template, &.{
|
||||
.getter = @import("../webapi/Window.zig").JsApi.index.getter,
|
||||
.getter = window_index.getter,
|
||||
.setter = null,
|
||||
.query = null,
|
||||
.query = window_index.query,
|
||||
.deleter = null,
|
||||
.enumerator = null,
|
||||
.definer = null,
|
||||
|
||||
+46
-12
@@ -398,20 +398,25 @@ pub const Indexed = struct {
|
||||
}.wrap;
|
||||
}
|
||||
|
||||
if (@typeInfo(@TypeOf(query)) != .null) {
|
||||
indexed.query = struct {
|
||||
fn wrap(idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 {
|
||||
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
||||
var caller: Caller = undefined;
|
||||
if (!caller.init(v8_isolate)) {
|
||||
return js.Intercepted.no;
|
||||
}
|
||||
defer caller.deinit();
|
||||
const query_func = if (@typeInfo(@TypeOf(query)) != .null)
|
||||
query
|
||||
else
|
||||
// Generate a Query handler by wrapping getter. With no setter, this
|
||||
// gets the ReadOnly attribute
|
||||
GetterQuery(getter, if (@typeInfo(@TypeOf(setter)) == .null) v8.ReadOnly else v8.None).query;
|
||||
|
||||
return caller.getIndexQuery(T, query, idx, handle.?);
|
||||
indexed.query = struct {
|
||||
fn wrap(idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 {
|
||||
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
||||
var caller: Caller = undefined;
|
||||
if (!caller.init(v8_isolate)) {
|
||||
return js.Intercepted.no;
|
||||
}
|
||||
}.wrap;
|
||||
}
|
||||
defer caller.deinit();
|
||||
|
||||
return caller.getIndexQuery(T, query_func, idx, handle.?);
|
||||
}
|
||||
}.wrap;
|
||||
|
||||
if (@typeInfo(@TypeOf(definer)) != .null) {
|
||||
indexed.definer = struct {
|
||||
@@ -433,6 +438,35 @@ pub const Indexed = struct {
|
||||
}
|
||||
};
|
||||
|
||||
// Default index query if one isn't provided. Uses the getter to determine the result
|
||||
fn GetterQuery(comptime getter: anytype, comptime attrs: u32) type {
|
||||
const params = @typeInfo(@TypeOf(getter)).@"fn".params;
|
||||
const Self = params[0].type.?;
|
||||
const Index = params[1].type.?;
|
||||
return struct {
|
||||
const query = if (params.len == 3) withGlobal else plain;
|
||||
|
||||
fn plain(self: Self, idx: Index) !u32 {
|
||||
return attributes(getter(self, idx));
|
||||
}
|
||||
|
||||
fn withGlobal(self: Self, idx: Index, global: params[2].type.?) !u32 {
|
||||
return attributes(getter(self, idx, global));
|
||||
}
|
||||
|
||||
fn attributes(ret: anytype) !u32 {
|
||||
const value = switch (@typeInfo(@TypeOf(ret))) {
|
||||
.error_union => try ret,
|
||||
else => ret,
|
||||
};
|
||||
if (@typeInfo(@TypeOf(value)) == .optional and value == null) {
|
||||
return error.NotHandled;
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const NamedIndexed = struct {
|
||||
getter: *const fn (c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32,
|
||||
setter: ?*const fn (c_name: ?*const v8.Name, c_value: ?*const v8.Value, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 = null,
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
<form id=f><input name=i1><select id=s><option>a</option><option>b</option></select></form>
|
||||
<div id=d class="x y" data-a=1></div>
|
||||
<iframe></iframe>
|
||||
|
||||
<script id=supported_indices>
|
||||
{
|
||||
// Web IDL: a supported index is enumerable, configurable and, unless
|
||||
// there's an indexed setter (HTMLOptionsCollection), read-only. An
|
||||
// unsupported one isn't there at all.
|
||||
const d = document.getElementById('d');
|
||||
const cases = {
|
||||
HTMLCollection: document.getElementsByTagName('div'),
|
||||
NodeList: document.querySelectorAll('div'),
|
||||
NamedNodeMap: d.attributes,
|
||||
HTMLOptionsCollection: document.getElementById('s').options,
|
||||
HTMLAllCollection: document.all,
|
||||
HTMLFormControlsCollection: document.getElementById('f').elements,
|
||||
DOMTokenList: d.classList,
|
||||
Window: window,
|
||||
};
|
||||
for (const [name, obj] of Object.entries(cases)) {
|
||||
const desc = Object.getOwnPropertyDescriptor(obj, 0);
|
||||
const writable = name === 'HTMLOptionsCollection';
|
||||
testing.expectEqual([name, true, true, writable].join(' '), [name, desc.enumerable, desc.configurable, desc.writable].join(' '));
|
||||
testing.expectEqual(name + ' true', name + ' ' + obj.propertyIsEnumerable(0));
|
||||
testing.expectEqual(name + ' true', name + ' ' + (0 in obj));
|
||||
testing.expectEqual(name + ' false', name + ' ' + (9999 in obj));
|
||||
testing.expectEqual(name + ' true', name + ' ' + (Object.getOwnPropertyDescriptor(obj, 9999) === undefined));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=enumeration>
|
||||
{
|
||||
// the query must not filter supported indices out of enumeration
|
||||
const list = document.querySelectorAll('div');
|
||||
testing.expectEqual(['0'], Object.keys(list));
|
||||
const seen = [];
|
||||
for (const k in list) seen.push(k);
|
||||
testing.expectEqual('0', seen[0]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=read_only>
|
||||
(function() {
|
||||
'use strict';
|
||||
const list = document.querySelectorAll('div');
|
||||
const first = list[0];
|
||||
testing.expectError('TypeError', () => { list[0] = 'x'; });
|
||||
testing.expectEqual(first, list[0]);
|
||||
})();
|
||||
</script>
|
||||
@@ -275,6 +275,44 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="options_indexed_setter">
|
||||
{
|
||||
const sel = document.createElement('select')
|
||||
const opts = sel.options
|
||||
const make = (v) => { const o = document.createElement('option'); o.value = v; return o }
|
||||
|
||||
// past the end pads with blank options, then appends
|
||||
opts[2] = make('c')
|
||||
testing.expectEqual(3, opts.length)
|
||||
testing.expectEqual(['', '', 'c'], Array.from(opts, (o) => o.value))
|
||||
|
||||
// in range replaces in place, including inside an optgroup (Firefox;
|
||||
// Chrome removes it and add()s the new one before the next option)
|
||||
opts[0] = make('a')
|
||||
testing.expectEqual(['a', '', 'c'], Array.from(opts, (o) => o.value))
|
||||
const group = document.createElement('optgroup')
|
||||
group.appendChild(opts[1])
|
||||
sel.appendChild(group)
|
||||
opts[2] = make('b')
|
||||
testing.expectEqual(group, opts[2].parentNode)
|
||||
testing.expectEqual(['a', 'c', 'b'], Array.from(opts, (o) => o.value))
|
||||
|
||||
// null removes; null past the end is a no-op
|
||||
opts[1] = null
|
||||
testing.expectEqual(['a', 'b'], Array.from(opts, (o) => o.value))
|
||||
opts[10] = null
|
||||
testing.expectEqual(2, opts.length)
|
||||
|
||||
// only options (or null) are accepted
|
||||
testing.expectError('TypeError', () => { opts[0] = document.createElement('div') })
|
||||
testing.expectEqual(2, opts.length)
|
||||
|
||||
const desc = Object.getOwnPropertyDescriptor(opts, 0)
|
||||
testing.expectEqual(true, desc.writable)
|
||||
testing.expectEqual(true, desc.enumerable)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="select_add">
|
||||
{
|
||||
const sel = document.createElement('select')
|
||||
|
||||
@@ -347,3 +347,8 @@ pub const JsApi = struct {
|
||||
pub const namedItem = bridge.function(HTMLCollection.getByName, .{});
|
||||
pub const symbol_iterator = bridge.iterator(HTMLCollection.iterator, .{});
|
||||
};
|
||||
|
||||
const testing = @import("../../../testing.zig");
|
||||
test "WebApi: indexed properties" {
|
||||
try testing.htmlRunner("collections/indexed_properties.html", .{});
|
||||
}
|
||||
@@ -104,6 +104,41 @@ pub fn remove(self: *HTMLOptionsCollection, index: i32, frame: *Frame) void {
|
||||
}
|
||||
}
|
||||
|
||||
// Chrome's cap (kMaxListItems): padding up to a huge index would otherwise
|
||||
// create that many options.
|
||||
const max_list_items = 100_000;
|
||||
|
||||
// The indexed setter: null removes the option at index; an index past the
|
||||
// end pads with blank options and then appends; otherwise the option at
|
||||
// index is replaced.
|
||||
fn setAtIndex(self: *HTMLOptionsCollection, index: u32, option_: ?*Option, frame: *Frame) !void {
|
||||
const existing = self.getAtIndex(index, frame);
|
||||
const option = (option_ orelse {
|
||||
if (existing) |element| {
|
||||
element.remove(frame);
|
||||
}
|
||||
return;
|
||||
}).asElement().asNode();
|
||||
|
||||
if (existing) |element| {
|
||||
const old = element.asNode();
|
||||
_ = try old.parentNode().?.replaceChild(option, old, frame);
|
||||
return;
|
||||
}
|
||||
|
||||
if (index >= max_list_items) {
|
||||
return;
|
||||
}
|
||||
|
||||
const select_node = self._select.asNode();
|
||||
const doc = select_node.ownerDocument(frame).?;
|
||||
for (self.length(frame)..index) |_| {
|
||||
const blank = try doc.createElementNS("http://www.w3.org/1999/xhtml", "option", frame);
|
||||
_ = try select_node.appendChild(blank.asNode(), frame);
|
||||
}
|
||||
_ = try select_node.appendChild(option, frame);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(HTMLOptionsCollection);
|
||||
|
||||
@@ -117,7 +152,7 @@ pub const JsApi = struct {
|
||||
pub const length = bridge.accessor(HTMLOptionsCollection.length, null, .{});
|
||||
|
||||
// Indexed access
|
||||
pub const @"[int]" = bridge.indexed(HTMLOptionsCollection.getAtIndex, null, .{ .null_as_undefined = true });
|
||||
pub const @"[int]" = bridge.indexedReadWrite(HTMLOptionsCollection.getAtIndex, setAtIndex, null, null, null, .{ .null_as_undefined = true, .ce_reactions = true });
|
||||
pub const @"[str]" = bridge.namedIndexed(HTMLOptionsCollection.getByName, null, null, null, struct {
|
||||
fn wrap(self: *HTMLOptionsCollection, name: []const u8, frame: *Frame) !u32 {
|
||||
if (self.getByName(name, frame) != null) {
|
||||
|
||||
Reference in new issue
Block a user